From 725aecd9bb4de8eda8a9ab1305c72d3567d2b1fc Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Wed, 24 Jul 2024 17:19:45 +0530 Subject: [PATCH 01/11] fix build --- core/blockchain.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 652dd96e9e..a94d965e06 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -159,8 +159,8 @@ type CacheConfig struct { } // arbitrum: exposing CacheConfig.triedbConfig to be used by Nitro when initializing arbos in database -func (c *CacheConfig) TriedbConfig() *triedb.Config { - return c.triedbConfig() +func (c *CacheConfig) TriedbConfig(sVerkle bool) *triedb.Config { + return c.triedbConfig(sVerkle) } // triedbConfig derives the configures for trie database. From 6b0eceab15f482f0d1e61044398f75d2dae8fd07 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 29 Jul 2024 14:35:15 +0530 Subject: [PATCH 02/11] fix test --- .../tracetest/testdata/call_tracer/inner_revert_reason.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json index bd938bc57e..2e06a33f5f 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json @@ -55,7 +55,7 @@ "purpose": "feePayment", "from": "0x3623191d4CCfBbdf09E8EBf6382A1F8257417bC1", "to": null, - "value": "0x254db1c2244000" + "value": "0x23f8a24459d000" } ], "afterEVMTransfers": [ @@ -63,7 +63,7 @@ "purpose": "gasRefund", "from": null, "to": "0x3623191d4CCfBbdf09E8EBf6382A1F8257417bC1", - "value": "0x2366bc81a79000" + "value": "0x22231133e19400" }, { "purpose": "tip", From 634913ad329887dbf2afad5cccdfbbfb89f9f432 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 29 Jul 2024 14:56:15 +0530 Subject: [PATCH 03/11] fix test --- .../frontier_create_outofstorage.json | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json index c46fe080f7..b2e1ea79cd 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/frontier_create_outofstorage.json @@ -77,6 +77,30 @@ "withLog": true }, "result": { + "beforeEVMTransfers": + [ + { + "purpose": "feePayment", + "from": "0x0047A8033CC6d6Ca2ED5044674Fd421F44884dE8", + "to": null, + "value": "0x13fbe85edc90000" + } + ], + "afterEVMTransfers": + [ + { + "purpose": "gasRefund", + "from": null, + "to": "0x0047A8033CC6d6Ca2ED5044674Fd421F44884dE8", + "value": "0xd52ed21cc43400" + }, + { + "purpose": "tip", + "from": null, + "to": "0xe48430c4E88A929bBa0eE3DCe284866a9937b609", + "value": "0x6a8fb3d104cc00" + } + ], "from": "0x0047a8033cc6d6ca2ed5044674fd421f44884de8", "gas": "0x1b7740", "gasUsed": "0x9274f", From bfe13fcc159dff50109d64ff768c16700b0f1fa1 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 29 Jul 2024 15:13:29 +0530 Subject: [PATCH 04/11] fix test --- eth/tracers/internal/tracetest/calltrace_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 89369feea7..a682cf06af 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -301,13 +301,13 @@ func TestInternals(t *testing.T) { byte(vm.CALL), }, tracer: mkTracer("callTracer", nil), - want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, originHex), + want: fmt.Sprintf(`{"beforeEVMTransfers":[{"purpose":"feePayment","from":"%s","to":null,"value":"0x13880"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"%s","value":"0xe3a8"},{"purpose":"tip","from":null,"to":"0x0000000000000000000000000000000000000000","value":"0x54d8"}],"from":"%s","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, origin.Hex(), origin.Hex(), originHex), }, { name: "Stack depletion in LOG0", code: []byte{byte(vm.LOG3)}, tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), - want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, originHex), + want: fmt.Sprintf(`{"beforeEVMTransfers":[{"purpose":"feePayment","from":"%s","to":null,"value":"0x13880"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"%s","value":"0x0"},{"purpose":"tip","from":null,"to":"0x0000000000000000000000000000000000000000","value":"0x13880"}],"from":"%s","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, origin.Hex(), origin.Hex(), originHex), }, { name: "Mem expansion in LOG0", @@ -320,7 +320,7 @@ func TestInternals(t *testing.T) { byte(vm.LOG0), }, tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), - want: fmt.Sprintf(`{"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, originHex), + want: fmt.Sprintf(`{"beforeEVMTransfers":[{"purpose":"feePayment","from":"%s","to":null,"value":"0x13880"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"%s","value":"0xdce2"},{"purpose":"tip","from":null,"to":"0x0000000000000000000000000000000000000000","value":"0x5b9e"}],"from":"%s","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, origin.Hex(), origin.Hex(), originHex), }, { // Leads to OOM on the prestate tracer From 6b20d214a357317c9d926b0dc9f724c61d28a809 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 29 Jul 2024 15:38:51 +0530 Subject: [PATCH 05/11] fix test --- .../testdata/call_tracer/blob_tx.json | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json b/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json index 549acb1fe6..9b48434e11 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json @@ -56,6 +56,30 @@ }, "input": "0x03f8b1820539806485174876e800825208940c2c51a0990aee1d73c1228de1586883415575088080c083020000f842a00100c9fbdf97f747e85847b4f3fff408f89c26842f77c882858bf2c89923849aa00138e3896f3c27f2389147507f8bcec52028b0efca6ee842ed83c9158873943880a0dbac3f97a532c9b00e6239b29036245a5bfbb96940b9d848634661abee98b945a03eec8525f261c2e79798f7b45a5d6ccaefa24576d53ba5023e919b86841c0675", "result": { + "beforeEVMTransfers": + [ + { + "purpose": "feePayment", + "from": "0x0c2c51a0990AeE1d73C1228de158688341557508", + "to": null, + "value": "0x2de364958" + } + ], + "afterEVMTransfers": + [ + { + "purpose": "gasRefund", + "from": null, + "to": "0x0c2c51a0990AeE1d73C1228de158688341557508", + "value": "0x0" + }, + { + "purpose": "tip", + "from": null, + "to": "0x0000000000000000000000000000000000000000", + "value": "0x200b20" + } + ], "from": "0x0c2c51a0990aee1d73c1228de158688341557508", "gas": "0x5208", "gasUsed": "0x5208", From 8d7da07e3d70efb00a55c0d6f18066b109a3b2d7 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 29 Jul 2024 15:49:18 +0530 Subject: [PATCH 06/11] revert blockchain.go --- core/blockchain.go | 644 +++++++++++++++++++++------------------------ 1 file changed, 295 insertions(+), 349 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index a94d965e06..8cf4d00d1a 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -38,7 +38,6 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/snapshot" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" @@ -52,6 +51,7 @@ import ( "github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb/hashdb" "github.com/ethereum/go-ethereum/triedb/pathdb" + "golang.org/x/exp/slices" ) var ( @@ -63,26 +63,26 @@ var ( chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil) - accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil) - accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil) - accountUpdateTimer = metrics.NewRegisteredResettingTimer("chain/account/updates", nil) - accountCommitTimer = metrics.NewRegisteredResettingTimer("chain/account/commits", nil) + accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil) + accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil) + accountUpdateTimer = metrics.NewRegisteredTimer("chain/account/updates", nil) + accountCommitTimer = metrics.NewRegisteredTimer("chain/account/commits", nil) - storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil) - storageHashTimer = metrics.NewRegisteredResettingTimer("chain/storage/hashes", nil) - storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil) - storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil) + storageReadTimer = metrics.NewRegisteredTimer("chain/storage/reads", nil) + storageHashTimer = metrics.NewRegisteredTimer("chain/storage/hashes", nil) + storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil) + storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil) - snapshotAccountReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/account/reads", nil) - snapshotStorageReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/storage/reads", nil) - snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil) + snapshotAccountReadTimer = metrics.NewRegisteredTimer("chain/snapshot/account/reads", nil) + snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storage/reads", nil) + snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil) - triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil) + triedbCommitTimer = metrics.NewRegisteredTimer("chain/triedb/commits", nil) - blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil) - blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil) - blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil) - blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil) + blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) + blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil) + blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil) + blockWriteTimer = metrics.NewRegisteredTimer("chain/write", nil) blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) @@ -105,7 +105,7 @@ const ( maxFutureBlocks = 256 maxTimeFutureBlocks = 30 DefaultTriesInMemory = 128 - TriesInMemory = 128 + // BlockChainVersion ensures that an incompatible database forces a resync from scratch. // // Changelog: @@ -159,16 +159,13 @@ type CacheConfig struct { } // arbitrum: exposing CacheConfig.triedbConfig to be used by Nitro when initializing arbos in database -func (c *CacheConfig) TriedbConfig(sVerkle bool) *triedb.Config { - return c.triedbConfig(sVerkle) +func (c *CacheConfig) TriedbConfig() *triedb.Config { + return c.triedbConfig() } // triedbConfig derives the configures for trie database. -func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config { - config := &triedb.Config{ - Preimages: c.Preimages, - IsVerkle: isVerkle, - } +func (c *CacheConfig) triedbConfig() *triedb.Config { + config := &triedb.Config{Preimages: c.Preimages} if c.StateScheme == rawdb.HashScheme { config.HashDB = &hashdb.Config{ CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, @@ -268,10 +265,11 @@ type BlockChain struct { bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] receiptsCache *lru.Cache[common.Hash, []*types.Receipt] blockCache *lru.Cache[common.Hash, *types.Block] - - txLookupLock sync.RWMutex txLookupCache *lru.Cache[common.Hash, txLookup] + // future blocks are blocks added for later processing + futureBlocks *lru.Cache[common.Hash, *types.Block] + wg sync.WaitGroup quit chan struct{} // shutdown signal, closed in Stop. stopping atomic.Bool // false if chain is running, true when stopped @@ -283,7 +281,6 @@ type BlockChain struct { processor Processor // Block transaction processor interface forker *ForkChoice vmConfig vm.Config - logger *tracing.Hooks numberOfBlocksToSkipStateSaving uint32 amountOfGasInBlocksToSkipStateSaving uint64 @@ -302,7 +299,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par cacheConfig = defaultCacheConfig } // Open trie database with provided config - triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(genesis != nil && genesis.IsVerkle())) + triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig()) var genesisHash common.Hash var genesisErr error @@ -342,9 +339,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit), blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit), txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit), + futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks), engine: engine, vmConfig: vmConfig, - logger: vmConfig.Tracer, } bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit)) bc.forker = NewForkChoice(bc, shouldPreserve) @@ -462,19 +459,19 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par // it in advance. bc.engine.VerifyHeader(bc, bc.CurrentHeader()) - if bc.logger != nil && bc.logger.OnBlockchainInit != nil { - bc.logger.OnBlockchainInit(chainConfig) - } - if bc.logger != nil && bc.logger.OnGenesisBlock != nil { - if block := bc.CurrentBlock(); block.Number.Uint64() == 0 { - alloc, err := getGenesisState(bc.db, block.Hash()) - if err != nil { - return nil, fmt.Errorf("failed to get genesis state: %w", err) - } - if alloc == nil { - return nil, errors.New("live blockchain tracer requires genesis alloc to be set") + // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain + for hash := range BadHashes { + if header := bc.GetHeaderByHash(hash); header != nil { + // get the canonical block corresponding to the offending header's number + headerByNumber := bc.GetHeaderByNumber(header.Number.Uint64()) + // make sure the headerByNumber (if present) is in our current canonical chain + if headerByNumber != nil && headerByNumber.Hash() == header.Hash() { + log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash) + if err := bc.SetHead(header.Number.Uint64() - 1); err != nil { + return nil, err + } + log.Error("Chain rewind was successful, resuming normal operation") } - bc.logger.OnGenesisBlock(bc.genesisBlock, alloc) } } @@ -499,6 +496,11 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) } + + // Start future block processor. + bc.wg.Add(1) + go bc.updateFutureBlocks() + // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) @@ -509,7 +511,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } rawdb.WriteChainConfig(db, genesisHash, chainConfig) } - // Start tx indexer if it's enabled. if txLookupLimit != nil { bc.txIndexer = newTxIndexer(*txLookupLimit, bc) @@ -674,172 +675,6 @@ func (bc *BlockChain) SetSafe(header *types.Header) { } } -// rewindHashHead implements the logic of rewindHead in the context of hash scheme. -func (bc *BlockChain) rewindHashHead(head *types.Header, root common.Hash) (*types.Header, uint64) { - var ( - limit uint64 // The oldest block that will be searched for this rewinding - beyondRoot = root == common.Hash{} // Flag whether we're beyond the requested root (no root, always true) - pivot = rawdb.ReadLastPivotNumber(bc.db) // Associated block number of pivot point state - rootNumber uint64 // Associated block number of requested root - - start = time.Now() // Timestamp the rewinding is restarted - logged = time.Now() // Timestamp last progress log was printed - ) - // The oldest block to be searched is determined by the pivot block or a constant - // searching threshold. The rationale behind this is as follows: - // - // - Snap sync is selected if the pivot block is available. The earliest available - // state is the pivot block itself, so there is no sense in going further back. - // - // - Full sync is selected if the pivot block does not exist. The hash database - // periodically flushes the state to disk, and the used searching threshold is - // considered sufficient to find a persistent state, even for the testnet. It - // might be not enough for a chain that is nearly empty. In the worst case, - // the entire chain is reset to genesis, and snap sync is re-enabled on top, - // which is still acceptable. - if pivot != nil { - limit = *pivot - } else if head.Number.Uint64() > params.FullImmutabilityThreshold { - limit = head.Number.Uint64() - params.FullImmutabilityThreshold - } - for { - logger := log.Trace - if time.Since(logged) > time.Second*8 { - logged = time.Now() - logger = log.Info - } - logger("Block state missing, rewinding further", "number", head.Number, "hash", head.Hash(), "elapsed", common.PrettyDuration(time.Since(start))) - - // If a root threshold was requested but not yet crossed, check - if !beyondRoot && head.Root == root { - beyondRoot, rootNumber = true, head.Number.Uint64() - } - // If search limit is reached, return the genesis block as the - // new chain head. - if head.Number.Uint64() < limit { - log.Info("Rewinding limit reached, resetting to genesis", "number", head.Number, "hash", head.Hash(), "limit", limit) - return bc.genesisBlock.Header(), rootNumber - } - // If the associated state is not reachable, continue searching - // backwards until an available state is found. - if !bc.HasState(head.Root) { - // If the chain is gapped in the middle, return the genesis - // block as the new chain head. - parent := bc.GetHeader(head.ParentHash, head.Number.Uint64()-1) - if parent == nil { - log.Error("Missing block in the middle, resetting to genesis", "number", head.Number.Uint64()-1, "hash", head.ParentHash) - return bc.genesisBlock.Header(), rootNumber - } - head = parent - - // If the genesis block is reached, stop searching. - if head.Number.Uint64() == 0 { - log.Info("Genesis block reached", "number", head.Number, "hash", head.Hash()) - return head, rootNumber - } - continue // keep rewinding - } - // Once the available state is found, ensure that the requested root - // has already been crossed. If not, continue rewinding. - if beyondRoot || head.Number.Uint64() == 0 { - log.Info("Rewound to block with state", "number", head.Number, "hash", head.Hash()) - return head, rootNumber - } - log.Debug("Skipping block with threshold state", "number", head.Number, "hash", head.Hash(), "root", head.Root) - head = bc.GetHeader(head.ParentHash, head.Number.Uint64()-1) // Keep rewinding - } -} - -// rewindPathHead implements the logic of rewindHead in the context of path scheme. -func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*types.Header, uint64) { - var ( - pivot = rawdb.ReadLastPivotNumber(bc.db) // Associated block number of pivot block - rootNumber uint64 // Associated block number of requested root - - // BeyondRoot represents whether the requested root is already - // crossed. The flag value is set to true if the root is empty. - beyondRoot = root == common.Hash{} - - // noState represents if the target state requested for search - // is unavailable and impossible to be recovered. - noState = !bc.HasState(root) && !bc.stateRecoverable(root) - - start = time.Now() // Timestamp the rewinding is restarted - logged = time.Now() // Timestamp last progress log was printed - ) - // Rewind the head block tag until an available state is found. - for { - logger := log.Trace - if time.Since(logged) > time.Second*8 { - logged = time.Now() - logger = log.Info - } - logger("Block state missing, rewinding further", "number", head.Number, "hash", head.Hash(), "elapsed", common.PrettyDuration(time.Since(start))) - - // If a root threshold was requested but not yet crossed, check - if !beyondRoot && head.Root == root { - beyondRoot, rootNumber = true, head.Number.Uint64() - } - // If the root threshold hasn't been crossed but the available - // state is reached, quickly determine if the target state is - // possible to be reached or not. - if !beyondRoot && noState && bc.HasState(head.Root) { - beyondRoot = true - log.Info("Disable the search for unattainable state", "root", root) - } - // Check if the associated state is available or recoverable if - // the requested root has already been crossed. - if beyondRoot && (bc.HasState(head.Root) || bc.stateRecoverable(head.Root)) { - break - } - // If pivot block is reached, return the genesis block as the - // new chain head. Theoretically there must be a persistent - // state before or at the pivot block, prevent endless rewinding - // towards the genesis just in case. - if pivot != nil && *pivot >= head.Number.Uint64() { - log.Info("Pivot block reached, resetting to genesis", "number", head.Number, "hash", head.Hash()) - return bc.genesisBlock.Header(), rootNumber - } - // If the chain is gapped in the middle, return the genesis - // block as the new chain head - parent := bc.GetHeader(head.ParentHash, head.Number.Uint64()-1) // Keep rewinding - if parent == nil { - log.Error("Missing block in the middle, resetting to genesis", "number", head.Number.Uint64()-1, "hash", head.ParentHash) - return bc.genesisBlock.Header(), rootNumber - } - head = parent - - // If the genesis block is reached, stop searching. - if head.Number.Uint64() == 0 { - log.Info("Genesis block reached", "number", head.Number, "hash", head.Hash()) - return head, rootNumber - } - } - // Recover if the target state if it's not available yet. - if !bc.HasState(head.Root) { - if err := bc.triedb.Recover(head.Root); err != nil { - log.Crit("Failed to rollback state", "err", err) - } - } - log.Info("Rewound to block with state", "number", head.Number, "hash", head.Hash()) - return head, rootNumber -} - -// rewindHead searches the available states in the database and returns the associated -// block as the new head block. -// -// If the given root is not empty, then the rewind should attempt to pass the specified -// state root and return the associated block number as well. If the root, typically -// representing the state corresponding to snapshot disk layer, is deemed impassable, -// then block number zero is returned, indicating that snapshot recovery is disabled -// and the whole snapshot should be auto-generated in case of head mismatch. -func (bc *BlockChain) rewindHead(head *types.Header, root common.Hash) (*types.Header, uint64) { - if bc.triedb.Scheme() == rawdb.PathScheme { - return bc.rewindPathHead(head, root) - } - return bc.rewindHashHead(head, root) -} - // setHeadBeyondRoot rewinds the local chain to a new head with the extra condition // that the rewind must pass the specified state root. The extra condition is // ignored if it causes rolling back more than rewindLimit Gas (0 meaning infinte). @@ -860,23 +695,88 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha } defer bc.chainmu.Unlock() - var ( - // Track the block number of the requested root hash - rootNumber uint64 // (no root == always 0) - rootFound bool - // Retrieve the last pivot block to short circuit rollbacks beyond it - // and the current freezer limit to start nuking it's underflown. - pivot = rawdb.ReadLastPivotNumber(bc.db) - ) + // Track the block number of the requested root hash + var blockNumber uint64 // (no root == always 0) + var rootFound bool + + // Retrieve the last pivot block to short circuit rollbacks beyond it and the + // current freezer limit to start nuking id underflown + pivot := rawdb.ReadLastPivotNumber(bc.db) + frozen, _ := bc.db.Ancients() + updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) { // Rewind the blockchain, ensuring we don't end up with a stateless head // block. Note, depth equality is permitted to allow using SetHead as a // chain reparation mechanism without deleting any data! if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() <= currentBlock.Number.Uint64() { - var newHeadBlock *types.Header - newHeadBlock, rootNumber = bc.rewindHead(header, root) - if newHeadBlock.Number.Cmp(bc.genesisBlock.Number()) != 0 { - rootFound = (root == common.Hash{}) || newHeadBlock.Root == root + newHeadBlock := bc.GetBlock(header.Hash(), header.Number.Uint64()) + if newHeadBlock == nil { + log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash()) + newHeadBlock = bc.genesisBlock + } else { + // Block exists, keep rewinding until we find one with state, + // keeping rewinding until we exceed the optional threshold + // root hash + rootFound = (root == common.Hash{}) // Flag whether we're beyond the requested root (no root, always true) + lastFullBlock := uint64(0) + lastFullBlockHash := common.Hash{} + gasRolledBack := uint64(0) + + for { + if rewindLimit > 0 && lastFullBlock != 0 { + // Arbitrum: track the amount of gas rolled back and stop the rollback early if necessary + gasUsedInBlock := newHeadBlock.GasUsed() + if bc.chainConfig.IsArbitrum() { + receipts := bc.GetReceiptsByHash(newHeadBlock.Hash()) + for _, receipt := range receipts { + gasUsedInBlock -= receipt.GasUsedForL1 + } + } + gasRolledBack += gasUsedInBlock + if gasRolledBack >= rewindLimit { + blockNumber = lastFullBlock + newHeadBlock = bc.GetBlock(lastFullBlockHash, lastFullBlock) + log.Debug("Rewound to block with state but not snapshot", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) + break + } + } + + // If a root threshold was requested but not yet crossed, check + if root != (common.Hash{}) && !rootFound && newHeadBlock.Root() == root { + rootFound, blockNumber = true, newHeadBlock.NumberU64() + } + if !bc.HasState(newHeadBlock.Root()) && !bc.stateRecoverable(newHeadBlock.Root()) { + log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) + if pivot == nil || newHeadBlock.NumberU64() > *pivot { + parent := bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) + if parent != nil { + newHeadBlock = parent + continue + } + log.Error("Missing block in the middle, aiming genesis", "number", newHeadBlock.NumberU64()-1, "hash", newHeadBlock.ParentHash()) + newHeadBlock = bc.genesisBlock + } else { + log.Trace("Rewind passed pivot, aiming genesis", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "pivot", *pivot) + newHeadBlock = bc.genesisBlock + } + } else if lastFullBlock == 0 { + lastFullBlock = newHeadBlock.NumberU64() + lastFullBlockHash = newHeadBlock.Hash() + } + if rootFound || newHeadBlock.NumberU64() <= bc.genesisBlock.NumberU64() { + if !bc.HasState(newHeadBlock.Root()) && bc.stateRecoverable(newHeadBlock.Root()) { + // Rewind to a block with recoverable state. If the state is + // missing, run the state recovery here. + if err := bc.triedb.Recover(newHeadBlock.Root()); err != nil { + log.Crit("Failed to rollback state", "err", err) // Shouldn't happen + } + log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) + } + break + } + log.Debug("Skipping block with threshold state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "root", newHeadBlock.Root()) + newHeadBlock = bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) // Keep rewinding + } } rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash()) @@ -884,19 +784,16 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha // In theory we should update all in-memory markers in the // last step, however the direction of SetHead is from high // to low, so it's safe to update in-memory markers directly. - bc.currentBlock.Store(newHeadBlock) - headBlockGauge.Update(int64(newHeadBlock.Number.Uint64())) + bc.currentBlock.Store(newHeadBlock.Header()) + headBlockGauge.Update(int64(newHeadBlock.NumberU64())) // The head state is missing, which is only possible in the path-based // scheme. This situation occurs when the chain head is rewound below // the pivot point. In this scenario, there is no possible recovery // approach except for rerunning a snap sync. Do nothing here until the // state syncer picks it up. - if !bc.HasState(newHeadBlock.Root) { - if newHeadBlock.Number.Uint64() != 0 { - log.Crit("Chain is stateless at a non-genesis block") - } - log.Info("Chain is stateless, wait state sync", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash()) + if !bc.HasState(newHeadBlock.Root()) { + log.Info("Chain is stateless, wait state sync", "number", newHeadBlock.Number(), "hash", newHeadBlock.Hash()) } } // Rewind the snap block in a simpleton way to the target head @@ -923,7 +820,6 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha // intent afterwards is full block importing, delete the chain segment // between the stateful-block and the sethead target. var wipe bool - frozen, _ := bc.db.Ancients() if headNumber+1 < frozen { wipe = pivot == nil || headNumber >= *pivot } @@ -954,7 +850,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha // touching the header chain altogether, unless the freezer is broken if repair { if target, force := updateFn(bc.db, bc.CurrentBlock()); force { - bc.hc.SetHead(target.Number.Uint64(), nil, delFn) + bc.hc.SetHead(target.Number.Uint64(), updateFn, delFn) } } else { // Rewind the chain to the requested head and keep going backwards until a @@ -973,6 +869,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha bc.receiptsCache.Purge() bc.blockCache.Purge() bc.txLookupCache.Purge() + bc.futureBlocks.Purge() // Clear safe block, finalized block if needed if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() { @@ -984,7 +881,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha bc.SetFinalized(nil) } - return rootNumber, rootFound, bc.loadLastState() + return blockNumber, rootFound, bc.loadLastState() } // SnapSyncCommitHead sets the current head block to the one defined by the hash @@ -1218,10 +1115,6 @@ func (bc *BlockChain) Stop() { } } } - // Allow tracers to clean-up and release resources. - if bc.logger != nil && bc.logger.OnClose != nil { - bc.logger.OnClose() - } // Close the trie database, release all the held resources as the last step. if err := bc.triedb.Close(); err != nil { log.Error("Failed to close trie db", "err", err) @@ -1241,6 +1134,24 @@ func (bc *BlockChain) insertStopped() bool { return bc.procInterrupt.Load() } +func (bc *BlockChain) procFutureBlocks() { + blocks := make([]*types.Block, 0, bc.futureBlocks.Len()) + for _, hash := range bc.futureBlocks.Keys() { + if block, exist := bc.futureBlocks.Peek(hash); exist { + blocks = append(blocks, block) + } + } + if len(blocks) > 0 { + slices.SortFunc(blocks, func(a, b *types.Block) int { + return a.Number().Cmp(b.Number()) + }) + // Insert one by one as chain insertion needs contiguous ancestry between blocks + for i := range blocks { + bc.InsertChain(blocks[i : i+1]) + } + } +} + // WriteStatus status of write type WriteStatus byte @@ -1639,6 +1550,17 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. return nil } +// WriteBlockAndSetHead writes the given block and all associated state to the database, +// and applies the block as the new chain head. +func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { + if !bc.chainmu.TryLock() { + return NonStatTy, errChainStopped + } + defer bc.chainmu.Unlock() + + return bc.writeBlockAndSetHead(block, receipts, logs, state, emitHeadEvent) +} + // writeBlockAndSetHead is the internal implementation of WriteBlockAndSetHead. // This function expects the chain mutex to be held. func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { @@ -1665,6 +1587,8 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types if status == CanonStatTy { bc.writeHeadBlock(block) } + bc.futureBlocks.Remove(block.Hash()) + if status == CanonStatTy { bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) if len(logs) > 0 { @@ -1684,6 +1608,25 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types return status, nil } +// addFutureBlock checks if the block is within the max allowed window to get +// accepted for future processing, and returns an error if the block is too far +// ahead and was not added. +// +// TODO after the transition, the future block shouldn't be kept. Because +// it's not checked in the Geth side anymore. +func (bc *BlockChain) addFutureBlock(block *types.Block) error { + max := uint64(time.Now().Unix() + maxTimeFutureBlocks) + if block.Time() > max { + return fmt.Errorf("future block timestamp %v > allowed %v", block.Time(), max) + } + if block.Difficulty().Cmp(common.Big0) == 0 { + // Never add PoS blocks into the future queue + return nil + } + bc.futureBlocks.Add(block.Hash(), block) + return nil +} + // InsertChain attempts to insert the given batch of blocks in to the canonical // chain or, otherwise, create a fork. If an error is returned it will return // the index number of the failing block as well an error describing what went @@ -1821,10 +1764,26 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) _, err := bc.recoverAncestors(block) return it.index, err } + // First block is future, shove it (and all children) to the future queue (unknown ancestor) + case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())): + for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) { + log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash()) + if err := bc.addFutureBlock(block); err != nil { + return it.index, err + } + block, err = it.next() + } + stats.queued += it.processed() + stats.ignored += it.remaining() + + // If there are any still remaining, mark as ignored + return it.index, err + // Some other error(except ErrKnownBlock) occurred, abort. // ErrKnownBlock is allowed here since some known blocks // still need re-execution to generate snapshots that are missing case err != nil && !errors.Is(err, ErrKnownBlock): + bc.futureBlocks.Remove(block.Hash()) stats.ignored += len(it.chain) bc.reportBlock(block, nil, err) return it.index, err @@ -1847,6 +1806,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) log.Debug("Abort during block processing") break } + // If the header is a banned one, straight out abort + if BadHashes[block.Hash()] { + bc.reportBlock(block, nil, ErrBannedHash) + return it.index, ErrBannedHash + } // If the block is known (in the middle of the chain), it's a special case for // Clique blocks where they can share state among each other, so importing an // older block might complete the state of the subsequent one. In this case, @@ -1880,14 +1844,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } stats.processed++ - if bc.logger != nil && bc.logger.OnSkippedBlock != nil { - bc.logger.OnSkippedBlock(tracing.BlockEvent{ - Block: block, - TD: bc.GetTd(block.ParentHash(), block.NumberU64()-1), - Finalized: bc.CurrentFinalBlock(), - Safe: bc.CurrentSafeBlock(), - }) - } // We can assume that logs are empty here, since the only way for consecutive // Clique blocks to have the same state is if there are no transactions. @@ -1905,7 +1861,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if err != nil { return it.index, err } - statedb.SetLogger(bc.logger) // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") @@ -1919,10 +1874,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) go func(start time.Time, followup *types.Block, throwaway *state.StateDB) { - // Disable tracing for prefetcher executions. - vmCfg := bc.vmConfig - vmCfg.Tracer = nil - bc.prefetcher.Prefetch(followup, throwaway, vmCfg, &followupInterrupt) + bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt) blockPrefetchExecuteTimer.Update(time.Since(start)) if followupInterrupt.Load() { @@ -1932,15 +1884,68 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } } - // The traced section of block import. - res, err := bc.processBlock(block, statedb, start, setHead) + // Process block using the parent state as reference point + pstart := time.Now() + receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) + if err != nil { + bc.reportBlock(block, receipts, err) + followupInterrupt.Store(true) + return it.index, err + } + ptime := time.Since(pstart) + + vstart := time.Now() + if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { + bc.reportBlock(block, receipts, err) + followupInterrupt.Store(true) + return it.index, err + } + vtime := time.Since(vstart) + proctime := time.Since(start) // processing + validation + + // Update the metrics touched during block processing and validation + accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) + storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) + snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing) + snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing) + accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) + storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) + accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) + storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) + triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing + trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update + trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read + trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read + blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing + blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation + + // Write the block to the chain and get the status. + var ( + wstart = time.Now() + status WriteStatus + ) + if !setHead { + // Don't set the head, only insert the block + err = bc.writeBlockWithState(block, receipts, statedb) + } else { + status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) + } followupInterrupt.Store(true) if err != nil { return it.index, err } + // Update the metrics touched during block commit + accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them + storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them + snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them + triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them + + blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) + blockInsertTimer.UpdateSince(start) + // Report the import stats before returning the various results stats.processed++ - stats.usedGas += res.usedGas + stats.usedGas += usedGas var snapDiffItems, snapBufItems common.StorageSize if bc.snaps != nil { @@ -1952,10 +1957,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if !setHead { // After merge we expect few side chains. Simply count // all blocks the CL gives us for GC processing time - bc.gcproc += res.procTime + bc.gcproc += proctime + return it.index, nil // Direct block insertion of a single block } - switch res.status { + switch status { case CanonStatTy: log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(), "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), @@ -1965,7 +1971,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) lastCanon = block // Only count canonical blocks for GC processing time - bc.gcproc += res.procTime + bc.gcproc += proctime case SideStatTy: log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(), @@ -1982,93 +1988,24 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) "root", block.Root()) } } - stats.ignored += it.remaining() - return it.index, err -} -// blockProcessingResult is a summary of block processing -// used for updating the stats. -type blockProcessingResult struct { - usedGas uint64 - procTime time.Duration - status WriteStatus -} - -// processBlock executes and validates the given block. If there was no error -// it writes the block and associated state to database. -func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) { - if bc.logger != nil && bc.logger.OnBlockStart != nil { - td := bc.GetTd(block.ParentHash(), block.NumberU64()-1) - bc.logger.OnBlockStart(tracing.BlockEvent{ - Block: block, - TD: td, - Finalized: bc.CurrentFinalBlock(), - Safe: bc.CurrentSafeBlock(), - }) - } - if bc.logger != nil && bc.logger.OnBlockEnd != nil { - defer func() { - bc.logger.OnBlockEnd(blockEndErr) - }() - } - - // Process block using the parent state as reference point - pstart := time.Now() - receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) - if err != nil { - bc.reportBlock(block, receipts, err) - return nil, err - } - ptime := time.Since(pstart) + // Any blocks remaining here? The only ones we care about are the future ones + if block != nil && errors.Is(err, consensus.ErrFutureBlock) { + if err := bc.addFutureBlock(block); err != nil { + return it.index, err + } + block, err = it.next() - vstart := time.Now() - if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { - bc.reportBlock(block, receipts, err) - return nil, err - } - vtime := time.Since(vstart) - proctime := time.Since(start) // processing + validation - - // Update the metrics touched during block processing and validation - accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) - storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) - snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing) - snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing) - accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) - storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) - accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) - storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) - triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing - trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update - trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read - trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read - blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing - blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation - - // Write the block to the chain and get the status. - var ( - wstart = time.Now() - status WriteStatus - ) - if !setHead { - // Don't set the head, only insert the block - err = bc.writeBlockWithState(block, receipts, statedb) - } else { - status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) - } - if err != nil { - return nil, err + for ; block != nil && errors.Is(err, consensus.ErrUnknownAncestor); block, err = it.next() { + if err := bc.addFutureBlock(block); err != nil { + return it.index, err + } + stats.queued++ + } } - // Update the metrics touched during block commit - accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them - storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them - snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them - triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them - - blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) - blockInsertTimer.UpdateSince(start) + stats.ignored += it.remaining() - return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil + return it.index, err } // insertSideChain is called when an import batch hits upon a pruned ancestor @@ -2376,14 +2313,14 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { // rewind the canonical chain to a lower point. log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain)) } - // Acquire the tx-lookup lock before mutation. This step is essential - // as the txlookups should be changed atomically, and all subsequent - // reads should be blocked until the mutation is complete. - bc.txLookupLock.Lock() + // Reset the tx lookup cache in case to clear stale txlookups. + // This is done before writing any new chain data to avoid the + // weird scenario that canonical chain is changed while the + // stale lookups are still cached. + bc.txLookupCache.Purge() - // Insert the new chain segment in incremental order, from the old - // to the new. The new chain head (newChain[0]) is not inserted here, - // as it will be handled separately outside of this function + // Insert the new chain(except the head block(reverse order)), + // taking care of the proper incremental order. for i := len(newChain) - 1; i >= 1; i-- { // Insert the block in the canonical way, re-writing history bc.writeHeadBlock(newChain[i]) @@ -2420,11 +2357,6 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { if err := indexesBatch.Write(); err != nil { log.Crit("Failed to delete useless indexes", "err", err) } - // Reset the tx lookup cache to clear stale txlookup cache. - bc.txLookupCache.Purge() - - // Release the tx-lookup lock after mutation. - bc.txLookupLock.Unlock() // Send out events for logs from the old canon chain, and 'reborn' // logs from the new canon chain. The number of logs can be very @@ -2527,6 +2459,20 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) { return head.Hash(), nil } +func (bc *BlockChain) updateFutureBlocks() { + futureTimer := time.NewTicker(5 * time.Second) + defer futureTimer.Stop() + defer bc.wg.Done() + for { + select { + case <-futureTimer.C: + bc.procFutureBlocks() + case <-bc.quit: + return + } + } +} + // skipBlock returns 'true', if the block being imported can be skipped over, meaning // that the block does not need to be processed but can be considered already fully 'done'. func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool { From 660c03975dedc4ec3e36c3f5835db00165b6fc59 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 29 Jul 2024 16:05:49 +0530 Subject: [PATCH 07/11] merge blockchain.go --- core/blockchain.go | 388 ++++++++++++++++++++------------------------- 1 file changed, 169 insertions(+), 219 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 8cf4d00d1a..7172475270 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -38,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" @@ -51,7 +52,6 @@ import ( "github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb/hashdb" "github.com/ethereum/go-ethereum/triedb/pathdb" - "golang.org/x/exp/slices" ) var ( @@ -63,26 +63,26 @@ var ( chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil) - accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil) - accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil) - accountUpdateTimer = metrics.NewRegisteredTimer("chain/account/updates", nil) - accountCommitTimer = metrics.NewRegisteredTimer("chain/account/commits", nil) + accountReadTimer = metrics.NewRegisteredResettingTimer("chain/account/reads", nil) + accountHashTimer = metrics.NewRegisteredResettingTimer("chain/account/hashes", nil) + accountUpdateTimer = metrics.NewRegisteredResettingTimer("chain/account/updates", nil) + accountCommitTimer = metrics.NewRegisteredResettingTimer("chain/account/commits", nil) - storageReadTimer = metrics.NewRegisteredTimer("chain/storage/reads", nil) - storageHashTimer = metrics.NewRegisteredTimer("chain/storage/hashes", nil) - storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil) - storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil) + storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil) + storageHashTimer = metrics.NewRegisteredResettingTimer("chain/storage/hashes", nil) + storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil) + storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil) - snapshotAccountReadTimer = metrics.NewRegisteredTimer("chain/snapshot/account/reads", nil) - snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storage/reads", nil) - snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil) + snapshotAccountReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/account/reads", nil) + snapshotStorageReadTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/storage/reads", nil) + snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil) - triedbCommitTimer = metrics.NewRegisteredTimer("chain/triedb/commits", nil) + triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil) - blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) - blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil) - blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil) - blockWriteTimer = metrics.NewRegisteredTimer("chain/write", nil) + blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil) + blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil) + blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil) + blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil) blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) @@ -102,8 +102,6 @@ const ( blockCacheLimit = 256 receiptsCacheLimit = 32 txLookupCacheLimit = 1024 - maxFutureBlocks = 256 - maxTimeFutureBlocks = 30 DefaultTriesInMemory = 128 // BlockChainVersion ensures that an incompatible database forces a resync from scratch. @@ -159,13 +157,16 @@ type CacheConfig struct { } // arbitrum: exposing CacheConfig.triedbConfig to be used by Nitro when initializing arbos in database -func (c *CacheConfig) TriedbConfig() *triedb.Config { - return c.triedbConfig() +func (c *CacheConfig) TriedbConfig(sVerkle bool) *triedb.Config { + return c.triedbConfig(sVerkle) } // triedbConfig derives the configures for trie database. -func (c *CacheConfig) triedbConfig() *triedb.Config { - config := &triedb.Config{Preimages: c.Preimages} +func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config { + config := &triedb.Config{ + Preimages: c.Preimages, + IsVerkle: isVerkle, + } if c.StateScheme == rawdb.HashScheme { config.HashDB = &hashdb.Config{ CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, @@ -265,10 +266,9 @@ type BlockChain struct { bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] receiptsCache *lru.Cache[common.Hash, []*types.Receipt] blockCache *lru.Cache[common.Hash, *types.Block] - txLookupCache *lru.Cache[common.Hash, txLookup] - // future blocks are blocks added for later processing - futureBlocks *lru.Cache[common.Hash, *types.Block] + txLookupLock sync.RWMutex + txLookupCache *lru.Cache[common.Hash, txLookup] wg sync.WaitGroup quit chan struct{} // shutdown signal, closed in Stop. @@ -281,6 +281,7 @@ type BlockChain struct { processor Processor // Block transaction processor interface forker *ForkChoice vmConfig vm.Config + logger *tracing.Hooks numberOfBlocksToSkipStateSaving uint32 amountOfGasInBlocksToSkipStateSaving uint64 @@ -299,7 +300,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par cacheConfig = defaultCacheConfig } // Open trie database with provided config - triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig()) + triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(genesis != nil && genesis.IsVerkle())) var genesisHash common.Hash var genesisErr error @@ -339,9 +340,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit), blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit), txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit), - futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks), engine: engine, vmConfig: vmConfig, + logger: vmConfig.Tracer, } bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit)) bc.forker = NewForkChoice(bc, shouldPreserve) @@ -459,19 +460,19 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par // it in advance. bc.engine.VerifyHeader(bc, bc.CurrentHeader()) - // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain - for hash := range BadHashes { - if header := bc.GetHeaderByHash(hash); header != nil { - // get the canonical block corresponding to the offending header's number - headerByNumber := bc.GetHeaderByNumber(header.Number.Uint64()) - // make sure the headerByNumber (if present) is in our current canonical chain - if headerByNumber != nil && headerByNumber.Hash() == header.Hash() { - log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash) - if err := bc.SetHead(header.Number.Uint64() - 1); err != nil { - return nil, err - } - log.Error("Chain rewind was successful, resuming normal operation") + if bc.logger != nil && bc.logger.OnBlockchainInit != nil { + bc.logger.OnBlockchainInit(chainConfig) + } + if bc.logger != nil && bc.logger.OnGenesisBlock != nil { + if block := bc.CurrentBlock(); block.Number.Uint64() == 0 { + alloc, err := getGenesisState(bc.db, block.Hash()) + if err != nil { + return nil, fmt.Errorf("failed to get genesis state: %w", err) + } + if alloc == nil { + return nil, errors.New("live blockchain tracer requires genesis alloc to be set") } + bc.logger.OnGenesisBlock(bc.genesisBlock, alloc) } } @@ -496,11 +497,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) } - - // Start future block processor. - bc.wg.Add(1) - go bc.updateFutureBlocks() - // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) @@ -511,6 +507,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } rawdb.WriteChainConfig(db, genesisHash, chainConfig) } + // Start tx indexer if it's enabled. if txLookupLimit != nil { bc.txIndexer = newTxIndexer(*txLookupLimit, bc) @@ -699,10 +696,9 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha var blockNumber uint64 // (no root == always 0) var rootFound bool - // Retrieve the last pivot block to short circuit rollbacks beyond it and the - // current freezer limit to start nuking id underflown + // Retrieve the last pivot block to short circuit rollbacks beyond it + // and the current freezer limit to start nuking it's underflown. pivot := rawdb.ReadLastPivotNumber(bc.db) - frozen, _ := bc.db.Ancients() updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) { // Rewind the blockchain, ensuring we don't end up with a stateless head @@ -785,7 +781,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha // last step, however the direction of SetHead is from high // to low, so it's safe to update in-memory markers directly. bc.currentBlock.Store(newHeadBlock.Header()) - headBlockGauge.Update(int64(newHeadBlock.NumberU64())) + headBlockGauge.Update(newHeadBlock.Number().Int64()) // The head state is missing, which is only possible in the path-based // scheme. This situation occurs when the chain head is rewound below @@ -793,7 +789,10 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha // approach except for rerunning a snap sync. Do nothing here until the // state syncer picks it up. if !bc.HasState(newHeadBlock.Root()) { - log.Info("Chain is stateless, wait state sync", "number", newHeadBlock.Number(), "hash", newHeadBlock.Hash()) + if newHeadBlock.Number().Uint64() != 0 { + log.Crit("Chain is stateless at a non-genesis block") + } + log.Info("Chain is stateless, wait state sync", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash()) } } // Rewind the snap block in a simpleton way to the target head @@ -820,6 +819,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha // intent afterwards is full block importing, delete the chain segment // between the stateful-block and the sethead target. var wipe bool + frozen, _ := bc.db.Ancients() if headNumber+1 < frozen { wipe = pivot == nil || headNumber >= *pivot } @@ -850,7 +850,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha // touching the header chain altogether, unless the freezer is broken if repair { if target, force := updateFn(bc.db, bc.CurrentBlock()); force { - bc.hc.SetHead(target.Number.Uint64(), updateFn, delFn) + bc.hc.SetHead(target.Number.Uint64(), nil, delFn) } } else { // Rewind the chain to the requested head and keep going backwards until a @@ -869,7 +869,6 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha bc.receiptsCache.Purge() bc.blockCache.Purge() bc.txLookupCache.Purge() - bc.futureBlocks.Purge() // Clear safe block, finalized block if needed if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() { @@ -1115,6 +1114,10 @@ func (bc *BlockChain) Stop() { } } } + // Allow tracers to clean-up and release resources. + if bc.logger != nil && bc.logger.OnClose != nil { + bc.logger.OnClose() + } // Close the trie database, release all the held resources as the last step. if err := bc.triedb.Close(); err != nil { log.Error("Failed to close trie db", "err", err) @@ -1134,24 +1137,6 @@ func (bc *BlockChain) insertStopped() bool { return bc.procInterrupt.Load() } -func (bc *BlockChain) procFutureBlocks() { - blocks := make([]*types.Block, 0, bc.futureBlocks.Len()) - for _, hash := range bc.futureBlocks.Keys() { - if block, exist := bc.futureBlocks.Peek(hash); exist { - blocks = append(blocks, block) - } - } - if len(blocks) > 0 { - slices.SortFunc(blocks, func(a, b *types.Block) int { - return a.Number().Cmp(b.Number()) - }) - // Insert one by one as chain insertion needs contiguous ancestry between blocks - for i := range blocks { - bc.InsertChain(blocks[i : i+1]) - } - } -} - // WriteStatus status of write type WriteStatus byte @@ -1550,17 +1535,6 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. return nil } -// WriteBlockAndSetHead writes the given block and all associated state to the database, -// and applies the block as the new chain head. -func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { - if !bc.chainmu.TryLock() { - return NonStatTy, errChainStopped - } - defer bc.chainmu.Unlock() - - return bc.writeBlockAndSetHead(block, receipts, logs, state, emitHeadEvent) -} - // writeBlockAndSetHead is the internal implementation of WriteBlockAndSetHead. // This function expects the chain mutex to be held. func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { @@ -1587,8 +1561,6 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types if status == CanonStatTy { bc.writeHeadBlock(block) } - bc.futureBlocks.Remove(block.Hash()) - if status == CanonStatTy { bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) if len(logs) > 0 { @@ -1608,25 +1580,6 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types return status, nil } -// addFutureBlock checks if the block is within the max allowed window to get -// accepted for future processing, and returns an error if the block is too far -// ahead and was not added. -// -// TODO after the transition, the future block shouldn't be kept. Because -// it's not checked in the Geth side anymore. -func (bc *BlockChain) addFutureBlock(block *types.Block) error { - max := uint64(time.Now().Unix() + maxTimeFutureBlocks) - if block.Time() > max { - return fmt.Errorf("future block timestamp %v > allowed %v", block.Time(), max) - } - if block.Difficulty().Cmp(common.Big0) == 0 { - // Never add PoS blocks into the future queue - return nil - } - bc.futureBlocks.Add(block.Hash(), block) - return nil -} - // InsertChain attempts to insert the given batch of blocks in to the canonical // chain or, otherwise, create a fork. If an error is returned it will return // the index number of the failing block as well an error describing what went @@ -1764,26 +1717,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) _, err := bc.recoverAncestors(block) return it.index, err } - // First block is future, shove it (and all children) to the future queue (unknown ancestor) - case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())): - for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) { - log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash()) - if err := bc.addFutureBlock(block); err != nil { - return it.index, err - } - block, err = it.next() - } - stats.queued += it.processed() - stats.ignored += it.remaining() - - // If there are any still remaining, mark as ignored - return it.index, err - // Some other error(except ErrKnownBlock) occurred, abort. // ErrKnownBlock is allowed here since some known blocks // still need re-execution to generate snapshots that are missing case err != nil && !errors.Is(err, ErrKnownBlock): - bc.futureBlocks.Remove(block.Hash()) stats.ignored += len(it.chain) bc.reportBlock(block, nil, err) return it.index, err @@ -1806,11 +1743,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) log.Debug("Abort during block processing") break } - // If the header is a banned one, straight out abort - if BadHashes[block.Hash()] { - bc.reportBlock(block, nil, ErrBannedHash) - return it.index, ErrBannedHash - } // If the block is known (in the middle of the chain), it's a special case for // Clique blocks where they can share state among each other, so importing an // older block might complete the state of the subsequent one. In this case, @@ -1844,6 +1776,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } stats.processed++ + if bc.logger != nil && bc.logger.OnSkippedBlock != nil { + bc.logger.OnSkippedBlock(tracing.BlockEvent{ + Block: block, + TD: bc.GetTd(block.ParentHash(), block.NumberU64()-1), + Finalized: bc.CurrentFinalBlock(), + Safe: bc.CurrentSafeBlock(), + }) + } // We can assume that logs are empty here, since the only way for consecutive // Clique blocks to have the same state is if there are no transactions. @@ -1861,6 +1801,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if err != nil { return it.index, err } + statedb.SetLogger(bc.logger) // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") @@ -1874,7 +1815,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) go func(start time.Time, followup *types.Block, throwaway *state.StateDB) { - bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt) + // Disable tracing for prefetcher executions. + vmCfg := bc.vmConfig + vmCfg.Tracer = nil + bc.prefetcher.Prefetch(followup, throwaway, vmCfg, &followupInterrupt) blockPrefetchExecuteTimer.Update(time.Since(start)) if followupInterrupt.Load() { @@ -1884,68 +1828,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } } - // Process block using the parent state as reference point - pstart := time.Now() - receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) - if err != nil { - bc.reportBlock(block, receipts, err) - followupInterrupt.Store(true) - return it.index, err - } - ptime := time.Since(pstart) - - vstart := time.Now() - if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { - bc.reportBlock(block, receipts, err) - followupInterrupt.Store(true) - return it.index, err - } - vtime := time.Since(vstart) - proctime := time.Since(start) // processing + validation - - // Update the metrics touched during block processing and validation - accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) - storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) - snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing) - snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing) - accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) - storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) - accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) - storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) - triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing - trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update - trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read - trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read - blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing - blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation - - // Write the block to the chain and get the status. - var ( - wstart = time.Now() - status WriteStatus - ) - if !setHead { - // Don't set the head, only insert the block - err = bc.writeBlockWithState(block, receipts, statedb) - } else { - status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) - } + // The traced section of block import. + res, err := bc.processBlock(block, statedb, start, setHead) followupInterrupt.Store(true) if err != nil { return it.index, err } - // Update the metrics touched during block commit - accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them - storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them - snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them - triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them - - blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) - blockInsertTimer.UpdateSince(start) - // Report the import stats before returning the various results stats.processed++ - stats.usedGas += usedGas + stats.usedGas += res.usedGas var snapDiffItems, snapBufItems common.StorageSize if bc.snaps != nil { @@ -1957,11 +1848,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if !setHead { // After merge we expect few side chains. Simply count // all blocks the CL gives us for GC processing time - bc.gcproc += proctime - + bc.gcproc += res.procTime return it.index, nil // Direct block insertion of a single block } - switch status { + switch res.status { case CanonStatTy: log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(), "uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(), @@ -1971,7 +1861,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) lastCanon = block // Only count canonical blocks for GC processing time - bc.gcproc += proctime + bc.gcproc += res.procTime case SideStatTy: log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(), @@ -1988,24 +1878,93 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) "root", block.Root()) } } + stats.ignored += it.remaining() + return it.index, err +} - // Any blocks remaining here? The only ones we care about are the future ones - if block != nil && errors.Is(err, consensus.ErrFutureBlock) { - if err := bc.addFutureBlock(block); err != nil { - return it.index, err - } - block, err = it.next() +// blockProcessingResult is a summary of block processing +// used for updating the stats. +type blockProcessingResult struct { + usedGas uint64 + procTime time.Duration + status WriteStatus +} - for ; block != nil && errors.Is(err, consensus.ErrUnknownAncestor); block, err = it.next() { - if err := bc.addFutureBlock(block); err != nil { - return it.index, err - } - stats.queued++ - } +// processBlock executes and validates the given block. If there was no error +// it writes the block and associated state to database. +func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) { + if bc.logger != nil && bc.logger.OnBlockStart != nil { + td := bc.GetTd(block.ParentHash(), block.NumberU64()-1) + bc.logger.OnBlockStart(tracing.BlockEvent{ + Block: block, + TD: td, + Finalized: bc.CurrentFinalBlock(), + Safe: bc.CurrentSafeBlock(), + }) + } + if bc.logger != nil && bc.logger.OnBlockEnd != nil { + defer func() { + bc.logger.OnBlockEnd(blockEndErr) + }() } - stats.ignored += it.remaining() - return it.index, err + // Process block using the parent state as reference point + pstart := time.Now() + receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) + if err != nil { + bc.reportBlock(block, receipts, err) + return nil, err + } + ptime := time.Since(pstart) + + vstart := time.Now() + if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { + bc.reportBlock(block, receipts, err) + return nil, err + } + vtime := time.Since(vstart) + proctime := time.Since(start) // processing + validation + + // Update the metrics touched during block processing and validation + accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) + storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) + snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing) + snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing) + accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) + storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) + accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) + storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) + triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing + trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update + trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read + trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read + blockExecutionTimer.Update(ptime - trieRead) // The time spent on EVM processing + blockValidationTimer.Update(vtime - (triehash + trieUpdate)) // The time spent on block validation + + // Write the block to the chain and get the status. + var ( + wstart = time.Now() + status WriteStatus + ) + if !setHead { + // Don't set the head, only insert the block + err = bc.writeBlockWithState(block, receipts, statedb) + } else { + status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) + } + if err != nil { + return nil, err + } + // Update the metrics touched during block commit + accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them + storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them + snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them + triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them + + blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits) + blockInsertTimer.UpdateSince(start) + + return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil } // insertSideChain is called when an import batch hits upon a pruned ancestor @@ -2313,14 +2272,14 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { // rewind the canonical chain to a lower point. log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain)) } - // Reset the tx lookup cache in case to clear stale txlookups. - // This is done before writing any new chain data to avoid the - // weird scenario that canonical chain is changed while the - // stale lookups are still cached. - bc.txLookupCache.Purge() + // Acquire the tx-lookup lock before mutation. This step is essential + // as the txlookups should be changed atomically, and all subsequent + // reads should be blocked until the mutation is complete. + bc.txLookupLock.Lock() - // Insert the new chain(except the head block(reverse order)), - // taking care of the proper incremental order. + // Insert the new chain segment in incremental order, from the old + // to the new. The new chain head (newChain[0]) is not inserted here, + // as it will be handled separately outside of this function for i := len(newChain) - 1; i >= 1; i-- { // Insert the block in the canonical way, re-writing history bc.writeHeadBlock(newChain[i]) @@ -2357,6 +2316,11 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { if err := indexesBatch.Write(); err != nil { log.Crit("Failed to delete useless indexes", "err", err) } + // Reset the tx lookup cache to clear stale txlookup cache. + bc.txLookupCache.Purge() + + // Release the tx-lookup lock after mutation. + bc.txLookupLock.Unlock() // Send out events for logs from the old canon chain, and 'reborn' // logs from the new canon chain. The number of logs can be very @@ -2459,20 +2423,6 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) { return head.Hash(), nil } -func (bc *BlockChain) updateFutureBlocks() { - futureTimer := time.NewTicker(5 * time.Second) - defer futureTimer.Stop() - defer bc.wg.Done() - for { - select { - case <-futureTimer.C: - bc.procFutureBlocks() - case <-bc.quit: - return - } - } -} - // skipBlock returns 'true', if the block being imported can be skipped over, meaning // that the block does not need to be processed but can be considered already fully 'done'. func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool { From dd1701edf4f000e534eed76965f01f4413b3edc5 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 29 Jul 2024 17:21:51 +0530 Subject: [PATCH 08/11] fix --- core/vm/contracts.go | 6 ++++- core/vm/contracts_fuzz_test.go | 2 +- core/vm/contracts_test.go | 8 +++---- core/vm/evm.go | 41 ++++++++++++++++++++++++++++++---- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index c1264b3e24..86ace388e3 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -215,7 +215,11 @@ type AdvancedPrecompile interface { // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger *tracing.Hooks, advancedInfo *AdvancedPrecompileCall) (ret []byte, remainingGas uint64, err error) { + advanced, isAdvanced := p.(AdvancedPrecompile) + if isAdvanced { + return advanced.RunAdvanced(input, suppliedGas, advancedInfo) + } gasCost := p.RequiredGas(input) if suppliedGas < gasCost { return nil, 0, ErrOutOfGas diff --git a/core/vm/contracts_fuzz_test.go b/core/vm/contracts_fuzz_test.go index 1e5cc80074..7e3851cae5 100644 --- a/core/vm/contracts_fuzz_test.go +++ b/core/vm/contracts_fuzz_test.go @@ -36,7 +36,7 @@ func FuzzPrecompiledContracts(f *testing.F) { return } inWant := string(input) - RunPrecompiledContract(p, input, gas, nil) + RunPrecompiledContract(p, input, gas, nil, nil) if inHave := string(input); inWant != inHave { t.Errorf("Precompiled %v modified input data", a) } diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index b9a8055f9b..dc68813b4d 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -100,7 +100,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(p, in, gas, nil); err != nil { + if res, _, err := RunPrecompiledContract(p, in, gas, nil, nil); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -122,7 +122,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { gas := p.RequiredGas(in) - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas, nil) + _, _, err := RunPrecompiledContract(p, in, gas, nil, nil) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -139,7 +139,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas, nil) + _, _, err := RunPrecompiledContract(p, in, gas, nil, nil) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -171,7 +171,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { copy(data, in) - res, _, err = RunPrecompiledContract(p, data, reqGas, nil) + res, _, err = RunPrecompiledContract(p, data, reqGas, nil, nil) } bench.StopTimer() elapsed := uint64(time.Since(start)) diff --git a/core/vm/evm.go b/core/vm/evm.go index 57917a9a32..de583c2451 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -223,7 +223,15 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + info := &AdvancedPrecompileCall{ + PrecompileAddress: addr, + ActingAsAddress: addr, + Caller: caller.Address(), + Value: value.ToBig(), + ReadOnly: false, + Evm: evm, + } + ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer, info) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -289,7 +297,15 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + info := &AdvancedPrecompileCall{ + PrecompileAddress: addr, + ActingAsAddress: caller.Address(), + Caller: caller.Address(), + Value: value.ToBig(), + ReadOnly: false, + Evm: evm, + } + ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer, info) } else { addrCopy := addr // Initialise a new contract and set the code that is to be used by the EVM. @@ -341,7 +357,16 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + caller := caller.(*Contract) + info := &AdvancedPrecompileCall{ + PrecompileAddress: addr, + ActingAsAddress: caller.Address(), + Caller: caller.CallerAddress, + Value: caller.Value().ToBig(), + ReadOnly: false, + Evm: evm, + } + ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer, info) } else { addrCopy := addr // Initialise a new contract and make initialise the delegate values @@ -396,7 +421,15 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte evm.StateDB.AddBalance(addr, new(uint256.Int), tracing.BalanceChangeTouchAccount) if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) + info := &AdvancedPrecompileCall{ + PrecompileAddress: addr, + ActingAsAddress: addr, + Caller: caller.Address(), + Value: new(big.Int), + ReadOnly: true, + Evm: evm, + } + ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer, info) } else { // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' From 1793a3be33cacd8610055a982d82bd062737a64b Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Thu, 1 Aug 2024 15:45:57 +0530 Subject: [PATCH 09/11] Fix calldefault gas limit --- eth/tracers/api.go | 5 +++-- internal/ethapi/api.go | 15 +++++++++------ internal/ethapi/transaction_args.go | 17 +++++++++++------ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 94307f3f06..82ac3fde53 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -945,11 +945,12 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc config.BlockOverrides.Apply(&vmctx) } // Execute the trace - if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil { + gasLimitNotSetByUser := args.Gas == nil + if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID, gasLimitNotSetByUser); err != nil { return nil, err } var ( - msg = args.ToMessage(vmctx.BaseFee, api.backend.RPCGasCap(), block.Header(), statedb, core.MessageEthcallMode) + msg = args.ToMessage(vmctx.BaseFee, api.backend.RPCGasCap(), block.Header(), statedb, core.MessageEthcallMode, api.backend.ChainConfig().ChainID, gasLimitNotSetByUser) tx = args.ToTransaction() traceConfig *TraceConfig ) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2a95371033..b3e4629fc8 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1130,10 +1130,11 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S // Get a new instance of the EVM. var err error - if err = args.CallDefaults(globalGasCap, blockCtx.BaseFee, b.ChainConfig().ChainID); err != nil { + gasLimitNotSetByUser := args.Gas == nil + if err = args.CallDefaults(globalGasCap, blockCtx.BaseFee, b.ChainConfig().ChainID, gasLimitNotSetByUser); err != nil { return nil, err } - msg := args.ToMessage(blockCtx.BaseFee, globalGasCap, header, state, runMode) + msg := args.ToMessage(blockCtx.BaseFee, globalGasCap, header, state, runMode, b.ChainConfig().ChainID, gasLimitNotSetByUser) // Arbitrum: support NodeInterface.sol by swapping out the message if needed var res *core.ExecutionResult @@ -1295,16 +1296,17 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr ErrorRatio: gasestimator.EstimateGasErrorRatio, RunScheduledTxes: runScheduledTxes, } - if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil { + gasLimitNotSetByUser := args.Gas == nil + if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID, gasLimitNotSetByUser); err != nil { return 0, err } // Run the gas estimation andwrap any revertals into a custom return // Arbitrum: this also appropriately recursively calls another args.ToMessage with increased gasCap by posterCostInL2Gas amount - call := args.ToMessage(header.BaseFee, gasCap, header, state, core.MessageGasEstimationMode) + call := args.ToMessage(header.BaseFee, gasCap, header, state, core.MessageGasEstimationMode, b.ChainConfig().ChainID, gasLimitNotSetByUser) // Arbitrum: raise the gas cap to ignore L1 costs so that it's compute-only { - gasCap, err = args.L2OnlyGasCap(header.BaseFee, gasCap, header, state, core.MessageGasEstimationMode) + gasCap, err = args.L2OnlyGasCap(header.BaseFee, gasCap, header, state, core.MessageGasEstimationMode, b.ChainConfig().ChainID, gasLimitNotSetByUser) if err != nil { return 0, err } @@ -1723,6 +1725,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } // Ensure any missing fields are filled, extract the recipient and input data + gasLimitNotSetByUser := args.Gas == nil if err := args.setDefaults(ctx, b, true); err != nil { return nil, 0, nil, err } @@ -1750,7 +1753,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH statedb := db.Copy() // Set the accesslist to the last al args.AccessList = &accessList - msg := args.ToMessage(header.BaseFee, b.RPCGasCap(), header, statedb, core.MessageEthcallMode) + msg := args.ToMessage(header.BaseFee, b.RPCGasCap(), header, statedb, core.MessageEthcallMode, b.ChainConfig().ChainID, gasLimitNotSetByUser) // Apply the transaction with the access list tracer tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 8f1d330e84..4563a72c7f 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -375,7 +375,7 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context) error { // CallDefaults sanitizes the transaction arguments, often filling in zero values, // for the purpose of eth_call class of RPC methods. -func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, chainID *big.Int) error { +func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, chainID *big.Int, gasLimitNotSetByUser bool) error { // Reject invalid combinations of pre- and post-1559 fee styles if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") @@ -387,7 +387,7 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, return fmt.Errorf("chainId does not match node's (have=%v, want=%v)", have, chainID) } } - if args.Gas == nil { + if gasLimitNotSetByUser { gas := globalGasCap if gas == 0 { gas = uint64(math.MaxUint64 / 2) @@ -427,7 +427,7 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, } // Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called. -func (args *TransactionArgs) ToMessage(baseFee *big.Int, globalGasCap uint64, header *types.Header, state *state.StateDB, runMode core.MessageRunMode) *core.Message { +func (args *TransactionArgs) ToMessage(baseFee *big.Int, globalGasCap uint64, header *types.Header, state *state.StateDB, runMode core.MessageRunMode, chainID *big.Int, gasLimitNotSetByUser bool) *core.Message { var ( gasPrice *big.Int gasFeeCap *big.Int @@ -484,15 +484,20 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, globalGasCap uint64, he // ToMessage recurses once to allow ArbOS to intercept the result for all callers // ArbOS uses this to modify globalGasCap so that the cap will ignore this tx's specific L1 data costs core.InterceptRPCGasCap(&globalGasCap, msg, header, state) - return args.ToMessage(baseFee, globalGasCap, header, nil, runMode) // we pass a nil to avoid another recursion + err := args.CallDefaults(globalGasCap, baseFee, chainID, gasLimitNotSetByUser) + // If we fail to call defaults, we should panic because it's a programming error since we've already called it once + if err != nil { + panic(fmt.Sprintf("CallDefaults failed: %v", err)) + } + return args.ToMessage(baseFee, globalGasCap, header, nil, runMode, chainID, gasLimitNotSetByUser) // we pass a nil to avoid another recursion } return msg } // Raises the vanilla gas cap by the tx's l1 data costs in l2 terms. This creates a new gas cap that after // data payments are made, equals the original vanilla cap for the remaining, L2-specific work the tx does. -func (args *TransactionArgs) L2OnlyGasCap(baseFee *big.Int, gasCap uint64, header *types.Header, state *state.StateDB, runMode core.MessageRunMode) (uint64, error) { - msg := args.ToMessage(baseFee, gasCap, header, nil, runMode) +func (args *TransactionArgs) L2OnlyGasCap(baseFee *big.Int, gasCap uint64, header *types.Header, state *state.StateDB, runMode core.MessageRunMode, chainID *big.Int, gasLimitNotSetByUser bool) (uint64, error) { + msg := args.ToMessage(baseFee, gasCap, header, nil, runMode, chainID, gasLimitNotSetByUser) core.InterceptRPCGasCap(&gasCap, msg, header, state) return gasCap, nil } From cbecc2f9eeeade5d31bd6846ab24ae7f8b21d0f7 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Thu, 1 Aug 2024 16:16:31 +0530 Subject: [PATCH 10/11] fix test --- internal/ethapi/transaction_args.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 4563a72c7f..692de5d09b 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -377,7 +377,7 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context) error { // for the purpose of eth_call class of RPC methods. func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, chainID *big.Int, gasLimitNotSetByUser bool) error { // Reject invalid combinations of pre- and post-1559 fee styles - if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { + if args.GasPrice != nil && ((args.MaxFeePerGas != nil && args.MaxFeePerGas.ToInt().Cmp(common.Big0) != 0) || (args.MaxPriorityFeePerGas != nil && args.MaxPriorityFeePerGas.ToInt().Cmp(common.Big0) != 0)) { return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } if args.ChainID == nil { From 4b4742a13481b7e22c86b1258115d565e5d237ca Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 5 Aug 2024 07:37:18 +0530 Subject: [PATCH 11/11] fix typo --- core/blockchain.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 7172475270..5321dbd90d 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -157,8 +157,8 @@ type CacheConfig struct { } // arbitrum: exposing CacheConfig.triedbConfig to be used by Nitro when initializing arbos in database -func (c *CacheConfig) TriedbConfig(sVerkle bool) *triedb.Config { - return c.triedbConfig(sVerkle) +func (c *CacheConfig) TriedbConfig(isVerkle bool) *triedb.Config { + return c.triedbConfig(isVerkle) } // triedbConfig derives the configures for trie database.