Transactions Hardhat Nodes – How to Replay Transactions in Hardhat Node Forked at an Earlier Block

hardhatnodestransactions

I am attempting to get the historical state of the chain within a block. That is, it is straightforward to use hardhat to fork at an earlier block but I want to be above to move up an arbitrary number of transactions within that block. For what it's worth, I am working on Python with Web3Py.

What I am attempting to do to solve this is to create a local fork up to the block I want to enter, then fetch the transactions from the next block from Alchemy:

from web3 import Web3
import json

web3_current = Web3(Web3.HTTPProvider('MY_ALCHEMY_API')) # get fork at current block which will contain the block we want to replay
web3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545/')) # connect to local fork of mainnet at the block we want to replay

block_number = 17895275 # where we want to replay from
txs = web3_current.eth.get_block(block_number)['transactions'] # get all transactions in the block we want to replicate
impersonated_tx_hash = txs[0] # get the first transaction in the block
impersonated_tx = web3_current.eth.get_transaction(impersonated_tx_hash) # get the transaction object

Then I impersonate this account and create a dict that contains all the info necessary to replay the transaction:

web3.provider.make_request('hardhat_impersonateAccount', [impersonated_tx['from']])

# Initialize replay_tx as an empty dictionary
replay_tx = {}

# Common fields for both legacy and EIP-1559 transactions
common_fields = ['from', 'to', 'gas', 'value', 'input', 'nonce', 'type', 'chainId', 'accessList', 'data']

for field in common_fields:
    if field in impersonated_tx:
        replay_tx[field] = impersonated_tx[field]

# For legacy transactions
if 'gasPrice' in impersonated_tx:
    replay_tx['gasPrice'] = impersonated_tx['gasPrice']

if 'maxFeePerGas' in impersonated_tx and 'maxPriorityFeePerGas' in impersonated_tx:
    replay_tx['maxFeePerGas'] = impersonated_tx['maxFeePerGas']
    replay_tx['maxPriorityFeePerGas'] = impersonated_tx['maxPriorityFeePerGas']

if 'input' in replay_tx:
    replay_tx['data'] = replay_tx.pop('input')

replay_tx['chainId'] = 31337 # need to hardcode for hardhat

if 'type' in replay_tx and replay_tx['type'] == 2:
    if 'gasPrice' in replay_tx:
        del replay_tx['gasPrice']

After this I simply send the transaction out:

tx_hash = web3.eth.send_transaction(replay_tx) # replay transaction on local fork impersonating the sender ('from')

The transaction is attempting to swap with the Uniswap Router. This isn't that critical, it's just happens to be the first transaction in the block. However, I get an error returned:

ValueError: {'code': -32603, 'message': "Error: VM Exception while processing transaction: reverted with reason string 'UniswapV2Router: EXPIRED'", 'data': {'message': "Error: VM Exception while processing transaction: reverted with reason string 'UniswapV2Router: EXPIRED'", 'txHash': '0x42a6b46f0e757ddb6994633aa231cfa4818235ce9542e03f54777a993f9c3512', 'data': '0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000018556e69737761705632526f757465723a20455850495245440000000000000000'}}

Uniswap describes the error:
enter image description here

Am I going about this incorrectly? Again, the fact that it's a Uniswap transaction is tangential; I want to be able to replay UP TO any transaction from an historical block. I have a hunch that it has something to do with timestamps but I'm not sure how that factors into things here and would imagine it would impact other transactions, as well.

The basic question is: assuming it is possible to do so, how does one "replay" transactions from a forked chain?

Best Answer

Before you replay the transaction you have to set the timestamp of the next block manually with setNextBlockTimestamp(timestamp). The timestamp you take will be the block you are trying to replay: web3_current.eth.get_block(block_number)

Related Topic