From e3058590c7148d212e371585bb8c46f1a4fad81a Mon Sep 17 00:00:00 2001 From: R-Niagra Date: Tue, 24 Dec 2024 14:24:53 -0500 Subject: [PATCH 1/3] api feat: adds method to find dependent Invalid txs in a block --- eth/tracers/api.go | 109 ++++++++++++++++++++++++++++++++++++ internal/web3ext/web3ext.go | 6 ++ 2 files changed, 115 insertions(+) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 22163030de..4961ab19e7 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -566,6 +566,115 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config return roots, nil } +// FindDependentInvalidTxs finds dependent txs that becomes invalid if the given txs were not part of the block at first place +func (api *API) FindDependentInvalidTxs(ctx context.Context, txs []common.Hash, blockNumber uint64) (int, error) { + var ( + defaultRexec uint64 = 10000 // default number of blocks to reexec to generate the state + inputTxs = make(map[common.Hash]bool) // mapping for the input txs + depInvalidTxs = make(map[common.Hash]struct{}) // mapping of dep txs that become invalid + ) + + for _, tx := range txs { + inputTxs[tx] = true + } + + if blockNumber == 0 { + return 0, errors.New("genesis block is not applicable") + } + + block, err := api.blockByNumber(ctx, rpc.BlockNumber(blockNumber)) + if err != nil { + return 0, err + } + + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber-1), block.ParentHash()) + if err != nil { + return 0, err + } + //generate the state at the parent block + statedb, release, err := api.backend.StateAtBlock(ctx, parent, defaultRexec, nil, true, false) + if err != nil { + return 0, err + } + defer release() + + err = api.findDepTxsInBlock(ctx, inputTxs, depInvalidTxs, statedb, block) + if err != nil { + return 0, err + } + log.Info("dependent txs that become invalid", "num", len(depInvalidTxs)) + return len(depInvalidTxs), nil +} + +// findDepTxsInBlock skips executing given txs and find the dependent txs that become invalid +func (api *API) findDepTxsInBlock(ctx context.Context, txs map[common.Hash]bool, depInvalid map[common.Hash]struct{}, state *state.StateDB, block *types.Block) error { + var ( + blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + evm = vm.NewEVM(blockCtx, state, api.backend.ChainConfig(), vm.Config{}) + gp = new(core.GasPool).AddGas(block.GasLimit()) + ) + + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + core.ProcessBeaconBlockRoot(*beaconRoot, evm) + } + // process prague related changes + if api.backend.ChainConfig().IsPrague(block.Number(), block.Time()) { + core.ProcessParentBlockHash(block.ParentHash(), evm) + } + + for i, tx := range block.Transactions() { + if _, ok := txs[tx.Hash()]; ok { + continue + } + // takes a snapshot so that if tx exec errors the subsequent txs + // are applied on the snapshot state + var ( + snap = state.Snapshot() + gas = gp.Gas() + ) + err := api.executeTx(ctx, i, block, tx, evm, state) + if err != nil { + state.RevertToSnapshot(snap) + gp.SetGas(gas) + //adds to the dependent txs as it became invalid + depInvalid[tx.Hash()] = struct{}{} + log.Info("dependent invalid tx found", "tx", tx.Hash(), "index", i) + } + } + return nil +} + +func (api *API) executeTx(ctx context.Context, index int, block *types.Block, tx *types.Transaction, evm *vm.EVM, state *state.StateDB) error { + var ( + blockNum = block.Number() + txctx = &Context{ + BlockHash: block.Hash(), + BlockNumber: blockNum, + TxIndex: index, + TxHash: tx.Hash(), + } + signer = types.MakeSigner(api.backend.ChainConfig(), blockNum, block.Time()) + usedGas uint64 + ) + + msg, err := core.TransactionToMessage(tx, signer, block.BaseFee()) + if err != nil { + return err + } + state.SetTxContext(txctx.TxHash, txctx.TxIndex) + _, err = core.ApplyTransactionWithEVM(msg, new(core.GasPool).AddGas(msg.GasLimit), state, txctx.BlockNumber, txctx.BlockHash, tx, &usedGas, evm) + if err != nil { + return err + } + + if evm.ChainConfig().IsByzantium(blockNum) { + evm.StateDB.Finalise(true) + } else { + state.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNum)).Bytes() + } + return nil +} + // StandardTraceBadBlockToFile dumps the structured logs created during the // execution of EVM against a block pulled from the pool of bad ones to the // local file system and returns a list of files to the caller. diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 8ac8f44958..170e14dad2 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -386,6 +386,12 @@ web3._extend({ params: 2, inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, null] }), + new web3._extend.Method({ + name: 'findDependentInvalidTxs', + call: 'debug_findDependentInvalidTxs', + params: 2, + inputFormatter: [null, null], + }), new web3._extend.Method({ name: 'traceBlockByHash', call: 'debug_traceBlockByHash', From 488cbc658e63c2161968ccdf0e833c13c7394d83 Mon Sep 17 00:00:00 2001 From: R-Niagra Date: Tue, 24 Dec 2024 21:16:40 -0500 Subject: [PATCH 2/3] api feat: findDependentInvalidTxs executes until the tip --- eth/api_backend.go | 4 ++++ eth/tracers/api.go | 53 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/eth/api_backend.go b/eth/api_backend.go index be2101c6ec..950a97766c 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -64,6 +64,10 @@ func (b *EthAPIBackend) SetHead(number uint64) { b.eth.blockchain.SetHead(number) } +func (b *EthAPIBackend) BlockChain() *core.BlockChain { + return b.eth.blockchain +} + func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { // Pending block is only known by the miner if number == rpc.PendingBlockNumber { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 4961ab19e7..b119ffd643 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -89,6 +89,7 @@ type Backend interface { ChainDb() ethdb.Database StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, StateReleaseFunc, error) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, StateReleaseFunc, error) + BlockChain() *core.BlockChain } // API is the collection of tracing APIs exposed over the private debugging endpoint. @@ -566,28 +567,38 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config return roots, nil } -// FindDependentInvalidTxs finds dependent txs that becomes invalid if the given txs were not part of the block at first place -func (api *API) FindDependentInvalidTxs(ctx context.Context, txs []common.Hash, blockNumber uint64) (int, error) { +type TxStatus int + +const ( + NotFound TxStatus = iota + Found +) + +// FindDependentInvalidTxs simulates the execution without the input txs and determines the num of subsequent dependent +// transactions that become invalid as a result. The execution continues until the tip is reached +// require: transaction hash is not one of the withdrawals +func (api *API) FindDependentInvalidTxs(ctx context.Context, txs []common.Hash, startBlock uint64) (int, error) { var ( defaultRexec uint64 = 10000 // default number of blocks to reexec to generate the state - inputTxs = make(map[common.Hash]bool) // mapping for the input txs + inputTxs = make(map[common.Hash]TxStatus) // mapping for the input txs depInvalidTxs = make(map[common.Hash]struct{}) // mapping of dep txs that become invalid + current = startBlock ) for _, tx := range txs { - inputTxs[tx] = true + inputTxs[tx] = NotFound // yet to find } - if blockNumber == 0 { + if startBlock == 0 { return 0, errors.New("genesis block is not applicable") } - block, err := api.blockByNumber(ctx, rpc.BlockNumber(blockNumber)) + block, err := api.blockByNumber(ctx, rpc.BlockNumber(current)) if err != nil { return 0, err } - parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber-1), block.ParentHash()) + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(current-1), block.ParentHash()) if err != nil { return 0, err } @@ -598,16 +609,34 @@ func (api *API) FindDependentInvalidTxs(ctx context.Context, txs []common.Hash, } defer release() - err = api.findDepTxsInBlock(ctx, inputTxs, depInvalidTxs, statedb, block) - if err != nil { - return 0, err + for { + err = api.findDepTxsInBlock(ctx, inputTxs, depInvalidTxs, statedb, block) + if err != nil { + return 0, err + } + // Finalize the block by applying any consensus specific updates + api.backend.Engine().Finalize(api.backend.BlockChain(), block.Header(), statedb, block.Body()) + + current++ + block, err = api.blockByNumber(ctx, rpc.BlockNumber(current)) + if err != nil { + // chain tip is reached + break + } } + + for k, v := range inputTxs { + if v == NotFound { + log.Info("input tx was not found", "hash", k) + } + } + log.Info("dependent txs that become invalid", "num", len(depInvalidTxs)) return len(depInvalidTxs), nil } // findDepTxsInBlock skips executing given txs and find the dependent txs that become invalid -func (api *API) findDepTxsInBlock(ctx context.Context, txs map[common.Hash]bool, depInvalid map[common.Hash]struct{}, state *state.StateDB, block *types.Block) error { +func (api *API) findDepTxsInBlock(ctx context.Context, txs map[common.Hash]TxStatus, depInvalid map[common.Hash]struct{}, state *state.StateDB, block *types.Block) error { var ( blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) evm = vm.NewEVM(blockCtx, state, api.backend.ChainConfig(), vm.Config{}) @@ -624,6 +653,7 @@ func (api *API) findDepTxsInBlock(ctx context.Context, txs map[common.Hash]bool, for i, tx := range block.Transactions() { if _, ok := txs[tx.Hash()]; ok { + txs[tx.Hash()] = Found continue } // takes a snapshot so that if tx exec errors the subsequent txs @@ -638,7 +668,6 @@ func (api *API) findDepTxsInBlock(ctx context.Context, txs map[common.Hash]bool, gp.SetGas(gas) //adds to the dependent txs as it became invalid depInvalid[tx.Hash()] = struct{}{} - log.Info("dependent invalid tx found", "tx", tx.Hash(), "index", i) } } return nil From 036ff656f4d1c7851037cb886f964dd9083c4470 Mon Sep 17 00:00:00 2001 From: R-Niagra Date: Tue, 24 Dec 2024 21:18:07 -0500 Subject: [PATCH 3/3] api test: fixes the testBackend with the missing method --- eth/tracers/api_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 13a7b0aaae..577eab688f 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -98,6 +98,10 @@ func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*type return b.chain.GetHeaderByHash(hash), nil } +func (b *testBackend) BlockChain() *core.BlockChain { + return b.chain +} + func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { return b.chain.CurrentHeader(), nil