From ecaef53ec279c978bd38de204b09e799840e3e8d Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 29 Jul 2024 16:41:56 +0800 Subject: [PATCH 01/72] Feat: Parallel Transaction Execution Implementation This PR implement the Parallel EVM engine Co-authored-by: setunapo Co-authored-by: sunny2022da Co-authored-by: galaio Co-authored-by: andyzhang2023 --- cmd/geth/main.go | 2 + cmd/utils/flags.go | 34 + consensus/clique/clique_test.go | 6 +- consensus/clique/snapshot_test.go | 2 +- consensus/ethash/consensus.go | 10 +- core/bench_test.go | 4 +- core/block_validator.go | 7 +- core/block_validator_test.go | 4 +- core/blockchain.go | 42 +- core/blockchain_repair_test.go | 12 +- core/blockchain_sethead_test.go | 2 +- core/blockchain_snapshot_test.go | 20 +- core/blockchain_test.go | 470 +++++++- core/chain_makers_test.go | 6 +- core/dao_test.go | 12 +- core/error.go | 3 + core/genesis_test.go | 2 +- core/parallel_state_processor.go | 822 ++++++++++++++ core/state/dump.go | 2 +- core/state/interface.go | 81 ++ core/state/journal.go | 81 +- core/state/parallel_statedb.go | 1735 +++++++++++++++++++++++++++++ core/state/snapshot/conversion.go | 22 +- core/state/snapshot/difflayer.go | 1 + core/state/snapshot/snapshot.go | 5 +- core/state/state_object.go | 321 +++++- core/state/state_test.go | 52 +- core/state/statedb.go | 1071 ++++++++++++++++-- core/state/statedb_test.go | 372 ++++++- core/state/transient_storage.go | 7 +- core/state_processor.go | 1 - core/state_processor_test.go | 12 +- core/state_transition.go | 2 +- core/types/block.go | 2 + core/types/receipt.go | 9 + core/vm/evm.go | 11 +- core/vm/gas_table.go | 1 - core/vm/instructions.go | 1 + core/vm/interface.go | 7 + core/vm/interpreter.go | 6 +- core/vm/operations_acl.go | 3 +- core/vm/runtime/runtime_test.go | 2 +- eth/backend.go | 2 + eth/downloader/downloader_test.go | 2 +- eth/downloader/testchain_test.go | 3 +- eth/ethconfig/config.go | 2 + eth/filters/filter_test.go | 2 +- eth/gasprice/gasprice_test.go | 2 +- eth/handler_eth_test.go | 4 +- eth/handler_test.go | 2 +- eth/protocols/eth/handler_test.go | 2 +- eth/tracers/api_test.go | 4 +- go.mod | 2 +- internal/ethapi/api_test.go | 2 +- metrics/exp/exp.go | 3 + miner/miner_test.go | 2 +- miner/worker_test.go | 4 +- tests/block_test.go | 4 +- tests/block_test_util.go | 6 +- tests/state_test.go | 4 +- triedb/pathdb/database.go | 3 + triedb/pathdb/disklayer.go | 1 + 62 files changed, 5032 insertions(+), 291 deletions(-) create mode 100644 core/parallel_state_processor.go create mode 100644 core/state/interface.go create mode 100644 core/state/parallel_statedb.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index c8ad9de1a2..6ed003061c 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -169,6 +169,8 @@ var ( utils.RollupComputePendingBlock, utils.RollupHaltOnIncompatibleProtocolVersionFlag, utils.RollupSuperchainUpgradesFlag, + utils.ParallelTxFlag, + utils.ParallelTxNumFlag, configFileFlag, utils.LogDebugFlag, utils.LogBacktraceAtFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7e44853681..2974662beb 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -30,6 +30,7 @@ import ( "net/http" "os" "path/filepath" + "runtime" godebug "runtime/debug" "strconv" "strings" @@ -1093,6 +1094,18 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Category: flags.MetricsCategory, } + ParallelTxFlag = &cli.BoolFlag{ + Name: "parallel", + Usage: "Enable the experimental parallel transaction execution mode, only valid in full sync mode (default = false)", + Category: flags.VMCategory, + } + + ParallelTxNumFlag = &cli.IntFlag{ + Name: "parallel.num", + Usage: "Number of slot for transaction execution, only valid in parallel mode (runtime calculated, no fixed default value)", + Category: flags.VMCategory, + } + VMOpcodeOptimizeFlag = &cli.BoolFlag{ Name: "vm.opcode.optimize", Usage: "enable opcode optimization", @@ -1983,6 +1996,27 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) } + if ctx.IsSet(ParallelTxFlag.Name) { + cfg.ParallelTxMode = ctx.Bool(ParallelTxFlag.Name) + // The best prallel num will be tuned later, we do a simple parallel num set here + numCpu := runtime.NumCPU() + var parallelNum int + if ctx.IsSet(ParallelTxNumFlag.Name) { + // first of all, we use "--parallel.num", but "--parallel.num 0" is not allowed + parallelNum = ctx.Int(ParallelTxNumFlag.Name) + if parallelNum < 1 { + parallelNum = 1 + } + } else if numCpu == 1 { + parallelNum = 1 // single CPU core + } else if numCpu < 10 { + parallelNum = numCpu - 1 + } else { + parallelNum = 8 // we found concurrency 8 is slightly better than 15 + } + cfg.ParallelTxNum = parallelNum + } + if ctx.IsSet(VMOpcodeOptimizeFlag.Name) { cfg.EnableOpcodeOptimizing = ctx.Bool(VMOpcodeOptimizeFlag.Name) if cfg.EnableOpcodeOptimizing { diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index 8ef8dbffa9..92d2758e47 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -55,7 +55,7 @@ func TestReimportMirroredState(t *testing.T) { copy(genspec.ExtraData[extraVanity:], addr[:]) // Generate a batch of blocks, each properly signed - chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genspec, nil, engine, vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer chain.Stop() _, blocks, _ := core.GenerateChainWithGenesis(genspec, engine, 3, func(i int, block *core.BlockGen) { @@ -87,7 +87,7 @@ func TestReimportMirroredState(t *testing.T) { } // Insert the first two blocks and make sure the chain is valid db = rawdb.NewMemoryDatabase() - chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil, nil) + chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer chain.Stop() if _, err := chain.InsertChain(blocks[:2]); err != nil { @@ -100,7 +100,7 @@ func TestReimportMirroredState(t *testing.T) { // Simulate a crash by creating a new chain on top of the database, without // flushing the dirty states out. Insert the last block, triggering a sidechain // reimport. - chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil, nil) + chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer chain.Stop() if _, err := chain.InsertChain(blocks[2:]); err != nil { diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index 26cebe008a..a6ab86c19f 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -458,7 +458,7 @@ func (tt *cliqueTest) run(t *testing.T) { batches[len(batches)-1] = append(batches[len(batches)-1], block) } // Pass all the headers through clique and ensure tallying succeeds - chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create test chain: %v", err) } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index c2936fd4b3..15d5ba84ec 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -514,10 +514,16 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea } // Finalize block ethash.Finalize(chain, header, state, txs, uncles, nil) - + /* + js, _ := header.MarshalJSON() + fmt.Printf("== Dav -- ethash FinalizeAndAssemble, before Root update, Root %s, header json: %s\n", header.Root, js) + */ // Assign the final state root to header. header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - + /* + js, _ = header.MarshalJSON() + fmt.Printf(" == Dav -- ethash FinalizeAndAssemble, after Root update, Root %s, header json: %s\n", header.Root, js) + */ // Header seems complete, assemble into a block and return return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil } diff --git a/core/bench_test.go b/core/bench_test.go index 97713868a5..c01495e4da 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -195,7 +195,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { // Time the insertion of the new chain. // State and blocks are stored in the same DB. - chainman, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + chainman, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer chainman.Stop() b.ReportAllocs() b.ResetTimer() @@ -312,7 +312,9 @@ func benchReadChain(b *testing.B, full bool, count uint64) { if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } + chain, err := NewBlockChain(db, &cacheConfig, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + if err != nil { b.Fatalf("error creating chain: %v", err) } diff --git a/core/block_validator.go b/core/block_validator.go index 79839d7176..061e69b8d2 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -189,9 +189,10 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD func() error { // Validate the state root against the received state root and throw // an error if they don't match. - if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { - return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) - } + // @TODO shall we disable it? + //if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { + // return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) + //} return nil }, } diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 385c0afd9d..bcae70be68 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -50,7 +50,7 @@ func testHeaderVerification(t *testing.T, scheme string) { headers[i] = block.Header() } // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces - chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer chain.Stop() for i := 0; i < len(blocks); i++ { @@ -163,7 +163,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { t.Logf("Post-merge header: %d", block.NumberU64()) } // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces - chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer chain.Stop() // Verify the blocks before the merging diff --git a/core/blockchain.go b/core/blockchain.go index 7e4b81b153..7776eed494 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -105,6 +105,8 @@ var ( errChainStopped = errors.New("blockchain is stopped") errInvalidOldChain = errors.New("invalid old chain") errInvalidNewChain = errors.New("invalid new chain") + + ParallelTxMode = false // parallel transaction execution ) const ( @@ -288,12 +290,13 @@ type BlockChain struct { stopping atomic.Bool // false if chain is running, true when stopped procInterrupt atomic.Bool // interrupt signaler for block processing - engine consensus.Engine - validator Validator // Block and state validator interface - prefetcher Prefetcher - processor Processor // Block transaction processor interface - forker *ForkChoice - vmConfig vm.Config + engine consensus.Engine + validator Validator // Block and state validator interface + prefetcher Prefetcher + processor Processor // Block transaction processor interface + forker *ForkChoice + vmConfig vm.Config + parallelExecution bool } // NewBlockChain returns a fully initialised block chain using information @@ -358,7 +361,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb) bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) - bc.processor = NewStateProcessor(chainConfig, bc, engine) err := proofKeeper.Start(bc, db) if err != nil { @@ -512,6 +514,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) } + if vmConfig.EnableParallelExec { + bc.EnableParallelProcessor(vmConfig.ParallelTxNum) + } else { + bc.processor = NewStateProcessor(chainConfig, bc, engine) + } + // Start future block processor. bc.wg.Add(1) go bc.updateFutureBlocks() @@ -1556,6 +1564,7 @@ func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types // 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) { + if err := bc.writeBlockWithState(block, receipts, state); err != nil { return NonStatTy, err } @@ -1597,6 +1606,7 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types } else { bc.chainSideFeed.Send(ChainSideEvent{Block: block}) } + return status, nil } @@ -1738,7 +1748,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } lastCanon = block - block, err = it.next() } // Falls through to the block import @@ -1878,7 +1887,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. - if !bc.cacheConfig.TrieCleanNoPrefetch { + // parallel mode has a pipeline, similar to this prefetch, to save CPU we disable this prefetch for parallel + if !bc.cacheConfig.TrieCleanNoPrefetch && !bc.parallelExecution { if followup, err := it.peek(); followup != nil && err == nil { throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) @@ -1907,6 +1917,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) 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) @@ -2598,6 +2609,19 @@ func (bc *BlockChain) GetTrieFlushInterval() time.Duration { return time.Duration(bc.flushInterval.Load()) } +func (bc *BlockChain) EnableParallelProcessor(parallelNum int) (*BlockChain, error) { + /* + if bc.snaps == nil { + // disable parallel processor if snapshot is not enabled to avoid concurrent issue for SecureTrie + log.Info("parallel processor is not enabled since snapshot is not enabled") + return bc, nil + } + */ + bc.parallelExecution = true + bc.processor = NewParallelStateProcessor(bc.Config(), bc, bc.engine, parallelNum) + return bc, nil +} + func (bc *BlockChain) NoTries() bool { return bc.stateCache.NoTries() } diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index b2df39d17b..7fe44bf14c 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1794,13 +1794,17 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s config.SnapshotLimit = 256 config.SnapshotWait = true } - chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } + + // fmt.Printf("Dav -- test -- testRepairWithScheme -- chain after NewBlockChain processor: %v, parallel: %v, vmConfig: %v\n", chain.processor, chain.parallelExecution, chain.vmConfig) + // If sidechain blocks are needed, make a light chain and import it var sideblocks types.Blocks if tt.sidechainBlocks > 0 { + //fmt.Printf("Dav -- test -- testRepairWithScheme -- tt.sidechainBlocks: %d\n", tt.sidechainBlocks) sideblocks, _ = GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0x01}) }) @@ -1855,7 +1859,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s } defer db.Close() - newChain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) + newChain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -1927,7 +1931,7 @@ func testIssue23496(t *testing.T, scheme string) { } engine = ethash.NewFullFaker() ) - chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -1977,7 +1981,7 @@ func testIssue23496(t *testing.T, scheme string) { } defer db.Close() - chain, err = NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + chain, err = NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index 1504c74e0e..bccee31216 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -1997,7 +1997,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme config.SnapshotLimit = 256 config.SnapshotWait = true } - chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index 348cc3f473..a84da19b2b 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -81,7 +81,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } engine = ethash.NewFullFaker() ) - chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(basic.scheme), gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(basic.scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -228,7 +228,7 @@ func (snaptest *snapshotTest) test(t *testing.T) { // Restart the chain normally chain.Stop() - newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -270,13 +270,13 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { // the crash, we do restart twice here: one after the crash and one // after the normal stop. It's used to ensure the broken snapshot // can be detected all the time. - newchain, err := NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } newchain.Stop() - newchain, err = NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -313,7 +313,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { SnapshotLimit: 0, StateScheme: snaptest.scheme, } - newchain, err := NewBlockChain(snaptest.db, cacheConfig, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, cacheConfig, snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -321,7 +321,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { newchain.Stop() // Restart the chain with enabling the snapshot - newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -349,7 +349,7 @@ func (snaptest *setHeadSnapshotTest) test(t *testing.T) { chain.SetHead(snaptest.setHead) chain.Stop() - newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -385,7 +385,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { SnapshotLimit: 0, StateScheme: snaptest.scheme, } - newchain, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -402,7 +402,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { SnapshotWait: false, // Don't wait rebuild StateScheme: snaptest.scheme, } - tmp, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + tmp, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -411,7 +411,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { tmp.triedb.Close() tmp.stopWithoutSaving() - newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 22db20a23e..9f799181d9 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -61,7 +61,7 @@ func newCanonical(engine consensus.Engine, n int, full bool, scheme string) (eth } ) // Initialize a fresh chain with only a genesis block - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) // Create and inject the requested chain if n == 0 { @@ -164,7 +164,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { return err } statedb.SetExpectedStateRoot(block.Root()) - receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) + receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}) if err != nil { blockchain.reportBlock(block, receipts, err) return err @@ -741,7 +741,7 @@ func testReorgBadHashes(t *testing.T, full bool, scheme string) { blockchain.Stop() // Create a new BlockChain and check that it rolled back the state. - ncm, err := NewBlockChain(blockchain.db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ncm, err := NewBlockChain(blockchain.db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create new chain manager: %v", err) } @@ -865,7 +865,7 @@ func testFastVsFullChains(t *testing.T, scheme string) { }) // Import the chain as an archive node for the comparison baseline archiveDb := rawdb.NewMemoryDatabase() - archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer archive.Stop() if n, err := archive.InsertChain(blocks); err != nil { @@ -873,7 +873,7 @@ func testFastVsFullChains(t *testing.T, scheme string) { } // Fast import the chain as a non-archive node to test fastDb := rawdb.NewMemoryDatabase() - fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer fast.Stop() headers := make([]*types.Header, len(blocks)) @@ -893,7 +893,7 @@ func testFastVsFullChains(t *testing.T, scheme string) { } defer ancientDb.Close() - ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers); err != nil { @@ -1013,7 +1013,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) { archiveCaching.TrieDirtyDisabled = true archiveCaching.StateScheme = scheme - archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if n, err := archive.InsertChain(blocks); err != nil { t.Fatalf("failed to process block %d: %v", n, err) } @@ -1026,7 +1026,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) { // Import the chain as a non-archive node and ensure all pointers are updated fastDb := makeDb() defer fastDb.Close() - fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer fast.Stop() headers := make([]*types.Header, len(blocks)) @@ -1046,7 +1046,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) { // Import the chain as a ancient-first node and ensure all pointers are updated ancientDb := makeDb() defer ancientDb.Close() - ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers); err != nil { @@ -1065,7 +1065,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) { // Import the chain as a light node and ensure all pointers are updated lightDb := makeDb() defer lightDb.Close() - light, _ := NewBlockChain(lightDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + light, _ := NewBlockChain(lightDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if n, err := light.InsertHeaderChain(headers); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } @@ -1138,7 +1138,7 @@ func testChainTxReorgs(t *testing.T, scheme string) { }) // Import the chain. This runs all block validation rules. db := rawdb.NewMemoryDatabase() - blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if i, err := blockchain.InsertChain(chain); err != nil { t.Fatalf("failed to insert original chain[%d]: %v", i, err) } @@ -1212,7 +1212,7 @@ func testLogReorgs(t *testing.T, scheme string) { signer = types.LatestSigner(gspec.Config) ) - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer blockchain.Stop() rmLogsCh := make(chan RemovedLogsEvent) @@ -1268,7 +1268,7 @@ func testLogRebirth(t *testing.T, scheme string) { gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} signer = types.LatestSigner(gspec.Config) engine = ethash.NewFaker() - blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) ) defer blockchain.Stop() @@ -1349,7 +1349,7 @@ func testSideLogRebirth(t *testing.T, scheme string) { addr1 = crypto.PubkeyToAddress(key1.PublicKey) gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} signer = types.LatestSigner(gspec.Config) - blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) ) defer blockchain.Stop() @@ -1448,7 +1448,7 @@ func testReorgSideEvent(t *testing.T, scheme string) { } signer = types.LatestSigner(gspec.Config) ) - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer blockchain.Stop() _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 3, func(i int, gen *BlockGen) {}) @@ -1631,8 +1631,7 @@ func testEIP155Transition(t *testing.T, scheme string) { block.AddTx(tx) } }) - - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer blockchain.Stop() if _, err := blockchain.InsertChain(blocks); err != nil { @@ -1725,7 +1724,7 @@ func testEIP161AccountRemoval(t *testing.T, scheme string) { block.AddTx(tx) }) // account must exist pre eip 161 - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer blockchain.Stop() if _, err := blockchain.InsertChain(types.Blocks{blocks[0]}); err != nil { @@ -1740,7 +1739,7 @@ func testEIP161AccountRemoval(t *testing.T, scheme string) { t.Fatal(err) } if st, _ := blockchain.State(); st.Exist(theAddr) { - t.Error("account should not exist") + t.Error("account should not exist", "triExist?", st.TriHasAccount(theAddr), "SnapExist?", st.SnapHasAccount(theAddr)) } // account mustn't be created post eip 161 @@ -1783,7 +1782,7 @@ func testBlockchainHeaderchainReorgConsistency(t *testing.T, scheme string) { } // Import the canonical and fork chain side by side, verifying the current block // and current header consistency - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1827,7 +1826,7 @@ func TestTrieForkGC(t *testing.T) { forks[i] = fork[0] } // Import the canonical and fork chain side by side, forcing the trie cache to cache both - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1873,7 +1872,7 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) { db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) defer db.Close() - chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1944,7 +1943,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) { t.Fatalf("failed to create temp freezer db: %v", err) } defer ancientDb.Close() - ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) headers := make([]*types.Header, len(blocks)) for i, block := range blocks { @@ -1964,7 +1963,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) { rawdb.WriteHeadFastBlockHash(ancientDb, midBlock.Hash()) // Reopen broken blockchain again - ancient, _ = NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ = NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer ancient.Stop() if num := ancient.CurrentBlock().Number.Uint64(); num != 0 { t.Errorf("head block mismatch: have #%v, want #%v", num, 0) @@ -2016,7 +2015,7 @@ func testInsertReceiptChainRollback(t *testing.T, scheme string) { } defer ancientDb.Close() - ancientChain, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancientChain, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer ancientChain.Stop() // Import the canonical header chain. @@ -2083,7 +2082,7 @@ func testLowDiffLongChain(t *testing.T, scheme string) { diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) defer diskdb.Close() - chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2145,7 +2144,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon mergeBlock = math.MaxInt32 ) // Generate and import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2171,6 +2170,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon } nonce++ }) + if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } @@ -2178,7 +2178,6 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon lastPrunedIndex := len(blocks) - TriesInMemory - 1 lastPrunedBlock := blocks[lastPrunedIndex] firstNonPrunedBlock := blocks[len(blocks)-TriesInMemory] - // Verify pruning of lastPrunedBlock if chain.HasBlockAndState(lastPrunedBlock.Hash(), lastPrunedBlock.NumberU64()) { t.Errorf("Block %d not pruned", lastPrunedBlock.NumberU64()) @@ -2187,7 +2186,6 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon if !chain.HasBlockAndState(firstNonPrunedBlock.Hash(), firstNonPrunedBlock.NumberU64()) { t.Errorf("Block %d pruned", firstNonPrunedBlock.NumberU64()) } - // Activate the transition in the middle of the chain if mergePoint == 1 { merger.ReachTTD() @@ -2303,7 +2301,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) { } defer chaindb.Close() - chain, err := NewBlockChain(chaindb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(chaindb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2474,7 +2472,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i } defer chaindb.Close() - chain, err := NewBlockChain(chaindb, nil, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(chaindb, nil, genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2588,7 +2586,7 @@ func getLongAndShortChains(scheme string) (*BlockChain, []*types.Block, []*types genDb, longChain, _ := GenerateChainWithGenesis(genesis, engine, 80, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to create tester chain: %v", err) } @@ -2724,6 +2722,191 @@ func testReorgToShorterRemovesCanonMappingHeaderChain(t *testing.T, scheme strin } } +func TestTransactionIndices(t *testing.T) { + // Configure and generate a sample block chain + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(100000000000000000) + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{address: {Balance: funds}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + signer = types.LatestSigner(gspec.Config) + ) + _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 128, func(i int, block *BlockGen) { + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key) + if err != nil { + panic(err) + } + block.AddTx(tx) + }) + + check := func(tail *uint64, chain *BlockChain) { + stored := rawdb.ReadTxIndexTail(chain.db) + if tail == nil && stored != nil { + t.Fatalf("Oldest indexded block mismatch, want nil, have %d", *stored) + } + if tail != nil && *stored != *tail { + t.Fatalf("Oldest indexded block mismatch, want %d, have %d", *tail, *stored) + } + if tail != nil { + for i := *tail; i <= chain.CurrentBlock().Number.Uint64(); i++ { + block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) + if block.Transactions().Len() == 0 { + continue + } + for _, tx := range block.Transactions() { + if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index == nil { + t.Fatalf("Miss transaction indice, number %d hash %s", i, tx.Hash().Hex()) + } + } + } + for i := uint64(0); i < *tail; i++ { + block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) + if block.Transactions().Len() == 0 { + continue + } + for _, tx := range block.Transactions() { + if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index != nil { + t.Fatalf("Transaction indice should be deleted, number %d hash %s", i, tx.Hash().Hex()) + } + } + } + } + } + // Init block chain with external ancients, check all needed indices has been indexed. + limit := []uint64{0, 32, 64, 128} + for _, l := range limit { + frdir := t.TempDir() + ancientDb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) + rawdb.WriteAncientBlocks(ancientDb, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) + + l := l + chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &l) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + chain.indexBlocks(rawdb.ReadTxIndexTail(ancientDb), 128, make(chan struct{})) + + var tail uint64 + if l != 0 { + tail = uint64(128) - l + 1 + } + check(&tail, chain) + chain.Stop() + ancientDb.Close() + os.RemoveAll(frdir) + } + + // Reconstruct a block chain which only reserves HEAD-64 tx indices + ancientDb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + defer ancientDb.Close() + + rawdb.WriteAncientBlocks(ancientDb, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) + limit = []uint64{0, 64 /* drop stale */, 32 /* shorten history */, 64 /* extend history */, 0 /* restore all */} + for _, l := range limit { + l := l + chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &l) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + var tail uint64 + if l != 0 { + tail = uint64(128) - l + 1 + } + chain.indexBlocks(rawdb.ReadTxIndexTail(ancientDb), 128, make(chan struct{})) + check(&tail, chain) + chain.Stop() + } +} + +func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { + testSkipStaleTxIndicesInSnapSync(t, rawdb.HashScheme) + testSkipStaleTxIndicesInSnapSync(t, rawdb.PathScheme) +} + +func testSkipStaleTxIndicesInSnapSync(t *testing.T, scheme string) { + // Configure and generate a sample block chain + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(100000000000000000) + gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} + signer = types.LatestSigner(gspec.Config) + ) + _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 128, func(i int, block *BlockGen) { + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key) + if err != nil { + panic(err) + } + block.AddTx(tx) + }) + + check := func(tail *uint64, chain *BlockChain) { + stored := rawdb.ReadTxIndexTail(chain.db) + if tail == nil && stored != nil { + t.Fatalf("Oldest indexded block mismatch, want nil, have %d", *stored) + } + if tail != nil && *stored != *tail { + t.Fatalf("Oldest indexded block mismatch, want %d, have %d", *tail, *stored) + } + if tail != nil { + for i := *tail; i <= chain.CurrentBlock().Number.Uint64(); i++ { + block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) + if block.Transactions().Len() == 0 { + continue + } + for _, tx := range block.Transactions() { + if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index == nil { + t.Fatalf("Miss transaction indice, number %d hash %s", i, tx.Hash().Hex()) + } + } + } + for i := uint64(0); i < *tail; i++ { + block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) + if block.Transactions().Len() == 0 { + continue + } + for _, tx := range block.Transactions() { + if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index != nil { + t.Fatalf("Transaction indice should be deleted, number %d hash %s", i, tx.Hash().Hex()) + } + } + } + } + } + + ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + if err != nil { + t.Fatalf("failed to create temp freezer db: %v", err) + } + defer ancientDb.Close() + + // Import all blocks into ancient db, only HEAD-32 indices are kept. + l := uint64(32) + chain, err := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &l) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + if n, err := chain.InsertHeaderChain(headers); err != nil { + t.Fatalf("failed to insert header %d: %v", n, err) + } + // The indices before ancient-N(32) should be ignored. After that all blocks should be indexed. + if n, err := chain.InsertReceiptChain(blocks, receipts, 64); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + tail := uint64(32) + check(&tail, chain) +} + // Benchmarks large blocks with value transfers to non-existing accounts func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) { var ( @@ -2764,7 +2947,7 @@ func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks in b.ResetTimer() for i := 0; i < b.N; i++ { // Import the shared chain and the original canonical one - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { b.Fatalf("failed to create tester chain: %v", err) } @@ -2851,7 +3034,7 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) { // Generate and import the canonical chain _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*TriesInMemory, nil) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2951,7 +3134,7 @@ func testDeleteCreateRevert(t *testing.T, scheme string) { b.AddTx(tx) }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3549,7 +3732,7 @@ func testEIP2718Transition(t *testing.T, scheme string) { }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3643,7 +3826,7 @@ func testEIP1559Transition(t *testing.T, scheme string) { b.AddTx(tx) }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3755,7 +3938,8 @@ func testSetCanonical(t *testing.T, scheme string) { diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) defer diskdb.Close() - chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, + vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3864,7 +4048,7 @@ func testCanonicalHashMarker(t *testing.T, scheme string) { _, forkB, _ := GenerateChainWithGenesis(gspec, engine, c.forkB, func(i int, gen *BlockGen) {}) // Initialize test chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3920,6 +4104,212 @@ func testCanonicalHashMarker(t *testing.T, scheme string) { } } +// TestTxIndexer tests the tx indexes are updated correctly. +func TestTxIndexer(t *testing.T) { + var ( + testBankKey, _ = crypto.GenerateKey() + testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) + testBankFunds = big.NewInt(1000000000000000000) + + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + engine = ethash.NewFaker() + nonce = uint64(0) + ) + _, blocks, receipts := GenerateChainWithGenesis(gspec, engine, 128, func(i int, gen *BlockGen) { + tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey) + gen.AddTx(tx) + nonce += 1 + }) + + // verifyIndexes checks if the transaction indexes are present or not + // of the specified block. + verifyIndexes := func(db ethdb.Database, number uint64, exist bool) { + if number == 0 { + return + } + block := blocks[number-1] + for _, tx := range block.Transactions() { + lookup := rawdb.ReadTxLookupEntry(db, tx.Hash()) + if exist && lookup == nil { + t.Fatalf("missing %d %x", number, tx.Hash().Hex()) + } + if !exist && lookup != nil { + t.Fatalf("unexpected %d %x", number, tx.Hash().Hex()) + } + } + } + // verifyRange runs verifyIndexes for a range of blocks, from and to are included. + verifyRange := func(db ethdb.Database, from, to uint64, exist bool) { + for number := from; number <= to; number += 1 { + verifyIndexes(db, number, exist) + } + } + verify := func(db ethdb.Database, expTail uint64) { + tail := rawdb.ReadTxIndexTail(db) + if tail == nil { + t.Fatal("Failed to write tx index tail") + } + if *tail != expTail { + t.Fatalf("Unexpected tx index tail, want %v, got %d", expTail, *tail) + } + if *tail != 0 { + verifyRange(db, 0, *tail-1, false) + } + verifyRange(db, *tail, 128, true) + } + + var cases = []struct { + limitA uint64 + tailA uint64 + limitB uint64 + tailB uint64 + limitC uint64 + tailC uint64 + }{ + { + // LimitA: 0 + // TailA: 0 + // + // all blocks are indexed + limitA: 0, + tailA: 0, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + { + // LimitA: 64 + // TailA: 65 + // + // block [65, 128] are indexed + limitA: 64, + tailA: 65, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + { + // LimitA: 127 + // TailA: 2 + // + // block [2, 128] are indexed + limitA: 127, + tailA: 2, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + { + // LimitA: 128 + // TailA: 1 + // + // block [2, 128] are indexed + limitA: 128, + tailA: 1, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + { + // LimitA: 129 + // TailA: 0 + // + // block [0, 128] are indexed + limitA: 129, + tailA: 0, + + // LimitB: 1 + // TailB: 128 + // + // block-128 is indexed + limitB: 1, + tailB: 128, + + // LimitB: 64 + // TailB: 65 + // + // block [65, 128] are indexed + limitC: 64, + tailC: 65, + }, + } + for _, c := range cases { + frdir := t.TempDir() + db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) + rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) + + // Index the initial blocks from ancient store + chain, _ := NewBlockChain(db, nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &c.limitA) + chain.indexBlocks(nil, 128, make(chan struct{})) + verify(db, c.tailA) + + chain.SetTxLookupLimit(c.limitB) + chain.indexBlocks(rawdb.ReadTxIndexTail(db), 128, make(chan struct{})) + verify(db, c.tailB) + + chain.SetTxLookupLimit(c.limitC) + chain.indexBlocks(rawdb.ReadTxIndexTail(db), 128, make(chan struct{})) + verify(db, c.tailC) + + // Recover all indexes + chain.SetTxLookupLimit(0) + chain.indexBlocks(rawdb.ReadTxIndexTail(db), 128, make(chan struct{})) + verify(db, 0) + + chain.Stop() + db.Close() + os.RemoveAll(frdir) + } +} + func TestCreateThenDeletePreByzantium(t *testing.T) { // We use Ropsten chain config instead of Testchain config, this is // deliberate: we want to use pre-byz rules where we have intermediate state roots @@ -4113,7 +4503,7 @@ func TestDeleteThenCreate(t *testing.T) { } }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index b46b898afb..12a0b00b0e 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -124,7 +124,7 @@ func TestGeneratePOSChain(t *testing.T) { }) // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, nil, gspec, nil, beacon.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, nil, gspec, nil, beacon.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer blockchain.Stop() if i, err := blockchain.InsertChain(genchain); err != nil { @@ -198,7 +198,7 @@ func ExampleGenerateChain() { db = rawdb.NewMemoryDatabase() genDb = rawdb.NewMemoryDatabase() ) - + // Ensure that key1 has some funds in the genesis block. gspec := &Genesis{ Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, @@ -239,7 +239,7 @@ func ExampleGenerateChain() { }) // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.HashScheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.HashScheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer blockchain.Stop() if i, err := blockchain.InsertChain(chain); err != nil { diff --git a/core/dao_test.go b/core/dao_test.go index b9a899ef2f..3d3192f5bd 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -50,7 +50,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), Config: &proConf, } - proBc, _ := NewBlockChain(proDb, nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + proBc, _ := NewBlockChain(proDb, nil, progspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer proBc.Stop() conDb := rawdb.NewMemoryDatabase() @@ -62,7 +62,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), Config: &conConf, } - conBc, _ := NewBlockChain(conDb, nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + conBc, _ := NewBlockChain(conDb, nil, congspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer conBc.Stop() if _, err := proBc.InsertChain(prefix); err != nil { @@ -74,7 +74,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { // Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks for i := int64(0); i < params.DAOForkExtraRange.Int64(); i++ { // Create a pro-fork block, and try to feed into the no-fork chain - bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64())) for j := 0; j < len(blocks)/2; j++ { @@ -97,7 +97,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err) } // Create a no-fork block, and try to feed into the pro-fork chain - bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64())) for j := 0; j < len(blocks)/2; j++ { @@ -121,7 +121,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { } } // Verify that contra-forkers accept pro-fork extra-datas after forking finishes - bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer bc.Stop() blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64())) @@ -139,7 +139,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err) } // Verify that pro-forkers accept contra-fork extra-datas after forking finishes - bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer bc.Stop() blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64())) diff --git a/core/error.go b/core/error.go index 8c691b17ff..0e8f8286c2 100644 --- a/core/error.go +++ b/core/error.go @@ -113,4 +113,7 @@ var ( // ErrSystemTxNotSupported is returned for any deposit tx with IsSystemTx=true after the Regolith fork ErrSystemTxNotSupported = errors.New("system tx not supported") + + // ErrParallelUnexpectedConflict is returned when execution finally get conflict error for more than block tx number + ErrParallelUnexpectedConflict = errors.New("parallel execution unexpected conflict") ) diff --git a/core/genesis_test.go b/core/genesis_test.go index 61be0bd252..6b70c2774e 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -133,7 +133,7 @@ func testSetupGenesis(t *testing.T, scheme string) { tdb := triedb.NewDatabase(db, newDbConfig(scheme)) oldcustomg.Commit(db, tdb) - bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) + bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer bc.Stop() _, blocks, _ := GenerateChainWithGenesis(&oldcustomg, ethash.NewFaker(), 4, nil) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go new file mode 100644 index 0000000000..5c47bd1b14 --- /dev/null +++ b/core/parallel_state_processor.go @@ -0,0 +1,822 @@ +package core + +import ( + "errors" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "runtime" + "sync" + "sync/atomic" +) + +const ( + parallelPrimarySlot = 0 + parallelShadowSlot = 1 + stage2CheckNumber = 30 // ConfirmStage2 will check this number of transaction, to avoid too busy stage2 check + stage2AheadNum = 3 // enter ConfirmStage2 in advance to avoid waiting for Fat Tx +) + +type ParallelStateProcessor struct { + StateProcessor + parallelNum int // leave a CPU to dispatcher + slotState []*SlotState // idle, or pending messages + allTxReqs []*ParallelTxRequest + txResultChan chan *ParallelTxResult // to notify dispatcher that a tx is done + mergedTxIndex int // the latest finalized tx index, fixme: use Atomic + pendingConfirmResults map[int][]*ParallelTxResult // tx could be executed several times, with several result to check + unconfirmedResults *sync.Map // this is for stage2 confirm, since pendingConfirmResults can not be accessed in stage2 loop + unconfirmedDBs *sync.Map + slotDBsToRelease []*state.ParallelStateDB + stopSlotChan chan struct{} + stopConfirmChan chan struct{} + debugConflictRedoNum int + // start for confirm stage2 + confirmStage2Chan chan int + stopConfirmStage2Chan chan struct{} + txReqExecuteRecord map[int]int + txReqExecuteCount int + inConfirmStage2 bool + targetStage2Count int // when executed txNUM reach it, enter stage2 RT confirm + nextStage2TxIndex int +} + +func NewParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine, parallelNum int) *ParallelStateProcessor { + processor := &ParallelStateProcessor{ + StateProcessor: *NewStateProcessor(config, bc, engine), + parallelNum: parallelNum, + } + processor.init() + return processor +} + +type MergedTxInfo struct { + slotDB *state.StateDB // used for SlotDb reuse only, otherwise, it can be discarded + StateObjectSuicided map[common.Address]struct{} + StateChangeSet map[common.Address]state.StateKeys + BalanceChangeSet map[common.Address]struct{} + CodeChangeSet map[common.Address]struct{} + AddrStateChangeSet map[common.Address]struct{} + txIndex int +} + +type SlotState struct { + pendingTxReqList []*ParallelTxRequest + primaryWakeUpChan chan struct{} + shadowWakeUpChan chan struct{} + primaryStopChan chan struct{} + shadowStopChan chan struct{} + activatedType int32 // 0: primary slot, 1: shadow slot +} + +type ParallelTxResult struct { + executedIndex int32 // the TxReq can be executed several time, increase index for each execution + slotIndex int // slot index + txReq *ParallelTxRequest + receipt *types.Receipt + slotDB *state.ParallelStateDB // if updated, it is not equal to txReq.slotDB + gpSlot *GasPool + evm *vm.EVM + result *ExecutionResult + originalNonce *uint64 + err error +} + +type ParallelTxRequest struct { + txIndex int + baseStateDB *state.StateDB + staticSlotIndex int // static dispatched id + tx *types.Transaction + gasLimit uint64 + msg *Message + block *types.Block + vmConfig vm.Config + usedGas *uint64 + curTxChan chan int + systemAddrRedo bool + runnable int32 // 0: not runnable, 1: runnable + executedNum int32 + retryNum int32 +} + +// to create and start the execution slot goroutines +func (p *ParallelStateProcessor) init() { + log.Info("Parallel execution mode is enabled", "Parallel Num", p.parallelNum, + "CPUNum", runtime.NumCPU()) + p.txResultChan = make(chan *ParallelTxResult, 200) + p.stopSlotChan = make(chan struct{}, 1) + p.stopConfirmChan = make(chan struct{}, 1) + p.stopConfirmStage2Chan = make(chan struct{}, 1) + + p.slotState = make([]*SlotState, p.parallelNum) + for i := 0; i < p.parallelNum; i++ { + p.slotState[i] = &SlotState{ + primaryWakeUpChan: make(chan struct{}, 1), + shadowWakeUpChan: make(chan struct{}, 1), + primaryStopChan: make(chan struct{}, 1), + shadowStopChan: make(chan struct{}, 1), + } + // start the primary slot's goroutine + go func(slotIndex int) { + p.runSlotLoop(slotIndex, parallelPrimarySlot) // this loop will be permanent live + }(i) + + // start the shadow slot. + // It is back up of the primary slot to make sure transaction can be redone ASAP, + // since the primary slot could be busy at executing another transaction + go func(slotIndex int) { + p.runSlotLoop(slotIndex, parallelShadowSlot) // this loop will be permanent live + }(i) + + } + + p.confirmStage2Chan = make(chan int, 10) + go func() { + p.runConfirmStage2Loop() // this loop will be permanent live + }() +} + +// clear slot state for each block. +func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { + if txNum == 0 { + return + } + p.mergedTxIndex = -1 + p.debugConflictRedoNum = 0 + p.inConfirmStage2 = false + + statedb.PrepareForParallel() + p.allTxReqs = make([]*ParallelTxRequest, 0) + p.slotDBsToRelease = make([]*state.ParallelStateDB, 0, txNum) + + stateDBsToRelease := p.slotDBsToRelease + go func() { + for _, slotDB := range stateDBsToRelease { + slotDB.PutSyncPool() + } + }() + for _, slot := range p.slotState { + slot.pendingTxReqList = make([]*ParallelTxRequest, 0) + slot.activatedType = parallelPrimarySlot + } + p.unconfirmedResults = new(sync.Map) + p.unconfirmedDBs = new(sync.Map) + p.pendingConfirmResults = make(map[int][]*ParallelTxResult, 200) + p.txReqExecuteRecord = make(map[int]int, 200) + p.txReqExecuteCount = 0 + p.nextStage2TxIndex = 0 +} + +// Benefits of StaticDispatch: +// +// ** try best to make Txs with same From() in same slot +// ** reduce IPC cost by dispatch in Unit +// ** make sure same From in same slot +// ** try to make it balanced, queue to the most hungry slot for new Address +func (p *ParallelStateProcessor) doStaticDispatch(txReqs []*ParallelTxRequest) { + fromSlotMap := make(map[common.Address]int, 100) + toSlotMap := make(map[common.Address]int, 100) + for _, txReq := range txReqs { + var slotIndex = -1 + if i, ok := fromSlotMap[txReq.msg.From]; ok { + // first: same From are all in same slot + slotIndex = i + } else if txReq.msg.To != nil { + // To Address, with txIndex sorted, could be in different slot. + if i, ok := toSlotMap[*txReq.msg.To]; ok { + slotIndex = i + } + } + + // not found, dispatch to most hungry slot + if slotIndex == -1 { + var workload = len(p.slotState[0].pendingTxReqList) + slotIndex = 0 + for i, slot := range p.slotState { // can start from index 1 + if len(slot.pendingTxReqList) < workload { + slotIndex = i + workload = len(slot.pendingTxReqList) + } + } + } + // update + fromSlotMap[txReq.msg.From] = slotIndex + if txReq.msg.To != nil { + toSlotMap[*txReq.msg.To] = slotIndex + } + + slot := p.slotState[slotIndex] + txReq.staticSlotIndex = slotIndex // txReq is better to be executed in this slot + slot.pendingTxReqList = append(slot.pendingTxReqList, txReq) + } +} + +// do conflict detect +func (p *ParallelStateProcessor) hasConflict(txResult *ParallelTxResult, isStage2 bool) bool { + slotDB := txResult.slotDB + if txResult.err != nil { + return true + } else if slotDB.NeedsRedo() { + // if this is any reason that indicates this transaction needs to redo, skip the conflict check + return true + } else { + // to check if what the slot db read is correct. + if !slotDB.IsParallelReadsValid(isStage2) { + return true + } + } + return false +} + +func (p *ParallelStateProcessor) switchSlot(slotIndex int) { + slot := p.slotState[slotIndex] + if atomic.CompareAndSwapInt32(&slot.activatedType, parallelPrimarySlot, parallelShadowSlot) { + // switch from normal to shadow slot + if len(slot.shadowWakeUpChan) == 0 { + slot.shadowWakeUpChan <- struct{}{} // only notify when target once + } + } else if atomic.CompareAndSwapInt32(&slot.activatedType, parallelShadowSlot, parallelPrimarySlot) { + // switch from shadow to normal slot + if len(slot.primaryWakeUpChan) == 0 { + slot.primaryWakeUpChan <- struct{}{} // only notify when target once + } + } +} + +func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxRequest) *ParallelTxResult { + atomic.AddInt32(&txReq.executedNum, 1) + slotDB := state.NewSlotDB(txReq.baseStateDB, txReq.txIndex, p.mergedTxIndex, p.unconfirmedDBs) + + blockContext := NewEVMBlockContext(txReq.block.Header(), p.bc, nil, p.config, slotDB) // can share blockContext within a block for efficiency + txContext := NewEVMTxContext(txReq.msg) + vmenv := vm.NewEVM(blockContext, txContext, slotDB, p.config, txReq.vmConfig) + + rules := p.config.Rules(txReq.block.Number(), blockContext.Random != nil, blockContext.Time) + slotDB.Prepare(rules, txReq.msg.From, vmenv.Context.Coinbase, txReq.msg.To, vm.ActivePrecompiles(rules), txReq.msg.AccessList) + + // gasLimit not accurate, but it is ok for block import. + // each slot would use its own gas pool, and will do gas limit check later + gpSlot := new(GasPool).AddGas(txReq.gasLimit) // block.GasLimit() + + on := txReq.tx.Nonce() + if txReq.msg.IsDepositTx && p.config.IsOptimismRegolith(vmenv.Context.Time) { + on = txReq.baseStateDB.GetNonce(txReq.msg.From) + } + + slotDB.SetTxContext(txReq.tx.Hash(), txReq.txIndex) + + evm, result, err := applyTransactionStageExecution(txReq.msg, gpSlot, slotDB, vmenv) + txResult := ParallelTxResult{ + executedIndex: atomic.LoadInt32(&txReq.executedNum), + slotIndex: slotIndex, + txReq: txReq, + receipt: nil, // receipt is generated in finalize stage + slotDB: slotDB, + err: err, + gpSlot: gpSlot, + evm: evm, + result: result, + originalNonce: &on, + } + + if err == nil { + p.unconfirmedDBs.Store(txReq.txIndex, slotDB) + } else { + // the transaction failed at check(nonce or balance), actually it has not been executed yet. + atomic.CompareAndSwapInt32(&txReq.runnable, 0, 1) + // the error could be caused by unconfirmed balance reference, + // the balance could insufficient to pay its gas limit, which cause it preCheck.buyGas() failed + // redo could solve it. + log.Debug("In slot execution error", "error", err, + "slotIndex", slotIndex, "txIndex", txReq.txIndex) + } + p.unconfirmedResults.Store(txReq.txIndex, &txResult) + return &txResult +} + +// to confirm a serial TxResults with same txIndex +func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bool) *ParallelTxResult { + if isStage2 { + if targetTxIndex <= p.mergedTxIndex+1 { + // `p.mergedTxIndex+1` is the one to be merged, + // in stage2, we do likely conflict check, for these not their turn. + return nil + } + } + + for { + // handle a targetTxIndex in a loop + var targetResult *ParallelTxResult + if isStage2 { + result, ok := p.unconfirmedResults.Load(targetTxIndex) + if !ok { + return nil + } + targetResult = result.(*ParallelTxResult) + + // in stage 2, don't schedule a new redo if the TxReq is: + // a.runnable: it will be redone + // b.running: the new result will be more reliable, we skip check right now + if atomic.LoadInt32(&targetResult.txReq.runnable) == 1 { + return nil + } + if targetResult.executedIndex < atomic.LoadInt32(&targetResult.txReq.executedNum) { + // skip the intermediate result that is not the latest. + return nil + } + } else { + // pop one result as target result. + results := p.pendingConfirmResults[targetTxIndex] + resultsLen := len(results) + if resultsLen == 0 { // there is no pending result can be verified, break and wait for incoming results + return nil + } + targetResult = results[len(results)-1] + // last is the freshest, stack based priority + p.pendingConfirmResults[targetTxIndex] = p.pendingConfirmResults[targetTxIndex][:resultsLen-1] // remove from the queue + } + + valid := p.toConfirmTxIndexResult(targetResult, isStage2) + if !valid { + staticSlotIndex := targetResult.txReq.staticSlotIndex // it is better to run the TxReq in its static dispatch slot + if isStage2 { + atomic.CompareAndSwapInt32(&targetResult.txReq.runnable, 0, 1) // needs redo + p.debugConflictRedoNum++ + // interrupt the slot's current routine, and switch to the other routine + p.switchSlot(staticSlotIndex) + return nil + } + + if len(p.pendingConfirmResults[targetTxIndex]) == 0 { // this is the last result to check, and it is not valid + blockTxCount := targetResult.txReq.block.Transactions().Len() + // This means that the tx has been executed more than blockTxCount times, so it exits with the error. + // TODO-dav: p.mergedTxIndex+2 may be more reasonable? - this is buggy for expected exit + if targetResult.txReq.txIndex == p.mergedTxIndex+1 { + // txReq is the next to merge + if atomic.LoadInt32(&targetResult.txReq.retryNum) <= int32(blockTxCount)+3000 { + atomic.AddInt32(&targetResult.txReq.retryNum, 1) + // conflict retry + } else { + // retry 100 times and still conflict, either the tx is expected to be wrong, or something wrong. + if targetResult.err != nil { + fmt.Printf("!!!!!!!!!!! Parallel execution exited with error!!!!!, txIndex:%d, err: %v\n", targetResult.txReq.txIndex, targetResult.err) + return targetResult + } else { + // abnormal exit with conflict error, need check the parallel algorithm + targetResult.err = ErrParallelUnexpectedConflict + + fmt.Printf("!!!!!!!!!!! Parallel execution exited unexpected conflict!!!!!, txIndex:%d\n", targetResult.txReq.txIndex) + + return targetResult + } + } + } + atomic.CompareAndSwapInt32(&targetResult.txReq.runnable, 0, 1) // needs redo + p.debugConflictRedoNum++ + // interrupt its current routine, and switch to the other routine + p.switchSlot(staticSlotIndex) + return nil + } + continue + } + if isStage2 { + // likely valid, but not sure, can not deliver + return nil + } + return targetResult + } +} + +// to confirm one txResult, return true if the result is valid +// if it is in Stage 2 it is a likely result, not 100% sure +func (p *ParallelStateProcessor) toConfirmTxIndexResult(txResult *ParallelTxResult, isStage2 bool) bool { + txReq := txResult.txReq + if p.hasConflict(txResult, isStage2) { + log.Debug("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex) + return false + } + if isStage2 { // not its turn + return true // likely valid, not sure, not finalized right now. + } + + // goroutine unsafe operation will be handled from here for safety + gasConsumed := txReq.gasLimit - txResult.gpSlot.Gas() + if gasConsumed != txResult.result.UsedGas { + log.Error("gasConsumed != result.UsedGas mismatch", + "gasConsumed", gasConsumed, "result.UsedGas", txResult.result.UsedGas) + } + + // ok, time to do finalize, stage2 should not be parallel + header := txReq.block.Header() + txResult.receipt, txResult.err = applyTransactionStageFinalization(txResult.evm, txResult.result, + *txReq.msg, p.config, txResult.slotDB, header, + txReq.tx, txReq.usedGas, txResult.originalNonce) + return true +} + +func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { + curSlot := p.slotState[slotIndex] + var wakeupChan chan struct{} + var stopChan chan struct{} + + if slotType == parallelPrimarySlot { + wakeupChan = curSlot.primaryWakeUpChan + stopChan = curSlot.primaryStopChan + } else { + wakeupChan = curSlot.shadowWakeUpChan + stopChan = curSlot.shadowStopChan + } + for { + select { + case <-stopChan: + p.stopSlotChan <- struct{}{} + continue + case <-wakeupChan: + } + + interrupted := false + for _, txReq := range curSlot.pendingTxReqList { + if txReq.txIndex <= p.mergedTxIndex { + continue + } + + if atomic.LoadInt32(&curSlot.activatedType) != slotType { + interrupted = true + // fmt.Printf("Dav -- runInLoop, - activatedType - TxREQ: %d\n", txReq.txIndex) + + break + } + if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { + // not swapped: txReq.runnable == 0 + //fmt.Printf("Dav -- runInLoop, - not runnable - TxREQ: %d\n", txReq.txIndex) + continue + } + // fmt.Printf("Dav -- runInLoop, - executeInSlot - TxREQ: %d\n", txReq.txIndex) + p.txResultChan <- p.executeInSlot(slotIndex, txReq) + // fmt.Printf("Dav -- runInLoop, - loopbody tail - TxREQ: %d\n", txReq.txIndex) + } + // switched to the other slot. + if interrupted { + continue + } + + // txReq in this Slot have all been executed, try steal one from other slot. + // as long as the TxReq is runnable, we steal it, mark it as stolen + for _, stealTxReq := range p.allTxReqs { + // fmt.Printf("Dav -- stealLoop, handle TxREQ: %d\n", stealTxReq.txIndex) + if stealTxReq.txIndex <= p.mergedTxIndex { + // fmt.Printf("Dav -- stealLoop, - txReq.txIndex <= p.mergedTxIndex - TxREQ: %d\n", stealTxReq.txIndex) + continue + } + if atomic.LoadInt32(&curSlot.activatedType) != slotType { + interrupted = true + // fmt.Printf("Dav -- stealLoop, - activatedType - TxREQ: %d\n", stealTxReq.txIndex) + + break + } + + if !atomic.CompareAndSwapInt32(&stealTxReq.runnable, 1, 0) { + // not swapped: txReq.runnable == 0 + // fmt.Printf("Dav -- stealLoop, - not runnable - TxREQ: %d\n", stealTxReq.txIndex) + + continue + } + // fmt.Printf("Dav -- stealLoop, - executeInSlot - TxREQ: %d\n", stealTxReq.txIndex) + p.txResultChan <- p.executeInSlot(slotIndex, stealTxReq) + // fmt.Printf("Dav -- stealLoop, - loopbody tail - TxREQ: %d\n", stealTxReq.txIndex) + } + } +} + +func (p *ParallelStateProcessor) runConfirmStage2Loop() { + for { + // var mergedTxIndex int + select { + case <-p.stopConfirmStage2Chan: + for len(p.confirmStage2Chan) > 0 { + <-p.confirmStage2Chan + } + p.stopSlotChan <- struct{}{} + continue + case <-p.confirmStage2Chan: + for len(p.confirmStage2Chan) > 0 { + <-p.confirmStage2Chan // drain the chan to get the latest merged txIndex + } + } + // stage 2,if all tx have been executed at least once, and its result has been received. + // in Stage 2, we will run check when merge is advanced. + // more aggressive tx result confirm, even for these Txs not in turn + // now we will be more aggressive: + // do conflict check , as long as tx result is generated, + // if lucky, it is the Tx's turn, we will do conflict check with WBNB makeup + // otherwise, do conflict check without WBNB makeup, but we will ignore WBNB's balance conflict. + // throw these likely conflicted tx back to re-execute + startTxIndex := p.mergedTxIndex + 2 // stage 2's will start from the next target merge index + endTxIndex := startTxIndex + stage2CheckNumber + txSize := len(p.allTxReqs) + if endTxIndex > (txSize - 1) { + endTxIndex = txSize - 1 + } + log.Debug("runConfirmStage2Loop", "startTxIndex", startTxIndex, "endTxIndex", endTxIndex) + // conflictNumMark := p.debugConflictRedoNum + for txIndex := startTxIndex; txIndex < endTxIndex; txIndex++ { + p.toConfirmTxIndex(txIndex, true) + } + // make sure all slots are wake up + for i := 0; i < p.parallelNum; i++ { + p.switchSlot(i) + } + } + +} + +func (p *ParallelStateProcessor) handleTxResults() *ParallelTxResult { + confirmedResult := p.toConfirmTxIndex(p.mergedTxIndex+1, false) + if confirmedResult == nil { + return nil + } + // schedule stage 2 when new Tx has been merged, schedule once and ASAP + // stage 2,if all tx have been executed at least once, and its result has been received. + // in Stage 2, we will run check when main DB is advanced, i.e., new Tx result has been merged. + if p.inConfirmStage2 && p.mergedTxIndex >= p.nextStage2TxIndex { + p.nextStage2TxIndex = p.mergedTxIndex + stage2CheckNumber + p.confirmStage2Chan <- p.mergedTxIndex + } + return confirmedResult +} + +// wait until the next Tx is executed and its result is merged to the main stateDB +func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *GasPool) *ParallelTxResult { + result := p.handleTxResults() + if result == nil { + return nil + } + // ok, the tx result is valid and can be merged + if result.err != nil { + return result + } + + if err := gp.SubGas(result.receipt.GasUsed); err != nil { + log.Error("gas limit reached", "block", result.txReq.block.Number(), + "txIndex", result.txReq.txIndex, "GasUsed", result.receipt.GasUsed, "gp.Gas", gp.Gas()) + } + + resultTxIndex := result.txReq.txIndex + + var root []byte + header := result.txReq.block.Header() + if p.config.IsByzantium(header.Number) { + result.slotDB.FinaliseForParallel(true, statedb) + } else { + root = result.slotDB.IntermediateRootForSlotDB(p.config.IsEIP158(header.Number), statedb).Bytes() + } + result.receipt.PostState = root + // merge slotDB into mainDB + statedb.MergeSlotDB(result.slotDB, result.receipt, resultTxIndex) + + if resultTxIndex != p.mergedTxIndex+1 { + log.Error("ProcessParallel tx result out of order", "resultTxIndex", resultTxIndex, + "p.mergedTxIndex", p.mergedTxIndex) + } + p.mergedTxIndex = resultTxIndex + + return result +} + +func (p *ParallelStateProcessor) doCleanUp() { + // 1.clean up all slot: primary and shadow, to make sure they are stopped + for _, slot := range p.slotState { + slot.primaryStopChan <- struct{}{} + slot.shadowStopChan <- struct{}{} + <-p.stopSlotChan + <-p.stopSlotChan + } + // 2.discard delayed txResults if any + for { + if len(p.txResultChan) > 0 { // drop prefetch addr? + <-p.txResultChan + continue + } + break + } + // 3.make sure the confirmation routine is stopped + p.stopConfirmStage2Chan <- struct{}{} + <-p.stopSlotChan +} + +// Implement BEP-130: Parallel Transaction Execution. +func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { + var ( + receipts types.Receipts + usedGas = new(uint64) + header = block.Header() + gp = new(GasPool).AddGas(block.GasLimit()) + ) + + // Mutate the block and state according to any hard-fork specs + if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { + misc.ApplyDAOHardFork(statedb) + } + if p.config.PreContractForkBlock != nil && p.config.PreContractForkBlock.Cmp(block.Number()) == 0 { + misc.ApplyPreContractHardFork(statedb) + } + + txNum := len(block.Transactions()) + p.resetState(txNum, statedb) + + // Iterate over and process the individual transactions + commonTxs := make([]*types.Transaction, 0, txNum) + + var ( + // with parallel mode, vmenv will be created inside of slot + blockContext = NewEVMBlockContext(block.Header(), p.bc, nil, p.config, statedb) + vmenv = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) + signer = types.MakeSigner(p.bc.chainConfig, block.Number(), block.Time()) + ) + + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } + + // var txReqs []*ParallelTxRequest + for i, tx := range block.Transactions() { + // can be moved it into slot for efficiency, but signer is not concurrent safe + // Parallel Execution 1.0&2.0 is for full sync mode, Nonce PreCheck is not necessary + // And since we will do out-of-order execution, the Nonce PreCheck could fail. + // We will disable it and leave it to Parallel 3.0 which is for validator mode + msg, err := TransactionToMessage(tx, signer, header.BaseFee) + if err != nil { + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + } + + // parallel start, wrap an exec message, which will be dispatched to a slot + txReq := &ParallelTxRequest{ + txIndex: i, + baseStateDB: statedb, + staticSlotIndex: -1, + tx: tx, + gasLimit: block.GasLimit(), // gp.Gas(). + msg: msg, + block: block, + vmConfig: cfg, + usedGas: usedGas, + curTxChan: make(chan int, 1), + systemAddrRedo: false, // set to true, when systemAddr access is detected. + runnable: 1, // 0: not runnable, 1: runnable + executedNum: 0, + retryNum: 0, + } + p.allTxReqs = append(p.allTxReqs, txReq) + } + // set up stage2 enter criteria + p.targetStage2Count = len(p.allTxReqs) + if p.targetStage2Count > 50 { + // usually, the last Tx could be the bottleneck it could be very slow, + // so it is better for us to enter stage 2 a bit earlier + p.targetStage2Count = p.targetStage2Count - stage2AheadNum + } + + p.doStaticDispatch(p.allTxReqs) // todo: put txReqs in unit? + + // after static dispatch, we notify the slot to work. + for _, slot := range p.slotState { + slot.primaryWakeUpChan <- struct{}{} + } + + // wait until all Txs have processed. + for { + if len(commonTxs) == txNum { + // put it ahead of chan receive to avoid waiting for empty block + break + } + unconfirmedResult := <-p.txResultChan + unconfirmedTxIndex := unconfirmedResult.txReq.txIndex + if unconfirmedTxIndex <= p.mergedTxIndex { + // log.Warn("drop merged txReq", "unconfirmedTxIndex", unconfirmedTxIndex, "p.mergedTxIndex", p.mergedTxIndex) + continue + } + p.pendingConfirmResults[unconfirmedTxIndex] = append(p.pendingConfirmResults[unconfirmedTxIndex], unconfirmedResult) + + // schedule prefetch once only when unconfirmedResult is valid + if unconfirmedResult.err == nil { + if _, ok := p.txReqExecuteRecord[unconfirmedTxIndex]; !ok { + p.txReqExecuteRecord[unconfirmedTxIndex] = 0 + p.txReqExecuteCount++ + statedb.AddrPrefetch(unconfirmedResult.slotDB) // todo: prefetch when it is not merged + // enter stage2, RT confirm + if !p.inConfirmStage2 && p.txReqExecuteCount == p.targetStage2Count { + p.inConfirmStage2 = true + } + } + p.txReqExecuteRecord[unconfirmedTxIndex]++ + } + + for { + result := p.confirmTxResults(statedb, gp) + if result == nil { + break + } + // update tx result + if result.err != nil { + log.Error("ProcessParallel a failed tx", "resultSlotIndex", result.slotIndex, + "resultTxIndex", result.txReq.txIndex, "result.err", result.err) + p.doCleanUp() + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", result.txReq.txIndex, result.txReq.tx.Hash().Hex(), result.err) + } + commonTxs = append(commonTxs, result.txReq.tx) + receipts = append(receipts, result.receipt) + } + } + + // to do clean up when the block is processed + p.doCleanUp() + + // len(commonTxs) could be 0, such as: https://bscscan.com/block/14580486 + if len(commonTxs) > 0 { + log.Info("ProcessParallel tx all done", "block", header.Number, "usedGas", *usedGas, + "txNum", txNum, + "len(commonTxs)", len(commonTxs), + "conflictNum", p.debugConflictRedoNum, + "redoRate(%)", 100*(p.debugConflictRedoNum)/len(commonTxs)) + } + + // Fail if Shanghai not enabled and len(withdrawals) is non-zero. + withdrawals := block.Withdrawals() + if len(withdrawals) > 0 && !p.config.IsShanghai(block.Number(), block.Time()) { + return nil, nil, 0, errors.New("withdrawals before shanghai") + } + // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) + p.engine.Finalize(p.bc, header, statedb, commonTxs, block.Uncles(), withdrawals) + + var allLogs []*types.Log + for _, receipt := range receipts { + allLogs = append(allLogs, receipt.Logs...) + } + return receipts, allLogs, *usedGas, nil +} + +func applyTransactionStageExecution(msg *Message, gp *GasPool, statedb *state.ParallelStateDB, evm *vm.EVM) (*vm.EVM, *ExecutionResult, error) { + // Create a new context to be used in the EVM environment. + txContext := NewEVMTxContext(msg) + evm.Reset(txContext, statedb) + + // Apply the transaction to the current state (included in the env). + result, err := ApplyMessage(evm, msg, gp) + + if err != nil { + return nil, nil, err + } + + return evm, result, err +} + +func applyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg Message, config *params.ChainConfig, + statedb *state.ParallelStateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, nonce *uint64) (*types.Receipt, error) { + + *usedGas += result.UsedGas + + // Create a new receipt for the transaction, storing the intermediate root and gas used + // by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: nil, CumulativeGasUsed: *usedGas} + if result.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } + receipt.TxHash = tx.Hash() + receipt.GasUsed = result.UsedGas + + if msg.IsDepositTx && config.IsOptimismRegolith(evm.Context.Time) { + // The actual nonce for deposit transactions is only recorded from Regolith onwards and + // otherwise must be nil. + receipt.DepositNonce = nonce + // The DepositReceiptVersion for deposit transactions is only recorded from Canyon onwards + // and otherwise must be nil. + if config.IsOptimismCanyon(evm.Context.Time) { + receipt.DepositReceiptVersion = new(uint64) + *receipt.DepositReceiptVersion = types.CanyonDepositReceiptVersion + } + } + if tx.Type() == types.BlobTxType { + receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob) + receipt.BlobGasPrice = evm.Context.BlobBaseFee + } + // If the transaction created a contract, store the creation address in the receipt. + if msg.To == nil { + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, *nonce) + } + // Set the receipt logs and create the bloom filter. + receipt.Logs = statedb.GetLogs(tx.Hash(), header.Number.Uint64(), header.Hash()) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + receipt.BlockHash = header.Hash() + receipt.BlockNumber = header.Number + receipt.TransactionIndex = uint(statedb.TxIndex()) + return receipt, nil +} diff --git a/core/state/dump.go b/core/state/dump.go index 55abb50f1c..1b0c0c0dae 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -160,7 +160,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] address = &addr account.Address = address } - obj := newObject(s, addr, &data) + obj := newObject(s, s.isParallel, addr, &data) if !conf.SkipCode { account.Code = obj.Code() } diff --git a/core/state/interface.go b/core/state/interface.go new file mode 100644 index 0000000000..3e808aa82e --- /dev/null +++ b/core/state/interface.go @@ -0,0 +1,81 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" +) + +// StateDBer is copied from vm/interface.go +// It is used by StateObject & Journal right now, to abstract StateDB & ParallelStateDB +type StateDBer interface { + getBaseStateDB() *StateDB + getStateObject(common.Address) *stateObject // only accessible for journal + storeStateObj(common.Address, *stateObject) // only accessible for journal + + CreateAccount(common.Address) + + SubBalance(common.Address, *uint256.Int) + AddBalance(common.Address, *uint256.Int) + GetBalance(common.Address) *uint256.Int + + GetNonce(common.Address) uint64 + SetNonce(common.Address, uint64) + + GetCodeHash(common.Address) common.Hash + GetCode(common.Address) []byte + SetCode(common.Address, []byte) + GetCodeSize(common.Address) int + + AddRefund(uint64) + SubRefund(uint64) + GetRefund() uint64 + + GetCommittedState(common.Address, common.Hash) common.Hash + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + + SelfDestruct(common.Address) + HasSelfDestructed(common.Address) bool + + // Exist reports whether the given account exists in state. + // Notably this should also return true for suicided accounts. + Exist(common.Address) bool + // Empty returns whether the given account is empty. Empty + // is defined according to EIP161 (balance = nonce = code = 0). + Empty(common.Address) bool + + //PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + AddressInAccessList(addr common.Address) bool + SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) + // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddAddressToAccessList(addr common.Address) + // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddSlotToAccessList(addr common.Address, slot common.Hash) + + RevertToSnapshot(int) + Snapshot() int + + AddLog(*types.Log) + AddPreimage(common.Hash, []byte) + + GetStateObjectFromUnconfirmedDB(addr common.Address) (*stateObject, bool) +} diff --git a/core/state/journal.go b/core/state/journal.go index 6cdc1fc868..635d516d49 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -17,6 +17,7 @@ package state import ( + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" ) @@ -25,7 +26,7 @@ import ( // reverted on demand. type journalEntry interface { // revert undoes the changes introduced by this journal entry. - revert(*StateDB) + revert(StateDBer) // dirtied returns the Ethereum address modified by this journal entry. dirtied() *common.Address @@ -49,6 +50,7 @@ func newJournal() *journal { // append inserts a new modification entry to the end of the change journal. func (j *journal) append(entry journalEntry) { j.entries = append(j.entries, entry) + if addr := entry.dirtied(); addr != nil { j.dirties[*addr]++ } @@ -56,10 +58,10 @@ func (j *journal) append(entry journalEntry) { // revert undoes a batch of journalled modifications along with any reverted // dirty handling too. -func (j *journal) revert(statedb *StateDB, snapshot int) { +func (j *journal) revert(dber StateDBer, snapshot int) { for i := len(j.entries) - 1; i >= snapshot; i-- { // Undo the changes made by the operation - j.entries[i].revert(statedb) + j.entries[i].revert(dber) // Drop any dirty tracking induced by the change if addr := j.entries[i].dirtied(); addr != nil { @@ -151,8 +153,18 @@ type ( } ) -func (ch createObjectChange) revert(s *StateDB) { - delete(s.stateObjects, *ch.account) +func (ch createObjectChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() + if s.parallel.isSlotDB { + delete(s.parallel.dirtiedStateObjectsInSlot, *ch.account) + delete(s.parallel.addrStateChangesInSlot, *ch.account) + delete(s.parallel.nonceChangesInSlot, *ch.account) + delete(s.parallel.balanceChangesInSlot, *ch.account) + delete(s.parallel.codeChangesInSlot, *ch.account) + delete(s.parallel.kvChangesInSlot, *ch.account) + } else { + s.deleteStateObj(*ch.account) + } delete(s.stateObjectsDirty, *ch.account) } @@ -160,10 +172,25 @@ func (ch createObjectChange) dirtied() *common.Address { return ch.account } -func (ch resetObjectChange) revert(s *StateDB) { - s.setStateObject(ch.prev) +func (ch resetObjectChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() + if s.parallel.isSlotDB { + + if ch.prev.address.Hex() == "0x6295eE1B4F6dD65047762F924Ecd367c17eaBf8f" { + fmt.Printf("Dav - revert() - set dirtiedStateObjectsInSlot[%s] = obj, obj.codehash: %s\n", + ch.prev.address, common.Bytes2Hex(ch.prev.CodeHash())) + } + // ch.prev must be from dirtiedStateObjectsInSlot, put it back + s.parallel.dirtiedStateObjectsInSlot[ch.prev.address] = ch.prev + } else { + // ch.prev was got from main DB, put it back to main DB. + s.storeStateObj(ch.prev.address, ch.prev) + } + if !ch.prevdestruct { + s.snapParallelLock.Lock() delete(s.stateObjectsDestruct, ch.prev.address) + s.snapParallelLock.Unlock() } if ch.prevAccount != nil { s.accounts[ch.prev.addrHash] = ch.prevAccount @@ -183,8 +210,8 @@ func (ch resetObjectChange) dirtied() *common.Address { return ch.account } -func (ch selfDestructChange) revert(s *StateDB) { - obj := s.getStateObject(*ch.account) +func (ch selfDestructChange) revert(dber StateDBer) { + obj := dber.getStateObject(*ch.account) if obj != nil { obj.selfDestructed = ch.prev obj.setBalance(ch.prevbalance) @@ -197,46 +224,47 @@ func (ch selfDestructChange) dirtied() *common.Address { var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") -func (ch touchChange) revert(s *StateDB) { +func (ch touchChange) revert(dber StateDBer) { } func (ch touchChange) dirtied() *common.Address { return ch.account } -func (ch balanceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setBalance(ch.prev) +func (ch balanceChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setBalance(ch.prev) } func (ch balanceChange) dirtied() *common.Address { return ch.account } -func (ch nonceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setNonce(ch.prev) +func (ch nonceChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setNonce(ch.prev) } func (ch nonceChange) dirtied() *common.Address { return ch.account } -func (ch codeChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) +func (ch codeChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) } func (ch codeChange) dirtied() *common.Address { return ch.account } -func (ch storageChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) +func (ch storageChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setState(ch.key, ch.prevalue) } func (ch storageChange) dirtied() *common.Address { return ch.account } -func (ch transientStorageChange) revert(s *StateDB) { +func (ch transientStorageChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() s.setTransientState(*ch.account, ch.key, ch.prevalue) } @@ -244,7 +272,8 @@ func (ch transientStorageChange) dirtied() *common.Address { return nil } -func (ch refundChange) revert(s *StateDB) { +func (ch refundChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() s.refund = ch.prev } @@ -252,7 +281,8 @@ func (ch refundChange) dirtied() *common.Address { return nil } -func (ch addLogChange) revert(s *StateDB) { +func (ch addLogChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() logs := s.logs[ch.txhash] if len(logs) == 1 { delete(s.logs, ch.txhash) @@ -266,7 +296,8 @@ func (ch addLogChange) dirtied() *common.Address { return nil } -func (ch addPreimageChange) revert(s *StateDB) { +func (ch addPreimageChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() delete(s.preimages, ch.hash) } @@ -274,7 +305,7 @@ func (ch addPreimageChange) dirtied() *common.Address { return nil } -func (ch accessListAddAccountChange) revert(s *StateDB) { +func (ch accessListAddAccountChange) revert(dber StateDBer) { /* One important invariant here, is that whenever a (addr, slot) is added, if the addr is not already present, the add causes two journal entries: @@ -284,6 +315,7 @@ func (ch accessListAddAccountChange) revert(s *StateDB) { (addr) at this point, since no storage adds can remain when come upon a single (addr) change. */ + s := dber.getBaseStateDB() s.accessList.DeleteAddress(*ch.address) } @@ -291,7 +323,8 @@ func (ch accessListAddAccountChange) dirtied() *common.Address { return nil } -func (ch accessListAddSlotChange) revert(s *StateDB) { +func (ch accessListAddSlotChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() s.accessList.DeleteSlot(*ch.address, *ch.slot) } diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go new file mode 100644 index 0000000000..34f0b95a6a --- /dev/null +++ b/core/state/parallel_statedb.go @@ -0,0 +1,1735 @@ +package state + +import ( + "bytes" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/holiman/uint256" + "runtime" + "sort" + "sync" + "time" +) + +const defaultNumOfSlots = 100 + +var parallelKvOnce sync.Once + +type ParallelKvCheckUnit struct { + addr common.Address + key common.Hash + val common.Hash +} + +type ParallelKvCheckMessage struct { + slotDB *ParallelStateDB + isStage2 bool + kvUnit ParallelKvCheckUnit +} + +var parallelKvCheckReqCh chan ParallelKvCheckMessage +var parallelKvCheckResCh chan bool + +type ParallelStateDB struct { + StateDB +} + +func (s *ParallelStateDB) GetRefund() uint64 { + return s.refund +} + +func (s *ParallelStateDB) AddressInAccessList(addr common.Address) bool { + return s.accessList.ContainsAddress(addr) +} + +func (s *ParallelStateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { + return s.accessList.Contains(addr, slot) +} + +func (s *ParallelStateDB) AddAddressToAccessList(addr common.Address) { + if s.accessList.AddAddress(addr) { + s.journal.append(accessListAddAccountChange{&addr}) + } +} + +func (s *ParallelStateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + addrMod, slotMod := s.accessList.AddSlot(addr, slot) + if addrMod { + // In practice, this should not happen, since there is no way to enter the + // scope of 'address' without having the 'address' become already added + // to the access list (via call-variant, create, etc). + // Better safe than sorry, though + s.journal.append(accessListAddAccountChange{&addr}) + } + if slotMod { + s.journal.append(accessListAddSlotChange{ + address: &addr, + slot: &slot, + }) + } +} + +func (s *ParallelStateDB) Snapshot() int { + id := s.nextRevisionId + s.nextRevisionId++ + s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()}) + return id +} + +func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash, val common.Hash, isStage2 bool) bool { + mainDB := slotDB.parallel.baseStateDB + + if isStage2 { // update slotDB's unconfirmed DB list and try + if valUnconfirm, ok := slotDB.getKVFromUnconfirmedDB(addr, key); ok { + if !bytes.Equal(val.Bytes(), valUnconfirm.Bytes()) { + log.Debug("IsSlotDBReadsValid KV read is invalid in unconfirmed", "addr", addr, + "valSlot", val, "valUnconfirm", valUnconfirm, + "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return true + } + } + } + valMain := mainDB.GetState(addr, key) + if !bytes.Equal(val.Bytes(), valMain.Bytes()) { + log.Debug("hasKvConflict is invalid", "addr", addr, + "key", key, "valSlot", val, + "valMain", valMain, "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return true // return false, Range will be terminated. + } + return false +} + +// StartKvCheckLoop start several routines to do conflict check +func StartKvCheckLoop() { + parallelKvCheckReqCh = make(chan ParallelKvCheckMessage, 200) + parallelKvCheckResCh = make(chan bool, 10) + for i := 0; i < runtime.NumCPU(); i++ { + go func() { + for { + kvEle1 := <-parallelKvCheckReqCh + parallelKvCheckResCh <- hasKvConflict(kvEle1.slotDB, kvEle1.kvUnit.addr, + kvEle1.kvUnit.key, kvEle1.kvUnit.val, kvEle1.isStage2) + } + }() + } +} + +// NewSlotDB creates a new State DB based on the provided StateDB. +// With parallel, each execution slot would have its own StateDB. +// This method must be called after the baseDB call PrepareParallel() +func NewSlotDB(db *StateDB, txIndex int, baseTxIndex int, unconfirmedDBs *sync.Map /*map[int]*ParallelStateDB*/) *ParallelStateDB { + slotDB := db.CopyForSlot() + slotDB.txIndex = txIndex + slotDB.originalRoot = db.originalRoot + slotDB.parallel.baseStateDB = db + slotDB.parallel.baseTxIndex = baseTxIndex + slotDB.parallel.unconfirmedDBs = unconfirmedDBs + + return slotDB +} + +// RevertSlotDB keep the Read list for conflict detect, +// discard all state changes except: +// - nonce and balance of from address +// - balance of system address: will be used on merge to update SystemAddress's balance +func (s *ParallelStateDB) RevertSlotDB(from common.Address) { + s.parallel.kvChangesInSlot = make(map[common.Address]StateKeys) + s.parallel.nonceChangesInSlot = make(map[common.Address]struct{}) + s.parallel.balanceChangesInSlot = make(map[common.Address]struct{}, 1) + s.parallel.addrStateChangesInSlot = make(map[common.Address]bool) // 0: created, 1: deleted + + selfStateObject := s.parallel.dirtiedStateObjectsInSlot[from] + s.parallel.dirtiedStateObjectsInSlot = make(map[common.Address]*stateObject, 2) + // keep these elements + if from.Hex() == "0x6295eE1B4F6dD65047762F924Ecd367c17eaBf8f" { + fmt.Printf("Dav - RevertSlotDB - set dirtiedStateObjectsInSlot[%s] = obj, obj.codehash: %s\n", + from, common.Bytes2Hex(selfStateObject.CodeHash())) + } + s.parallel.dirtiedStateObjectsInSlot[from] = selfStateObject + s.parallel.balanceChangesInSlot[from] = struct{}{} + s.parallel.nonceChangesInSlot[from] = struct{}{} +} + +func (s *ParallelStateDB) getBaseStateDB() *StateDB { + return &s.StateDB +} + +func (s *ParallelStateDB) SetSlotIndex(index int) { + s.parallel.SlotIndex = index +} + +// for parallel execution mode, try to get dirty StateObject in slot first. +// it is mainly used by journal revert right now. +func (s *ParallelStateDB) getStateObject(addr common.Address) *stateObject { + var ret *stateObject + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if obj.deleted { + return nil + } + ret = obj + } else { + // can not call s.StateDB.getStateObject(), since `newObject` need ParallelStateDB as the interface + ret = s.getStateObjectNoSlot(addr) + } + return ret +} + +func (s *ParallelStateDB) storeStateObj(addr common.Address, stateObject *stateObject) { + // When a state object is stored into s.parallel.stateObjects, + // it belongs to base StateDB, it is confirmed and valid. + // todo Dav: why need change this? -- delete me ! + // stateObject.db = s.parallel.baseStateDB + // stateObject.dbItf = s.parallel.baseStateDB + + // the object could be created in SlotDB, if it got the object from DB and + // update it to the shared `s.parallel.stateObjects`` + stateObject.db.storeParallelLock.Lock() + if _, ok := s.parallel.stateObjects.Load(addr); !ok { + s.parallel.stateObjects.Store(addr, stateObject) + } + stateObject.db.storeParallelLock.Unlock() +} + +func (s *ParallelStateDB) getStateObjectNoSlot(addr common.Address) *stateObject { + if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { + return obj + } + return nil +} + +// createObject creates a new state object. If there is an existing account with +// the given address, it is overwritten and returned as the second return value. + +// prev is used for CreateAccount to get its balance +// Parallel mode: +// if prev in dirty: revert is ok +// if prev in unconfirmed DB: addr state read record, revert should not put it back +// if prev in main DB: addr state read record, revert should not put it back +// if pre no exist: addr state read record, + +// `prev` is used to handle revert, to recover with the `prev` object +// In Parallel mode, we only need to recover to `prev` in SlotDB, +// +// a.if it is not in SlotDB, `revert` will remove it from the SlotDB +// b.if it is existed in SlotDB, `revert` will recover to the `prev` in SlotDB +// c.as `snapDestructs` it is the same +func (s *ParallelStateDB) createObject(addr common.Address) (newobj *stateObject) { + prev := s.parallel.dirtiedStateObjectsInSlot[addr] + // TODO-dav: check + // There can be tx0 create an obj at addr0, tx1 destruct it, and tx2 recreate it use create2. + // so if tx0 is finalized, and tx1 is unconfirmed, we have to check the states of unconfirmed, otherwise there + // will be wrong behavior that we recreate an object that is already there. see. test "TestDeleteThenCreate" + var prevdestruct bool + + if s.snap != nil && prev != nil { + s.snapParallelLock.Lock() + _, prevdestruct = s.snapDestructs[prev.address] + s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct + if !prevdestruct { + // To destroy the previous trie node first and update the trie tree + // with the new object on block commit. + s.snapDestructs[prev.address] = struct{}{} + } + s.snapParallelLock.Unlock() + } + newobj = newObject(s, s.isParallel, addr, nil) + newobj.setNonce(0) // sets the object to dirty + if prev == nil { + s.journal.append(createObjectChange{account: &addr}) + } else { + s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) + } + + s.parallel.addrStateChangesInSlot[addr] = true // the object is created + s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} + // notice: all the KVs are cleared if any + s.parallel.kvChangesInSlot[addr] = make(StateKeys) + newobj.created = true + s.parallel.dirtiedStateObjectsInSlot[addr] = newobj + return newobj +} + +// getDeletedStateObject is similar to getStateObject, but instead of returning +// nil for a deleted state object, it returns the actual object with the deleted +// flag set. This is needed by the state journal to revert to the correct s- +// destructed object instead of wiping all knowledge about the state object. +func (s *ParallelStateDB) getDeletedStateObject(addr common.Address) *stateObject { + + // Prefer live objects if any is available + if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { + return obj + } + + data, ok := s.getStateObjectFromSnapshotOrTrie(addr) + if !ok { + return nil + } + + // this is why we have to use a separate getDeletedStateObject for ParallelStateDB + // `s` has to be the ParallelStateDB + obj := newObject(s, s.isParallel, addr, data) + s.storeStateObj(addr, obj) + return obj +} + +// GetOrNewStateObject retrieves a state object or create a new state object if nil. +// dirtyInSlot -> Unconfirmed DB -> main DB -> snapshot, no? create one +func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *stateObject { + var object *stateObject + var ok bool + if object, ok = s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return object + } + + // try unconfirmedDB + object, _ = s.getStateObjectFromUnconfirmedDB(addr) + if object != nil { + // object found in unconfirmedDB, check existence + if object.deleted || object.selfDestructed { + object = s.createObject(addr) + s.parallel.addrStateReadsInSlot[addr] = false + return object + } + } else { + object = s.getStateObjectNoSlot(addr) // try to get from base db + } + // not found, or found in NoSlot or found in unconfirmed. + exist := true + // TODO-dav: the check of nil and delete already done by NoSlot and unconfirmedDB, may optimize it for dirty only. + if object == nil || object.deleted { + object = s.createObject(addr) + exist = false + } + s.parallel.addrStateReadsInSlot[addr] = exist // true: exist, false: not exist + return object +} + +// Exist reports whether the given account address exists in the state. +// Notably this also returns true for suicided accounts. +func (s *ParallelStateDB) Exist(addr common.Address) bool { + // 1.Try to get from dirty + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if obj.deleted { + log.Error("Exist in dirty, but marked as deleted or suicided", + "txIndex", s.txIndex, "baseTxIndex:", s.parallel.baseTxIndex) + return false + } + return true + } + // 2.Try to get from unconfirmed & main DB + // 2.1 Already read before + if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { + return exist + } + + // 2.2 Try to get from unconfirmed DB if exist + if exist, ok := s.getAddrStateFromUnconfirmedDB(addr, false); ok { + s.parallel.addrStateReadsInSlot[addr] = exist // update and cache + return exist + } + + // 3.Try to get from main StateDB + exist := s.getStateObjectNoSlot(addr) != nil + s.parallel.addrStateReadsInSlot[addr] = exist // update and cache + return exist +} + +// Empty returns whether the state object is either non-existent +// or empty according to the EIP161 specification (balance = nonce = code = 0) +func (s *ParallelStateDB) Empty(addr common.Address) bool { + // 1.Try to get from dirty + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // dirty object is light copied and fixup on need, + // empty could be wrong, except it is created with this TX + if _, ok := s.parallel.addrStateChangesInSlot[addr]; ok { + return obj.empty() + } + // so we have to check it manually + // empty means: Nonce == 0 && Balance == 0 && CodeHash == emptyCodeHash + if s.GetBalance(addr).Sign() != 0 { // check balance first, since it is most likely not zero + return false + } + if s.GetNonce(addr) != 0 { + return false + } + codeHash := s.GetCodeHash(addr) + return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty + } + // 2.Try to get from unconfirmed & main DB + // 2.1 Already read before + if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { + // exist means not empty + return !exist + } + // 2.2 Try to get from unconfirmed DB if exist + if exist, ok := s.getAddrStateFromUnconfirmedDB(addr, true); ok { + s.parallel.addrStateReadsInSlot[addr] = exist // update and cache + return !exist + } + // 2.3 Try to get from NoSlot. + so := s.getStateObjectNoSlot(addr) + exist := so != nil + empty := (!exist) || so.empty() + + s.parallel.addrStateReadsInSlot[addr] = exist // update read cache + return empty +} + +// GetBalance retrieves the balance from the given address or 0 if object not found +// GetFrom the dirty list => from unconfirmed DB => get from main stateDB +func (s *ParallelStateDB) GetBalance(addr common.Address) *uint256.Int { + var dirtyObj *stateObject + // 0. Test whether it is deleted in dirty. + if o, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if o.deleted { + return common.U2560 + } + dirtyObj = o + } + + // 1.Try to get from dirty + if _, ok := s.parallel.balanceChangesInSlot[addr]; ok { + // on balance fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup balance based on unconfirmed DB or main DB + return dirtyObj.Balance() + } + // 2.Try to get from unconfirmed DB or main DB + // 2.1 Already read before + if balance, ok := s.parallel.balanceReadsInSlot[addr]; ok { + return balance + } + + balance := common.U2560 + // 2.2 Try to get from unconfirmed DB if exist + if blc := s.getBalanceFromUnconfirmedDB(addr); blc != nil { + balance = blc + } else { + // 3. Try to get from main StateObject + blc = common.U2560 + object := s.getStateObjectNoSlot(addr) + if object != nil { + blc = object.Balance() + } + balance = blc + } + s.parallel.balanceReadsInSlot[addr] = balance + + // fixup dirties + if dirtyObj != nil && dirtyObj.Balance() != balance { + dirtyObj.setBalance(balance) + } + + return balance +} + +func (s *ParallelStateDB) GetNonce(addr common.Address) uint64 { + var dirtyObj *stateObject + // 0. Test whether it is deleted in dirty. + if o, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if o.deleted { + return 0 + } + dirtyObj = o + } + + // 1.Try to get from dirty + if _, ok := s.parallel.nonceChangesInSlot[addr]; ok { + // on nonce fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup nonce based on unconfirmed DB or main DB + return dirtyObj.Nonce() + } + // 2.Try to get from unconfirmed DB or main DB + // 2.1 Already read before + if nonce, ok := s.parallel.nonceReadsInSlot[addr]; ok { + return nonce + } + + var nonce uint64 = 0 + // 2.2 Try to get from unconfirmed DB if exist + if nc, ok := s.getNonceFromUnconfirmedDB(addr); ok { + nonce = nc + } else { + // 3.Try to get from main StateDB + nc = 0 + object := s.getStateObjectNoSlot(addr) + if object != nil { + nc = object.Nonce() + } + nonce = nc + } + s.parallel.nonceReadsInSlot[addr] = nonce + + // fixup dirties + if dirtyObj != nil && dirtyObj.Nonce() < nonce { + dirtyObj.setNonce(nonce) + } + return nonce +} + +func (s *ParallelStateDB) GetCode(addr common.Address) []byte { + var dirtyObj *stateObject + // 0. Test whether it is deleted in dirty. + if o, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if o.deleted { + return nil + } + dirtyObj = o + } + + // 1.Try to get from dirty + if _, ok := s.parallel.codeChangesInSlot[addr]; ok { + // on code fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup code based on unconfirmed DB or main DB + return dirtyObj.Code() + } + // 2.Try to get from unconfirmed DB or main DB + // 2.1 Already read before + if code, ok := s.parallel.codeReadsInSlot[addr]; ok { + return code + } + var code []byte + // 2.2 Try to get from unconfirmed DB if exist + if cd, ok := s.getCodeFromUnconfirmedDB(addr); ok { + code = cd + } else { + // 3. Try to get from main StateObject + object := s.getStateObjectNoSlot(addr) + if object != nil { + code = object.Code() + } + } + s.parallel.codeReadsInSlot[addr] = code + + // fixup dirties + if dirtyObj != nil && !bytes.Equal(dirtyObj.code, code) { + dirtyObj.code = code + } + return code +} + +func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { + var dirtyObj *stateObject + // 0. Test whether it is deleted in dirty. + if o, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if o.deleted { + return 0 + } + dirtyObj = o + } + // 1.Try to get from dirty + if _, ok := s.parallel.codeChangesInSlot[addr]; ok { + // on code fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup code based on unconfirmed DB or main DB + return dirtyObj.CodeSize() + } + // 2.Try to get from unconfirmed DB or main DB + // 2.1 Already read before + if code, ok := s.parallel.codeReadsInSlot[addr]; ok { + return len(code) // len(nil) is 0 too + } + + cs := 0 + var code []byte + // 2.2 Try to get from unconfirmed DB if exist + if cd, ok := s.getCodeFromUnconfirmedDB(addr); ok { + cs = len(cd) // len(nil) is 0 too + code = cd + } else { + // 3. Try to get from main StateObject + var cc []byte + object := s.getStateObjectNoSlot(addr) + if object != nil { + // This is where we update the code from possible db.ContractCode if the original object.code is nil. + cc = object.Code() + cs = object.CodeSize() + } + code = cc + } + s.parallel.codeReadsInSlot[addr] = code + // fixup dirties + if dirtyObj != nil { + if !bytes.Equal(dirtyObj.code, code) { + dirtyObj.code = code + } + } + return cs +} + +// GetCodeHash return: +// - common.Hash{}: the address does not exist +// - emptyCodeHash: the address exist, but code is empty +// - others: the address exist, and code is not empty +func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { + var dirtyObj *stateObject + // 0. Test whether it is deleted in dirty. + if o, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if o.deleted { + return common.Hash{} + } + dirtyObj = o + } + + // 1.Try to get from dirty + if _, ok := s.parallel.codeChangesInSlot[addr]; ok { + // on code fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup balance based on unconfirmed DB or main DB + return common.BytesToHash(dirtyObj.CodeHash()) + } + // 2.Try to get from unconfirmed DB or main DB + // 2.1 Already read before + if codeHash, ok := s.parallel.codeHashReadsInSlot[addr]; ok { + return codeHash + } + codeHash := common.Hash{} + // 2.2 Try to get from unconfirmed DB if exist + if cHash, ok := s.getCodeHashFromUnconfirmedDB(addr); ok { + codeHash = cHash + } else { + // 3. Try to get from main StateObject + object := s.getStateObjectNoSlot(addr) + + if object != nil { + codeHash = common.BytesToHash(object.CodeHash()) + } + } + s.parallel.codeHashReadsInSlot[addr] = codeHash + + // fill slots in dirty if exist. + // A case for this: + // TX0: createAccount at addr 0x123, set code and codehash + // TX1: AddBalance - now an obj in dirty with empty codehash, and codeChangesInSlot is false (not changed) + // GetCodeHash - get from unconfirmedDB or mainDB, set codeHashReadsInSlot to the new val. + // SELFDESTRUCT - set codeChangesInSlot, but the obj in dirty is with Empty codehash. + // obj marked selfdestructed but not deleted. so CodeHash is not empty. + // GetCodeHash - since the codeChangesInslot is marked, get the object from dirty, and get the + // wrong 'empty' hash. + if dirtyObj != nil { + // found one + if dirtyObj.CodeHash() == nil || bytes.Equal(dirtyObj.CodeHash(), emptyCodeHash) { + if bytes.Equal(codeHash.Bytes(), emptyCodeHash) { + fmt.Printf("Dav -- update codehash to empty in dirty - addr: %s\n", addr) + } + dirtyObj.data.CodeHash = codeHash.Bytes() + } + } + return codeHash +} + +// GetState retrieves a value from the given account's storage trie. +// For parallel mode wih, get from the state in order: +// +// -> self dirty, both Slot & MainProcessor +// -> pending of self: Slot on merge +// -> pending of unconfirmed DB +// -> pending of main StateDB +// -> origin +func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + var dirtyObj *stateObject + // 0. Test whether it is deleted in dirty. + if o, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if o == nil || o.deleted { + return common.Hash{} + } + dirtyObj = o + } + // 1.Try to get from dirty + if exist, ok := s.parallel.addrStateChangesInSlot[addr]; ok { + if !exist { + // it could be suicided within this SlotDB? + // it should be able to get state from suicided address within a Tx: + // e.g. within a transaction: call addr:suicide -> get state: should be ok + // return common.Hash{} + log.Info("ParallelStateDB GetState suicided", "addr", addr, "hash", hash) + } else { + // It is possible that an object get created but not dirtied since there is no state set, such as recreate. + // In this case, simply return common.Hash{}. + // This is for corner case: + // B0: TX0 --> createAccount @addr1 -- merged into DB + // B1: Tx1 and Tx2 + // Tx1 account@addr1 selfDestruct -- unconfirmed + // Tx2 recreate account@addr2 -- executing + // Since any state change and suicide could record in s.parallel.addrStateChangeInSlot, it is save to simple + // return common.Hash{} for this case as the previous TX must has the object destructed. + // P.S. if the Tx2 both destruct and recreate the object, it will not fall into this logic, as the change + // will be recorded in dirtiedStateObjectsInSlot. + + // it could be suicided within this SlotDB? + // it should be able to get state from suicided address within a Tx: + // e.g. within a transaction: call addr:suicide -> get state: should be ok + // return common.Hash{} + log.Info("ParallelStateDB GetState suicided", "addr", addr, "hash", hash) + + if dirtyObj == nil { + log.Error("ParallelStateDB GetState access untouched object after create, may check create2") + return common.Hash{} + } + return dirtyObj.GetState(hash) + } + } + + if keys, ok := s.parallel.kvChangesInSlot[addr]; ok { + if _, ok := keys[hash]; ok { + return dirtyObj.GetState(hash) + } + } + // 2.Try to get from unconfirmed DB or main DB + // 2.1 Already read before + if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { + if val, ok := storage.GetValue(hash); ok { + return val + } + } + + value := common.Hash{} + // 2.2 Try to get from unconfirmed DB if exist + if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { + value = val + } else { + // 3.Get from main StateDB + object := s.getStateObjectNoSlot(addr) + val = common.Hash{} + if object != nil { + val = object.GetState(hash) + // TODO-dav: delete following originStorage change, as lightCopy is copy originStorage now. + // test dirty, there can be a case the object saved in dirty by other changes such as SetBalance. But the + // addrStateChangesInSlot[addr] does not record it. So later load from the dirties would cause flaw because the + // first value loaded from main stateDB is not updated to the object in dirties. + // Moreover, there is also an issue that the other kv in the object get from snap or trie that is accessed from + // previous tx in same block but not touched in current tx, is missed in the dirty. which may cause issues when + // calculate the root. + _, recorded := s.parallel.addrStateChangesInSlot[addr] + obj, isDirty := s.parallel.dirtiedStateObjectsInSlot[addr] + if !recorded && isDirty { + v, ok := obj.originStorage.GetValue(hash) + + if !(ok && v.Cmp(val) == 0) { + obj.originStorage.StoreValue(hash, val) + } + } + } + value = val + } + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) + } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, value) // update cache + + // fixup Dirty + if dirtyObj != nil { + old := dirtyObj.GetState(hash) + if old.Cmp(value) != 0 { + dirtyObj.setState(hash, value) + } + } + return value +} + +// GetCommittedState retrieves a value from the given account's committed storage trie. +func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + // 0. Test whether it is deleted. + var dirtyObj *stateObject + if o, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if o.deleted { + return common.Hash{} + } + dirtyObj = o + } + // 2.Try to get from unconfirmed DB or main DB + // KVs in unconfirmed DB can be seen as pending storage + // KVs in main DB are merged from SlotDB and has done finalise() on merge, can be seen as pending storage too. + // 2.1 Already read before + if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { + if val, ok := storage.GetValue(hash); ok { + return val + } + } + value := common.Hash{} + // 2.2 Try to get from unconfirmed DB if exist + if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { + value = val + } else { + // 3. Try to get from main DB + val = common.Hash{} + object := s.getStateObjectNoSlot(addr) + if object != nil { + val = object.GetCommittedState(hash) + } + value = val + } + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) + } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, value) // update cache + + // fixup Dirty + if dirtyObj != nil { + old := dirtyObj.GetState(hash) + if old.Cmp(value) != 0 { + dirtyObj.setState(hash, value) + } + } + + return value +} + +func (s *ParallelStateDB) HasSelfDestructed(addr common.Address) bool { + // 1.Try to get from dirty + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + if obj == nil || obj.deleted { + return false + } + return obj.selfDestructed + } + // 2.Try to get from unconfirmed + if exist, ok := s.getAddrStateFromUnconfirmedDB(addr, false); ok { + return !exist + } + + object := s.getDeletedStateObject(addr) + if object != nil { + return object.selfDestructed + } + return false +} + +// AddBalance adds amount to the account associated with addr. +func (s *ParallelStateDB) AddBalance(addr common.Address, amount *uint256.Int) { + // add balance will perform a read operation first + // if amount == 0, no balance change, but there is still an empty check. + object := s.GetOrNewStateObject(addr) + if object != nil { + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := object.lightCopy(s) // light copy from main DB + // do balance fixup from the confirmed DB, it could be more reliable than main DB + balance := s.GetBalance(addr) // it will record the balance read operation + newStateObject.setBalance(balance) + newStateObject.AddBalance(amount) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + s.parallel.balanceChangesInSlot[addr] = struct{}{} + return + } + // already dirty, make sure the balance is fixed up since it could be previously dirtied by nonce or KV... + balance := s.GetBalance(addr) + if object.Balance().Cmp(balance) != 0 { + log.Warn("AddBalance in dirty, but balance has not do fixup", "txIndex", s.txIndex, "addr", addr, + "stateObject.Balance()", object.Balance(), "s.GetBalance(addr)", balance) + object.setBalance(balance) + } + + object.AddBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} + } +} + +// SubBalance subtracts amount from the account associated with addr. +func (s *ParallelStateDB) SubBalance(addr common.Address, amount *uint256.Int) { + // unlike add, sub 0 balance will not touch empty object + object := s.GetOrNewStateObject(addr) + if object != nil { + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := object.lightCopy(s) // light copy from main DB + // do balance fixup from the confirmed DB, it could be more reliable than main DB + balance := s.GetBalance(addr) + newStateObject.setBalance(balance) + newStateObject.SubBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return + } + // already dirty, make sure the balance is fixed up since it could be previously dirtied by nonce or KV... + balance := s.GetBalance(addr) + if object.Balance().Cmp(balance) != 0 { + log.Warn("SubBalance in dirty, but balance is incorrect", "txIndex", s.txIndex, "addr", addr, + "stateObject.Balance()", object.Balance(), "s.GetBalance(addr)", balance) + object.setBalance(balance) + } + object.SubBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} + } +} + +func (s *ParallelStateDB) SetBalance(addr common.Address, amount *uint256.Int) { + object := s.GetOrNewStateObject(addr) + if object != nil { + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := object.lightCopy(s) + // update balance for revert, in case child contract is reverted, + // it should revert to the previous balance + balance := s.GetBalance(addr) + newStateObject.setBalance(balance) + newStateObject.SetBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return + } + + balance := s.GetBalance(addr) + object.setBalance(balance) + object.SetBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} + } +} + +func (s *ParallelStateDB) SetNonce(addr common.Address, nonce uint64) { + object := s.GetOrNewStateObject(addr) + if object != nil { + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := object.lightCopy(s) + noncePre := s.GetNonce(addr) + newStateObject.setNonce(noncePre) // nonce fixup + newStateObject.SetNonce(nonce) + s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return + } + noncePre := s.GetNonce(addr) + object.setNonce(noncePre) // nonce fixup + object.SetNonce(nonce) + s.parallel.nonceChangesInSlot[addr] = struct{}{} + } +} + +func (s *ParallelStateDB) SetCode(addr common.Address, code []byte) { + object := s.GetOrNewStateObject(addr) + if object != nil { + codeHash := crypto.Keccak256Hash(code) + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := object.lightCopy(s) + codePre := s.GetCode(addr) // code fixup + codeHashPre := crypto.Keccak256Hash(codePre) + newStateObject.setCode(codeHashPre, codePre) + newStateObject.SetCode(codeHash, code) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + s.parallel.codeChangesInSlot[addr] = struct{}{} + return + } + codePre := s.GetCode(addr) // code fixup + codeHashPre := crypto.Keccak256Hash(codePre) + object.setCode(codeHashPre, codePre) + object.SetCode(codeHash, code) + s.parallel.codeChangesInSlot[addr] = struct{}{} + } +} + +func (s *ParallelStateDB) SetState(addr common.Address, key, value common.Hash) { + object := s.GetOrNewStateObject(addr) // attention: if StateObject's lightCopy, its storage is only a part of the full storage, + if object != nil { + if s.parallel.baseTxIndex+1 == s.txIndex { + // we check if state is unchanged + // only when current transaction is the next transaction to be committed + // fixme: there is a bug, block: 14,962,284, + // stateObject is in dirty (light copy), but the key is in mainStateDB + // stateObject dirty -> committed, will skip mainStateDB dirty + if s.GetState(addr, key) == value { + log.Debug("Skip set same state", "baseTxIndex", s.parallel.baseTxIndex, + "txIndex", s.txIndex, "addr", addr, + "key", key, "value", value) + return + } + } + + if s.parallel.kvChangesInSlot[addr] == nil { + s.parallel.kvChangesInSlot[addr] = make(StateKeys) // make(Storage, defaultNumOfSlots) + } + + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := object.lightCopy(s) + newStateObject.SetState(key, value) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + s.parallel.addrStateChangesInSlot[addr] = true + return + } + // do State Update + object.SetState(key, value) + s.parallel.addrStateChangesInSlot[addr] = true + } +} + +// SelfDestruct marks the given account as suicided. +// This clears the account balance. +// +// The account's state object is still available until the state is committed, +// getStateObject will return a non-nil account after Suicide. +func (s *ParallelStateDB) SelfDestruct(addr common.Address) { + var object *stateObject + // 1.Try to get from dirty, it could be suicided inside of contract call + object = s.parallel.dirtiedStateObjectsInSlot[addr] + + if object != nil && object.deleted { + return + } + + if object == nil { + // 2.Try to get from unconfirmed, if deleted return false, since the address does not exist + if obj, ok := s.getStateObjectFromUnconfirmedDB(addr); ok { + object = obj + // Treat selfDestructed in unconfirmedDB as deleted since it will be finalised at merge phase. + deleted := object.deleted || object.selfDestructed + s.parallel.addrStateReadsInSlot[addr] = !deleted // true: exist, false: deleted + if deleted { + return + } + } + } + + if object == nil { + // 3.Try to get from main StateDB + object = s.getStateObjectNoSlot(addr) + if object == nil || object.deleted { + s.parallel.addrStateReadsInSlot[addr] = false // true: exist, false: deleted + return + } + s.parallel.addrStateReadsInSlot[addr] = true // true: exist, false: deleted + } + + s.journal.append(selfDestructChange{ + account: &addr, + prev: object.selfDestructed, // todo: must be false? + prevbalance: new(uint256.Int).Set(s.GetBalance(addr)), + }) + + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + // do copy-on-write for suicide "write" + newStateObject := object.lightCopy(s) + newStateObject.markSelfdestructed() + newStateObject.data.Balance = new(uint256.Int) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, + // s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} + // s.parallel.kvChangesInSlot[addr] = make(StateKeys) // all key changes are discarded + return + } + + s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist anymore + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} + object.markSelfdestructed() + object.data.Balance = new(uint256.Int) +} + +func (s *ParallelStateDB) Selfdestruct6780(addr common.Address) { + object := s.getStateObject(addr) + if object == nil { + return + } + if object.created { + s.SelfDestruct(addr) + } +} + +// CreateAccount explicitly creates a state object. If a state object with the address +// already exists the balance is carried over to the new account. +// +// CreateAccount is called during the EVM CREATE operation. The situation might arise that +// a contract does the following: +// +// 1. sends funds to sha(account ++ (nonce + 1)) +// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) +// +// Carrying over the balance ensures that Ether doesn't disappear. +func (s *ParallelStateDB) CreateAccount(addr common.Address) { + // no matter it is got from dirty, unconfirmed or main DB + // if addr not exist, preBalance will be common.U2560, it is same as new(uint256.Int) which + // is the value newObject(), + preBalance := s.GetBalance(addr) // parallel balance read will be recorded inside GetBalance + newObj := s.createObject(addr) + newObj.setBalance(new(uint256.Int).Set(preBalance)) // new uint256.Int for newObj +} + +// RevertToSnapshot reverts all state changes made since the given revision. +func (s *ParallelStateDB) RevertToSnapshot(revid int) { + // Find the snapshot in the stack of valid snapshots. + idx := sort.Search(len(s.validRevisions), func(i int) bool { + return s.validRevisions[i].id >= revid + }) + if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid { + panic(fmt.Errorf("revision id %v cannot be reverted", revid)) + } + snapshot := s.validRevisions[idx].journalIndex + + // Replay the journal to undo changes and remove invalidated snapshots + s.journal.revert(s, snapshot) + s.validRevisions = s.validRevisions[:idx] +} + +// AddRefund adds gas to the refund counter +// journal.append will use ParallelState for revert +func (s *ParallelStateDB) AddRefund(gas uint64) { // todo: not needed, can be deleted + s.journal.append(refundChange{prev: s.refund}) + s.refund += gas +} + +// SubRefund removes gas from the refund counter. +// This method will panic if the refund counter goes below zero +func (s *ParallelStateDB) SubRefund(gas uint64) { + s.journal.append(refundChange{prev: s.refund}) + if gas > s.refund { + // we don't need to panic here if we read the wrong state in parallel mode + // we just need to redo this transaction + log.Info(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund), "tx", s.thash.String()) + s.parallel.needsRedo = true + return + } + s.refund -= gas +} + +// For Parallel Execution Mode, it can be seen as Penetrated Access: +// +// ------------------------------------------------------- +// | BaseTxIndex | Unconfirmed Txs... | Current TxIndex | +// ------------------------------------------------------- +// +// Access from the unconfirmed DB with range&priority: txIndex -1(previous tx) -> baseTxIndex + 1 +func (s *ParallelStateDB) getBalanceFromUnconfirmedDB(addr common.Address) *uint256.Int { + for i := s.txIndex - 1; i >= 0 && i > s.BaseTxIndex(); i-- { + db_, ok := s.parallel.unconfirmedDBs.Load(i) + if !ok { + continue + } + db := db_.(*ParallelStateDB) + // 1.Refer the state of address, exist or not in dirtiedStateObjectsInSlot + balanceHit := false + if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { + balanceHit = true + } + if _, exist := db.parallel.balanceChangesInSlot[addr]; exist { // only changed balance is reliable + balanceHit = true + } + if !balanceHit { + continue + } + obj := db.parallel.dirtiedStateObjectsInSlot[addr] + balance := obj.Balance() + if obj.deleted { + balance = common.U2560 + } + return balance + + } + return nil +} + +// Similar to getBalanceFromUnconfirmedDB +func (s *ParallelStateDB) getNonceFromUnconfirmedDB(addr common.Address) (uint64, bool) { + for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { + db_, ok := s.parallel.unconfirmedDBs.Load(i) + if !ok { + continue + } + db := db_.(*ParallelStateDB) + + nonceHit := false + if _, ok := db.parallel.addrStateChangesInSlot[addr]; ok { + nonceHit = true + } else if _, ok := db.parallel.nonceChangesInSlot[addr]; ok { + nonceHit = true + } + if !nonceHit { + // nonce refer not hit, try next unconfirmedDb + continue + } + // nonce hit, return the nonce + obj := db.parallel.dirtiedStateObjectsInSlot[addr] + if obj == nil { + // could not exist, if it is changed but reverted + // fixme: revert should remove the change record + log.Debug("Get nonce from UnconfirmedDB, changed but object not exist, ", + "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) + continue + } + // deleted object with nonce == 0 + if obj.deleted || obj.selfDestructed { + return 0, true + } + nonce := obj.Nonce() + return nonce, true + } + return 0, false +} + +// Similar to getBalanceFromUnconfirmedDB +// It is not only for code, but also codeHash and codeSize, we return the *stateObject for convenience. +func (s *ParallelStateDB) getCodeFromUnconfirmedDB(addr common.Address) ([]byte, bool) { + for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { + db_, ok := s.parallel.unconfirmedDBs.Load(i) + if !ok { + continue + } + db := db_.(*ParallelStateDB) + + codeHit := false + if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { + codeHit = true + } + if _, exist := db.parallel.codeChangesInSlot[addr]; exist { + codeHit = true + } + if !codeHit { + // try next unconfirmedDb + continue + } + obj := db.parallel.dirtiedStateObjectsInSlot[addr] + if obj == nil { + // could not exist, if it is changed but reverted + // fixme: revert should remove the change record + log.Debug("Get code from UnconfirmedDB, changed but object not exist, ", + "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) + continue + } + if obj.deleted || obj.selfDestructed { + return nil, true + } + code := obj.Code() + return code, true + } + return nil, false +} + +// Similar to getCodeFromUnconfirmedDB +// but differ when address is deleted or not exist +func (s *ParallelStateDB) getCodeHashFromUnconfirmedDB(addr common.Address) (common.Hash, bool) { + for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { + db_, ok := s.parallel.unconfirmedDBs.Load(i) + if !ok { + continue + } + db := db_.(*ParallelStateDB) + + hashHit := false + if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { + hashHit = true + } + if _, exist := db.parallel.codeChangesInSlot[addr]; exist { + hashHit = true + } + if !hashHit { + // try next unconfirmedDb + continue + } + obj := db.parallel.dirtiedStateObjectsInSlot[addr] + if obj == nil { + // could not exist, if it is changed but reverted + // fixme: revert should remove the change record + log.Debug("Get codeHash from UnconfirmedDB, changed but object not exist, ", + "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) + continue + } + if obj.deleted || obj.selfDestructed { + return common.Hash{}, true + } + codeHash := common.BytesToHash(obj.CodeHash()) + return codeHash, true + } + return common.Hash{}, false +} + +// Similar to getCodeFromUnconfirmedDB +// It is for address state check of: Exist(), Empty() and HasSuicided() +// Since the unconfirmed DB should have done Finalise() with `deleteEmptyObjects = true` +// If the dirty address is empty or suicided, it will be marked as deleted, so we only need to return `deleted` or not. +func (s *ParallelStateDB) getAddrStateFromUnconfirmedDB(addr common.Address, testEmpty bool) (bool, bool) { + // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) + for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { + db_, ok := s.parallel.unconfirmedDBs.Load(i) + if !ok { + continue + } + db := db_.(*ParallelStateDB) + if exist, ok := db.parallel.addrStateChangesInSlot[addr]; ok { + if obj, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + // could not exist, if it is changed but reverted + // fixme: revert should remove the change record + log.Debug("Get addr State from UnconfirmedDB, changed but object not exist, ", + "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) + continue + } else { + if obj.selfDestructed || obj.deleted { + return false, true + } + if testEmpty && obj.empty() { + return false, true + } + } + return exist, true + } + } + return false, false +} + +func (s *ParallelStateDB) getKVFromUnconfirmedDB(addr common.Address, key common.Hash) (common.Hash, bool) { + // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) + for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { + db_, ok := s.parallel.unconfirmedDBs.Load(i) + if !ok { + continue + } + db := db_.(*ParallelStateDB) + if _, ok := db.parallel.kvChangesInSlot[addr]; ok { + obj := db.parallel.dirtiedStateObjectsInSlot[addr] + if obj.deleted || obj.selfDestructed { + return common.Hash{}, true + } + if val, exist := obj.dirtyStorage.GetValue(key); exist { + return val, true + } + } + } + return common.Hash{}, false +} + +func (s *ParallelStateDB) GetStateObjectFromUnconfirmedDB(addr common.Address) (*stateObject, bool) { + return s.getStateObjectFromUnconfirmedDB(addr) +} + +func (s *ParallelStateDB) getStateObjectFromUnconfirmedDB(addr common.Address) (*stateObject, bool) { + // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) + for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { + db_, ok := s.parallel.unconfirmedDBs.Load(i) + if !ok { + continue + } + db := db_.(*ParallelStateDB) + if obj, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return obj, true + } + } + return nil, false +} + +// IsParallelReadsValid If stage2 is true, it is a likely conflict check, +// to detect these potential conflict results in advance and schedule redo ASAP. +func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { + parallelKvOnce.Do(func() { + StartKvCheckLoop() + }) + + mainDB := slotDB.parallel.baseStateDB + // for nonce + for addr, nonceSlot := range slotDB.parallel.nonceReadsInSlot { + if isStage2 { // update slotDB's unconfirmed DB list and try + if nonceUnconfirm, ok := slotDB.getNonceFromUnconfirmedDB(addr); ok { + if nonceSlot != nonceUnconfirm { + log.Debug("IsSlotDBReadsValid nonce read is invalid in unconfirmed", "addr", addr, + "nonceSlot", nonceSlot, "nonceUnconfirm", nonceUnconfirm, "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + } + nonceMain := mainDB.GetNonce(addr) + if nonceSlot != nonceMain { + log.Debug("IsSlotDBReadsValid nonce read is invalid", "addr", addr, + "nonceSlot", nonceSlot, "nonceMain", nonceMain, "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + // balance + for addr, balanceSlot := range slotDB.parallel.balanceReadsInSlot { + if isStage2 { // update slotDB's unconfirmed DB list and try + if balanceUnconfirm := slotDB.getBalanceFromUnconfirmedDB(addr); balanceUnconfirm != nil { + if balanceSlot.Cmp(balanceUnconfirm) == 0 { + continue + } + return false + } + } + + balanceMain := mainDB.GetBalance(addr) + if balanceSlot.Cmp(balanceMain) != 0 { + log.Debug("IsSlotDBReadsValid balance read is invalid", "addr", addr, + "balanceSlot", balanceSlot, "balanceMain", balanceMain, "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + // check KV + var units []ParallelKvCheckUnit // todo: pre-allocate to make it faster + for addr, read := range slotDB.parallel.kvReadsInSlot { + read.Range(func(keySlot, valSlot interface{}) bool { + units = append(units, ParallelKvCheckUnit{addr, keySlot.(common.Hash), valSlot.(common.Hash)}) + return true + }) + } + readLen := len(units) + // TODO-dav: change back to 8 or 1? + if readLen < 80000 || isStage2 { + for _, unit := range units { + if hasKvConflict(slotDB, unit.addr, unit.key, unit.val, isStage2) { + return false + } + } + } else { + msgHandledNum := 0 + msgSendNum := 0 + for _, unit := range units { + for { // make sure the unit is consumed + consumed := false + select { + case conflict := <-parallelKvCheckResCh: + msgHandledNum++ + if conflict { + // make sure all request are handled or discarded + for { + if msgHandledNum == msgSendNum { + break + } + select { + case <-parallelKvCheckReqCh: + msgHandledNum++ + case <-parallelKvCheckResCh: + msgHandledNum++ + } + } + return false + } + case parallelKvCheckReqCh <- ParallelKvCheckMessage{slotDB, isStage2, unit}: + msgSendNum++ + consumed = true + } + if consumed { + break + } + } + } + for { + if msgHandledNum == readLen { + break + } + conflict := <-parallelKvCheckResCh + msgHandledNum++ + if conflict { + // make sure all request are handled or discarded + for { + if msgHandledNum == msgSendNum { + break + } + select { + case <-parallelKvCheckReqCh: + msgHandledNum++ + case <-parallelKvCheckResCh: + msgHandledNum++ + } + } + return false + } + } + } + if isStage2 { // stage2 skip check code, or state, since they are likely unchanged. + return true + } + + // check code + for addr, codeSlot := range slotDB.parallel.codeReadsInSlot { + codeMain := mainDB.GetCode(addr) + if !bytes.Equal(codeSlot, codeMain) { + log.Debug("IsSlotDBReadsValid code read is invalid", "addr", addr, + "len codeSlot", len(codeSlot), "len codeMain", len(codeMain), "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + // check codeHash + for addr, codeHashSlot := range slotDB.parallel.codeHashReadsInSlot { + codeHashMain := mainDB.GetCodeHash(addr) + if !bytes.Equal(codeHashSlot.Bytes(), codeHashMain.Bytes()) { + log.Debug("IsSlotDBReadsValid codehash read is invalid", "addr", addr, + "codeHashSlot", codeHashSlot, "codeHashMain", codeHashMain, "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + // addr state check + for addr, stateSlot := range slotDB.parallel.addrStateReadsInSlot { + stateMain := false // addr not exist + if mainDB.getStateObject(addr) != nil { + stateMain = true // addr exist in main DB + } + if stateSlot != stateMain { + log.Debug("IsSlotDBReadsValid addrState read invalid(true: exist, false: not exist)", + "addr", addr, "stateSlot", stateSlot, "stateMain", stateMain, + "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + // snapshot destructs check + for addr, destructRead := range slotDB.parallel.addrSnapDestructsReadsInSlot { + mainObj := mainDB.getStateObject(addr) + if mainObj == nil { + log.Debug("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", + "addr", addr, "destruct", destructRead, + "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + slotDB.snapParallelLock.RLock() // fixme: this lock is not needed + _, destructMain := mainDB.snapDestructs[addr] // addr not exist + slotDB.snapParallelLock.RUnlock() + if destructRead != destructMain { + log.Debug("IsSlotDBReadsValid snapshot destructs read invalid", + "addr", addr, "destructRead", destructRead, "destructMain", destructMain, + "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + return true +} + +// NeedsRedo returns true if there is any clear reason that we need to redo this transaction +func (s *ParallelStateDB) NeedsRedo() bool { + return s.parallel.needsRedo +} + +// FinaliseForParallel finalises the state by removing the destructed objects and clears +// the journal as well as the refunds. Finalise, however, will not push any updates +// into the tries just yet. Only IntermediateRoot or Commit will do that. +// It also handle the mainDB dirties for the first TX. +func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *StateDB) { + addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) + + if s.TxIndex() == 0 && len(mainDB.journal.dirties) > 0 { + for addr := range mainDB.journal.dirties { + var obj *stateObject + var exist bool + + obj, exist = mainDB.getStateObjectFromStateObjects(addr) + if !exist { + // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 + // That tx goes out of gas, and although the notion of 'touched' does not exist there, the + // touch-event will still be recorded in the journal. Since ripeMD is a special snowflake, + // it will persist in the journal even though the journal is reverted. In this special circumstance, + // it may exist in `s.journal.dirties` but not in `s.stateObjects`. + // Thus, we can safely ignore it here + + continue + } + + if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { + obj.deleted = true + + // We need to maintain account deletions explicitly (will remain + // set indefinitely). Note only the first occurred self-destruct + // event is tracked. + if _, ok := mainDB.stateObjectsDestruct[obj.address]; !ok { + mainDB.stateObjectsDestruct[obj.address] = obj.origin + } + // Note, we can't do this only at the end of a block because multiple + // transactions within the same block might self destruct and then + // resurrect an account; but the snapshotter needs both events. + delete(mainDB.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(mainDB.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) + delete(mainDB.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(mainDB.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + } else { + obj.finalise(true) // Prefetch slots in the background + } + + obj.created = false + mainDB.stateObjectsPending[addr] = struct{}{} + mainDB.stateObjectsDirty[addr] = struct{}{} + + // At this point, also ship the address off to the precacher. The precacher + // will start loading tries, and when the change is eventually committed, + // the commit-phase will be a lot faster + addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure + } + mainDB.clearJournalAndRefund() + } + + for addr := range s.journal.dirties { + var obj *stateObject + var exist bool + if s.parallel.isSlotDB { + obj = s.parallel.dirtiedStateObjectsInSlot[addr] + if obj != nil { + exist = true + } else { + log.Error("StateDB Finalise dirty addr not in dirtiedStateObjectsInSlot", + "addr", addr) + } + } else { + obj, exist = s.getStateObjectFromStateObjects(addr) + } + if !exist { + // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 + // That tx goes out of gas, and although the notion of 'touched' does not exist there, the + // touch-event will still be recorded in the journal. Since ripeMD is a special snowflake, + // it will persist in the journal even though the journal is reverted. In this special circumstance, + // it may exist in `s.journal.dirties` but not in `s.stateObjects`. + // Thus, we can safely ignore it here + continue + } + + if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { + obj.deleted = true + + // We need to maintain account deletions explicitly (will remain + // set indefinitely). Note only the first occurred self-destruct + // event is tracked. + if _, ok := s.stateObjectsDestruct[obj.address]; !ok { + s.stateObjectsDestruct[obj.address] = obj.origin + } + // Note, we can't do this only at the end of a block because multiple + // transactions within the same block might self destruct and then + // resurrect an account; but the snapshotter needs both events. + delete(s.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) + delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + + if s.parallel.isSlotDB { + s.parallel.accountsDeletedRecord = append(s.parallel.accountsDeletedRecord, obj.addrHash) + s.parallel.storagesDeleteRecord = append(s.parallel.storagesDeleteRecord, obj.addrHash) + s.parallel.accountsOriginDeleteRecord = append(s.parallel.accountsOriginDeleteRecord, obj.address) + s.parallel.storagesOriginDeleteRecord = append(s.parallel.storagesOriginDeleteRecord, obj.address) + } + } else { + // 1.none parallel mode, we do obj.finalise(true) as normal + // 2.with parallel mode, we do obj.finalise(true) on dispatcher, not on slot routine + // obj.finalise(true) will clear its dirtyStorage, will make prefetch broken. + if !s.isParallel || !s.parallel.isSlotDB { + obj.finalise(true) // Prefetch slots in the background + } + } + + obj.created = false + s.stateObjectsPending[addr] = struct{}{} + s.stateObjectsDirty[addr] = struct{}{} + // At this point, also ship the address off to the precacher. The precacher + // will start loading tries, and when the change is eventually committed, + // the commit-phase will be a lot faster + addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure + } + + if mainDB.prefetcher != nil && len(addressesToPrefetch) > 0 { + mainDB.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch) + } + // Invalidate journal because reverting across transactions is not allowed. + s.clearJournalAndRefund() +} + +// IntermediateRootForSlotDB computes the current root hash of the state trie. +// It is called in between transactions to get the root hash that +// goes into transaction receipts. +// For parallel SlotDB, the intermediateRoot can be used to calculate the temporary root after executing single tx. +func (s *ParallelStateDB) IntermediateRootForSlotDB(deleteEmptyObjects bool, mainDB *StateDB) common.Hash { + // Finalise all the dirty storage states and write them into the tries + s.FinaliseForParallel(deleteEmptyObjects, mainDB) + // If there was a trie prefetcher operating, it gets aborted and irrevocably + // modified after we start retrieving tries. Remove it from the statedb after + // this round of use. + // + // This is weird pre-byzantium since the first tx runs with a prefetcher and + // the remainder without, but pre-byzantium even the initial prefetcher is + // useless, so no sleep lost. + prefetcher := mainDB.prefetcher + if mainDB.prefetcher != nil { + defer func() { + mainDB.prefetcher.close() + mainDB.prefetcher = nil + }() + } + + if s.TxIndex() == 0 && len(mainDB.stateObjectsPending) > 0 { + for addr := range mainDB.stateObjectsPending { + var obj *stateObject + if obj, _ = mainDB.getStateObjectFromStateObjects(addr); !obj.deleted { + obj.updateRoot() + } + } + } + + // Although naively it makes sense to retrieve the account trie and then do + // the contract storage and account updates sequentially, that short circuits + // the account prefetcher. Instead, let's process all the storage updates + // first, giving the account prefetches just a few more milliseconds of time + // to pull useful data from disk. + for addr := range s.stateObjectsPending { + var obj *stateObject + if s.parallel.isSlotDB { + if obj = s.parallel.dirtiedStateObjectsInSlot[addr]; !obj.deleted { + obj.updateRoot() + } + } else { + if obj, _ = s.getStateObjectFromStateObjects(addr); !obj.deleted { + obj.updateRoot() + } + } + } + // Now we're about to start to write changes to the trie. The trie is so far + // _untouched_. We can check with the prefetcher, if it can give us a trie + // which has the same root, but also has some content loaded into it. + // The parallel execution do the change incrementally, so can not check the prefetcher here + if prefetcher != nil { + if trie := prefetcher.trie(common.Hash{}, mainDB.originalRoot); trie != nil { + mainDB.trie = trie + } + } + + usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) + + if s.TxIndex() == 0 && len(mainDB.stateObjectsPending) > 0 { + usedAddrs = make([][]byte, 0, len(s.stateObjectsPending)+len(mainDB.stateObjectsPending)) + for addr := range mainDB.stateObjectsPending { + if obj, _ := s.getStateObjectFromStateObjects(addr); obj.deleted { + mainDB.deleteStateObject(obj) + mainDB.AccountDeleted += 1 + } else { + mainDB.updateStateObject(obj) + mainDB.AccountUpdated += 1 + } + usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure + } + } + + for addr := range s.stateObjectsPending { + if s.parallel.isSlotDB { + if obj := s.parallel.dirtiedStateObjectsInSlot[addr]; obj.deleted { + mainDB.deleteStateObject(obj) + mainDB.AccountDeleted += 1 + } else { + mainDB.updateStateObject(obj) + mainDB.AccountUpdated += 1 + } + } else if obj, _ := s.getStateObjectFromStateObjects(addr); obj.deleted { + mainDB.deleteStateObject(obj) + mainDB.AccountDeleted += 1 + } else { + mainDB.updateStateObject(obj) + mainDB.AccountUpdated += 1 + } + usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure + } + + if prefetcher != nil { + prefetcher.used(common.Hash{}, mainDB.originalRoot, usedAddrs) + } + // parallel slotDB trie will be updated to mainDB since intermediateRoot happens after conflict check. + // so it should be save to clear pending here. + // otherwise there can be a case that the deleted object get ignored and processes as live object in verify phase. + + if s.TxIndex() == 0 && len(mainDB.stateObjectsPending) > 0 { + mainDB.stateObjectsPending = make(map[common.Address]struct{}) + } + + if /*s.isParallel == false &&*/ len(s.stateObjectsPending) > 0 { + s.stateObjectsPending = make(map[common.Address]struct{}) + } + // Track the amount of time wasted on hashing the account trie + if metrics.EnabledExpensive { + defer func(start time.Time) { mainDB.AccountHashes += time.Since(start) }(time.Now()) + } + ret := mainDB.trie.Hash() + return ret +} diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 8a0fd1989a..65d3af7525 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -243,7 +243,7 @@ func runReport(stats *generateStats, stop chan bool) { // generateTrieRoot generates the trie hash based on the snapshot iterator. // It can be used for generating account trie, storage trie or even the // whole state which connects the accounts and the corresponding storages. -func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { +func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accountExt common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { var ( in = make(chan trieKV) // chan to pass leaves out = make(chan common.Hash, 1) // chan to collect result @@ -254,7 +254,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou wg.Add(1) go func() { defer wg.Done() - generatorFn(db, scheme, account, in, out) + generatorFn(db, scheme, accountExt, in, out) }() // Spin up a go-routine for progress logging if report && stats != nil { @@ -294,12 +294,13 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou ) // Start to feed leaves for it.Next() { - if account == (common.Hash{}) { + if accountExt == (common.Hash{}) { var ( err error fullData []byte ) if leafCallback == nil { + fullData, err = types.FullAccountRLP(it.(AccountIterator).Account()) if err != nil { return stop(err) @@ -323,7 +324,12 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou return } if account.Root != subroot { - results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot) + + // results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot) + + results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x\n accountEXT: %s, account.ROOT: %v, codehash: %s\n", + hash, account.Root, subroot, accountExt.Hex(), account.Root, common.Bytes2Hex(account.CodeHash)) + return } results <- nil @@ -342,20 +348,20 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou // Accumulate the generation statistic if it's required. processed++ if time.Since(logged) > 3*time.Second && stats != nil { - if account == (common.Hash{}) { + if accountExt == (common.Hash{}) { stats.progressAccounts(it.Hash(), processed) } else { - stats.progressContract(account, it.Hash(), processed) + stats.progressContract(accountExt, it.Hash(), processed) } logged, processed = time.Now(), 0 } } // Commit the last part statistic. if processed > 0 && stats != nil { - if account == (common.Hash{}) { + if accountExt == (common.Hash{}) { stats.finishAccounts(processed) } else { - stats.finishContract(account, processed) + stats.finishContract(accountExt, processed) } } return stop(nil) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 70c9f44189..8bd863b350 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -458,6 +458,7 @@ func (dl *diffLayer) flatten() snapshot { comboData[storageHash] = data } } + // Return the combo parent return &diffLayer{ parent: parent.parent, diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 3077468b48..807a10c35f 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -369,7 +369,6 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m // Save the new snapshot for later t.lock.Lock() defer t.lock.Unlock() - t.layers[snap.root] = snap return nil } @@ -412,7 +411,6 @@ func (t *Tree) Cap(root common.Hash, layers int) error { diff.lock.RLock() base := diffToDisk(diff.flatten().(*diffLayer)) diff.lock.RUnlock() - // Replace the entire snapshot tree with the flat base t.layers = map[common.Hash]snapshot{base.root: base} return nil @@ -519,7 +517,6 @@ func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { bottom.lock.RLock() base := diffToDisk(bottom) bottom.lock.RUnlock() - t.layers[base.root] = base diff.parent = base return base @@ -752,6 +749,7 @@ func (t *Tree) Rebuild(root common.Hash) { // Start generating a new snapshot from scratch on a background thread. The // generator will run a wiper first if there's not one running right now. log.Info("Rebuilding state snapshot") + t.layers = map[common.Hash]snapshot{ root: generateSnapshot(t.diskdb, t.triedb, t.config.CacheSize, root), } @@ -798,7 +796,6 @@ func (t *Tree) Verify(root common.Hash) error { return common.Hash{}, err } defer storageIt.Release() - hash, err := generateTrieRoot(nil, "", storageIt, accountHash, stackTrieGenerate, nil, stat, false) if err != nil { return common.Hash{}, err diff --git a/core/state/state_object.go b/core/state/state_object.go index 8696557845..e12c274b4b 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "io" + "math/big" "sync" "time" @@ -34,29 +35,114 @@ import ( "github.com/holiman/uint256" ) +var emptyCodeHash = crypto.Keccak256(nil) + type Code []byte func (c Code) String() string { return string(c) //strings.Join(Disassemble(c), " ") } -type Storage map[common.Hash]common.Hash +type Storage interface { + String() string + GetValue(hash common.Hash) (common.Hash, bool) + StoreValue(hash common.Hash, value common.Hash) + Length() (length int) + Copy() Storage + Range(func(key, value interface{}) bool) +} + +type StorageMap map[common.Hash]common.Hash -func (s Storage) String() (str string) { +func (s StorageMap) String() (str string) { for key, value := range s { str += fmt.Sprintf("%X : %X\n", key, value) } return } -func (s Storage) Copy() Storage { - cpy := make(Storage, len(s)) +func (s StorageMap) Copy() Storage { + cpy := make(StorageMap, len(s)) for key, value := range s { cpy[key] = value } + return cpy } +func (s StorageMap) GetValue(hash common.Hash) (common.Hash, bool) { + value, ok := s[hash] + return value, ok +} + +func (s StorageMap) StoreValue(hash common.Hash, value common.Hash) { + s[hash] = value +} + +func (s StorageMap) Length() int { + return len(s) +} + +func (s StorageMap) Range(f func(hash, value interface{}) bool) { + for k, v := range s { + result := f(k, v) + if !result { + return + } + } +} + +type StorageSyncMap struct { + sync.Map +} + +func (s *StorageSyncMap) String() (str string) { + s.Range(func(key, value interface{}) bool { + str += fmt.Sprintf("%X : %X\n", key, value) + return true + }) + + return +} + +func (s *StorageSyncMap) GetValue(hash common.Hash) (common.Hash, bool) { + value, ok := s.Load(hash) + if !ok { + return common.Hash{}, ok + } + + return value.(common.Hash), ok +} + +func (s *StorageSyncMap) StoreValue(hash common.Hash, value common.Hash) { + s.Store(hash, value) +} + +func (s *StorageSyncMap) Length() (length int) { + s.Range(func(key, value interface{}) bool { + length++ + return true + }) + return length +} + +func (s *StorageSyncMap) Copy() Storage { + cpy := StorageSyncMap{} + s.Range(func(key, value interface{}) bool { + cpy.Store(key, value) + return true + }) + + return &cpy +} + +func newStorage(isParallel bool) Storage { + if isParallel { + return &StorageSyncMap{} + } + return make(StorageMap) +} + // stateObject represents an Ethereum account which is being modified. // // The usage pattern is as follows: @@ -64,7 +150,8 @@ func (s Storage) Copy() Storage { // - Account values as well as storages can be accessed and modified through the object. // - Finally, call commit to return the changes of storage trie and update account data. type stateObject struct { - db *StateDB + db *StateDB // The baseDB for parallel. + dbItf StateDBer // The slotDB for parallel. address common.Address // address of ethereum account addrHash common.Hash // hash of ethereum address of the account origin *types.StateAccount // Account original data without any change applied, nil means it was not existent @@ -74,6 +161,10 @@ type stateObject struct { trie Trie // storage trie, which becomes non-nil on first access code Code // contract bytecode, which gets set when code is loaded + // isParallel indicates this state object is used in parallel mode, in which mode the + // storage would be sync.Map instead of map + isParallel bool + originStorage Storage // Storage cache of original entries to dedup rewrites pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block dirtyStorage Storage // Storage entries that have been modified in the current transaction execution, reset for every transaction @@ -96,11 +187,55 @@ type stateObject struct { // empty returns whether the account is considered empty. func (s *stateObject) empty() bool { - return s.data.Nonce == 0 && s.data.Balance.IsZero() && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes()) + // return s.data.Nongn() == 0 && bytes.ce == 0 && s.data.Balance.SiEqual(s.data.CodeHash, types.EmptyCodeHash.Bytes()) + // return s.data.Nonce == 0 && s.data.Balance.Sign() == 0 && bytes.Equal(s.data.CodeHash, emptyCodeHash) + + // empty() has 3 use cases: + // 1.StateDB.Empty(), to empty check + // A: It is ok, we have handled it in Empty(), to make sure nonce, balance, codeHash are solid + // 2:AddBalance 0, empty check for touch event + // empty() will add a touch event. + // if we misjudge it, the touch event could be lost, which make address not deleted. // fixme + // 3.Finalise(), to do empty delete + // the address should be dirtied or touched + // if it nonce dirtied, it is ok, since nonce is monotonically increasing, won't be zero + // if balance is dirtied, balance could be zero, we should refer solid nonce & codeHash // fixme + // if codeHash is dirtied, it is ok, since code will not be updated. + // if suicide, it is ok + // if object is new created, it is ok + // if CreateAccount, recreate the address, it is ok. + + // Slot 0 tx 0: AddBalance(100) to addr_1, => addr_1: balance = 100, nonce = 0, code is empty + // Slot 1 tx 1: addr_1 Transfer 99.9979 with GasFee 0.0021, => addr_1: balance = 0, nonce = 1, code is empty + // notice: balance transfer cost 21,000 gas, with gasPrice = 100Gwei, GasFee will be 0.0021 + // Slot 0 tx 2: add balance 0 to addr_1(empty check for touch event), + // the object was lightCopied from tx 0, + + // in parallel mode, we should not check empty by raw nonce, balance, codeHash anymore, + // since it could be invalid. + // e.g., AddBalance() to an address, we will do lightCopy to get a new StateObject, we did balance fixup to + // make sure object's Balance is reliable. But we did not fixup nonce or code, we only do nonce or codehash + // fixup on need, that's when we wanna to update the nonce or codehash. + // So nonce, balance + // Before the block is processed, addr_1 account: nonce = 0, emptyCodeHash, balance = 100 + // Slot 0 tx 0: no access to addr_1 + // Slot 1 tx 1: sub balance 100, it is empty and deleted + // Slot 0 tx 2: GetNonce, lightCopy based on main DB(balance = 100) , not empty + + if s.dbItf.GetBalance(s.address).Sign() != 0 { // check balance first, since it is most likely not zero + return false + } + if s.dbItf.GetNonce(s.address) != 0 { + return false + } + codeHash := s.dbItf.GetCodeHash(s.address) + return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty + } // newObject creates a state object. -func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject { +func newObject(dbItf StateDBer, isParallel bool, address common.Address, acct *types.StateAccount) *stateObject { + db := dbItf.getBaseStateDB() var ( origin = acct created = acct == nil // true if the account was not existent @@ -110,13 +245,15 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s } return &stateObject{ db: db, + dbItf: dbItf, address: address, addrHash: crypto.Keccak256Hash(address[:]), origin: origin, data: *acct, - originStorage: make(Storage), - pendingStorage: make(Storage), - dirtyStorage: make(Storage), + isParallel: isParallel, + originStorage: newStorage(isParallel), + pendingStorage: newStorage(isParallel), + dirtyStorage: newStorage(isParallel), created: created, } } @@ -165,7 +302,7 @@ func (s *stateObject) getTrie() (Trie, error) { // GetState retrieves a value from the account storage trie. func (s *stateObject) GetState(key common.Hash) common.Hash { // If we have a dirty value for this state entry, return it - value, dirty := s.dirtyStorage[key] + value, dirty := s.dirtyStorage.GetValue(key) if dirty { return value } @@ -176,21 +313,45 @@ func (s *stateObject) GetState(key common.Hash) common.Hash { // GetCommittedState retrieves a value from the committed account storage trie. func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // If we have a pending write or clean cached, return that - if value, pending := s.pendingStorage[key]; pending { + // if value, pending := s.pendingStorage[key]; pending { + if value, pending := s.pendingStorage.GetValue(key); pending { return value } - if value, cached := s.originStorage[key]; cached { + if value, cached := s.originStorage.GetValue(key); cached { return value } + + // Add-Dav: + // Need to confirm the object is not destructed in unconfirmed db and resurrected in this tx. + // otherwise there is an issue for cases like: + // B0: TX0 --> createAccount @addr1 -- merged into DB + // B1: Tx1 and Tx2 + // Tx1 account@addr1, setState(key0), setState(key1) selfDestruct -- unconfirmed + // Tx2 recreate account@addr2, setState(key0) -- executing + // TX2 GetState(addr2, key1) --- + // key1 is never set after recurrsect, and should not return state in trie as it destructed in unconfirmed + // TODO - dav: do we need try storages from unconfirmedDB? - currently not because conflict detection need it for get from mainDB. + obj, exist := s.dbItf.GetStateObjectFromUnconfirmedDB(s.address) + if exist { + if obj.deleted || obj.selfDestructed { + return common.Hash{} + } + } + // If the object was destructed in *this* block (and potentially resurrected), // the storage has been cleared out, and we should *not* consult the previous // database about any storage values. The only possible alternatives are: // 1) resurrect happened, and new slot values were set -- those should // have been handles via pendingStorage above. // 2) we don't have new values, and can deliver empty response back - if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { + //if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { + s.db.snapParallelLock.RLock() + if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { // fixme: use sync.Map, instead of RWMutex? + s.db.snapParallelLock.RUnlock() return common.Hash{} } + s.db.snapParallelLock.RUnlock() + // If no live objects are available, attempt to use snapshots var ( enc []byte @@ -229,14 +390,22 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { } value.SetBytes(val) } - s.originStorage[key] = value + s.originStorage.StoreValue(key, value) + return value } // SetState updates a value in account storage. func (s *stateObject) SetState(key, value common.Hash) { // If the new value is the same as old, don't set - prev := s.GetState(key) + // In parallel mode, it has to get from StateDB, in case: + // a.the Slot did not set the key before and try to set it to `val_1` + // b.Unconfirmed DB has set the key to `val_2` + // c.if we use StateObject.GetState, and the key load from the main DB is `val_1` + // this `SetState could be skipped` + // d.Finally, the key's value will be `val_2`, while it should be `val_1` + // such as: https://bscscan.com/txs?block=2491181 + prev := s.dbItf.GetState(s.address, key) if prev == value { return } @@ -246,28 +415,35 @@ func (s *stateObject) SetState(key, value common.Hash) { key: key, prevalue: prev, }) + + if s.db.parallel.isSlotDB { + s.db.parallel.kvChangesInSlot[s.address][key] = struct{}{} // should be moved to here, after `s.db.GetState()` + } s.setState(key, value) } func (s *stateObject) setState(key, value common.Hash) { - s.dirtyStorage[key] = value + s.dirtyStorage.StoreValue(key, value) } // finalise moves all dirty storage slots into the pending area to be hashed or // committed later. It is invoked at the end of every transaction. func (s *stateObject) finalise(prefetch bool) { - slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage)) - for key, value := range s.dirtyStorage { - s.pendingStorage[key] = value - if value != s.originStorage[key] { - slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure + slotsToPrefetch := make([][]byte, 0, s.dirtyStorage.Length()) + s.dirtyStorage.Range(func(key, value interface{}) bool { + s.pendingStorage.StoreValue(key.(common.Hash), value.(common.Hash)) + originalValue, _ := s.originStorage.GetValue(key.(common.Hash)) + if value.(common.Hash) != originalValue { + originalKey := key.(common.Hash) + slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(originalKey[:])) // Copy needed for closure } - } + return true + }) if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch) } - if len(s.dirtyStorage) > 0 { - s.dirtyStorage = make(Storage) + if s.dirtyStorage.Length() > 0 { + s.dirtyStorage = newStorage(s.isParallel) } } @@ -282,7 +458,7 @@ func (s *stateObject) updateTrie() (Trie, error) { s.finalise(false) // Short circuit if nothing changed, don't bother with hashing anything - if len(s.pendingStorage) == 0 { + if s.pendingStorage.Length() == 0 { return s.trie, nil } // Track the amount of time wasted on updating the storage trie @@ -300,14 +476,18 @@ func (s *stateObject) updateTrie() (Trie, error) { s.db.setError(err) return nil, err } + // Insert all the pending storage updates into the trie - usedStorage := make([][]byte, 0, len(s.pendingStorage)) + usedStorage := make([][]byte, 0, s.pendingStorage.Length()) dirtyStorage := make(map[common.Hash][]byte) - for key, value := range s.pendingStorage { + s.pendingStorage.Range(func(keyItf, valueItf interface{}) bool { + key := keyItf.(common.Hash) + value := valueItf.(common.Hash) // Skip noop changes, persist actual changes - if value == s.originStorage[key] { - continue + originalValue, _ := s.originStorage.GetValue(key) + if value == originalValue { + return true } var v []byte if value != (common.Hash{}) { @@ -315,7 +495,8 @@ func (s *stateObject) updateTrie() (Trie, error) { v = common.TrimLeftZeroes(value[:]) } dirtyStorage[key] = v - } + return true + }) var wg sync.WaitGroup wg.Add(1) go func() { @@ -365,8 +546,8 @@ func (s *stateObject) updateTrie() (Trie, error) { storage[khash] = encoded // encoded will be nil if it's deleted // Track the original value of slot only if it's mutated first time - prev := s.originStorage[key] - s.originStorage[key] = common.BytesToHash(value) // fill back left zeroes by BytesToHash + prev, _ := s.originStorage.GetValue(key) + s.originStorage.StoreValue(key, common.BytesToHash(value)) // fill back left zeroes by BytesToHash if _, ok := origin[khash]; !ok { if prev == (common.Hash{}) { origin[khash] = nil // nil if it was not present previously @@ -383,7 +564,7 @@ func (s *stateObject) updateTrie() (Trie, error) { if s.db.prefetcher != nil { s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage) } - s.pendingStorage = make(Storage) // reset pending map + s.pendingStorage = newStorage(s.isParallel) // reset pending map return tr, nil } @@ -434,6 +615,7 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) { // Update original account data after commit s.origin = s.data.Copy() + return nodes, nil } @@ -472,13 +654,57 @@ func (s *stateObject) setBalance(amount *uint256.Int) { s.data.Balance = amount } +// ReturnGas Return the gas back to the origin. Used by the Virtual machine or Closures +func (s *stateObject) ReturnGas(gas *big.Int) {} + +func (s *stateObject) lightCopy(db *ParallelStateDB) *stateObject { + object := newObject(db, s.isParallel, s.address, &s.data) + object.code = s.code + object.selfDestructed = s.selfDestructed // should be false + object.dirtyCode = s.dirtyCode // it is not used in slot, but keep it is ok + object.deleted = s.deleted // should be false + + // we must copy because it is possible that s comes from unconfirmedDB and hence storage is necessary. + // otherwise there is problem that the light copied obj is in dirty and addrStateChangeInSlot is marked, but + // GetState get empty from storages and load from mainDB, which is inconsistent with real execution. + // Moreover, as the wrong object already in dirty, no KVStateRead recorded. and hence can not identified in + // conflict detection. + // example: block contains tx1 tx2 + // after execution of tx1, it store object@theAddr in unconfirmedDB. + // at tx2, it first AddBalance of theAddr, which cause lightcopy and store in dirty, and mark the addrStateChangeInSlot + // Then the GetState(theAddr) find addrStateChangeInSlot and get obj in dirty, but the slot is empty so it load from + // mainDB, and return is inconsistent with unconfirmedDB of Tx1 result. (and as object in dirty, it doesn't mark KVStateRead.) + if object.address.Hex() == "0x864BbDA5C698aC34b47a9ea3BD4228802cC5ce3b" { + fmt.Printf("Dav -- ligthCopy -- update storage :%s\n storages:\ndirty:", object.address.Hex()) + s.dirtyStorage.Range(func(key, value interface{}) bool { + fmt.Printf("key: %s, value: %s\n", + key.(common.Hash), value.(common.Hash)) + return true + }) + fmt.Printf("\npending:\n") + s.pendingStorage.Range(func(key, value interface{}) bool { + fmt.Printf("key: %s, value: %s\n", + key.(common.Hash), value.(common.Hash)) + return true + }) + } + + object.dirtyStorage = s.dirtyStorage.Copy() + object.originStorage = s.originStorage.Copy() + object.pendingStorage = s.pendingStorage.Copy() + + return object +} + func (s *stateObject) deepCopy(db *StateDB) *stateObject { obj := &stateObject{ - db: db, - address: s.address, - addrHash: s.addrHash, - origin: s.origin, - data: s.data, + db: db.getBaseStateDB(), + dbItf: db, + address: s.address, + addrHash: s.addrHash, + origin: s.origin, + data: s.data, + isParallel: s.isParallel, } if s.trie != nil { obj.trie = db.db.CopyTrie(s.trie) @@ -493,6 +719,15 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { return obj } +func (s *stateObject) MergeSlotObject(db Database, dirtyObjs *stateObject, keys StateKeys) { + for key := range keys { + // In parallel mode, always GetState by StateDB, not by StateObject directly, + // since it the KV could exist in unconfirmed DB. + // But here, it should be ok, since the KV should be changed and valid in the SlotDB, + s.setState(key, dirtyObjs.GetState(key)) + } +} + // // Attribute accessors // @@ -507,13 +742,16 @@ func (s *stateObject) Code() []byte { if s.code != nil { return s.code } + if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { return nil } + code, err := s.db.db.ContractCode(s.address, common.BytesToHash(s.CodeHash())) if err != nil { s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err)) } + s.code = code return code } @@ -536,7 +774,7 @@ func (s *stateObject) CodeSize() int { } func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { - prevcode := s.Code() + prevcode := s.dbItf.GetCode(s.address) s.db.journal.append(codeChange{ account: &s.address, prevhash: s.CodeHash(), @@ -553,9 +791,10 @@ func (s *stateObject) setCode(codeHash common.Hash, code []byte) { } func (s *stateObject) SetNonce(nonce uint64) { + prevNonce := s.dbItf.GetNonce(s.address) s.db.journal.append(nonceChange{ account: &s.address, - prev: s.data.Nonce, + prev: prevNonce, }) s.setNonce(nonce) } diff --git a/core/state/state_test.go b/core/state/state_test.go index 9be610f962..063d4b5567 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -269,30 +269,46 @@ func compareStateObjects(so0, so1 *stateObject, t *testing.T) { t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code) } - if len(so1.dirtyStorage) != len(so0.dirtyStorage) { - t.Errorf("Dirty storage size mismatch: have %d, want %d", len(so1.dirtyStorage), len(so0.dirtyStorage)) + if so1.dirtyStorage.Length() != so0.dirtyStorage.Length() { + t.Errorf("Dirty storage size mismatch: have %d, want %d", so1.dirtyStorage.Length(), so0.dirtyStorage.Length()) } - for k, v := range so1.dirtyStorage { - if so0.dirtyStorage[k] != v { - t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, so0.dirtyStorage[k], v) + so1.dirtyStorage.Range(func(key, value interface{}) bool { + k, v := key.(common.Hash), value.(common.Hash) + + if tmpV, _ := so0.dirtyStorage.GetValue(k); tmpV != v { + t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, tmpV.String(), v) } - } - for k, v := range so0.dirtyStorage { - if so1.dirtyStorage[k] != v { + return true + }) + + so0.dirtyStorage.Range(func(key, value interface{}) bool { + k, v := key.(common.Hash), value.(common.Hash) + + if tmpV, _ := so1.dirtyStorage.GetValue(k); tmpV != v { t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v) } + return true + }) + + if so1.originStorage.Length() != so0.originStorage.Length() { + t.Errorf("Origin storage size mismatch: have %d, want %d", so1.originStorage.Length(), so0.originStorage.Length()) } - if len(so1.originStorage) != len(so0.originStorage) { - t.Errorf("Origin storage size mismatch: have %d, want %d", len(so1.originStorage), len(so0.originStorage)) - } - for k, v := range so1.originStorage { - if so0.originStorage[k] != v { - t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[k], v) + + so1.originStorage.Range(func(key, value interface{}) bool { + k, v := key.(common.Hash), value.(common.Hash) + + if tmpV, _ := so0.originStorage.GetValue(k); tmpV != v { + t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, tmpV, v) } - } - for k, v := range so0.originStorage { - if so1.originStorage[k] != v { + return true + }) + + so0.originStorage.Range(func(key, value interface{}) bool { + k, v := key.(common.Hash), value.(common.Hash) + + if tmpV, _ := so1.originStorage.GetValue(k); tmpV != v { t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v) } - } + return true + }) } diff --git a/core/state/statedb.go b/core/state/statedb.go index f5464eb23c..a2780c2a03 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,6 +18,7 @@ package state import ( + "bytes" "fmt" "runtime" "sort" @@ -51,6 +52,116 @@ type revision struct { journalIndex int } +var emptyAddr = common.Address{} + +type StateKeys map[common.Hash]struct{} + +type StateObjectSyncMap struct { + sync.Map +} + +func (s *StateObjectSyncMap) LoadStateObject(addr common.Address) (*stateObject, bool) { + so, ok := s.Load(addr) + if !ok { + return nil, ok + } + return so.(*stateObject), ok +} + +func (s *StateObjectSyncMap) StoreStateObject(addr common.Address, stateObject *stateObject) { + s.Store(addr, stateObject) +} + +// loadStateObj is the entry for loading state object from stateObjects in StateDB or stateObjects in parallel +func (s *StateDB) loadStateObj(addr common.Address) (*stateObject, bool) { + + if s.isParallel { + ret, ok := s.parallel.stateObjects.LoadStateObject(addr) + return ret, ok + } + + obj, ok := s.stateObjects[addr] + return obj, ok +} + +// storeStateObj is the entry for storing state object to stateObjects in StateDB or stateObjects in parallel +func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { + if s.isParallel { + // When a state object is stored into s.parallel.stateObjects, + // it belongs to base StateDB, it is confirmed and valid. + // TODO-dav: remove the lock/unlock? + stateObject.db.storeParallelLock.Lock() + s.parallel.stateObjects.Store(addr, stateObject) + stateObject.db.storeParallelLock.Unlock() + } else { + s.stateObjects[addr] = stateObject + } +} + +// deleteStateObj is the entry for deleting state object to stateObjects in StateDB or stateObjects in parallel +func (s *StateDB) deleteStateObj(addr common.Address) { + if s.isParallel { + s.parallel.stateObjects.Delete(addr) + } else { + delete(s.stateObjects, addr) + } +} + +// ParallelState is for parallel mode only +type ParallelState struct { + isSlotDB bool // denotes StateDB is used in slot, we will try to remove it + SlotIndex int // for debug, to be removed + // stateObjects holds the state objects in the base slot db + // the reason for using stateObjects instead of stateObjects on the outside is + // we need a thread safe map to hold state objects since there are many slots will read + // state objects from it; + // And we will merge all the changes made by the concurrent slot into it. + stateObjects *StateObjectSyncMap + + baseStateDB *StateDB // for parallel mode, there will be a base StateDB in dispatcher routine. + baseTxIndex int // slotDB is created base on this tx index. + dirtiedStateObjectsInSlot map[common.Address]*stateObject + unconfirmedDBs *sync.Map /*map[int]*ParallelStateDB*/ // do unconfirmed reference in same slot. + + // we will record the read detail for conflict check and + // the changed addr or key for object merge, the changed detail can be achieved from the dirty object + nonceChangesInSlot map[common.Address]struct{} + nonceReadsInSlot map[common.Address]uint64 + balanceChangesInSlot map[common.Address]struct{} // the address's balance has been changed + balanceReadsInSlot map[common.Address]*uint256.Int // the address's balance has been read and used. + // codeSize can be derived based on code, but codeHash can not be directly derived based on code + // - codeSize is 0 for address not exist or empty code + // - codeHash is `common.Hash{}` for address not exist, emptyCodeHash(`Keccak256Hash(nil)`) for empty code, + // so we use codeReadsInSlot & codeHashReadsInSlot to keep code and codeHash, codeSize is derived from code + codeReadsInSlot map[common.Address][]byte // empty if address not exist or no code in this address + codeHashReadsInSlot map[common.Address]common.Hash + codeChangesInSlot map[common.Address]struct{} + kvReadsInSlot map[common.Address]Storage + kvChangesInSlot map[common.Address]StateKeys // value will be kept in dirtiedStateObjectsInSlot + // Actions such as SetCode, Suicide will change address's state. + // Later call like Exist(), Empty(), HasSuicided() depend on the address's state. + addrStateReadsInSlot map[common.Address]bool // true: exist, false: not exist or deleted + addrStateChangesInSlot map[common.Address]bool // true: created, false: deleted + + addrSnapDestructsReadsInSlot map[common.Address]bool + + accountsDeletedRecord []common.Hash + storagesDeleteRecord []common.Hash + accountsOriginDeleteRecord []common.Address + storagesOriginDeleteRecord []common.Address + + // Transaction will pay gas fee to system address. + // Parallel execution will clear system address's balance at first, in order to maintain transaction's + // gas fee value. Normal transaction will access system address twice, otherwise it means the transaction + // needs real system address's balance, the transaction will be marked redo with keepSystemAddressBalance = true + // systemAddress common.Address + // systemAddressOpsCount int + // keepSystemAddressBalance bool + + // we may need to redo for some specific reasons, like we read the wrong state and need to panic in sequential mode in SubRefund + needsRedo bool +} + // StateDB structs within the ethereum protocol are used to store anything // within the merkle trie. StateDBs take care of caching and storing // nested states. It's the general query interface to retrieve: @@ -71,6 +182,13 @@ type StateDB struct { snaps *snapshot.Tree // Nil if snapshot is not available snap snapshot.Snapshot // Nil if snapshot is not available + storeParallelLock sync.RWMutex + snapParallelLock sync.RWMutex // for parallel mode, for main StateDB, slot will read snapshot, while processor will write. + trieParallelLock sync.Mutex // for parallel mode, for getting states/objects from trie, to handle trie tracer. + snapDestructs map[common.Address]struct{} + snapAccounts map[common.Address][]byte + snapStorage map[common.Address]map[string][]byte + // originalRoot is the pre-state root, before any changes were made. // It will be updated when the Commit is called. originalRoot common.Hash @@ -149,13 +267,20 @@ type StateDB struct { AccountDeleted int StorageDeleted int + isParallel bool + parallel ParallelState // to keep all the parallel execution elements // Testing hooks onCommit func(states *triestate.Set) // Hook invoked when commit is performed } +func (s *StateDB) GetStateObjectFromUnconfirmedDB(addr common.Address) (*stateObject, bool) { + return nil, false +} + // New creates a new state from a given trie. func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) { tr, err := db.OpenTrie(root) + if err != nil { return nil, err } @@ -178,6 +303,11 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) accessList: newAccessList(), transientStorage: newTransientStorage(), hasher: crypto.NewKeccakState(), + + parallel: ParallelState{ + SlotIndex: -1, + }, + txIndex: -1, } if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) @@ -215,6 +345,18 @@ func NewStateDBByTrie(tr Trie, db Database, snaps *snapshot.Tree) (*StateDB, err return sdb, nil } +func (s *StateDB) IsParallel() bool { + return s.isParallel +} + +func (s *StateDB) getBaseStateDB() *StateDB { + return s +} + +func (s *StateDB) getStateObjectFromStateObjects(addr common.Address) (*stateObject, bool) { + return s.loadStateObj(addr) +} + // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. @@ -264,7 +406,6 @@ func (s *StateDB) Error() error { func (s *StateDB) AddLog(log *types.Log) { s.journal.append(addLogChange{txhash: s.thash}) - log.TxHash = s.thash log.TxIndex = uint(s.txIndex) log.Index = s.logSize @@ -336,6 +477,7 @@ func (s *StateDB) Empty(addr common.Address) bool { } // GetBalance retrieves the balance from the given address or 0 if object not found + func (s *StateDB) GetBalance(addr common.Address) *uint256.Int { stateObject := s.getStateObject(addr) if stateObject != nil { @@ -346,20 +488,19 @@ func (s *StateDB) GetBalance(addr common.Address) *uint256.Int { // GetNonce retrieves the nonce from the given address or 0 if object not found func (s *StateDB) GetNonce(addr common.Address) uint64 { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Nonce() + object := s.getStateObject(addr) + if object != nil { + return object.Nonce() } - return 0 } // GetStorageRoot retrieves the storage root from the given address or empty // if object not found. func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Root() + object := s.getStateObject(addr) + if object != nil { + return object.Root() } return common.Hash{} } @@ -369,22 +510,31 @@ func (s *StateDB) TxIndex() int { return s.txIndex } +// BaseTxIndex returns the tx index that slot db based. +func (s *StateDB) BaseTxIndex() int { + return s.parallel.baseTxIndex +} + func (s *StateDB) GetCode(addr common.Address) []byte { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Code() + object := s.getStateObject(addr) + if object != nil { + return object.Code() } return nil } func (s *StateDB) GetCodeSize(addr common.Address) int { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.CodeSize() + object := s.getStateObject(addr) + if object != nil { + return object.CodeSize() } return 0 } +// GetCodeHash return: +// - common.Hash{}: the address does not exist +// - emptyCodeHash: the address exist, but code is empty +// - others: the address exist, and code is not empty func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { stateObject := s.getStateObject(addr) if stateObject != nil { @@ -395,18 +545,18 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { // GetState retrieves a value from the given account's storage trie. func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.GetState(hash) + object := s.getStateObject(addr) + if object != nil { + return object.GetState(hash) } return common.Hash{} } // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.GetCommittedState(hash) + object := s.getStateObject(addr) + if object != nil { + return object.GetCommittedState(hash) } return common.Hash{} } @@ -417,9 +567,9 @@ func (s *StateDB) Database() Database { } func (s *StateDB) HasSelfDestructed(addr common.Address) bool { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.selfDestructed + object := s.getStateObject(addr) + if object != nil { + return object.selfDestructed } return false } @@ -466,6 +616,7 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) { } func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { + stateObject := s.getOrNewStateObject(addr) if stateObject != nil { stateObject.SetState(key, value) @@ -485,6 +636,7 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // TODO(rjl493456442) this function should only be supported by 'unwritable' // state and all mutations made should all be discarded afterwards. if _, ok := s.stateObjectsDestruct[addr]; !ok { + fmt.Printf("Dav -- setStorage - stateObjectsDestruct[%s] = nil\n", addr) s.stateObjectsDestruct[addr] = nil } stateObject := s.getOrNewStateObject(addr) @@ -513,12 +665,11 @@ func (s *StateDB) SelfDestruct(addr common.Address) { } func (s *StateDB) Selfdestruct6780(addr common.Address) { - stateObject := s.getStateObject(addr) - if stateObject == nil { + object := s.getStateObject(addr) + if object == nil { return } - - if stateObject.created { + if object.created { s.SelfDestruct(addr) } } @@ -601,6 +752,7 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { } // Delete the account from the trie addr := obj.Address() + if err := s.trie.DeleteAccount(addr); err != nil { s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) } @@ -610,23 +762,50 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { // the object is not found or was deleted in this execution context. If you need // to differentiate between non-existent/just-deleted, use getDeletedStateObject. func (s *StateDB) getStateObject(addr common.Address) *stateObject { - if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { + obj := s.getDeletedStateObject(addr) + if obj != nil && !obj.deleted { return obj } return nil } -// getDeletedStateObject is similar to getStateObject, but instead of returning -// nil for a deleted state object, it returns the actual object with the deleted -// flag set. This is needed by the state journal to revert to the correct s- -// destructed object instead of wiping all knowledge about the state object. -func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { - // Prefer live objects if any is available - if obj := s.stateObjects[addr]; obj != nil { - return obj +func (s *StateDB) GetStateObjectFromSnapshotOrTrie(addr common.Address) (data *types.StateAccount, ok bool) { + return s.getStateObjectFromSnapshotOrTrie(addr) +} + +func (s *StateDB) SnapHasAccount(addr common.Address) (exist bool) { + if s.snap == nil { + fmt.Printf("Dav -- Test Snap have account snap is nil\n") + return false + } + + acc, _ := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())) + fmt.Printf("Dav -- Test Snap have account, root %s, have? %v\n", s.snap.Root(), acc != nil) + return acc != nil +} + +func (s *StateDB) TriHasAccount(addr common.Address) (exist bool) { + if s.trie == nil { + return false + } + + acc, _ := s.trie.GetAccount(addr) + return acc != nil +} + +func (s *StateDB) GetTrie() Trie { + if s.trie == nil { + return nil } + return s.trie +} + +func (s *StateDB) SetTrie(trie Trie) { + s.trie = trie +} + +func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *types.StateAccount, ok bool) { // If no live objects are available, attempt to use snapshots - var data *types.StateAccount if s.snap != nil { start := time.Now() acc, err := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())) @@ -635,7 +814,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { } if err == nil { if acc == nil { - return nil + return nil, false } data = &types.StateAccount{ Nonce: acc.Nonce, @@ -644,53 +823,117 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { Root: common.BytesToHash(acc.Root), } if len(data.CodeHash) == 0 { - data.CodeHash = types.EmptyCodeHash.Bytes() + data.CodeHash = emptyCodeHash } if data.Root == (common.Hash{}) { data.Root = types.EmptyRootHash } } } + // If snapshot unavailable or reading from it failed, load from the database if data == nil { + var trie Trie + if s.isParallel { + // hold lock for parallel + s.trieParallelLock.Lock() + defer s.trieParallelLock.Unlock() + if s.parallel.isSlotDB { + if s.parallel.baseStateDB == nil { + return nil, false + } else { + tr, err := s.parallel.baseStateDB.db.OpenTrie(s.originalRoot) + if err != nil { + log.Error("Can not openTrie for parallel SlotDB\n") + return nil, false + } + trie = tr + } + } else { + trie = s.trie + } + } else { + trie = s.trie + } + start := time.Now() var err error - data, err = s.trie.GetAccount(addr) + data, err = trie.GetAccount(addr) if metrics.EnabledExpensive { s.AccountReads += time.Since(start) } if err != nil { s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err)) - return nil + return nil, false } if data == nil { - return nil + return nil, false } } + + return data, true +} + +// getDeletedStateObject is similar to getStateObject, but instead of returning +// nil for a deleted state object, it returns the actual object with the deleted +// flag set. This is needed by the state journal to revert to the correct s- +// destructed object instead of wiping all knowledge about the state object. +func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { + // Prefer live objects if any is available + if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { + return obj + } + + data, ok := s.getStateObjectFromSnapshotOrTrie(addr) + if !ok { + return nil + } // Insert into the live set - obj := newObject(s, addr, data) - s.setStateObject(obj) + obj := newObject(s, s.isParallel, addr, data) + s.storeStateObj(addr, obj) return obj } func (s *StateDB) setStateObject(object *stateObject) { - s.stateObjects[object.Address()] = object + if s.isParallel { + // When a state object is stored into s.parallel.stateObjects, + // it belongs to base StateDB, it is confirmed and valid. + s.storeParallelLock.Lock() + s.parallel.stateObjects.Store(object.address, object) + s.storeParallelLock.Unlock() + } else { + s.stateObjects[object.Address()] = object + } + } // getOrNewStateObject retrieves a state object or create a new state object if nil. func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { stateObject := s.getStateObject(addr) if stateObject == nil { - stateObject, _ = s.createObject(addr) + stateObject = s.createObject(addr) } return stateObject } // createObject creates a new state object. If there is an existing account with // the given address, it is overwritten and returned as the second return value. -func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { - prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! - newobj = newObject(s, addr, nil) +// prev is used for CreateAccount to get its balance +// Parallel mode: +// if prev in dirty: revert is ok +// if prev in unconfirmed DB: addr state read record, revert should not put it back +// if prev in main DB: addr state read record, revert should not put it back +// if pre no exist: addr state read record, + +// `prev` is used to handle revert, to recover with the `prev` object +// In Parallel mode, we only need to recover to `prev` in SlotDB, +// +// a.if it is not in SlotDB, `revert` will remove it from the SlotDB +// b.if it is existed in SlotDB, `revert` will recover to the `prev` in SlotDB +// c.as `snapDestructs` it is the same +func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { + prev := s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! + newobj = newObject(s, s.isParallel, addr, nil) if prev == nil { s.journal.append(createObjectChange{account: &addr}) } else { @@ -698,6 +941,8 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) // account and storage data should be cleared as well. Note, it must // be done here, otherwise the destruction event of "original account" // will be lost. + s.snapParallelLock.Lock() // fixme: with new dispatch policy, the ending Tx could running, while the block have processed. + _, prevdestruct := s.stateObjectsDestruct[prev.address] if !prevdestruct { s.stateObjectsDestruct[prev.address] = prev.origin @@ -720,12 +965,19 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) delete(s.storages, prev.addrHash) delete(s.accountsOrigin, prev.address) delete(s.storagesOrigin, prev.address) + + if s.parallel.isSlotDB { + s.parallel.accountsDeletedRecord = append(s.parallel.accountsDeletedRecord, prev.addrHash) + s.parallel.storagesDeleteRecord = append(s.parallel.storagesDeleteRecord, prev.addrHash) + s.parallel.accountsOriginDeleteRecord = append(s.parallel.accountsOriginDeleteRecord, prev.address) + s.parallel.storagesOriginDeleteRecord = append(s.parallel.storagesOriginDeleteRecord, prev.address) + } + s.snapParallelLock.Unlock() } + + newobj.created = true s.setStateObject(newobj) - if prev != nil && !prev.deleted { - return newobj, prev - } - return newobj, nil + return newobj } // CreateAccount explicitly creates a state object. If a state object with the address @@ -739,15 +991,26 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) // // Carrying over the balance ensures that Ether doesn't disappear. func (s *StateDB) CreateAccount(addr common.Address) { - newObj, prev := s.createObject(addr) - if prev != nil { - newObj.setBalance(prev.data.Balance) - } + // no matter it is got from dirty, unconfirmed or main DB + // if addr not exist, preBalance will be common.Big0, it is same as new(big.Int) which + // is the value newObject(), + preBalance := s.GetBalance(addr) + newObj := s.createObject(addr) + newObj.setBalance(new(uint256.Int).Set(preBalance)) // new big.Int for newObj } // Copy creates a deep, independent copy of the state. // Snapshots of the copied state cannot be applied to the copy. func (s *StateDB) Copy() *StateDB { + return s.copyInternal(false) +} + +// CopyDoPrefetch It is mainly for state prefetcher to do trie prefetch right now. +func (s *StateDB) CopyDoPrefetch() *StateDB { + return s.copyInternal(true) +} + +func (s *StateDB) copyInternal(doPrefetch bool) *StateDB { // Copy all the basic fields, initialize the memory ones state := &StateDB{ db: s.db, @@ -774,6 +1037,8 @@ func (s *StateDB) Copy() *StateDB { // miner to operate trie-backed only. snaps: s.snaps, snap: s.snap, + + parallel: ParallelState{}, } // Copy the dirty states, logs, and preimages for addr := range s.journal.dirties { @@ -781,11 +1046,11 @@ func (s *StateDB) Copy() *StateDB { // and in the Finalise-method, there is a case where an object is in the journal but not // in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for // nil - if object, exist := s.stateObjects[addr]; exist { + if object, exist := s.getStateObjectFromStateObjects(addr); exist { // Even though the original object is dirty, we are not copying the journal, // so we need to make sure that any side-effect the journal would have caused // during a commit (or similar op) is already applied to the copy. - state.stateObjects[addr] = object.deepCopy(state) + state.storeStateObj(addr, object.deepCopy(state)) state.stateObjectsDirty[addr] = struct{}{} // Mark the copy dirty to force internal (code/state) commits state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits @@ -796,19 +1061,22 @@ func (s *StateDB) Copy() *StateDB { // is empty. Thus, here we iterate over stateObjects, to enable copies // of copies. for addr := range s.stateObjectsPending { - if _, exist := state.stateObjects[addr]; !exist { - state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state) + if _, exist := state.getStateObjectFromStateObjects(addr); !exist { + object, _ := s.getStateObjectFromStateObjects(addr) + state.storeStateObj(addr, object.deepCopy(state)) } state.stateObjectsPending[addr] = struct{}{} } for addr := range s.stateObjectsDirty { - if _, exist := state.stateObjects[addr]; !exist { - state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state) + if _, exist := state.getStateObjectFromStateObjects(addr); !exist { + object, _ := s.getStateObjectFromStateObjects(addr) + state.storeStateObj(addr, object.deepCopy(state)) } state.stateObjectsDirty[addr] = struct{}{} } // Deep copy the destruction markers. for addr, value := range s.stateObjectsDestruct { + // fmt.Printf("Dav -- copyInternal - stateObjectsDestruct[%s] = (%p) : %v \n", addr, value, value) state.stateObjectsDestruct[addr] = value } // Deep copy the state changes made in the scope of block @@ -849,6 +1117,280 @@ func (s *StateDB) Copy() *StateDB { return state } +var journalPool = sync.Pool{ + New: func() interface{} { + return &journal{ + dirties: make(map[common.Address]int, defaultNumOfSlots), + entries: make([]journalEntry, 0, defaultNumOfSlots), + } + }, +} + +var addressToStructPool = sync.Pool{ + New: func() interface{} { return make(map[common.Address]struct{}, defaultNumOfSlots) }, +} + +var addressToStateKeysPool = sync.Pool{ + New: func() interface{} { return make(map[common.Address]StateKeys, defaultNumOfSlots) }, +} + +var addressToStoragePool = sync.Pool{ + New: func() interface{} { return make(map[common.Address]Storage, defaultNumOfSlots) }, +} + +var addressToStateObjectsPool = sync.Pool{ + New: func() interface{} { return make(map[common.Address]*stateObject, defaultNumOfSlots) }, +} + +var balancePool = sync.Pool{ + New: func() interface{} { return make(map[common.Address]*uint256.Int, defaultNumOfSlots) }, +} + +var addressToHashPool = sync.Pool{ + New: func() interface{} { return make(map[common.Address]common.Hash, defaultNumOfSlots) }, +} + +var addressToBytesPool = sync.Pool{ + New: func() interface{} { return make(map[common.Address][]byte, defaultNumOfSlots) }, +} + +var addressToBoolPool = sync.Pool{ + New: func() interface{} { return make(map[common.Address]bool, defaultNumOfSlots) }, +} + +var addressToUintPool = sync.Pool{ + New: func() interface{} { return make(map[common.Address]uint64, defaultNumOfSlots) }, +} + +var snapStoragePool = sync.Pool{ + New: func() interface{} { return make(map[common.Address]map[string][]byte, defaultNumOfSlots) }, +} + +var snapStorageValuePool = sync.Pool{ + New: func() interface{} { return make(map[string][]byte, defaultNumOfSlots) }, +} + +var logsPool = sync.Pool{ + New: func() interface{} { return make(map[common.Hash][]*types.Log, defaultNumOfSlots) }, +} + +func (s *StateDB) PutSyncPool() { + for key := range s.parallel.codeReadsInSlot { + delete(s.parallel.codeReadsInSlot, key) + } + addressToBytesPool.Put(s.parallel.codeReadsInSlot) + + for key := range s.parallel.codeHashReadsInSlot { + delete(s.parallel.codeHashReadsInSlot, key) + } + addressToHashPool.Put(s.parallel.codeHashReadsInSlot) + + for key := range s.parallel.codeChangesInSlot { + delete(s.parallel.codeChangesInSlot, key) + } + addressToStructPool.Put(s.parallel.codeChangesInSlot) + + for key := range s.parallel.kvChangesInSlot { + delete(s.parallel.kvChangesInSlot, key) + } + addressToStateKeysPool.Put(s.parallel.kvChangesInSlot) + + for key := range s.parallel.kvReadsInSlot { + delete(s.parallel.kvReadsInSlot, key) + } + addressToStoragePool.Put(s.parallel.kvReadsInSlot) + + for key := range s.parallel.balanceChangesInSlot { + delete(s.parallel.balanceChangesInSlot, key) + } + addressToStructPool.Put(s.parallel.balanceChangesInSlot) + + for key := range s.parallel.balanceReadsInSlot { + delete(s.parallel.balanceReadsInSlot, key) + } + balancePool.Put(s.parallel.balanceReadsInSlot) + + for key := range s.parallel.addrStateReadsInSlot { + delete(s.parallel.addrStateReadsInSlot, key) + } + addressToBoolPool.Put(s.parallel.addrStateReadsInSlot) + + for key := range s.parallel.addrStateChangesInSlot { + delete(s.parallel.addrStateChangesInSlot, key) + } + addressToBoolPool.Put(s.parallel.addrStateChangesInSlot) + + for key := range s.parallel.nonceChangesInSlot { + delete(s.parallel.nonceChangesInSlot, key) + } + addressToStructPool.Put(s.parallel.nonceChangesInSlot) + + for key := range s.parallel.nonceReadsInSlot { + delete(s.parallel.nonceReadsInSlot, key) + } + addressToUintPool.Put(s.parallel.nonceReadsInSlot) + + for key := range s.parallel.addrSnapDestructsReadsInSlot { + delete(s.parallel.addrSnapDestructsReadsInSlot, key) + } + addressToBoolPool.Put(s.parallel.addrSnapDestructsReadsInSlot) + + for key := range s.parallel.dirtiedStateObjectsInSlot { + delete(s.parallel.dirtiedStateObjectsInSlot, key) + } + addressToStateObjectsPool.Put(s.parallel.dirtiedStateObjectsInSlot) + + for key := range s.stateObjectsPending { + delete(s.stateObjectsPending, key) + } + addressToStructPool.Put(s.stateObjectsPending) + + for key := range s.stateObjectsDirty { + delete(s.stateObjectsDirty, key) + } + addressToStructPool.Put(s.stateObjectsDirty) + + for key := range s.logs { + delete(s.logs, key) + } + logsPool.Put(s.logs) + + for key := range s.journal.dirties { + delete(s.journal.dirties, key) + } + s.journal.entries = s.journal.entries[:0] + journalPool.Put(s.journal) + + for key := range s.snapDestructs { + delete(s.snapDestructs, key) + } + addressToStructPool.Put(s.snapDestructs) + + for key := range s.snapAccounts { + delete(s.snapAccounts, key) + } + addressToBytesPool.Put(s.snapAccounts) + + for key, storage := range s.snapStorage { + for key := range storage { + delete(storage, key) + } + snapStorageValuePool.Put(storage) + delete(s.snapStorage, key) + } + snapStoragePool.Put(s.snapStorage) +} + +// CopyForSlot copy all the basic fields, initialize the memory ones +func (s *StateDB) CopyForSlot() *ParallelStateDB { + parallel := ParallelState{ + // The stateObjects in Parallel is thread-local. + // The base stateDB's stateObjects is thread-unsafe as it is not guarded by lock. + // The base stateDB's parallel.stateObjects is SyncMap and thread-safe. and no extra lock needed (TODO-dav). + // The base stateDB's parallel.stateObjects are updated by mergeSlotDB with Lock. + // The base stateDB's stateObject is read-only and never be updated once parallel execution happens. + // AND, presumably, the stateDB's stateObject is usually empty for real on-chain cases. + // Before execution, the slotDB should copy objects from base stateDB's parallel.stateObjects and stateObjects + // NOTICE: + // We are not reusing the base slot db's stateObjects although copy can be avoid. Because multiple thread + // access has lock check and there might be tricky bug such as thread1 handle tx0 at the same time with thread2 + // handle tx1, so what thread1's slotDB see in the s.parallel.stateObjects might be the middle result of Thread2. + // + // We are not do simple copy (lightweight pointer copy) as the stateObject can be accessed by different thread. + // Todo-dav: remove lock guard of parallel.stateObject access. + + stateObjects: &StateObjectSyncMap{}, // s.parallel.stateObjects, + codeReadsInSlot: addressToBytesPool.Get().(map[common.Address][]byte), + codeHashReadsInSlot: addressToHashPool.Get().(map[common.Address]common.Hash), + codeChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), + kvChangesInSlot: addressToStateKeysPool.Get().(map[common.Address]StateKeys), + kvReadsInSlot: addressToStoragePool.Get().(map[common.Address]Storage), + balanceChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), + balanceReadsInSlot: balancePool.Get().(map[common.Address]*uint256.Int), + addrStateReadsInSlot: addressToBoolPool.Get().(map[common.Address]bool), + addrStateChangesInSlot: addressToBoolPool.Get().(map[common.Address]bool), + nonceChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), + nonceReadsInSlot: addressToUintPool.Get().(map[common.Address]uint64), + addrSnapDestructsReadsInSlot: addressToBoolPool.Get().(map[common.Address]bool), + isSlotDB: true, + dirtiedStateObjectsInSlot: addressToStateObjectsPool.Get().(map[common.Address]*stateObject), + accountsDeletedRecord: make([]common.Hash, 10), + storagesDeleteRecord: make([]common.Hash, 10), + accountsOriginDeleteRecord: make([]common.Address, 10), + storagesOriginDeleteRecord: make([]common.Address, 10), + } + state := &ParallelStateDB{ + StateDB: StateDB{ + db: s.db, + trie: nil, // Parallel StateDB may access the trie, but it takes no effect to the baseDB. + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), + stateObjects: make(map[common.Address]*stateObject), // replaced by parallel.stateObjects in parallel mode + stateObjectsPending: addressToStructPool.Get().(map[common.Address]struct{}), + stateObjectsDirty: addressToStructPool.Get().(map[common.Address]struct{}), + stateObjectsDestruct: make(map[common.Address]*types.StateAccount), + refund: 0, // should be 0 + logs: logsPool.Get().(map[common.Hash][]*types.Log), + logSize: 0, + preimages: make(map[common.Hash][]byte, len(s.preimages)), + journal: journalPool.Get().(*journal), + hasher: crypto.NewKeccakState(), + isParallel: true, + parallel: parallel, + }, + } + // no need to copy preimages, comment out and remove later + // for hash, preimage := range s.preimages { + // state.preimages[hash] = preimage + // } + + // copy parallel stateObjects + s.storeParallelLock.Lock() + s.parallel.stateObjects.Range(func(addr any, stateObj any) bool { + state.parallel.stateObjects.StoreStateObject(addr.(common.Address), stateObj.(*stateObject).lightCopy(state)) + return true + }) + s.storeParallelLock.Unlock() + if s.snaps != nil { + // In order for the miner to be able to use and make additions + // to the snapshot tree, we need to copy that as well. + // Otherwise, any block mined by ourselves will cause gaps in the tree, + // and force the miner to operate trie-backed only + state.snaps = s.snaps + state.snap = s.snap + // deep copy needed + state.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) + s.snapParallelLock.RLock() + for k, v := range s.snapDestructs { + state.snapDestructs[k] = v + } + s.snapParallelLock.RUnlock() + // snapAccounts is useless in SlotDB, comment out and remove later + // state.snapAccounts = make(map[common.Address][]byte) // snapAccountPool.Get().(map[common.Address][]byte) + // for k, v := range s.snapAccounts { + // state.snapAccounts[k] = v + // } + + // snapStorage is useless in SlotDB either, it is updated on updateTrie, which is validation phase to update the snapshot of a finalized block. + // state.snapStorage = snapStoragePool.Get().(map[common.Address]map[string][]byte) + // for k, v := range s.snapStorage { + // temp := snapStorageValuePool.Get().(map[string][]byte) + // for kk, vv := range v { + // temp[kk] = vv + // } + // state.snapStorage[k] = temp + // } + + // trie prefetch should be done by dispatcher on StateObject Merge, + // disable it in parallel slot + // state.prefetcher = s.prefetcher + } + + return state +} + // Snapshot returns an identifier for the current revision of the state. func (s *StateDB) Snapshot() int { id := s.nextRevisionId @@ -883,8 +1425,21 @@ func (s *StateDB) GetRefund() uint64 { // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) + for addr := range s.journal.dirties { - obj, exist := s.stateObjects[addr] + var obj *stateObject + var exist bool + if s.parallel.isSlotDB { + obj = s.parallel.dirtiedStateObjectsInSlot[addr] + if obj != nil { + exist = true + } else { + log.Error("StateDB Finalise dirty addr not in dirtiedStateObjectsInSlot", + "addr", addr) + } + } else { + obj, exist = s.getStateObjectFromStateObjects(addr) + } if !exist { // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 // That tx goes out of gas, and although the notion of 'touched' does not exist there, the @@ -894,6 +1449,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // Thus, we can safely ignore it here continue } + if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { obj.deleted = true @@ -910,9 +1466,23 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + + if s.parallel.isSlotDB { + s.parallel.accountsDeletedRecord = append(s.parallel.accountsDeletedRecord, obj.addrHash) + s.parallel.storagesDeleteRecord = append(s.parallel.storagesDeleteRecord, obj.addrHash) + s.parallel.accountsOriginDeleteRecord = append(s.parallel.accountsOriginDeleteRecord, obj.address) + s.parallel.storagesOriginDeleteRecord = append(s.parallel.storagesOriginDeleteRecord, obj.address) + } + } else { - obj.finalise(true) // Prefetch slots in the background + // 1.none parallel mode, we do obj.finalise(true) as normal + // 2.with parallel mode, we do obj.finalise(true) on dispatcher, not on slot routine + // obj.finalise(true) will clear its dirtyStorage, will make prefetch broken. + if !s.isParallel || !s.parallel.isSlotDB { + obj.finalise(true) // Prefetch slots in the background + } } + obj.created = false s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} @@ -932,6 +1502,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // IntermediateRoot computes the current root hash of the state trie. // It is called in between transactions to get the root hash that // goes into transaction receipts. +// TODO: For parallel SlotDB, IntermediateRootForSlot is used, need to clean up this method. func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Finalise all the dirty storage states and write them into the tries s.Finalise(deleteEmptyObjects) @@ -991,18 +1562,39 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { // the remainder without, but pre-byzantium even the initial prefetcher is // useless, so no sleep lost. prefetcher := s.prefetcher + r := s.originalRoot if s.prefetcher != nil { defer func() { s.prefetcher.close() s.prefetcher = nil }() + if s.isParallel { + r = s.trie.Hash() + } + } + // Although naively it makes sense to retrieve the account trie and then do + // the contract storage and account updates sequentially, that short circuits + // the account prefetcher. Instead, let's process all the storage updates + // first, giving the account prefetches just a few more milliseconds of time + // to pull useful data from disk. + for addr := range s.stateObjectsPending { + var obj *stateObject + if s.parallel.isSlotDB { + if obj = s.parallel.dirtiedStateObjectsInSlot[addr]; !obj.deleted { + obj.updateRoot() + } + } else { + if obj, _ = s.getStateObjectFromStateObjects(addr); !obj.deleted { + obj.updateRoot() + } + } } - // Now we're about to start to write changes to the trie. The trie is so far // _untouched_. We can check with the prefetcher, if it can give us a trie // which has the same root, but also has some content loaded into it. + // The parallel execution do the change incrementally, so can not check the prefetcher here if prefetcher != nil { - if trie := prefetcher.trie(common.Hash{}, s.originalRoot); trie != nil { + if trie := prefetcher.trie(common.Hash{}, r); trie != nil { s.trie = trie } } @@ -1015,8 +1607,17 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { } usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) + for addr := range s.stateObjectsPending { - if obj := s.stateObjects[addr]; obj.deleted { + if s.parallel.isSlotDB { + if obj := s.parallel.dirtiedStateObjectsInSlot[addr]; obj.deleted { + s.deleteStateObject(obj) + s.AccountDeleted += 1 + } else { + s.updateStateObject(obj) + s.AccountUpdated += 1 + } + } else if obj, _ := s.getStateObjectFromStateObjects(addr); obj.deleted { s.deleteStateObject(obj) s.AccountDeleted += 1 } else { @@ -1028,8 +1629,11 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { if prefetcher != nil { prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs) } + // parallel slotDB trie will be updated to mainDB since intermediateRoot happens after conflict check. + // so it should be save to clear pending here. + // otherwise there can be a case that the deleted object get ignored and processes as live object in verify phase. - if len(s.stateObjectsPending) > 0 { + if /*s.isParallel == false &&*/ len(s.stateObjectsPending) > 0 { s.stateObjectsPending = make(map[common.Address]struct{}) } // Track the amount of time wasted on hashing the account trie @@ -1221,6 +1825,7 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A if s.db.TrieDB().Scheme() == rawdb.HashScheme { return incomplete, nil } + for addr, prev := range s.stateObjectsDestruct { // The original account was non-existing, and it's marked as destructed // in the scope of block. It can be case (a) or (b). @@ -1241,6 +1846,7 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A if prev.Root == types.EmptyRootHash { continue } + // Remove storage slots belong to the account. aborted, slots, set, err := s.deleteStorage(addr, addrHash, prev.Root) if err != nil { @@ -1253,6 +1859,9 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A if aborted { incomplete[addr] = struct{}{} delete(s.storagesOrigin, addr) + if s.parallel.isSlotDB { + s.parallel.storagesOriginDeleteRecord = append(s.parallel.storagesOriginDeleteRecord, addr) + } continue } if s.storagesOrigin[addr] == nil { @@ -1652,3 +2261,327 @@ func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common. } return copied } + +// PrepareForParallel prepares for state db to be used in parallel execution mode. +func (s *StateDB) PrepareForParallel() { + s.isParallel = true + s.parallel.stateObjects = &StateObjectSyncMap{} + // copy objects in stateObjects into parallel if not exist. + // This is lock free as the PrepareForParallel() is invoked at serial phase. + for addr, objPtr := range s.stateObjects { + if _, exist := s.parallel.stateObjects.LoadStateObject(addr); !exist { + newObj := objPtr.deepCopy(s) + s.parallel.stateObjects.StoreStateObject(addr, newObj) + } + } +} + +func (s *StateDB) AddrPrefetch(slotDb *ParallelStateDB) { + addressesToPrefetch := make([][]byte, 0, len(slotDb.parallel.dirtiedStateObjectsInSlot)) + for addr, obj := range slotDb.parallel.dirtiedStateObjectsInSlot { + addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure + if obj.deleted { + continue + } + // copied from obj.finalise(true) + slotsToPrefetch := make([][]byte, 0, obj.dirtyStorage.Length()) + obj.dirtyStorage.Range(func(key, value interface{}) bool { + originalValue, _ := obj.originStorage.GetValue(key.(common.Hash)) + if value.(common.Hash) != originalValue { + originalKey := key.(common.Hash) + slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(originalKey[:])) // Copy needed for closure + } + return true + }) + if s.prefetcher != nil && len(slotsToPrefetch) > 0 { + s.prefetcher.prefetch(obj.addrHash, obj.data.Root, obj.address, slotsToPrefetch) + } + } + + if s.prefetcher != nil && len(addressesToPrefetch) > 0 { + // log.Info("AddrPrefetch", "slotDb.TxIndex", slotDb.TxIndex(), + // "len(addressesToPrefetch)", len(slotDb.parallel.addressesToPrefetch)) + s.prefetcher.prefetch(common.Hash{}, s.originalRoot, emptyAddr, addressesToPrefetch) + } +} + +// MergeSlotDB is for Parallel execution mode, when the transaction has been +// finalized(dirty -> pending) on execution slot, the execution results should be +// merged back to the main StateDB. +func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receipt, txIndex int) *StateDB { + s.SetTxContext(slotDb.thash, slotDb.txIndex) + + for s.nextRevisionId < slotDb.nextRevisionId { + if len(slotDb.validRevisions) > 0 { + r := slotDb.validRevisions[s.nextRevisionId] + s.validRevisions = append(s.validRevisions, r) + } + s.nextRevisionId++ + if len(slotDb.validRevisions) < s.nextRevisionId { + continue + } + } + + // receipt.Logs use unified log index within a block + // align slotDB's log index to the block stateDB's logSize + for _, l := range slotReceipt.Logs { + l.Index += s.logSize + s.logs[s.thash] = append(s.logs[s.thash], l) + } + + s.logSize += slotDb.logSize + + // only merge dirty objects + addressesToPrefetch := make([][]byte, 0, len(slotDb.stateObjectsDirty)) + + for addr := range slotDb.stateObjectsDirty { + if _, exist := s.stateObjectsDirty[addr]; !exist { + s.stateObjectsDirty[addr] = struct{}{} + } + + // stateObjects: KV, balance, nonce... + dirtyObj, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr] + if !ok { + log.Error("parallel merge, but dirty object not exist!", "SlotIndex", slotDb.parallel.SlotIndex, "txIndex:", slotDb.txIndex, "addr", addr) + continue + } + mainObj, exist := s.loadStateObj(addr) + + if !exist || mainObj.deleted { + + // fixme: it is also state change + // addr not exist on main DB, do ownership transfer + // dirtyObj.db = s + // dirtyObj.finalise(true) // true: prefetch on dispatcher + mainObj = dirtyObj.deepCopy(s) + /* if addr == WBNBAddress && slotDb.wbnbMakeUpBalance != nil { + mainObj.setBalance(slotDb.wbnbMakeUpBalance) + }*/ + if !dirtyObj.deleted { + mainObj.finalise(true) + } + s.storeStateObj(addr, mainObj) + + // fixme: should not delete, would cause unconfirmed DB incorrect? + // delete(slotDb.parallel.dirtiedStateObjectsInSlot, addr) // transfer ownership, fixme: shared read? + if dirtyObj.deleted { + // remove the addr from snapAccounts&snapStorage only when object is deleted. + // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for + // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts + delete(s.snapAccounts, addr) + delete(s.snapStorage, addr) + delete(s.accounts, dirtyObj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storages, dirtyObj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) + delete(s.accountsOrigin, dirtyObj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storagesOrigin, dirtyObj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + } + } else { + // addr already in main DB, do merge: balance, KV, code, State(create, suicide) + // can not do copy or ownership transfer directly, since dirtyObj could have outdated + // data(maybe updated within the conflict window) + var newMainObj = mainObj // we don't need to copy the object since the storages are thread safe + if _, ok := slotDb.parallel.addrStateChangesInSlot[addr]; ok { + // there are 3 kinds of state change: + // 1.Suicide + // 2.Empty Delete + // 3.createObject + // a: AddBalance,SetState to a non-exist or deleted(suicide, empty delete) address. + // b: CreateAccount: like DAO the fork, regenerate an account carry its balance without KV + // For these state change, do ownership transfer for efficiency: + // dirtyObj.db = s + // newMainObj = dirtyObj + + // The deepCopy() here introduces issue that the pendingStorage may not empty until block validation. + // so the pendingStorage filled by the execution of previous txs in same block may get overwritten by + // deepCopy here, which causes issue in root calculation. + newMainObj = dirtyObj.deepCopy(s) + + // Merge Storages. Only merge ones doesn't exist, since dirtyObj is newer than mainObj + mainObj.originStorage.Range(func(key, value interface{}) bool { + if _, found := newMainObj.originStorage.GetValue(key.(common.Hash)); !found { + newMainObj.originStorage.StoreValue(key.(common.Hash), value.(common.Hash)) + } + return true + }) + + mainObj.pendingStorage.Range(func(key, value interface{}) bool { + if _, found := newMainObj.pendingStorage.GetValue(key.(common.Hash)); !found { + newMainObj.pendingStorage.StoreValue(key.(common.Hash), value.(common.Hash)) + } + return true + }) + + // TODO - dav: check - the dirtyStorage should be always empty for mainObj as it should be moved to + // pendingStorage by Finalise in execution phase. + mainObj.dirtyStorage.Range(func(key, value interface{}) bool { + if _, found := newMainObj.dirtyStorage.GetValue(key.(common.Hash)); !found { + newMainObj.dirtyStorage.StoreValue(key.(common.Hash), value.(common.Hash)) + } + return true + }) + + // should not delete, would cause unconfirmed DB incorrect. + // delete(slotDb.parallel.dirtiedStateObjectsInSlot, addr) // transfer ownership, fixme: shared read? + if dirtyObj.deleted { + // remove the addr from snapAccounts&snapStorage only when object is deleted. + // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for + // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts + delete(s.snapAccounts, addr) + delete(s.snapStorage, addr) + delete(s.accounts, dirtyObj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storages, dirtyObj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) + delete(s.accountsOrigin, dirtyObj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storagesOrigin, dirtyObj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + } + } else { + // deepCopy a temporary *stateObject for safety, since slot could read the address, + // dispatch should avoid overwrite the StateObject directly otherwise, it could + // crash for: concurrent map iteration and map write + + if _, balanced := slotDb.parallel.balanceChangesInSlot[addr]; balanced { + newMainObj.setBalance(dirtyObj.Balance()) + } + if _, coded := slotDb.parallel.codeChangesInSlot[addr]; coded { + if bytes.Equal(dirtyObj.data.CodeHash, types.EmptyCodeHash.Bytes()) { // addr.Hex() == "0x0000000000000000000000000000000000000100" { + fmt.Printf("Dav -- MergeSlotDB - codeChangeInSlot - setObjectCodeHash to Empty, addr: %s\n", addr) + } + newMainObj.code = dirtyObj.code + newMainObj.data.CodeHash = dirtyObj.data.CodeHash + newMainObj.dirtyCode = true + } + if keys, stated := slotDb.parallel.kvChangesInSlot[addr]; stated { + newMainObj.MergeSlotObject(s.db, dirtyObj, keys) + } + if _, nonced := slotDb.parallel.nonceChangesInSlot[addr]; nonced { + // dirtyObj.Nonce() should not be less than newMainObj + newMainObj.setNonce(dirtyObj.Nonce()) + } + newMainObj.deleted = dirtyObj.deleted + } + if !newMainObj.deleted { + newMainObj.finalise(true) // true: prefetch on dispatcher + } + // update the object + s.storeStateObj(addr, newMainObj) + } + addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure + } + + if s.prefetcher != nil && len(addressesToPrefetch) > 0 { + s.prefetcher.prefetch(common.Hash{}, s.originalRoot, emptyAddr, addressesToPrefetch) // prefetch for trie node of account + } + + for addr := range slotDb.stateObjectsPending { + if _, exist := s.stateObjectsPending[addr]; !exist { + s.stateObjectsPending[addr] = struct{}{} + } + } + + for addr := range slotDb.stateObjectsDestruct { + if acc, exist := s.stateObjectsDestruct[addr]; !exist { + s.stateObjectsDestruct[addr] = acc + } + } + // slotDb.logs: logs will be kept in receipts, no need to do merge + + for hash, preimage := range slotDb.preimages { + s.preimages[hash] = preimage + } + if s.accessList != nil && slotDb.accessList != nil { + s.accessList = slotDb.accessList.Copy() + } + + // handle accounts, storages and origins + for _, addr := range slotDb.parallel.accountsDeletedRecord { + if _, ok := s.accounts[addr]; ok { + delete(s.accounts, addr) + } + } + for addr, val := range slotDb.accounts { + s.accounts[addr] = val + } + + // storages + for _, addr := range slotDb.parallel.storagesDeleteRecord { + if _, ok := s.storages[addr]; ok { + delete(s.storages, addr) + } + } + + for addr, slotStMap := range slotDb.storages { + mainStMap := s.storages[addr] + if mainStMap == nil { + mainStMap = make(map[common.Hash][]byte) + } + for k, v := range slotStMap { + mainStMap[k] = v + } + s.storages[addr] = mainStMap + } + + // accountsOrigin + for _, addr := range slotDb.parallel.accountsOriginDeleteRecord { + if _, ok := s.accountsOrigin[addr]; ok { + delete(s.accountsOrigin, addr) + } + } + + for addr, val := range slotDb.accountsOrigin { + s.accountsOrigin[addr] = val + } + + // storagesOrigin + for _, addr := range slotDb.parallel.storagesOriginDeleteRecord { + if _, ok := s.storagesOrigin[addr]; ok { + delete(s.storagesOrigin, addr) + } + } + + for addr, slotStOrgMap := range slotDb.storagesOrigin { + mainStOrgMap := s.storagesOrigin[addr] + if mainStOrgMap == nil { + mainStOrgMap = make(map[common.Hash][]byte) + } + for k, v := range slotStOrgMap { + mainStOrgMap[k] = v + } + s.storagesOrigin[addr] = mainStOrgMap + } + + if slotDb.snaps != nil { + for k := range slotDb.snapDestructs { + // There could be a race condition for parallel transaction execution + // One transaction add balance 0 to an empty address, will delete it(delete empty is enabled). + // While another concurrent transaction could add a none-zero balance to it, make it not empty + // We fixed it by add an addr state read record for add balance 0 + s.snapParallelLock.Lock() + s.snapDestructs[k] = struct{}{} + s.snapParallelLock.Unlock() + } + } + + return s +} + +func (s *StateDB) ParallelMakeUp(common.Address, []byte) { + // do nothing, this API is for parallel mode +} + +func (s *StateDB) PrintParallelStateObjects() { + if s.parallel.stateObjects == nil { + return + } + s.parallel.stateObjects.Range(func(a any, v any) bool { + fmt.Printf("Dav - .parallel.stateObjects addr %v, val: %v\n", a, v) + return true + }) +} + +func (s *StateDB) GetNonceFromBaseDB(addr common.Address) uint64 { + return s.getBaseStateDB().GetNonce(addr) +} + +// delete me! +func (s *StateDB) GetDB() Database { + return s.db +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index e71c984f12..0ffeca0e22 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -19,6 +19,7 @@ package state import ( "bytes" "encoding/binary" + "encoding/hex" "errors" "fmt" "math" @@ -43,6 +44,10 @@ import ( "github.com/holiman/uint256" ) +var ( + systemAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") +) + // Tests that updating a state trie does not leak any database writes prior to // actually committing the state. func TestUpdateLeaks(t *testing.T) { @@ -180,6 +185,7 @@ func TestCopy(t *testing.T) { // modify all in memory for i := byte(0); i < 255; i++ { + origObj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) copyObj := copy.getOrNewStateObject(common.BytesToAddress([]byte{i})) ccopyObj := ccopy.getOrNewStateObject(common.BytesToAddress([]byte{i})) @@ -465,7 +471,7 @@ func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.H for it.Next() { key := common.BytesToHash(s.trie.GetKey(it.Key)) - if value, dirty := so.dirtyStorage[key]; dirty { + if value, dirty := so.dirtyStorage.GetValue(key); dirty { if !cb(key, value) { return nil } @@ -1191,3 +1197,367 @@ func TestDeleteStorage(t *testing.T) { t.Fatalf("difference found:\nfast: %v\nslow: %v\n", fastRes, slowRes) } } + +func TestSuicide(t *testing.T) { + // Create an initial state with a few accounts + db := rawdb.NewMemoryDatabase() + state, _ := New(types.EmptyRootHash, NewDatabase(db), nil) + unconfirmedDBs := new(sync.Map) + + state.PrepareForParallel() + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + + addr := common.BytesToAddress([]byte("so")) + slotDb.SetBalance(addr, big.NewInt(1)) + + slotDb.SelfDestruct(addr) + + if _, ok := slotDb.parallel.addrStateChangesInSlot[addr]; !ok { + t.Fatalf("address should exist in addrStateChangesInSlot") + } + + if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + t.Fatalf("address should exist in dirtiedStateObjectsInSlot") + } + + hasSuicide := slotDb.HasSelfDestructed(addr) + if !hasSuicide { + t.Fatalf("address should be suicided") + } + + if _, ok := slotDb.parallel.addrStateReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in addrStateReadsInSlot") + } +} + +func TestSetAndGetState(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(types.EmptyRootHash, db, nil) + + addr := common.BytesToAddress([]byte("so")) + state.SetBalance(addr, big.NewInt(1)) + unconfirmedDBs := new(sync.Map) + state.PrepareForParallel() + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb.SetState(addr, common.BytesToHash([]byte("test key")), common.BytesToHash([]byte("test store"))) + + if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + t.Fatalf("address should exist in dirtiedStateObjectsInSlot") + } + + if _, ok := slotDb.parallel.addrStateChangesInSlot[addr]; !ok { + t.Fatalf("address should exist in stateChangesInSlot") + } + + oldValueRead := state.GetState(addr, common.BytesToHash([]byte("test key"))) + emptyHash := common.Hash{} + if oldValueRead != emptyHash { + t.Fatalf("value read in old state should be empty") + } + + valueRead := slotDb.GetState(addr, common.BytesToHash([]byte("test key"))) + if valueRead != common.BytesToHash([]byte("test store")) { + t.Fatalf("value read should be equal to the stored value") + } + + if _, ok := slotDb.parallel.addrStateReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in stateReadsInSlot") + } +} + +func TestSetAndGetCode(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + + addr := common.BytesToAddress([]byte("so")) + state.SetBalance(addr, big.NewInt(1)) + state.PrepareForParallel() + + unconfirmedDBs := new(sync.Map) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; ok { + t.Fatalf("address should not exist in dirtiedStateObjectsInSlot") + } + + slotDb.SetCode(addr, []byte("test code")) + + if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + t.Fatalf("address should exist in dirtiedStateObjectsInSlot") + } + + if _, ok := slotDb.parallel.codeChangesInSlot[addr]; !ok { + t.Fatalf("address should exist in codeChangesInSlot") + } + + codeRead := slotDb.GetCode(addr) + if string(codeRead) != "test code" { + t.Fatalf("code read should be equal to the code stored") + } + + if _, ok := slotDb.parallel.codeReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in codeReadsInSlot") + } +} + +func TestGetCodeSize(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + + addr := common.BytesToAddress([]byte("so")) + state.SetBalance(addr, big.NewInt(1)) + state.PrepareForParallel() + + unconfirmedDBs := new(sync.Map) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb.SetCode(addr, []byte("test code")) + + codeSize := slotDb.GetCodeSize(addr) + if codeSize != 9 { + t.Fatalf("code size should be 9") + } + + if _, ok := slotDb.parallel.codeReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in codeReadsInSlot") + } +} + +func TestGetCodeHash(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + + addr := common.BytesToAddress([]byte("so")) + state.SetBalance(addr, big.NewInt(1)) + state.PrepareForParallel() + unconfirmedDBs := new(sync.Map) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + + slotDb.SetCode(addr, []byte("test code")) + + codeSize := slotDb.GetCodeHash(addr) + + if hex.EncodeToString(codeSize[:]) != "6e73fa02f7828b28608b078b007a4023fb40453c3e102b83828a3609a94d8cbb" { + t.Fatalf("code hash should be 6e73fa02f7828b28608b078b007a4023fb40453c3e102b83828a3609a94d8cbb") + } + if _, ok := slotDb.parallel.codeReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in codeReadsInSlot") + } +} + +func TestSetNonce(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + + addr := common.BytesToAddress([]byte("so")) + state.SetBalance(addr, big.NewInt(1)) + state.SetNonce(addr, 1) + state.PrepareForParallel() + + unconfirmedDBs := new(sync.Map) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb.SetNonce(addr, 2) + + oldNonce := state.GetNonce(addr) + if oldNonce != 1 { + t.Fatalf("old nonce should be 1") + } + + newNonce := slotDb.GetNonce(addr) + if newNonce != 2 { + t.Fatalf("new nonce should be 2") + } + if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + t.Fatalf("address should exist in dirtiedStateObjectsInSlot") + } +} + +func TestSetAndGetBalance(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + + addr := systemAddress + state.SetBalance(addr, big.NewInt(1)) + state.PrepareForParallel() + unconfirmedDBs := new(sync.Map) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + + slotDb.SetBalance(addr, big.NewInt(2)) + + oldBalance := state.GetBalance(addr) + if oldBalance.Int64() != 1 { + t.Fatalf("old balance should be 1") + } + + if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + t.Fatalf("address should exist in dirtiedStateObjectsInSlot") + } + + if _, ok := slotDb.parallel.balanceChangesInSlot[addr]; !ok { + t.Fatalf("address should exist in balanceChangesInSlot") + } + + newBalance := slotDb.GetBalance(addr) + if newBalance.Int64() != 2 { + t.Fatalf("new nonce should be 2") + } + + if _, ok := slotDb.parallel.balanceReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in balanceReadsInSlot") + } +} + +func TestSubBalance(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + addr := systemAddress + state.SetBalance(addr, big.NewInt(2)) + + state.PrepareForParallel() + unconfirmedDBs := new(sync.Map) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb.SubBalance(addr, big.NewInt(1)) + + oldBalance := state.GetBalance(addr) + if oldBalance.Int64() != 2 { + t.Fatalf("old balance should be 1") + } + + if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + t.Fatalf("address should exist in dirtiedStateObjectsInSlot") + } + + if _, ok := slotDb.parallel.balanceChangesInSlot[addr]; !ok { + t.Fatalf("address should exist in balanceChangesInSlot") + } + + if _, ok := slotDb.parallel.balanceReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in balanceReadsInSlot") + } + + newBalance := slotDb.GetBalance(addr) + if newBalance.Int64() != 1 { + t.Fatalf("new nonce should be 2") + } +} + +func TestAddBalance(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + addr := systemAddress + state.SetBalance(addr, big.NewInt(2)) + state.PrepareForParallel() + unconfirmedDBs := new(sync.Map) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb.AddBalance(addr, big.NewInt(1)) + + oldBalance := state.GetBalance(addr) + if oldBalance.Int64() != 2 { + t.Fatalf("old balance should be 1") + } + + if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + t.Fatalf("address should exist in dirtiedStateObjectsInSlot") + } + + if _, ok := slotDb.parallel.balanceChangesInSlot[addr]; !ok { + t.Fatalf("address should exist in balanceChangesInSlot") + } + + if _, ok := slotDb.parallel.balanceReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in balanceReadsInSlot") + } + + newBalance := slotDb.GetBalance(addr) + if newBalance.Int64() != 3 { + t.Fatalf("new nonce should be 2") + } +} + +func TestEmpty(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + addr := systemAddress + state.SetBalance(addr, big.NewInt(2)) + state.PrepareForParallel() + + unconfirmedDBs := new(sync.Map) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + + empty := slotDb.Empty(addr) + if empty { + t.Fatalf("address should exist") + } + + if _, ok := slotDb.parallel.addrStateReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in addrStateReadsInSlot") + } +} + +func TestExist(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + addr := systemAddress + state.SetBalance(addr, big.NewInt(2)) + state.PrepareForParallel() + unconfirmedDBs := new(sync.Map) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + + exist := slotDb.Exist(addr) + if !exist { + t.Fatalf("address should exist") + } + + if _, ok := slotDb.parallel.addrStateReadsInSlot[addr]; !ok { + t.Fatalf("address should exist in addrStateReadsInSlot") + } +} + +func TestMergeSlotDB(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + state.PrepareForParallel() + unconfirmedDBs := new(sync.Map) + + oldSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + + newSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + + addr := systemAddress + newSlotDb.SetBalance(addr, big.NewInt(2)) + newSlotDb.SetState(addr, common.BytesToHash([]byte("test key")), common.BytesToHash([]byte("test store"))) + newSlotDb.SetCode(addr, []byte("test code")) + newSlotDb.SelfDestruct(addr) + newSlotDb.Finalise(true) + + changeList := oldSlotDb.MergeSlotDB(newSlotDb, &types.Receipt{}, 0) + + if ok := changeList.getDeletedStateObject(addr); ok == nil || !ok.selfDestructed { + t.Fatalf("address should exist in StateObjectSuicided") + } + + if ok := changeList.getStateObject(addr); ok != nil { + t.Fatalf("address should exist in StateChangeSet") + } + + if ok := changeList.GetBalance(addr); ok != common.Big0 { + t.Fatalf("address should exist in StateChangeSet") + } + + if ok := changeList.GetCode(addr); ok != nil { + t.Fatalf("address should exist in CodeChangeSet") + } + + if ok := changeList.getStateObject(addr); ok != nil { + t.Fatalf("address should exist in AddrStateChangeSet") + } +} diff --git a/core/state/transient_storage.go b/core/state/transient_storage.go index 66e563efa7..ea2b5bfefe 100644 --- a/core/state/transient_storage.go +++ b/core/state/transient_storage.go @@ -21,7 +21,7 @@ import ( ) // transientStorage is a representation of EIP-1153 "Transient Storage". -type transientStorage map[common.Address]Storage +type transientStorage map[common.Address]StorageMap // newTransientStorage creates a new instance of a transientStorage. func newTransientStorage() transientStorage { @@ -31,7 +31,7 @@ func newTransientStorage() transientStorage { // Set sets the transient-storage `value` for `key` at the given `addr`. func (t transientStorage) Set(addr common.Address, key, value common.Hash) { if _, ok := t[addr]; !ok { - t[addr] = make(Storage) + t[addr] = make(StorageMap) } t[addr][key] = value } @@ -49,7 +49,8 @@ func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash func (t transientStorage) Copy() transientStorage { storage := make(transientStorage) for key, value := range t { - storage[key] = value.Copy() + m := value.Copy() + storage[key] = m.(StorageMap) } return storage } diff --git a/core/state_processor.go b/core/state_processor.go index c9df98536c..541d303ceb 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -129,7 +129,6 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta if msg.IsDepositTx && config.IsOptimismRegolith(evm.Context.Time) { nonce = statedb.GetNonce(msg.From) } - // Apply the transaction to the current state (included in the env). result, err := ApplyMessage(evm, msg, gp) if err != nil { diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 77efaede58..fbc6632a75 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -18,6 +18,7 @@ package core import ( "crypto/ecdsa" + "github.com/holiman/uint256" "math/big" "testing" @@ -34,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" - "github.com/holiman/uint256" "golang.org/x/crypto/sha3" ) @@ -73,6 +73,7 @@ func TestStateProcessorErrors(t *testing.T) { tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, key) return tx } + var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction { tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ Nonce: nonce, @@ -111,7 +112,6 @@ func TestStateProcessorErrors(t *testing.T) { } return tx } - { // Tests against a 'recent' chain definition var ( db = rawdb.NewMemoryDatabase() @@ -128,7 +128,7 @@ func TestStateProcessorErrors(t *testing.T) { }, }, } - blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} ) @@ -147,6 +147,7 @@ func TestStateProcessorErrors(t *testing.T) { }, want: "could not apply tx 1 [0x0026256b3939ed97e2c4a6f3fce8ecf83bdcfa6d507c47838c308a1fb0436f62]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", }, + { // ErrNonceTooHigh txs: []*types.Transaction{ makeTx(key1, 100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), @@ -288,7 +289,7 @@ func TestStateProcessorErrors(t *testing.T) { }, }, } - blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) ) defer blockchain.Stop() for i, tt := range []struct { @@ -312,7 +313,6 @@ func TestStateProcessorErrors(t *testing.T) { } } } - // ErrSenderNoEOA, for this we need the sender to have contract code { var ( @@ -327,7 +327,7 @@ func TestStateProcessorErrors(t *testing.T) { }, }, } - blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) ) defer blockchain.Stop() for i, tt := range []struct { diff --git a/core/state_transition.go b/core/state_transition.go index a23a26468e..0e174a0a7c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -554,6 +554,7 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { ReturnData: ret, }, nil } + effectiveTip := msg.GasPrice if rules.IsLondon { effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)) @@ -589,7 +590,6 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256) } } - return &ExecutionResult{ UsedGas: st.gasUsed(), RefundedGas: gasRefund, diff --git a/core/types/block.go b/core/types/block.go index 1a357baa3a..0e0b621974 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -237,6 +237,8 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* } else { b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher) b.header.Bloom = CreateBloom(receipts) + //fmt.Printf("Dav -- NewBlock -- ReceptHash: %s\nRecepts: %v\nBloom: %s\n", b.header.ReceiptHash, receipts, hexutils.BytesToHex(b.header.Bloom.Bytes())) + //debug.PrintStack() } if len(uncles) == 0 { diff --git a/core/types/receipt.go b/core/types/receipt.go index 67c1addb3d..8cb2bbdad8 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -18,6 +18,7 @@ package types import ( "bytes" + "encoding/json" "errors" "fmt" "io" @@ -605,3 +606,11 @@ func u32ptrTou64ptr(a *uint32) *uint64 { b := uint64(*a) return &b } + +// Debug PrettyPrint +func (r Receipt) PrettyPrint() (string, error) { + b, err := r.MarshalJSON() + var prettyJSON bytes.Buffer + json.Indent(&prettyJSON, b, "", "\t") + return prettyJSON.String(), err +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 43ab27308b..0b2dc98db0 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -17,10 +17,11 @@ package vm import ( - "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" "math/big" "sync/atomic" + "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" + "github.com/holiman/uint256" "github.com/ethereum/go-ethereum/common" @@ -261,6 +262,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas contract.optimized, code = tryGetOptimizedCode(evm, codeHash, code) contract.SetCallCode(&addrCopy, codeHash, code) ret, err = evm.interpreter.Run(contract, input, false) + evm.StateDB.ParallelMakeUp(addr, input) gas = contract.Gas } else { addrCopy := addr @@ -269,6 +271,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas contract := NewContract(caller, AccountRef(addrCopy), value, gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) ret, err = evm.interpreter.Run(contract, input, false) + evm.StateDB.ParallelMakeUp(addr, input) gas = contract.Gas } } @@ -523,14 +526,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, common.Address{}, gas, ErrNonceUintOverflow } evm.StateDB.SetNonce(caller.Address(), nonce+1) + // We add this to the access list _before_ taking a snapshot. Even if the creation fails, // the access-list change should not be rolled back if evm.chainRules.IsBerlin { evm.StateDB.AddAddressToAccessList(address) } + // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) - if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { + // debug + no := evm.StateDB.GetNonce(address) + if no != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { return nil, common.Address{}, 0, ErrContractAddressCollision } // Create a new account on the state diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 4b141d8f9a..f6dd7c2377 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -18,7 +18,6 @@ package vm import ( "errors" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 431b287415..822a118b4c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -798,6 +798,7 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext if interpreter.readOnly { return nil, ErrWriteProtection } + beneficiary := scope.Stack.pop() balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) diff --git a/core/vm/interface.go b/core/vm/interface.go index 25bfa06720..bf2f42e994 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -79,6 +79,13 @@ type StateDB interface { AddLog(*types.Log) AddPreimage(common.Hash, []byte) + + ParallelMakeUp(addr common.Address, input []byte) + + // todo -dav : delete following + PrintParallelStateObjects() + GetNonceFromBaseDB(addr common.Address) uint64 + TxIndex() int } // CallContext provides a basic interface for the EVM calling conventions. The EVM diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 80acdcc013..67978d877f 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -33,6 +33,8 @@ type Config struct { NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages ExtraEips []int // Additional EIPS that are to be enabled + EnableParallelExec bool // Whether to execute transaction in parallel mode when do full sync + ParallelTxNum int // Number of slot for transaction execution OptimismPrecompileOverrides PrecompileOverrides // Precompile overrides for Optimism EnableOpcodeOptimizations bool // Enable opcode optimization } @@ -174,6 +176,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } }() } + // The Interpreter main run loop (contextual). This loop runs until either an // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during // the execution of one of the operations or until the done flag is set by the @@ -197,6 +200,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( if !contract.UseGas(cost) { return nil, ErrOutOfGas } + if operation.dynamicGas != nil { // All ops with a dynamic memory usage also has a dynamic gas cost. var memorySize uint64 @@ -242,10 +246,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } pc++ } - if err == errStopToken { err = nil // clear stop token error } - return res, err } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index f420a24105..28ad9c2824 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -18,7 +18,6 @@ package vm import ( "errors" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" @@ -37,6 +36,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { current = evm.StateDB.GetState(contract.Address(), slot) cost = uint64(0) ) + // Check slot presence in the access list if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { cost = params.ColdSloadCostEIP2929 @@ -50,7 +50,6 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { } } value := common.Hash(y.Bytes32()) - if current == value { // noop (1) // EIP 2200 original clause: // return params.SloadGasEIP2200, nil diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 52756b4093..362ecf73e4 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -188,7 +188,7 @@ func benchmarkEVM_Create(bench *testing.B, code string) { EIP155Block: new(big.Int), EIP158Block: new(big.Int), }, - EVMConfig: vm.Config{}, + EVMConfig: vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, } // Warm up the intpools and stuff bench.ResetTimer() diff --git a/eth/backend.go b/eth/backend.go index b690938e87..a8ed4fe836 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -223,6 +223,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { var ( vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, + EnableParallelExec: config.ParallelTxMode, + ParallelTxNum: config.ParallelTxNum, EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, } cacheConfig = &core.CacheConfig{ diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 097888f024..68858972d8 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -72,7 +72,7 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester { Alloc: types.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, BaseFee: big.NewInt(params.InitialBaseFee), } - chain, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { panic(err) } diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index 46f3febd8b..e4e10849fe 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -64,7 +64,6 @@ func init() { fsHeaderContCheck = 500 * time.Millisecond testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis) - var forkLen = int(fullMaxForkAncestry + 50) var wg sync.WaitGroup @@ -218,7 +217,7 @@ func newTestBlockchain(blocks []*types.Block) *core.BlockChain { if pregenerated { panic("Requested chain generation outside of init") } - chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, testGspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, testGspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { panic(err) } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 383641ffc3..77080f5870 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -218,6 +218,8 @@ type Config struct { RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string + ParallelTxMode bool // Whether to execute transaction in parallel mode when do full sync + ParallelTxNum int // Number of slot for transaction execution EnableOpcodeOptimizing bool } diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index 659ca5ce19..1c8a1fcb38 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -250,7 +250,7 @@ func TestFilters(t *testing.T) { } }) var l uint64 - bc, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) + bc, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &l) if err != nil { t.Fatal(err) } diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 79217502f7..f860735fed 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -164,7 +164,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke b.AddTx(types.MustSignNewTx(key, signer, txdata)) }) // Construct testing chain - chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("Failed to create local chain, %v", err) } diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 1eb9a9ea49..c6532b54ce 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -99,8 +99,8 @@ func testForkIDSplit(t *testing.T, protocol uint) { gspecNoFork = &core.Genesis{Config: configNoFork} gspecProFork = &core.Genesis{Config: configProFork} - chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, gspecNoFork, nil, engine, vm.Config{}, nil, nil) - chainProFork, _ = core.NewBlockChain(dbProFork, nil, gspecProFork, nil, engine, vm.Config{}, nil, nil) + chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, gspecNoFork, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chainProFork, _ = core.NewBlockChain(dbProFork, nil, gspecProFork, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) _, blocksNoFork, _ = core.GenerateChainWithGenesis(gspecNoFork, engine, 2, nil) _, blocksProFork, _ = core.GenerateChainWithGenesis(gspecProFork, engine, 2, nil) diff --git a/eth/handler_test.go b/eth/handler_test.go index eacdc52aa6..8b7b86b9de 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -171,7 +171,7 @@ func newTestHandlerWithBlocks(blocks int) *testHandler { Config: params.TestChainConfig, Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, } - chain, _ := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) _, bs, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), blocks, nil) if _, err := chain.InsertChain(bs); err != nil { diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index fdf551ef21..47a21b0ac6 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -104,7 +104,7 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, Config: config, Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}}, } - chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) _, bs, _ := core.GenerateChainWithGenesis(gspec, engine, blocks, generator) if _, err := chain.InsertChain(bs); err != nil { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 9c3a423f6f..8f3fdd50a5 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -158,7 +158,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i i SnapshotLimit: 0, TrieDirtyDisabled: true, // Archive mode } - chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, gspec, nil, backend.engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, gspec, nil, backend.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -254,7 +254,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block if idx == txIndex { return msg, context, statedb, release, nil } - vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{}) + vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } diff --git a/go.mod b/go.mod index 73768e2685..2cc116668f 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/panjf2000/ants/v2 v2.4.5 github.com/peterh/liner v1.2.0 + github.com/prometheus/client_golang v1.14.0 github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 github.com/prysmaticlabs/prysm/v4 v4.2.0 github.com/rs/cors v1.8.3 @@ -148,7 +149,6 @@ require ( github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index f65c98a50a..49de12d5e7 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -607,7 +607,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E // Generate blocks for testing db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator) txlookupLimit := uint64(0) - chain, err := core.NewBlockChain(db, cacheConfig, gspec, nil, engine, vm.Config{}, nil, &txlookupLimit) + chain, err := core.NewBlockChain(db, cacheConfig, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &txlookupLimit) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index 7e3f82a075..4530097a2c 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -5,6 +5,7 @@ package exp import ( "expvar" "fmt" + "github.com/prometheus/client_golang/prometheus/promhttp" "net/http" "sync" @@ -44,6 +45,7 @@ func Exp(r metrics.Registry) { // http.HandleFunc("/debug/vars", e.expHandler) // haven't found an elegant way, so just use a different endpoint http.Handle("/debug/metrics", h) + http.Handle("/debug/metrics/go_prometheus", promhttp.Handler()) http.Handle("/debug/metrics/prometheus", prometheus.Handler(r)) } @@ -58,6 +60,7 @@ func ExpHandler(r metrics.Registry) http.Handler { func Setup(address string) { m := http.NewServeMux() m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry)) + m.Handle("/debug/metrics/go_prometheus", promhttp.Handler()) m.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry)) log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address)) go func() { diff --git a/miner/miner_test.go b/miner/miner_test.go index 5907fb4464..4629dca13b 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -310,7 +310,7 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { // Create consensus engine engine := clique.New(chainConfig.Clique, chainDB) // Create Ethereum backend - bc, err := core.NewBlockChain(chainDB, nil, genesis, nil, engine, vm.Config{}, nil, nil) + bc, err := core.NewBlockChain(chainDB, nil, genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("can't create new chain %v", err) } diff --git a/miner/worker_test.go b/miner/worker_test.go index 7a78b6898f..f82fad5dbf 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -130,7 +130,7 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine default: t.Fatalf("unexpected consensus engine type: %T", engine) } - chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) if err != nil { t.Fatalf("core.NewBlockChain failed: %v", err) } @@ -181,7 +181,7 @@ func TestGenerateAndImportBlock(t *testing.T) { defer w.close() // This test chain imports the mined blocks. - chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, b.genesis, nil, engine, vm.Config{}, nil, nil) + chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, b.genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) defer chain.Stop() // Ignore empty commit here for less noise. diff --git a/tests/block_test.go b/tests/block_test.go index fb355085fd..ac11974e66 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -21,8 +21,9 @@ import ( "runtime" "testing" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + + "github.com/ethereum/go-ethereum/common" ) func TestBlockchain(t *testing.T) { @@ -90,4 +91,5 @@ func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { t.Errorf("test in path mode with snapshotter failed: %v", err) return } + } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 5f77a1c326..bc90d524a9 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -151,15 +151,19 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po cache.SnapshotWait = true } chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{ - Tracer: tracer, + EnableParallelExec: true, + ParallelTxNum: 4, + Tracer: tracer, }, nil, nil) if err != nil { + fmt.Printf("Dav -- Test - NewBlockChain fail, err: %s\n", err) return err } defer chain.Stop() validBlocks, err := t.insertBlocks(chain) if err != nil { + fmt.Printf("Dav -- Test - t.insertBlocks fail, err: %s\n", err) return err } // Import succeeded: regardless of whether the _test_ succeeds or not, schedule diff --git a/tests/state_test.go b/tests/state_test.go index fc1c351f07..6e7e672f24 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -163,7 +163,7 @@ const traceErrorLimit = 400000 func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) { // Use config from command line arguments. - config := vm.Config{} + config := vm.Config{EnableParallelExec: true, ParallelTxNum: 1} err := test(config) if err == nil { return @@ -237,7 +237,7 @@ func runBenchmark(b *testing.B, t *StateTest) { key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) b.Run(key, func(b *testing.B) { - vmconfig := vm.Config{} + vmconfig := vm.Config{EnableParallelExec: true, ParallelTxNum: 1} config, eips, err := GetChainConfig(subtest.Fork) if err != nil { diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index 4139dfc8b3..a352020359 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -395,11 +395,14 @@ func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error start = time.Now() dl = db.tree.bottom() ) + // fmt.Printf("Dav -- pathdb Recover, dl, root: %s\n", dl.rootHash()) for dl.rootHash() != root { + // fmt.Printf("Dav -- pathdb Recover, not equal, dl.root %s, root: %s\n", dl.rootHash(), root) h, err := readHistory(db.freezer, dl.stateID()) if err != nil { return err } + dl, err = dl.revert(h, loader) if err != nil { return err diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go index a0cb6f25a9..325afba7ee 100644 --- a/triedb/pathdb/disklayer.go +++ b/triedb/pathdb/disklayer.go @@ -380,6 +380,7 @@ func (dl *diskLayer) revert(h *history, loader triestate.TrieLoader) (*diskLayer // Apply the reverse state changes upon the current state. This must // be done before holding the lock in order to access state in "this" // layer. + nodes, err := triestate.Apply(h.meta.parent, h.meta.root, h.accounts, h.storages, loader) if err != nil { return nil, err From 247f63cd4afebf3f30384754865bae7e65414016 Mon Sep 17 00:00:00 2001 From: DavidZangNR Date: Wed, 3 Jul 2024 22:36:41 +0800 Subject: [PATCH 02/72] Fix: fix incorrectly set origin storage. Fix an issue of incorrectly set the origin storage at parallel stateDB's GetState(). Remove this code because it is already solved by lightCopy PR: #2 --- core/state/parallel_statedb.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 34f0b95a6a..7fe68bd5bc 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -697,22 +697,6 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common val = common.Hash{} if object != nil { val = object.GetState(hash) - // TODO-dav: delete following originStorage change, as lightCopy is copy originStorage now. - // test dirty, there can be a case the object saved in dirty by other changes such as SetBalance. But the - // addrStateChangesInSlot[addr] does not record it. So later load from the dirties would cause flaw because the - // first value loaded from main stateDB is not updated to the object in dirties. - // Moreover, there is also an issue that the other kv in the object get from snap or trie that is accessed from - // previous tx in same block but not touched in current tx, is missed in the dirty. which may cause issues when - // calculate the root. - _, recorded := s.parallel.addrStateChangesInSlot[addr] - obj, isDirty := s.parallel.dirtiedStateObjectsInSlot[addr] - if !recorded && isDirty { - v, ok := obj.originStorage.GetValue(hash) - - if !(ok && v.Cmp(val) == 0) { - obj.originStorage.StoreValue(hash, val) - } - } } value = val } From dedf04e449c88f085b8c739eed7e41d68a422a10 Mon Sep 17 00:00:00 2001 From: galaio Date: Sat, 6 Jul 2024 00:06:29 +0800 Subject: [PATCH 03/72] Feat: TxDAG: support TxDAG rwset: support collect rwset from statedb; mvstates: support export DAG; dag: support travel all execution paths; dag: refactor versioned TxDAG; dag: support profile parallel execution path; protocol: support to transfer TxDAG in NewBLock msg; PR: #4 --- core/block_validator.go | 6 + core/blockchain.go | 9 + core/chain_makers_test.go | 2 +- core/state/journal.go | 2 +- core/state/state_object.go | 84 +++++- core/state/statedb.go | 297 ++++++++++++++++----- core/state_processor.go | 11 + core/state_transition.go | 8 + core/types/block.go | 23 ++ core/types/dag.go | 392 +++++++++++++++++++++++++++ core/types/dag_test.go | 260 ++++++++++++++++++ core/types/mvstates.go | 487 ++++++++++++++++++++++++++++++++++ core/vm/interface.go | 4 + eth/handler_eth.go | 4 + eth/protocols/eth/peer.go | 1 + eth/protocols/eth/protocol.go | 3 + miner/worker.go | 17 ++ 17 files changed, 1538 insertions(+), 72 deletions(-) create mode 100644 core/types/dag.go create mode 100644 core/types/dag_test.go create mode 100644 core/types/mvstates.go diff --git a/core/block_validator.go b/core/block_validator.go index 061e69b8d2..551fe2d58b 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -157,6 +157,12 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { return ancestorErr } + // TODO(galaio): add more TxDAG hash when TxDAG in consensus, txDAG check here + if len(block.TxDAG()) > 0 { + if _, err := types.DecodeTxDAG(block.TxDAG()); err != nil { + return errors.New("wrong TxDAG in block body") + } + } return nil } diff --git a/core/blockchain.go b/core/blockchain.go index 7776eed494..ee2d9b1e08 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1881,6 +1881,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } + // TODO(galaio): use txDAG in some accelerate scenarios. + if len(block.TxDAG()) > 0 { + txDAG, err := types.DecodeTxDAG(block.TxDAG()) + if err != nil { + return it.index, err + } + log.Info("Insert chain", "block", block.NumberU64(), "txDAG", txDAG) + } + // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") activeState = statedb diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 12a0b00b0e..8faba299fb 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -198,7 +198,7 @@ func ExampleGenerateChain() { db = rawdb.NewMemoryDatabase() genDb = rawdb.NewMemoryDatabase() ) - + // Ensure that key1 has some funds in the genesis block. gspec := &Genesis{ Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, diff --git a/core/state/journal.go b/core/state/journal.go index 635d516d49..3ba29a4b3b 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -189,7 +189,7 @@ func (ch resetObjectChange) revert(dber StateDBer) { if !ch.prevdestruct { s.snapParallelLock.Lock() - delete(s.stateObjectsDestruct, ch.prev.address) + s.deleteStateObjectsDestruct(ch.prev.address) s.snapParallelLock.Unlock() } if ch.prevAccount != nil { diff --git a/core/state/state_object.go b/core/state/state_object.go index e12c274b4b..0bc7c9b97d 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -20,11 +20,11 @@ import ( "bytes" "fmt" "io" - "math/big" "sync" "time" "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" + "golang.org/x/exp/slices" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -157,6 +157,11 @@ type stateObject struct { origin *types.StateAccount // Account original data without any change applied, nil means it was not existent data types.StateAccount // Account data with all mutations applied in the scope of block + // dirty account state + dirtyBalance *uint256.Int + dirtyNonce *uint64 + dirtyCodeHash []byte + // Write caches. trie Trie // storage trie, which becomes non-nil on first access code Code // contract bytecode, which gets set when code is loaded @@ -243,7 +248,7 @@ func newObject(dbItf StateDBer, isParallel bool, address common.Address, acct *t if acct == nil { acct = types.NewEmptyStateAccount() } - return &stateObject{ + s := &stateObject{ db: db, dbItf: dbItf, address: address, @@ -256,6 +261,15 @@ func newObject(dbItf StateDBer, isParallel bool, address common.Address, acct *t dirtyStorage: newStorage(isParallel), created: created, } + + // dirty data when create a new account + if acct == nil { + s.dirtyBalance = new(uint256.Int).Set(acct.Balance) + s.dirtyNonce = new(uint64) + *s.dirtyNonce = acct.Nonce + s.dirtyCodeHash = acct.CodeHash + } + return s } // EncodeRLP implements rlp.Encoder. @@ -346,7 +360,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // 2) we don't have new values, and can deliver empty response back //if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { s.db.snapParallelLock.RLock() - if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { // fixme: use sync.Map, instead of RWMutex? + if _, destructed := s.db.queryStateObjectsDestruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? s.db.snapParallelLock.RUnlock() return common.Hash{} } @@ -439,6 +453,18 @@ func (s *stateObject) finalise(prefetch bool) { } return true }) + if s.dirtyNonce != nil { + s.data.Nonce = *s.dirtyNonce + s.dirtyNonce = nil + } + if s.dirtyBalance != nil { + s.data.Balance = s.dirtyBalance + s.dirtyBalance = nil + } + if s.dirtyCodeHash != nil { + s.data.CodeHash = s.dirtyCodeHash + s.dirtyCodeHash = nil + } if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch) } @@ -447,6 +473,28 @@ func (s *stateObject) finalise(prefetch bool) { } } +func (s *stateObject) finaliseRWSet() { + s.dirtyStorage.Range(func(key, value interface{}) bool { + // three are some unclean dirtyStorage from previous reverted txs, it will skip finalise + // so add a new rule, if val has no change, then skip it + if value == s.GetCommittedState(key.(common.Hash)) { + return true + } + s.db.RecordWrite(types.StorageStateKey(s.address, key.(common.Hash)), value.(common.Hash)) + return true + }) + + if s.dirtyNonce != nil && *s.dirtyNonce != s.data.Nonce { + s.db.RecordWrite(types.AccountStateKey(s.address, types.AccountNonce), *s.dirtyNonce) + } + if s.dirtyBalance != nil && s.dirtyBalance.Cmp(s.data.Balance) != 0 { + s.db.RecordWrite(types.AccountStateKey(s.address, types.AccountBalance), new(uint256.Int).Set(s.dirtyBalance)) + } + if s.dirtyCodeHash != nil && !slices.Equal(s.dirtyCodeHash, s.data.CodeHash) { + s.db.RecordWrite(types.AccountStateKey(s.address, types.AccountCodeHash), s.dirtyCodeHash) + } +} + // updateTrie is responsible for persisting cached storage changes into the // object's storage trie. In case the storage trie is not yet loaded, this // function will load the trie automatically. If any issues arise during the @@ -645,17 +693,17 @@ func (s *stateObject) SubBalance(amount *uint256.Int) { func (s *stateObject) SetBalance(amount *uint256.Int) { s.db.journal.append(balanceChange{ account: &s.address, - prev: new(uint256.Int).Set(s.data.Balance), + prev: new(uint256.Int).Set(s.Balance()), }) s.setBalance(amount) } func (s *stateObject) setBalance(amount *uint256.Int) { - s.data.Balance = amount + s.dirtyBalance = amount } // ReturnGas Return the gas back to the origin. Used by the Virtual machine or Closures -func (s *stateObject) ReturnGas(gas *big.Int) {} +func (s *stateObject) ReturnGas(gas *uint256.Int) {} func (s *stateObject) lightCopy(db *ParallelStateDB) *stateObject { object := newObject(db, s.isParallel, s.address, &s.data) @@ -716,6 +764,17 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { obj.selfDestructed = s.selfDestructed obj.dirtyCode = s.dirtyCode obj.deleted = s.deleted + + // dirty states + if s.dirtyNonce != nil { + obj.dirtyNonce = new(uint64) + *obj.dirtyNonce = *s.dirtyNonce + } + if s.dirtyBalance != nil { + obj.dirtyBalance = new(uint256.Int).Set(s.dirtyBalance) + } + obj.dirtyCodeHash = s.dirtyCodeHash + return obj } @@ -785,7 +844,7 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { func (s *stateObject) setCode(codeHash common.Hash, code []byte) { s.code = code - s.data.CodeHash = codeHash[:] + s.dirtyCodeHash = codeHash[:] s.dirtyCode = true compiler.GenOrLoadOptimizedCode(codeHash, s.code) } @@ -800,18 +859,27 @@ func (s *stateObject) SetNonce(nonce uint64) { } func (s *stateObject) setNonce(nonce uint64) { - s.data.Nonce = nonce + s.dirtyNonce = &nonce } func (s *stateObject) CodeHash() []byte { + if len(s.dirtyCodeHash) > 0 { + return s.dirtyCodeHash + } return s.data.CodeHash } func (s *stateObject) Balance() *uint256.Int { + if s.dirtyBalance != nil { + return s.dirtyBalance + } return s.data.Balance } func (s *stateObject) Nonce() uint64 { + if s.dirtyNonce != nil { + return *s.dirtyNonce + } return s.data.Nonce } diff --git a/core/state/statedb.go b/core/state/statedb.go index a2780c2a03..684a904b5f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -19,6 +19,7 @@ package state import ( "bytes" + "errors" "fmt" "runtime" "sort" @@ -208,10 +209,11 @@ type StateDB struct { // This map holds 'live' objects, which will get modified while processing // a state transition. - stateObjects map[common.Address]*stateObject - stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie - stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution - stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value + stateObjects map[common.Address]*stateObject + stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie + stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution + stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value + stateObjectsDestructDirty map[common.Address]*types.StateAccount // DB error. // State objects are used by the consensus core and VM which are @@ -231,6 +233,11 @@ type StateDB struct { logs map[common.Hash][]*types.Log logSize uint + // parallel EVM related + rwSet *types.RWSet + mvStates *types.MVStates + es *types.ExeStat + // Preimages occurred seen by VM in the scope of block. preimages map[common.Hash][]byte @@ -285,24 +292,25 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return nil, err } sdb := &StateDB{ - db: db, - trie: tr, - originalRoot: root, - snaps: snaps, - accounts: make(map[common.Hash][]byte), - storages: make(map[common.Hash]map[common.Hash][]byte), - accountsOrigin: make(map[common.Address][]byte), - storagesOrigin: make(map[common.Address]map[common.Hash][]byte), - stateObjects: make(map[common.Address]*stateObject), - stateObjectsPending: make(map[common.Address]struct{}), - stateObjectsDirty: make(map[common.Address]struct{}), - stateObjectsDestruct: make(map[common.Address]*types.StateAccount), - logs: make(map[common.Hash][]*types.Log), - preimages: make(map[common.Hash][]byte), - journal: newJournal(), - accessList: newAccessList(), - transientStorage: newTransientStorage(), - hasher: crypto.NewKeccakState(), + db: db, + trie: tr, + originalRoot: root, + snaps: snaps, + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), + stateObjects: make(map[common.Address]*stateObject), + stateObjectsPending: make(map[common.Address]struct{}), + stateObjectsDirty: make(map[common.Address]struct{}), + stateObjectsDestruct: make(map[common.Address]*types.StateAccount), + stateObjectsDestructDirty: make(map[common.Address]*types.StateAccount, defaultNumOfSlots), + logs: make(map[common.Hash][]*types.Log), + preimages: make(map[common.Hash][]byte), + journal: newJournal(), + accessList: newAccessList(), + transientStorage: newTransientStorage(), + hasher: crypto.NewKeccakState(), parallel: ParallelState{ SlotIndex: -1, @@ -477,17 +485,22 @@ func (s *StateDB) Empty(addr common.Address) bool { } // GetBalance retrieves the balance from the given address or 0 if object not found - -func (s *StateDB) GetBalance(addr common.Address) *uint256.Int { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Balance() +func (s *StateDB) GetBalance(addr common.Address) (ret *uint256.Int) { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), ret) + }() + object := s.getStateObject(addr) + if object != nil { + return object.Balance() } return common.U2560 } // GetNonce retrieves the nonce from the given address or 0 if object not found -func (s *StateDB) GetNonce(addr common.Address) uint64 { +func (s *StateDB) GetNonce(addr common.Address) (ret uint64) { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountNonce), ret) + }() object := s.getStateObject(addr) if object != nil { return object.Nonce() @@ -516,6 +529,9 @@ func (s *StateDB) BaseTxIndex() int { } func (s *StateDB) GetCode(addr common.Address) []byte { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), s.GetCodeHash(addr)) + }() object := s.getStateObject(addr) if object != nil { return object.Code() @@ -524,6 +540,9 @@ func (s *StateDB) GetCode(addr common.Address) []byte { } func (s *StateDB) GetCodeSize(addr common.Address) int { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), s.GetCodeHash(addr)) + }() object := s.getStateObject(addr) if object != nil { return object.CodeSize() @@ -535,16 +554,22 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { // - common.Hash{}: the address does not exist // - emptyCodeHash: the address exist, but code is empty // - others: the address exist, and code is not empty -func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return common.BytesToHash(stateObject.CodeHash()) +func (s *StateDB) GetCodeHash(addr common.Address) (ret common.Hash) { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), ret.Bytes()) + }() + object := s.getStateObject(addr) + if object == nil { + return common.Hash{} } return common.Hash{} } // GetState retrieves a value from the given account's storage trie. -func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { +func (s *StateDB) GetState(addr common.Address, hash common.Hash) (ret common.Hash) { + defer func() { + s.RecordRead(types.StorageStateKey(addr, hash), ret) + }() object := s.getStateObject(addr) if object != nil { return object.GetState(hash) @@ -553,7 +578,10 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { } // GetCommittedState retrieves a value from the given account's committed storage trie. -func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { +func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) (ret common.Hash) { + defer func() { + s.RecordRead(types.StorageStateKey(addr, hash), ret) + }() object := s.getStateObject(addr) if object != nil { return object.GetCommittedState(hash) @@ -580,18 +608,24 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool { // AddBalance adds amount to the account associated with addr. func (s *StateDB) AddBalance(addr common.Address, amount *uint256.Int) { - stateObject := s.getOrNewStateObject(addr) - if stateObject != nil { - stateObject.AddBalance(amount) + object := s.getOrNewStateObject(addr) + if object != nil { + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), object.Balance()) + object.AddBalance(amount) + return } + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), common.U2560) } // SubBalance subtracts amount from the account associated with addr. func (s *StateDB) SubBalance(addr common.Address, amount *uint256.Int) { - stateObject := s.getOrNewStateObject(addr) - if stateObject != nil { - stateObject.SubBalance(amount) + object := s.getOrNewStateObject(addr) + if object != nil { + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), object.Balance()) + object.SubBalance(amount) + return } + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), common.U2560) } func (s *StateDB) SetBalance(addr common.Address, amount *uint256.Int) { @@ -635,9 +669,9 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // // TODO(rjl493456442) this function should only be supported by 'unwritable' // state and all mutations made should all be discarded afterwards. - if _, ok := s.stateObjectsDestruct[addr]; !ok { + if _, ok := s.queryStateObjectsDestruct(addr); !ok { fmt.Printf("Dav -- setStorage - stateObjectsDestruct[%s] = nil\n", addr) - s.stateObjectsDestruct[addr] = nil + s.tagStateObjectsDestruct(addr, nil) } stateObject := s.getOrNewStateObject(addr) for k, v := range storage { @@ -879,6 +913,7 @@ func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *t // flag set. This is needed by the state journal to revert to the correct s- // destructed object instead of wiping all knowledge about the state object. func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { + s.RecordRead(types.AccountStateKey(addr, types.AccountSelf), struct{}{}) // Prefer live objects if any is available if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { return obj @@ -943,9 +978,9 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { // will be lost. s.snapParallelLock.Lock() // fixme: with new dispatch policy, the ending Tx could running, while the block have processed. - _, prevdestruct := s.stateObjectsDestruct[prev.address] + _, prevdestruct := s.queryStateObjectsDestruct(prev.address) if !prevdestruct { - s.stateObjectsDestruct[prev.address] = prev.origin + s.tagStateObjectsDestruct(prev.address, prev.origin) } // There may be some cached account/storage data already since IntermediateRoot // will be called for each transaction before byzantium fork which will always @@ -992,7 +1027,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { // Carrying over the balance ensures that Ether doesn't disappear. func (s *StateDB) CreateAccount(addr common.Address) { // no matter it is got from dirty, unconfirmed or main DB - // if addr not exist, preBalance will be common.Big0, it is same as new(big.Int) which + // if addr not exist, preBalance will be common.U2560, it is same as new(big.Int) which // is the value newObject(), preBalance := s.GetBalance(addr) newObj := s.createObject(addr) @@ -1013,23 +1048,24 @@ func (s *StateDB) CopyDoPrefetch() *StateDB { func (s *StateDB) copyInternal(doPrefetch bool) *StateDB { // Copy all the basic fields, initialize the memory ones state := &StateDB{ - db: s.db, - trie: s.db.CopyTrie(s.trie), - originalRoot: s.originalRoot, - accounts: make(map[common.Hash][]byte), - storages: make(map[common.Hash]map[common.Hash][]byte), - accountsOrigin: make(map[common.Address][]byte), - storagesOrigin: make(map[common.Address]map[common.Hash][]byte), - stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), - stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), - stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), - stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)), - refund: s.refund, - logs: make(map[common.Hash][]*types.Log, len(s.logs)), - logSize: s.logSize, - preimages: make(map[common.Hash][]byte, len(s.preimages)), - journal: newJournal(), - hasher: crypto.NewKeccakState(), + db: s.db, + trie: s.db.CopyTrie(s.trie), + originalRoot: s.originalRoot, + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), + stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), + stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), + stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), + stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)), + stateObjectsDestructDirty: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestructDirty)), + refund: s.refund, + logs: make(map[common.Hash][]*types.Log, len(s.logs)), + logSize: s.logSize, + preimages: make(map[common.Hash][]byte, len(s.preimages)), + journal: newJournal(), + hasher: crypto.NewKeccakState(), // In order for the block producer to be able to use and make additions // to the snapshot tree, we need to copy that as well. Otherwise, any @@ -1079,6 +1115,9 @@ func (s *StateDB) copyInternal(doPrefetch bool) *StateDB { // fmt.Printf("Dav -- copyInternal - stateObjectsDestruct[%s] = (%p) : %v \n", addr, value, value) state.stateObjectsDestruct[addr] = value } + for addr, value := range s.stateObjectsDestructDirty { + state.stateObjectsDestructDirty[addr] = value + } // Deep copy the state changes made in the scope of block // along with their original values. state.accounts = copySet(s.accounts) @@ -1114,6 +1153,12 @@ func (s *StateDB) copyInternal(doPrefetch bool) *StateDB { if s.prefetcher != nil { state.prefetcher = s.prefetcher.copy() } + + // parallel EVM related + if s.mvStates != nil { + state.mvStates = s.mvStates + } + return state } @@ -1426,6 +1471,11 @@ func (s *StateDB) GetRefund() uint64 { func (s *StateDB) Finalise(deleteEmptyObjects bool) { addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) + // finalise stateObjectsDestruct + for addr, acc := range s.stateObjectsDestructDirty { + s.stateObjectsDestruct[addr] = acc + } + s.stateObjectsDestructDirty = make(map[common.Address]*types.StateAccount) for addr := range s.journal.dirties { var obj *stateObject var exist bool @@ -2241,6 +2291,129 @@ func (s *StateDB) GetSnap() snapshot.Snapshot { return s.snap } +func (s *StateDB) BeforeTxTransition() { + log.Debug("BeforeTxTransition", "mvStates", s.mvStates == nil, "rwSet", s.rwSet == nil) + if s.mvStates == nil { + return + } + s.rwSet = types.NewRWSet(types.StateVersion{ + TxIndex: s.txIndex, + }) +} + +func (s *StateDB) BeginTxStat(index int) { + if s.mvStates == nil { + return + } + s.es = types.NewExeStat(index).Begin() +} + +func (s *StateDB) StopTxStat(usedGas uint64) { + if s.mvStates == nil { + return + } + // record stat first + if s.es != nil { + s.es.Done().WithGas(usedGas).WithRead(len(s.rwSet.ReadSet())) + } +} + +func (s *StateDB) RecordRead(key types.RWKey, val interface{}) { + if s.mvStates == nil || s.rwSet == nil { + return + } + // TODO: read from MVStates, record with ver + s.rwSet.RecordRead(key, types.StateVersion{ + TxIndex: -1, + }, val) +} + +func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) { + if s.mvStates == nil || s.rwSet == nil { + return + } + s.rwSet.RecordWrite(key, val) +} + +func (s *StateDB) ResetMVStates(txCount int) { + s.mvStates = types.NewMVStates(txCount) + s.rwSet = nil +} + +func (s *StateDB) FinaliseRWSet() error { + if s.mvStates == nil || s.rwSet == nil { + return nil + } + // finalise stateObjectsDestruct + for addr, acc := range s.stateObjectsDestructDirty { + s.stateObjectsDestruct[addr] = acc + s.RecordWrite(types.AccountStateKey(addr, types.AccountSuicide), struct{}{}) + } + for addr := range s.journal.dirties { + obj, exist := s.stateObjects[addr] + if !exist { + continue + } + if obj.selfDestructed || obj.empty() { + // We need to maintain account deletions explicitly (will remain + // set indefinitely). Note only the first occurred self-destruct + // event is tracked. + if _, ok := s.stateObjectsDestruct[obj.address]; !ok { + log.Debug("FinaliseRWSet find Destruct", "tx", s.txIndex, "addr", addr, "selfDestructed", obj.selfDestructed) + s.RecordWrite(types.AccountStateKey(addr, types.AccountSuicide), struct{}{}) + } + } else { + // finalise account & storages + obj.finaliseRWSet() + } + } + ver := types.StateVersion{ + TxIndex: s.txIndex, + } + if ver != s.rwSet.Version() { + return errors.New("you finalize a wrong ver of RWSet") + } + + return s.mvStates.FulfillRWSet(s.rwSet, s.es) +} + +func (s *StateDB) queryStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) { + if acc, ok := s.stateObjectsDestructDirty[addr]; ok { + return acc, ok + } + acc, ok := s.stateObjectsDestruct[addr] + return acc, ok +} + +func (s *StateDB) tagStateObjectsDestruct(addr common.Address, acc *types.StateAccount) { + s.stateObjectsDestructDirty[addr] = acc +} + +func (s *StateDB) deleteStateObjectsDestruct(addr common.Address) { + delete(s.stateObjectsDestructDirty, addr) +} + +func (s *StateDB) MVStates2TxDAG() (types.TxDAG, map[int]*types.ExeStat) { + if s.mvStates == nil { + return types.NewEmptyTxDAG(), nil + } + + return s.mvStates.ResolveTxDAG(), s.mvStates.Stats() +} + +func (s *StateDB) MVStates() *types.MVStates { + return s.mvStates +} + +func (s *StateDB) RecordSystemTxRWSet(index int) { + if s.mvStates == nil { + return + } + s.mvStates.FulfillRWSet(types.NewRWSet(types.StateVersion{ + TxIndex: index, + }).WithSerialFlag(), types.NewExeStat(index).WithSerialFlag()) +} + // copySet returns a deep-copied set. func copySet[k comparable](set map[k][]byte) map[k][]byte { copied := make(map[k][]byte, len(set)) diff --git a/core/state_processor.go b/core/state_processor.go index 541d303ceb..85441dbc6b 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" ) @@ -90,8 +91,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } statedb.MarkFullProcessed() + statedb.ResetMVStates(len(block.Transactions())) // Iterate over and process the individual transactions for i, tx := range block.Transactions() { + statedb.BeginTxStat(i) start := time.Now() msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { @@ -108,6 +111,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if metrics.EnabledExpensive { processTxTimer.UpdateSince(start) } + statedb.StopTxStat(receipt.GasUsed) } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. withdrawals := block.Withdrawals() @@ -117,6 +121,13 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) + // TODO(galaio): append dag into block body, TxDAGPerformance will print metrics when profile is enabled + // compare input TxDAG when it enable in consensus + dag, exrStats := statedb.MVStates2TxDAG() + types.EvaluateTxDAGPerformance(dag, exrStats) + //fmt.Print(types.EvaluateTxDAGPerformance(dag, exrStats)) + log.Info("Process result", "block", block.NumberU64(), "txDAG", dag) + return receipts, allLogs, *usedGas, nil } diff --git a/core/state_transition.go b/core/state_transition.go index 0e174a0a7c..065c260b71 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -443,6 +443,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { + // start record rw set in here + st.state.BeforeTxTransition() // First check this message satisfies all consensus rules before // applying the message. The rules include these clauses // @@ -534,10 +536,16 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { ReturnData: ret, }, nil } + // stop record rw set in here, skip gas fee distribution + if err := st.state.FinaliseRWSet(); err != nil { + return nil, err + } + // Note for deposit tx there is no ETH refunded for unused gas, but that's taken care of by the fact that gasPrice // is always 0 for deposit tx. So calling refundGas will ensure the gasUsed accounting is correct without actually // changing the sender's balance var gasRefund uint64 + if !rules.IsLondon { // Before EIP-3529: refunds were capped to gasUsed / 2 gasRefund = st.refundGas(params.RefundQuotient) diff --git a/core/types/block.go b/core/types/block.go index 0e0b621974..f4da10994a 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -171,6 +171,8 @@ type Body struct { Transactions []*Transaction Uncles []*Header Withdrawals []*Withdrawal `rlp:"optional"` + // TODO: add TxDAG in block body + //TxDAG []byte `rlp:"optional"` } // Block represents an Ethereum block. @@ -195,6 +197,8 @@ type Block struct { uncles []*Header transactions Transactions withdrawals Withdrawals + // TODO(galaio): package txDAG in consensus later + txDAG []byte // caches hash atomic.Value @@ -423,6 +427,10 @@ func (b *Block) SanityCheck() error { return b.header.SanityCheck() } +func (b *Block) TxDAG() []byte { + return b.txDAG +} + type writeCounter uint64 func (c *writeCounter) Write(b []byte) (int, error) { @@ -452,6 +460,7 @@ func (b *Block) WithSeal(header *Header) *Block { transactions: b.transactions, uncles: b.uncles, withdrawals: b.withdrawals, + txDAG: b.txDAG, } } @@ -462,6 +471,7 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { transactions: make([]*Transaction, len(transactions)), uncles: make([]*Header, len(uncles)), withdrawals: b.withdrawals, + txDAG: b.txDAG, } copy(block.transactions, transactions) for i := range uncles { @@ -476,6 +486,7 @@ func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block { header: b.header, transactions: b.transactions, uncles: b.uncles, + txDAG: b.txDAG, } if withdrawals != nil { block.withdrawals = make([]*Withdrawal, len(withdrawals)) @@ -484,6 +495,18 @@ func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block { return block } +// WithTxDAG returns a block containing the given txDAG. +func (b *Block) WithTxDAG(txDAG []byte) *Block { + block := &Block{ + header: b.header, + transactions: b.transactions, + uncles: b.uncles, + withdrawals: b.withdrawals, + txDAG: txDAG, + } + return block +} + // Hash returns the keccak256 hash of b's header. // The hash is computed on the first call and cached thereafter. func (b *Block) Hash() common.Hash { diff --git a/core/types/dag.go b/core/types/dag.go new file mode 100644 index 0000000000..a4d111458b --- /dev/null +++ b/core/types/dag.go @@ -0,0 +1,392 @@ +package types + +import ( + "bytes" + "errors" + "fmt" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/exp/slices" + "strings" + "time" +) + +// TxDAGType Used to extend TxDAG and customize a new DAG structure +const ( + EmptyTxDAGType byte = iota + PlainTxDAGType +) + +type TxDAG interface { + // Type return TxDAG type + Type() byte + + // Inner return inner instance + Inner() interface{} + + // DelayGasDistribution check if delay the distribution of GasFee + DelayGasDistribution() bool + + // TxDep query TxDeps from TxDAG + TxDep(int) TxDep + + // TxCount return tx count + TxCount() int +} + +func EncodeTxDAG(dag TxDAG) ([]byte, error) { + if dag == nil { + return nil, errors.New("input nil TxDAG") + } + var buf bytes.Buffer + buf.WriteByte(dag.Type()) + if err := rlp.Encode(&buf, dag.Inner()); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func DecodeTxDAG(enc []byte) (TxDAG, error) { + if len(enc) <= 1 { + return nil, errors.New("too short TxDAG bytes") + } + + switch enc[0] { + case EmptyTxDAGType: + return NewEmptyTxDAG(), nil + case PlainTxDAGType: + dag := new(PlainTxDAG) + if err := rlp.DecodeBytes(enc[1:], dag); err != nil { + return nil, err + } + return dag, nil + default: + return nil, errors.New("unsupported TxDAG bytes") + } +} + +// EmptyTxDAG indicate that execute txs in sequence +// It means no transactions or need timely distribute transaction fees +// it only keep partial serial execution when tx cannot delay the distribution or just execute txs in sequence +type EmptyTxDAG struct { +} + +func NewEmptyTxDAG() TxDAG { + return &EmptyTxDAG{} +} + +func (d *EmptyTxDAG) Type() byte { + return EmptyTxDAGType +} + +func (d *EmptyTxDAG) Inner() interface{} { + return d +} + +func (d *EmptyTxDAG) DelayGasDistribution() bool { + return false +} + +func (d *EmptyTxDAG) TxDep(int) TxDep { + return TxDep{ + Relation: 1, + TxIndexes: nil, + } +} + +func (d *EmptyTxDAG) TxCount() int { + return 0 +} + +func (d *EmptyTxDAG) String() string { + return "None" +} + +// PlainTxDAG indicate how to use the dependency of txs, and delay the distribution of GasFee +type PlainTxDAG struct { + // Tx Dependency List, the list index is equal to TxIndex + TxDeps []TxDep +} + +func (d *PlainTxDAG) Type() byte { + return PlainTxDAGType +} + +func (d *PlainTxDAG) Inner() interface{} { + return d +} + +func (d *PlainTxDAG) DelayGasDistribution() bool { + return true +} + +func (d *PlainTxDAG) TxDep(i int) TxDep { + return d.TxDeps[i] +} + +func (d *PlainTxDAG) TxCount() int { + return len(d.TxDeps) +} + +func NewPlainTxDAG(txLen int) *PlainTxDAG { + return &PlainTxDAG{ + TxDeps: make([]TxDep, txLen), + } +} + +func (d *PlainTxDAG) String() string { + builder := strings.Builder{} + exePaths := travelExecutionPaths(d) + for _, path := range exePaths { + builder.WriteString(fmt.Sprintf("%v\n", path)) + } + return builder.String() +} + +func (d *PlainTxDAG) Size() int { + enc, err := EncodeTxDAG(d) + if err != nil { + return 0 + } + return len(enc) +} + +func travelExecutionPaths(d TxDAG) [][]uint64 { + txCount := d.TxCount() + deps := make([]TxDep, txCount) + for i := 0; i < txCount; i++ { + dep := d.TxDep(i) + if dep.Relation == 0 { + deps[i] = dep + } + + // recover to relation 0 + for j := 0; j < i; j++ { + if !dep.Exist(j) { + deps[i].AppendDep(j) + } + } + } + + exePaths := make([][]uint64, 0) + // travel tx deps with BFS + for i := uint64(0); i < uint64(txCount); i++ { + exePaths = append(exePaths, travelTargetPath(deps, i)) + } + return exePaths +} + +// TxDep store the current tx dependency relation with other txs +type TxDep struct { + // It describes the Relation with below txs + // 0: this tx depends on below txs + // 1: this transaction does not depend on below txs, all other previous txs depend on + Relation uint8 + TxIndexes []uint64 +} + +func (d *TxDep) AppendDep(i int) { + d.TxIndexes = append(d.TxIndexes, uint64(i)) +} + +func (d *TxDep) Exist(i int) bool { + for _, index := range d.TxIndexes { + if index == uint64(i) { + return true + } + } + + return false +} + +var ( + longestTimeTimer = metrics.NewRegisteredTimer("dag/longesttime", nil) + longestGasTimer = metrics.NewRegisteredTimer("dag/longestgas", nil) + serialTimeTimer = metrics.NewRegisteredTimer("dag/serialtime", nil) + totalTxMeter = metrics.NewRegisteredMeter("dag/txcnt", nil) + totalNoDepMeter = metrics.NewRegisteredMeter("dag/nodepcntcnt", nil) + total2DepMeter = metrics.NewRegisteredMeter("dag/2depcntcnt", nil) + total4DepMeter = metrics.NewRegisteredMeter("dag/4depcntcnt", nil) + total8DepMeter = metrics.NewRegisteredMeter("dag/8depcntcnt", nil) + total16DepMeter = metrics.NewRegisteredMeter("dag/16depcntcnt", nil) + total32DepMeter = metrics.NewRegisteredMeter("dag/32depcntcnt", nil) +) + +func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) string { + if len(stats) != dag.TxCount() || dag.TxCount() == 0 { + return "" + } + sb := strings.Builder{} + //sb.WriteString("TxDAG:\n") + //for i, dep := range dag.TxDeps { + // if stats[i].mustSerialFlag { + // continue + // } + // sb.WriteString(fmt.Sprintf("%v: %v\n", i, dep.TxIndexes)) + //} + //sb.WriteString("Parallel Execution Path:\n") + paths := travelExecutionPaths(dag) + // Attention: this is based on best schedule, it will reduce a lot by executing previous txs in parallel + // It assumes that there is no parallel thread limit + txCount := dag.TxCount() + var ( + maxGasIndex int + maxGas uint64 + maxTimeIndex int + maxTime time.Duration + txTimes = make([]time.Duration, txCount) + txGases = make([]uint64, txCount) + txReads = make([]int, txCount) + noDepdencyCount int + ) + + totalTxMeter.Mark(int64(txCount)) + for i, path := range paths { + if stats[i].mustSerialFlag { + continue + } + if len(path) <= 1 { + noDepdencyCount++ + totalNoDepMeter.Mark(1) + } + if len(path) <= 3 { + total2DepMeter.Mark(1) + } + if len(path) <= 5 { + total4DepMeter.Mark(1) + } + if len(path) <= 9 { + total8DepMeter.Mark(1) + } + if len(path) <= 17 { + total16DepMeter.Mark(1) + } + if len(path) <= 33 { + total32DepMeter.Mark(1) + } + + // find the biggest cost time from dependency txs + for j := 0; j < len(path)-1; j++ { + prev := path[j] + if txTimes[prev] > txTimes[i] { + txTimes[i] = txTimes[prev] + } + if txGases[prev] > txGases[i] { + txGases[i] = txGases[prev] + } + if txReads[prev] > txReads[i] { + txReads[i] = txReads[prev] + } + } + txTimes[i] += stats[i].costTime + txGases[i] += stats[i].usedGas + txReads[i] += stats[i].readCount + + //sb.WriteString(fmt.Sprintf("Tx%v, %.2fms|%vgas|%vreads\npath: %v\n", i, float64(txTimes[i].Microseconds())/1000, txGases[i], txReads[i], path)) + //sb.WriteString(fmt.Sprintf("%v: %v\n", i, path)) + // try to find max gas + if txGases[i] > maxGas { + maxGas = txGases[i] + maxGasIndex = i + } + if txTimes[i] > maxTime { + maxTime = txTimes[i] + maxTimeIndex = i + } + } + + sb.WriteString(fmt.Sprintf("LargestGasPath: %.2fms|%vgas|%vreads\npath: %v\n", float64(txTimes[maxGasIndex].Microseconds())/1000, txGases[maxGasIndex], txReads[maxGasIndex], paths[maxGasIndex])) + sb.WriteString(fmt.Sprintf("LongestTimePath: %.2fms|%vgas|%vreads\npath: %v\n", float64(txTimes[maxTimeIndex].Microseconds())/1000, txGases[maxTimeIndex], txReads[maxTimeIndex], paths[maxTimeIndex])) + longestTimeTimer.Update(txTimes[maxTimeIndex]) + longestGasTimer.Update(txTimes[maxGasIndex]) + // serial path + var ( + sTime time.Duration + sGas uint64 + sRead int + sPath []int + ) + for i, stat := range stats { + if stat.mustSerialFlag { + continue + } + sPath = append(sPath, i) + sTime += stat.costTime + sGas += stat.usedGas + sRead += stat.readCount + } + if sTime == 0 { + return "" + } + sb.WriteString(fmt.Sprintf("SerialPath: %.2fms|%vgas|%vreads\npath: %v\n", float64(sTime.Microseconds())/1000, sGas, sRead, sPath)) + maxParaTime := txTimes[maxTimeIndex] + sb.WriteString(fmt.Sprintf("Estimated saving: %.2fms, %.2f%%, %.2fX, noDepCnt: %v|%.2f%%\n", + float64((sTime-maxParaTime).Microseconds())/1000, float64(sTime-maxParaTime)/float64(sTime)*100, + float64(sTime)/float64(maxParaTime), noDepdencyCount, float64(noDepdencyCount)/float64(txCount)*100)) + serialTimeTimer.Update(sTime) + return sb.String() +} + +func travelTargetPath(deps []TxDep, from uint64) []uint64 { + q := make([]uint64, 0, len(deps)) + path := make([]uint64, 0, len(deps)) + + q = append(q, from) + path = append(path, from) + for len(q) > 0 { + t := make([]uint64, 0, len(deps)) + for _, i := range q { + for _, dep := range deps[i].TxIndexes { + if !slices.Contains(path, dep) { + path = append(path, dep) + t = append(t, dep) + } + } + } + q = t + } + slices.Sort(path) + return path +} + +// ExeStat records tx execution info +type ExeStat struct { + txIndex int + usedGas uint64 + readCount int + startTime time.Time + costTime time.Duration + // TODO: consider system tx, gas fee issues, may need to use different flag + mustSerialFlag bool +} + +func NewExeStat(txIndex int) *ExeStat { + return &ExeStat{ + txIndex: txIndex, + } +} + +func (s *ExeStat) Begin() *ExeStat { + s.startTime = time.Now() + return s +} + +func (s *ExeStat) Done() *ExeStat { + s.costTime = time.Since(s.startTime) + return s +} + +func (s *ExeStat) WithSerialFlag() *ExeStat { + s.mustSerialFlag = true + return s +} + +func (s *ExeStat) WithGas(gas uint64) *ExeStat { + s.usedGas = gas + return s +} + +func (s *ExeStat) WithRead(rc int) *ExeStat { + s.readCount = rc + return s +} diff --git a/core/types/dag_test.go b/core/types/dag_test.go new file mode 100644 index 0000000000..e86fff110c --- /dev/null +++ b/core/types/dag_test.go @@ -0,0 +1,260 @@ +package types + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +var ( + mockAddr = common.HexToAddress("0x482bA86399ab6Dcbe54071f8d22258688B4509b1") + mockHash = common.HexToHash("0xdc13f8d7bdb8ec4de02cd4a50a1aa2ab73ec8814e0cdb550341623be3dd8ab7a") +) + +func TestTxDAG(t *testing.T) { + dag := mockSimpleDAG() + t.Log(dag) + dag = mockSystemTxDAG() + t.Log(dag) +} + +func TestEvaluateTxDAG(t *testing.T) { + dag := mockSystemTxDAG() + stats := make(map[int]*ExeStat, dag.TxCount()) + for i := 0; i < dag.TxCount(); i++ { + stats[i] = NewExeStat(i).WithGas(uint64(i)).WithRead(i) + stats[i].costTime = time.Duration(i) + if dag.TxDep(i).Relation == 1 { + stats[i].WithSerialFlag() + } + } + t.Log(EvaluateTxDAGPerformance(dag, stats)) +} + +func TestSimpleMVStates2TxDAG(t *testing.T) { + ms := NewMVStates(10) + + ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) + ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) + ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) + ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) + ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) + ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) + ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) + ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}) + ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) + ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) + + dag := ms.ResolveTxDAG() + require.Equal(t, mockSimpleDAG(), dag) + t.Log(dag) +} + +func TestSystemTxMVStates2TxDAG(t *testing.T) { + ms := NewMVStates(12) + + ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) + ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) + ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) + ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) + ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) + ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) + ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) + ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}) + ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) + ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) + ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithSerialFlag() + ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithSerialFlag() + + dag := ms.ResolveTxDAG() + require.Equal(t, mockSystemTxDAG(), dag) + t.Log(dag) +} + +func TestIsEqualRWVal(t *testing.T) { + tests := []struct { + key RWKey + src interface{} + compared interface{} + isEqual bool + }{ + { + key: AccountStateKey(mockAddr, AccountNonce), + src: uint64(0), + compared: uint64(0), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountNonce), + src: uint64(0), + compared: uint64(1), + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: new(uint256.Int).SetUint64(1), + compared: new(uint256.Int).SetUint64(1), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: nil, + compared: new(uint256.Int).SetUint64(1), + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: (*uint256.Int)(nil), + compared: new(uint256.Int).SetUint64(1), + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: (*uint256.Int)(nil), + compared: (*uint256.Int)(nil), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: []byte{1}, + compared: []byte{1}, + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: nil, + compared: []byte{1}, + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: ([]byte)(nil), + compared: []byte{1}, + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: ([]byte)(nil), + compared: ([]byte)(nil), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountSuicide), + src: struct{}{}, + compared: struct{}{}, + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountSuicide), + src: nil, + compared: struct{}{}, + isEqual: false, + }, + { + key: StorageStateKey(mockAddr, mockHash), + src: mockHash, + compared: mockHash, + isEqual: true, + }, + { + key: StorageStateKey(mockAddr, mockHash), + src: nil, + compared: mockHash, + isEqual: false, + }, + } + + for i, item := range tests { + require.Equal(t, item.isEqual, isEqualRWVal(item.key, item.src, item.compared), i) + } +} + +func mockSimpleDAG() TxDAG { + dag := NewPlainTxDAG(10) + dag.TxDeps[0].TxIndexes = []uint64{} + dag.TxDeps[1].TxIndexes = []uint64{} + dag.TxDeps[2].TxIndexes = []uint64{} + dag.TxDeps[3].TxIndexes = []uint64{0} + dag.TxDeps[4].TxIndexes = []uint64{0} + dag.TxDeps[5].TxIndexes = []uint64{1, 2} + dag.TxDeps[6].TxIndexes = []uint64{2, 5} + dag.TxDeps[7].TxIndexes = []uint64{6} + dag.TxDeps[8].TxIndexes = []uint64{} + dag.TxDeps[9].TxIndexes = []uint64{8} + return dag +} + +func mockSystemTxDAG() TxDAG { + dag := NewPlainTxDAG(12) + dag.TxDeps[0].TxIndexes = []uint64{} + dag.TxDeps[1].TxIndexes = []uint64{} + dag.TxDeps[2].TxIndexes = []uint64{} + dag.TxDeps[3].TxIndexes = []uint64{0} + dag.TxDeps[4].TxIndexes = []uint64{0} + dag.TxDeps[5].TxIndexes = []uint64{1, 2} + dag.TxDeps[6].TxIndexes = []uint64{2, 5} + dag.TxDeps[7].TxIndexes = []uint64{6} + dag.TxDeps[8].TxIndexes = []uint64{} + dag.TxDeps[9].TxIndexes = []uint64{8} + dag.TxDeps[10] = TxDep{ + Relation: 1, + TxIndexes: []uint64{}, + } + dag.TxDeps[11] = TxDep{ + Relation: 1, + TxIndexes: []uint64{}, + } + return dag +} + +func mockRWSet(index int, read []string, write []string) *RWSet { + ver := StateVersion{ + TxIndex: index, + } + set := NewRWSet(ver) + for _, k := range read { + key := RWKey{} + if len(k) > len(key) { + k = k[:len(key)] + } + copy(key[:], k) + set.readSet[key] = &ReadRecord{ + StateVersion: ver, + Val: struct{}{}, + } + } + for _, k := range write { + key := RWKey{} + if len(k) > len(key) { + k = k[:len(key)] + } + copy(key[:], k) + set.writeSet[key] = &WriteRecord{ + Val: struct{}{}, + } + } + + return set +} + +func TestTxDAG_Encode_Decode(t *testing.T) { + expected := TxDAG(&EmptyTxDAG{}) + enc, err := EncodeTxDAG(expected) + require.NoError(t, err) + actual, err := DecodeTxDAG(enc) + require.NoError(t, err) + require.Equal(t, expected, actual) + + expected = mockSimpleDAG() + enc, err = EncodeTxDAG(expected) + require.NoError(t, err) + actual, err = DecodeTxDAG(enc) + require.NoError(t, err) + require.Equal(t, expected, actual) + enc[0] = 2 + _, err = DecodeTxDAG(enc) + require.Error(t, err) +} diff --git a/core/types/mvstates.go b/core/types/mvstates.go new file mode 100644 index 0000000000..64c1a4fc7d --- /dev/null +++ b/core/types/mvstates.go @@ -0,0 +1,487 @@ +package types + +import ( + "encoding/hex" + "errors" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" + "slices" + "strings" + "sync" +) + +const ( + AccountStatePrefix = 'a' + StorageStatePrefix = 's' +) + +type RWKey [1 + common.AddressLength + common.HashLength]byte + +type AccountState byte + +const ( + AccountSelf AccountState = iota + AccountNonce + AccountBalance + AccountCodeHash + AccountSuicide +) + +func AccountStateKey(account common.Address, state AccountState) RWKey { + var key RWKey + key[0] = AccountStatePrefix + copy(key[1:], account.Bytes()) + key[1+common.AddressLength] = byte(state) + return key +} + +func StorageStateKey(account common.Address, state common.Hash) RWKey { + var key RWKey + key[0] = StorageStatePrefix + copy(key[1:], account.Bytes()) + copy(key[1+common.AddressLength:], state.Bytes()) + return key +} + +func (key *RWKey) IsAccountState() (bool, AccountState) { + return AccountStatePrefix == key[0], AccountState(key[1+common.AddressLength]) +} + +func (key *RWKey) IsAccountSelf() bool { + ok, s := key.IsAccountState() + if !ok { + return false + } + return s == AccountSelf +} + +func (key *RWKey) IsAccountSuicide() bool { + ok, s := key.IsAccountState() + if !ok { + return false + } + return s == AccountSuicide +} + +func (key *RWKey) ToAccountSelf() RWKey { + return AccountStateKey(key.Addr(), AccountSelf) +} + +func (key *RWKey) IsStorageState() bool { + return StorageStatePrefix == key[0] +} + +func (key *RWKey) String() string { + return hex.EncodeToString(key[:]) +} + +func (key *RWKey) Addr() common.Address { + return common.BytesToAddress(key[1 : 1+common.AddressLength]) +} + +// StateVersion record specific TxIndex & TxIncarnation +// if TxIndex equals to -1, it means the state read from DB. +type StateVersion struct { + TxIndex int + // TODO(galaio): used for multi ver state + TxIncarnation int +} + +// ReadRecord keep read value & its version +type ReadRecord struct { + StateVersion + Val interface{} +} + +// WriteRecord keep latest state value & change count +type WriteRecord struct { + Val interface{} +} + +// RWSet record all read & write set in txs +// Attention: this is not a concurrent safety structure +type RWSet struct { + ver StateVersion + readSet map[RWKey]*ReadRecord + writeSet map[RWKey]*WriteRecord + + // some flags + mustSerial bool +} + +func NewRWSet(ver StateVersion) *RWSet { + return &RWSet{ + ver: ver, + readSet: make(map[RWKey]*ReadRecord), + writeSet: make(map[RWKey]*WriteRecord), + } +} + +func (s *RWSet) RecordRead(key RWKey, ver StateVersion, val interface{}) { + // only record the first read version + if _, exist := s.readSet[key]; exist { + return + } + s.readSet[key] = &ReadRecord{ + StateVersion: ver, + Val: val, + } +} + +func (s *RWSet) RecordWrite(key RWKey, val interface{}) { + wr, exist := s.writeSet[key] + if !exist { + s.writeSet[key] = &WriteRecord{ + Val: val, + } + return + } + wr.Val = val +} + +func (s *RWSet) Version() StateVersion { + return s.ver +} + +func (s *RWSet) ReadSet() map[RWKey]*ReadRecord { + return s.readSet +} + +func (s *RWSet) WriteSet() map[RWKey]*WriteRecord { + return s.writeSet +} + +func (s *RWSet) WithSerialFlag() *RWSet { + s.mustSerial = true + return s +} + +func (s *RWSet) String() string { + builder := strings.Builder{} + builder.WriteString(fmt.Sprintf("tx: %v, inc: %v\nreadSet: [", s.ver.TxIndex, s.ver.TxIncarnation)) + i := 0 + for key, _ := range s.readSet { + if i > 0 { + builder.WriteString(fmt.Sprintf(", %v", key.String())) + continue + } + builder.WriteString(fmt.Sprintf("%v", key.String())) + i++ + } + builder.WriteString("]\nwriteSet: [") + i = 0 + for key, _ := range s.writeSet { + if i > 0 { + builder.WriteString(fmt.Sprintf(", %v", key.String())) + continue + } + builder.WriteString(fmt.Sprintf("%v", key.String())) + i++ + } + builder.WriteString("]\n") + return builder.String() +} + +// isEqualRWVal compare state +func isEqualRWVal(key RWKey, src interface{}, compared interface{}) bool { + if ok, state := key.IsAccountState(); ok { + switch state { + case AccountBalance: + if src != nil && compared != nil { + return equalUint256(src.(*uint256.Int), compared.(*uint256.Int)) + } + return src == compared + case AccountNonce: + return src.(uint64) == compared.(uint64) + case AccountCodeHash: + if src != nil && compared != nil { + return slices.Equal(src.([]byte), compared.([]byte)) + } + return src == compared + } + return false + } + + if src != nil && compared != nil { + return src.(common.Hash) == compared.(common.Hash) + } + return src == compared +} + +func equalUint256(s, c *uint256.Int) bool { + if s != nil && c != nil { + return s.Eq(c) + } + + return s == c +} + +type PendingWrite struct { + Ver StateVersion + Val interface{} +} + +func NewPendingWrite(ver StateVersion, wr *WriteRecord) *PendingWrite { + return &PendingWrite{ + Ver: ver, + Val: wr.Val, + } +} + +func (w *PendingWrite) TxIndex() int { + return w.Ver.TxIndex +} + +func (w *PendingWrite) TxIncarnation() int { + return w.Ver.TxIncarnation +} + +type PendingWrites struct { + list []*PendingWrite +} + +func NewPendingWrites() *PendingWrites { + return &PendingWrites{ + list: make([]*PendingWrite, 0), + } +} + +func (w *PendingWrites) Append(pw *PendingWrite) { + if i, found := w.SearchTxIndex(pw.TxIndex()); found { + w.list[i] = pw + return + } + + w.list = append(w.list, pw) + for i := len(w.list) - 1; i > 0; i-- { + if w.list[i].TxIndex() > w.list[i-1].TxIndex() { + break + } + w.list[i-1], w.list[i] = w.list[i], w.list[i-1] + } +} + +func (w *PendingWrites) SearchTxIndex(txIndex int) (int, bool) { + n := len(w.list) + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) + // i ≤ h < j + if w.list[h].TxIndex() < txIndex { + i = h + 1 + } else { + j = h + } + } + return i, i < n && w.list[i].TxIndex() == txIndex +} + +func (w *PendingWrites) FindLastWrite(txIndex int) *PendingWrite { + var i, _ = w.SearchTxIndex(txIndex) + for j := i - 1; j >= 0; j-- { + if w.list[j].TxIndex() < txIndex { + return w.list[j] + } + } + + return nil +} + +type MVStates struct { + rwSets map[int]*RWSet + pendingWriteSet map[RWKey]*PendingWrites + + // dependency map cache for generating TxDAG + // depsCache[i].exist(j) means j->i, and i > j + depsCache map[int]TxDepMap + + // execution stat infos + stats map[int]*ExeStat + lock sync.RWMutex +} + +func NewMVStates(txCount int) *MVStates { + return &MVStates{ + rwSets: make(map[int]*RWSet, txCount), + pendingWriteSet: make(map[RWKey]*PendingWrites, txCount*8), + depsCache: make(map[int]TxDepMap, txCount), + stats: make(map[int]*ExeStat, txCount), + } +} + +func (s *MVStates) RWSets() map[int]*RWSet { + s.lock.RLock() + defer s.lock.RUnlock() + return s.rwSets +} + +func (s *MVStates) Stats() map[int]*ExeStat { + s.lock.RLock() + defer s.lock.RUnlock() + return s.stats +} + +func (s *MVStates) RWSet(index int) *RWSet { + s.lock.RLock() + defer s.lock.RUnlock() + if index >= len(s.rwSets) { + return nil + } + return s.rwSets[index] +} + +// ReadState TODO(galaio): read state from MVStates +func (s *MVStates) ReadState(key RWKey) (interface{}, bool) { + return nil, false +} + +// FulfillRWSet it can execute as async, and rwSet & stat must guarantee read-only +// TODO(galaio): try to generate TxDAG, when fulfill RWSet +// TODO(galaio): support flag to stat execution as optional +func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { + log.Debug("FulfillRWSet", "s.len", len(s.rwSets), "cur", rwSet.ver.TxIndex, "reads", len(rwSet.readSet), "writes", len(rwSet.writeSet)) + s.lock.Lock() + defer s.lock.Unlock() + index := rwSet.ver.TxIndex + if s := s.rwSets[index]; s != nil { + return errors.New("refill a exist RWSet") + } + if stat != nil { + if stat.txIndex != index { + return errors.New("wrong execution stat") + } + s.stats[index] = stat + } + + // analysis dep, if the previous transaction is not executed/validated, re-analysis is required + if _, ok := s.depsCache[index]; !ok { + s.depsCache[index] = NewTxDeps(0) + } + for prev := 0; prev < index; prev++ { + // if there are some parallel execution or system txs, it will fulfill in advance + // it's ok, and try re-generate later + if _, ok := s.rwSets[prev]; !ok { + continue + } + if checkDependency(s.rwSets[prev].writeSet, rwSet.readSet) { + s.depsCache[index].add(prev) + // clear redundancy deps compared with prev + for dep := range s.depsCache[index] { + if s.depsCache[prev].exist(dep) { + s.depsCache[index].remove(dep) + } + } + } + } + + // append to pending write set + for k, v := range rwSet.writeSet { + // TODO(galaio): this action is only for testing, it can be removed in production mode. + // ignore no changed write record + checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet) + if _, exist := s.pendingWriteSet[k]; !exist { + s.pendingWriteSet[k] = NewPendingWrites() + } + s.pendingWriteSet[k].Append(NewPendingWrite(rwSet.ver, v)) + } + s.rwSets[index] = rwSet + return nil +} + +func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*ReadRecord, writeSet map[RWKey]*WriteRecord) bool { + var ( + readOk bool + writeOk bool + r *WriteRecord + ) + + if k.IsAccountSuicide() { + _, readOk = readSet[k.ToAccountSelf()] + } else { + _, readOk = readSet[k] + } + + r, writeOk = writeSet[k] + if readOk != writeOk { + // check if it's correct? read nil, write non-nil + log.Info("checkRWSetInconsistent find inconsistent", "tx", index, "k", k.String(), "read", readOk, "write", writeOk, "val", r.Val) + return true + } + + return false +} + +// ResolveTxDAG generate TxDAG from RWSets +func (s *MVStates) ResolveTxDAG() TxDAG { + rwSets := s.RWSets() + txDAG := NewPlainTxDAG(len(rwSets)) + for i := len(rwSets) - 1; i >= 0; i-- { + txDAG.TxDeps[i].TxIndexes = []uint64{} + if rwSets[i].mustSerial { + txDAG.TxDeps[i].Relation = 1 + continue + } + if s.depsCache[i] != nil { + txDAG.TxDeps[i].TxIndexes = s.depsCache[i].toArray() + continue + } + readSet := rwSets[i].ReadSet() + // TODO: check if there are RW with system address + // check if there has written op before i + for j := 0; j < i; j++ { + if checkDependency(rwSets[j].writeSet, readSet) { + txDAG.TxDeps[i].AppendDep(j) + } + } + } + + return txDAG +} + +func checkDependency(writeSet map[RWKey]*WriteRecord, readSet map[RWKey]*ReadRecord) bool { + // check tx dependency, only check key, skip version + for k, _ := range writeSet { + // check suicide, add read address flag, it only for check suicide quickly, and cannot for other scenarios. + if k.IsAccountSuicide() { + if _, ok := readSet[k.ToAccountSelf()]; ok { + return true + } + continue + } + if _, ok := readSet[k]; ok { + return true + } + } + + return false +} + +type TxDepMap map[int]struct{} + +func NewTxDeps(cap int) TxDepMap { + return make(map[int]struct{}, cap) +} + +func (m TxDepMap) add(index int) { + m[index] = struct{}{} +} + +func (m TxDepMap) exist(index int) bool { + _, ok := m[index] + return ok +} + +func (m TxDepMap) toArray() []uint64 { + ret := make([]uint64, 0, len(m)) + for index := range m { + ret = append(ret, uint64(index)) + } + slices.Sort(ret) + return ret +} + +func (m TxDepMap) remove(index int) { + delete(m, index) +} diff --git a/core/vm/interface.go b/core/vm/interface.go index bf2f42e994..1f0295bc3b 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -86,6 +86,10 @@ type StateDB interface { PrintParallelStateObjects() GetNonceFromBaseDB(addr common.Address) uint64 TxIndex() int + + // parallel DAG related + BeforeTxTransition() + FinaliseRWSet() error } // CallContext provides a basic interface for the EVM calling conventions. The EVM diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 6a11bf3689..47d63691cd 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -81,6 +81,9 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { return h.handleBlockAnnounces(peer, hashes, numbers) case *eth.NewBlockPacket: + if len(packet.TxDAG) != 0 { + packet.Block = packet.Block.WithTxDAG(packet.TxDAG) + } return h.handleBlockBroadcast(peer, packet.Block, packet.TD) case *eth.NewPooledTransactionHashesPacket: @@ -137,6 +140,7 @@ func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td if h.merger.PoSFinalized() { return errors.New("disallowed block broadcast") } + // Schedule the block for import h.blockFetcher.Enqueue(peer.ID(), block) diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index ffd78b0594..265de7a2a9 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -282,6 +282,7 @@ func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{ Block: block, TD: td, + TxDAG: block.TxDAG(), }) } diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 47e8d97244..1da4cda9a9 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -187,6 +187,7 @@ type BlockHeadersRLPPacket struct { type NewBlockPacket struct { Block *types.Block TD *big.Int + TxDAG []byte `rlp:"optional"` } // sanityCheck verifies that the values are reasonable, as a DoS protection @@ -237,6 +238,8 @@ type BlockBody struct { Transactions []*types.Transaction // Transactions contained within a block Uncles []*types.Header // Uncles contained within a block Withdrawals []*types.Withdrawal `rlp:"optional"` // Withdrawals contained within a block + // TODO(galio): add block body later + //TxDAGs [][]byte `rlp:"optional"` // TxDAGs contained within a block } // Unpack retrieves the transactions and uncles from the range packet and returns diff --git a/miner/worker.go b/miner/worker.go index c5686f4d5d..c93751c378 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1179,6 +1179,7 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err w.mu.RUnlock() start := time.Now() + // Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees filter := txpool.PendingFilter{ MinTip: tip, @@ -1202,6 +1203,8 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err localPlainTxs, remotePlainTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingPlainTxs localBlobTxs, remoteBlobTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingBlobTxs + env.state.ResetMVStates(0) + for _, account := range w.eth.TxPool().Locals() { if txs := remotePlainTxs[account]; len(txs) > 0 { delete(remotePlainTxs, account) @@ -1438,6 +1441,20 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti if err != nil { return err } + + // Because the TxDAG appends after sidecar, so we only enable after cancun + if w.chainConfig.IsCancun(env.header.Number, env.header.Time) { + for i := len(env.txs); i < len(block.Transactions()); i++ { + env.state.RecordSystemTxRWSet(i) + } + txDAG, _ := env.state.MVStates2TxDAG() + rawTxDAG, err := types.EncodeTxDAG(txDAG) + if err != nil { + return err + } + block = block.WithTxDAG(rawTxDAG) + } + // If we're post merge, just ignore if !w.isTTDReached(block.Header()) { select { From 0f9be14d1842869037b9759a980857742adfed78 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:43:29 +0800 Subject: [PATCH 04/72] fix several UT with racing issues (#5) * fix several UT with racing issues * fix incorrect nonce balance codehash issue case: TestEIP1559 / TestDeleteThenCreate * Fix ExecutionSpec tests mainly root caused by balance not updated to dirty correctly. also fix similar issue with nonce and codehash. * fix TestBlockChain testcase issue TestBlockchain/ValidBlocks/bcStateTests/refundReset.json Co-authored-by: Sunny --- core/block_validator.go | 7 +- core/blockchain.go | 20 +++ core/parallel_state_processor.go | 58 ++++--- core/state/journal.go | 6 - core/state/parallel_statedb.go | 83 ++++----- core/state/state_object.go | 244 ++++++++++++++++++-------- core/state/statedb.go | 290 ++++++++++++++----------------- core/types/block.go | 2 - core/types/mvstates.go | 2 +- core/vm/interface.go | 2 - tests/block_test_util.go | 2 - 11 files changed, 392 insertions(+), 324 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index 551fe2d58b..f2446927cc 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -195,10 +195,9 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD func() error { // Validate the state root against the received state root and throw // an error if they don't match. - // @TODO shall we disable it? - //if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { - // return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) - //} + if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { + return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) + } return nil }, } diff --git a/core/blockchain.go b/core/blockchain.go index ee2d9b1e08..caba259bed 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1959,8 +1959,28 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if !setHead { // Don't set the head, only insert the block err = bc.writeBlockWithState(block, receipts, statedb) + if false { + fmt.Printf("Dav -- After writeBlockWithState: %d check balance\n", block.NumberU64()) + actual := statedb.GetBalance(block.Coinbase()) + fmt.Printf("Dav -- AfterwriteBlockWithState: %d balance: %d\n", block.NumberU64(), actual.Uint64()) + } } else { status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) + if false { + fmt.Printf("Dav -- After writeBlockAndSetHead: %d check balance\n", block.NumberU64()) + actual := statedb.GetBalance(block.Coinbase()) + fmt.Printf("Dav -- writeBlockAndSetHead: %d balance: %d\n", block.NumberU64(), actual.Uint64()) + + s, _ := bc.State() + bk := bc.CurrentBlock() + fmt.Printf("Dav -- writeBlockAndSetHead - currentBlock: %d root: %s\n", bk.Number.Uint64(), bk.Root) + obj, _ := s.GetStateObjectFromSnapshotOrTrie(block.Coinbase()) + + //obj, _ := statedb.GetStateObjectFromSnapshotOrTrie(block.Coinbase()) + + fmt.Printf("Dav -- writeBlockAndSetHead: %d obj from snap or trie: %p\n", block.NumberU64(), obj) + + } } followupInterrupt.Store(true) if err != nil { diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 5c47bd1b14..0968f74e02 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -30,7 +30,7 @@ type ParallelStateProcessor struct { slotState []*SlotState // idle, or pending messages allTxReqs []*ParallelTxRequest txResultChan chan *ParallelTxResult // to notify dispatcher that a tx is done - mergedTxIndex int // the latest finalized tx index, fixme: use Atomic + mergedTxIndex atomic.Int32 // the latest finalized tx index, fixme: use Atomic pendingConfirmResults map[int][]*ParallelTxResult // tx could be executed several times, with several result to check unconfirmedResults *sync.Map // this is for stage2 confirm, since pendingConfirmResults can not be accessed in stage2 loop unconfirmedDBs *sync.Map @@ -102,7 +102,7 @@ type ParallelTxRequest struct { curTxChan chan int systemAddrRedo bool runnable int32 // 0: not runnable, 1: runnable - executedNum int32 + executedNum atomic.Int32 retryNum int32 } @@ -148,7 +148,7 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { if txNum == 0 { return } - p.mergedTxIndex = -1 + p.mergedTxIndex.Store(-1) p.debugConflictRedoNum = 0 p.inConfirmStage2 = false @@ -251,8 +251,8 @@ func (p *ParallelStateProcessor) switchSlot(slotIndex int) { } func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxRequest) *ParallelTxResult { - atomic.AddInt32(&txReq.executedNum, 1) - slotDB := state.NewSlotDB(txReq.baseStateDB, txReq.txIndex, p.mergedTxIndex, p.unconfirmedDBs) + execNum := txReq.executedNum.Add(1) + slotDB := state.NewSlotDB(txReq.baseStateDB, txReq.txIndex, int(p.mergedTxIndex.Load()), p.unconfirmedDBs) blockContext := NewEVMBlockContext(txReq.block.Header(), p.bc, nil, p.config, slotDB) // can share blockContext within a block for efficiency txContext := NewEVMTxContext(txReq.msg) @@ -274,7 +274,7 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR evm, result, err := applyTransactionStageExecution(txReq.msg, gpSlot, slotDB, vmenv) txResult := ParallelTxResult{ - executedIndex: atomic.LoadInt32(&txReq.executedNum), + executedIndex: execNum, slotIndex: slotIndex, txReq: txReq, receipt: nil, // receipt is generated in finalize stage @@ -304,7 +304,7 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR // to confirm a serial TxResults with same txIndex func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bool) *ParallelTxResult { if isStage2 { - if targetTxIndex <= p.mergedTxIndex+1 { + if targetTxIndex <= int(p.mergedTxIndex.Load())+1 { // `p.mergedTxIndex+1` is the one to be merged, // in stage2, we do likely conflict check, for these not their turn. return nil @@ -327,7 +327,7 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo if atomic.LoadInt32(&targetResult.txReq.runnable) == 1 { return nil } - if targetResult.executedIndex < atomic.LoadInt32(&targetResult.txReq.executedNum) { + if targetResult.executedIndex < targetResult.txReq.executedNum.Load() { // skip the intermediate result that is not the latest. return nil } @@ -358,22 +358,24 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo blockTxCount := targetResult.txReq.block.Transactions().Len() // This means that the tx has been executed more than blockTxCount times, so it exits with the error. // TODO-dav: p.mergedTxIndex+2 may be more reasonable? - this is buggy for expected exit - if targetResult.txReq.txIndex == p.mergedTxIndex+1 { + if targetResult.txReq.txIndex == int(p.mergedTxIndex.Load())+1 { // txReq is the next to merge if atomic.LoadInt32(&targetResult.txReq.retryNum) <= int32(blockTxCount)+3000 { atomic.AddInt32(&targetResult.txReq.retryNum, 1) // conflict retry } else { - // retry 100 times and still conflict, either the tx is expected to be wrong, or something wrong. + // retry many times and still conflict, either the tx is expected to be wrong, or something wrong. if targetResult.err != nil { - fmt.Printf("!!!!!!!!!!! Parallel execution exited with error!!!!!, txIndex:%d, err: %v\n", targetResult.txReq.txIndex, targetResult.err) + if true { // TODO: delete the printf + fmt.Printf("!!!!!!!!!!! Parallel execution exited with error!!!!!, txIndex:%d, err: %v\n", targetResult.txReq.txIndex, targetResult.err) + } return targetResult } else { // abnormal exit with conflict error, need check the parallel algorithm targetResult.err = ErrParallelUnexpectedConflict - - fmt.Printf("!!!!!!!!!!! Parallel execution exited unexpected conflict!!!!!, txIndex:%d\n", targetResult.txReq.txIndex) - + if true { + fmt.Printf("!!!!!!!!!!! Parallel execution exited unexpected conflict!!!!!, txIndex:%d\n", targetResult.txReq.txIndex) + } return targetResult } } @@ -443,7 +445,7 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { interrupted := false for _, txReq := range curSlot.pendingTxReqList { - if txReq.txIndex <= p.mergedTxIndex { + if txReq.txIndex <= int(p.mergedTxIndex.Load()) { continue } @@ -471,7 +473,7 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { // as long as the TxReq is runnable, we steal it, mark it as stolen for _, stealTxReq := range p.allTxReqs { // fmt.Printf("Dav -- stealLoop, handle TxREQ: %d\n", stealTxReq.txIndex) - if stealTxReq.txIndex <= p.mergedTxIndex { + if stealTxReq.txIndex <= int(p.mergedTxIndex.Load()) { // fmt.Printf("Dav -- stealLoop, - txReq.txIndex <= p.mergedTxIndex - TxREQ: %d\n", stealTxReq.txIndex) continue } @@ -518,7 +520,7 @@ func (p *ParallelStateProcessor) runConfirmStage2Loop() { // if lucky, it is the Tx's turn, we will do conflict check with WBNB makeup // otherwise, do conflict check without WBNB makeup, but we will ignore WBNB's balance conflict. // throw these likely conflicted tx back to re-execute - startTxIndex := p.mergedTxIndex + 2 // stage 2's will start from the next target merge index + startTxIndex := int(p.mergedTxIndex.Load()) + 2 // stage 2's will start from the next target merge index endTxIndex := startTxIndex + stage2CheckNumber txSize := len(p.allTxReqs) if endTxIndex > (txSize - 1) { @@ -538,16 +540,16 @@ func (p *ParallelStateProcessor) runConfirmStage2Loop() { } func (p *ParallelStateProcessor) handleTxResults() *ParallelTxResult { - confirmedResult := p.toConfirmTxIndex(p.mergedTxIndex+1, false) + confirmedResult := p.toConfirmTxIndex(int(p.mergedTxIndex.Load())+1, false) if confirmedResult == nil { return nil } // schedule stage 2 when new Tx has been merged, schedule once and ASAP // stage 2,if all tx have been executed at least once, and its result has been received. // in Stage 2, we will run check when main DB is advanced, i.e., new Tx result has been merged. - if p.inConfirmStage2 && p.mergedTxIndex >= p.nextStage2TxIndex { - p.nextStage2TxIndex = p.mergedTxIndex + stage2CheckNumber - p.confirmStage2Chan <- p.mergedTxIndex + if p.inConfirmStage2 && int(p.mergedTxIndex.Load()) >= p.nextStage2TxIndex { + p.nextStage2TxIndex = int(p.mergedTxIndex.Load()) + stage2CheckNumber + p.confirmStage2Chan <- int(p.mergedTxIndex.Load()) } return confirmedResult } @@ -581,11 +583,11 @@ func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *Ga // merge slotDB into mainDB statedb.MergeSlotDB(result.slotDB, result.receipt, resultTxIndex) - if resultTxIndex != p.mergedTxIndex+1 { + if resultTxIndex != int(p.mergedTxIndex.Load())+1 { log.Error("ProcessParallel tx result out of order", "resultTxIndex", resultTxIndex, - "p.mergedTxIndex", p.mergedTxIndex) + "p.mergedTxIndex", p.mergedTxIndex.Load()) } - p.mergedTxIndex = resultTxIndex + p.mergedTxIndex.Store(int32(resultTxIndex)) return result } @@ -628,6 +630,8 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat misc.ApplyPreContractHardFork(statedb) } + misc.EnsureCreate2Deployer(p.config, block.Time(), statedb) + txNum := len(block.Transactions()) p.resetState(txNum, statedb) @@ -645,6 +649,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } + statedb.MarkFullProcessed() // var txReqs []*ParallelTxRequest for i, tx := range block.Transactions() { // can be moved it into slot for efficiency, but signer is not concurrent safe @@ -670,9 +675,9 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat curTxChan: make(chan int, 1), systemAddrRedo: false, // set to true, when systemAddr access is detected. runnable: 1, // 0: not runnable, 1: runnable - executedNum: 0, retryNum: 0, } + txReq.executedNum.Store(0) p.allTxReqs = append(p.allTxReqs, txReq) } // set up stage2 enter criteria @@ -683,6 +688,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat p.targetStage2Count = p.targetStage2Count - stage2AheadNum } + // From now on, entering parallel execution. p.doStaticDispatch(p.allTxReqs) // todo: put txReqs in unit? // after static dispatch, we notify the slot to work. @@ -698,7 +704,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat } unconfirmedResult := <-p.txResultChan unconfirmedTxIndex := unconfirmedResult.txReq.txIndex - if unconfirmedTxIndex <= p.mergedTxIndex { + if unconfirmedTxIndex <= int(p.mergedTxIndex.Load()) { // log.Warn("drop merged txReq", "unconfirmedTxIndex", unconfirmedTxIndex, "p.mergedTxIndex", p.mergedTxIndex) continue } diff --git a/core/state/journal.go b/core/state/journal.go index 3ba29a4b3b..23e1a6c48c 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -17,7 +17,6 @@ package state import ( - "fmt" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" ) @@ -175,11 +174,6 @@ func (ch createObjectChange) dirtied() *common.Address { func (ch resetObjectChange) revert(dber StateDBer) { s := dber.getBaseStateDB() if s.parallel.isSlotDB { - - if ch.prev.address.Hex() == "0x6295eE1B4F6dD65047762F924Ecd367c17eaBf8f" { - fmt.Printf("Dav - revert() - set dirtiedStateObjectsInSlot[%s] = obj, obj.codehash: %s\n", - ch.prev.address, common.Bytes2Hex(ch.prev.CodeHash())) - } // ch.prev must be from dirtiedStateObjectsInSlot, put it back s.parallel.dirtiedStateObjectsInSlot[ch.prev.address] = ch.prev } else { diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 7fe68bd5bc..b1eaf9d778 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -146,10 +146,6 @@ func (s *ParallelStateDB) RevertSlotDB(from common.Address) { selfStateObject := s.parallel.dirtiedStateObjectsInSlot[from] s.parallel.dirtiedStateObjectsInSlot = make(map[common.Address]*stateObject, 2) // keep these elements - if from.Hex() == "0x6295eE1B4F6dD65047762F924Ecd367c17eaBf8f" { - fmt.Printf("Dav - RevertSlotDB - set dirtiedStateObjectsInSlot[%s] = obj, obj.codehash: %s\n", - from, common.Bytes2Hex(selfStateObject.CodeHash())) - } s.parallel.dirtiedStateObjectsInSlot[from] = selfStateObject s.parallel.balanceChangesInSlot[from] = struct{}{} s.parallel.nonceChangesInSlot[from] = struct{}{} @@ -166,17 +162,17 @@ func (s *ParallelStateDB) SetSlotIndex(index int) { // for parallel execution mode, try to get dirty StateObject in slot first. // it is mainly used by journal revert right now. func (s *ParallelStateDB) getStateObject(addr common.Address) *stateObject { - var ret *stateObject + var object *stateObject if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { if obj.deleted { return nil } - ret = obj + object = obj } else { // can not call s.StateDB.getStateObject(), since `newObject` need ParallelStateDB as the interface - ret = s.getStateObjectNoSlot(addr) + object = s.getStateObjectNoSlot(addr) } - return ret + return object } func (s *ParallelStateDB) storeStateObj(addr common.Address, stateObject *stateObject) { @@ -188,11 +184,11 @@ func (s *ParallelStateDB) storeStateObj(addr common.Address, stateObject *stateO // the object could be created in SlotDB, if it got the object from DB and // update it to the shared `s.parallel.stateObjects`` - stateObject.db.storeParallelLock.Lock() + stateObject.db.parallelStateAccessLock.Lock() if _, ok := s.parallel.stateObjects.Load(addr); !ok { s.parallel.stateObjects.Store(addr, stateObject) } - stateObject.db.storeParallelLock.Unlock() + stateObject.db.parallelStateAccessLock.Unlock() } func (s *ParallelStateDB) getStateObjectNoSlot(addr common.Address) *stateObject { @@ -613,9 +609,6 @@ func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { if dirtyObj != nil { // found one if dirtyObj.CodeHash() == nil || bytes.Equal(dirtyObj.CodeHash(), emptyCodeHash) { - if bytes.Equal(codeHash.Bytes(), emptyCodeHash) { - fmt.Printf("Dav -- update codehash to empty in dirty - addr: %s\n", addr) - } dirtyObj.data.CodeHash = codeHash.Bytes() } } @@ -686,9 +679,15 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common return val } } + // 2.2 Object in dirty because of other changes, such as getBalance etc. + // load from dirty directly and the stateObject.GetState() will care of the KvReadInSlot update. + // So there is no chance for create different objects with same address. (one in dirty and one from non-slot, and inconsistency) + if dirtyObj != nil { + return dirtyObj.GetState(hash) + } value := common.Hash{} - // 2.2 Try to get from unconfirmed DB if exist + // 2.3 Try to get from unconfirmed DB if exist if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { value = val } else { @@ -703,28 +702,13 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common if s.parallel.kvReadsInSlot[addr] == nil { s.parallel.kvReadsInSlot[addr] = newStorage(false) } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, value) // update cache - - // fixup Dirty - if dirtyObj != nil { - old := dirtyObj.GetState(hash) - if old.Cmp(value) != 0 { - dirtyObj.setState(hash, value) - } - } return value } // GetCommittedState retrieves a value from the given account's committed storage trie. +// So it should not access/update dirty, and not check delete of dirty objects. func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { - // 0. Test whether it is deleted. - var dirtyObj *stateObject - if o, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - if o.deleted { - return common.Hash{} - } - dirtyObj = o - } + // 2.Try to get from unconfirmed DB or main DB // KVs in unconfirmed DB can be seen as pending storage // KVs in main DB are merged from SlotDB and has done finalise() on merge, can be seen as pending storage too. @@ -752,14 +736,6 @@ func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Has } s.parallel.kvReadsInSlot[addr].StoreValue(hash, value) // update cache - // fixup Dirty - if dirtyObj != nil { - old := dirtyObj.GetState(hash) - if old.Cmp(value) != 0 { - dirtyObj.setState(hash, value) - } - } - return value } @@ -983,7 +959,7 @@ func (s *ParallelStateDB) SelfDestruct(addr common.Address) { // do copy-on-write for suicide "write" newStateObject := object.lightCopy(s) newStateObject.markSelfdestructed() - newStateObject.data.Balance = new(uint256.Int) + newStateObject.setBalance(new(uint256.Int)) s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, // s.parallel.nonceChangesInSlot[addr] = struct{}{} @@ -997,7 +973,7 @@ func (s *ParallelStateDB) SelfDestruct(addr common.Address) { s.parallel.balanceChangesInSlot[addr] = struct{}{} s.parallel.codeChangesInSlot[addr] = struct{}{} object.markSelfdestructed() - object.data.Balance = new(uint256.Int) + object.setBalance(new(uint256.Int)) } func (s *ParallelStateDB) Selfdestruct6780(addr common.Address) { @@ -1486,7 +1462,6 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S for addr := range mainDB.journal.dirties { var obj *stateObject var exist bool - obj, exist = mainDB.getStateObjectFromStateObjects(addr) if !exist { // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 @@ -1495,11 +1470,11 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S // it will persist in the journal even though the journal is reverted. In this special circumstance, // it may exist in `s.journal.dirties` but not in `s.stateObjects`. // Thus, we can safely ignore it here - continue } if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { + obj.deleted = true // We need to maintain account deletions explicitly (will remain @@ -1508,13 +1483,16 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S if _, ok := mainDB.stateObjectsDestruct[obj.address]; !ok { mainDB.stateObjectsDestruct[obj.address] = obj.origin } + // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then // resurrect an account; but the snapshotter needs both events. + mainDB.accountStorageParallelLock.Lock() delete(mainDB.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) delete(mainDB.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(mainDB.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) delete(mainDB.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + mainDB.accountStorageParallelLock.Unlock() } else { obj.finalise(true) // Prefetch slots in the background } @@ -1557,20 +1535,22 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { obj.deleted = true - // We need to maintain account deletions explicitly (will remain // set indefinitely). Note only the first occurred self-destruct // event is tracked. if _, ok := s.stateObjectsDestruct[obj.address]; !ok { s.stateObjectsDestruct[obj.address] = obj.origin } + // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then // resurrect an account; but the snapshotter needs both events. - delete(s.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) - delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) - delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) - delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + mainDB.accountStorageParallelLock.Lock() + delete(mainDB.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(mainDB.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) + delete(mainDB.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(mainDB.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + mainDB.accountStorageParallelLock.Unlock() if s.parallel.isSlotDB { s.parallel.accountsDeletedRecord = append(s.parallel.accountsDeletedRecord, obj.addrHash) @@ -1578,18 +1558,22 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S s.parallel.accountsOriginDeleteRecord = append(s.parallel.accountsOriginDeleteRecord, obj.address) s.parallel.storagesOriginDeleteRecord = append(s.parallel.storagesOriginDeleteRecord, obj.address) } + } else { // 1.none parallel mode, we do obj.finalise(true) as normal // 2.with parallel mode, we do obj.finalise(true) on dispatcher, not on slot routine // obj.finalise(true) will clear its dirtyStorage, will make prefetch broken. if !s.isParallel || !s.parallel.isSlotDB { obj.finalise(true) // Prefetch slots in the background + } else { + obj.fixUpOriginAndResetPendingStorage() } } obj.created = false s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} + // At this point, also ship the address off to the precacher. The precacher // will start loading tries, and when the change is eventually committed, // the commit-phase will be a lot faster @@ -1610,6 +1594,7 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S func (s *ParallelStateDB) IntermediateRootForSlotDB(deleteEmptyObjects bool, mainDB *StateDB) common.Hash { // Finalise all the dirty storage states and write them into the tries s.FinaliseForParallel(deleteEmptyObjects, mainDB) + // If there was a trie prefetcher operating, it gets aborted and irrevocably // modified after we start retrieving tries. Remove it from the statedb after // this round of use. @@ -1651,6 +1636,7 @@ func (s *ParallelStateDB) IntermediateRootForSlotDB(deleteEmptyObjects bool, mai } } } + // Now we're about to start to write changes to the trie. The trie is so far // _untouched_. We can check with the prefetcher, if it can give us a trie // which has the same root, but also has some content loaded into it. @@ -1715,5 +1701,6 @@ func (s *ParallelStateDB) IntermediateRootForSlotDB(deleteEmptyObjects bool, mai defer func(start time.Time) { mainDB.AccountHashes += time.Since(start) }(time.Now()) } ret := mainDB.trie.Hash() + return ret } diff --git a/core/state/state_object.go b/core/state/state_object.go index 0bc7c9b97d..061d61aa90 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -149,6 +149,10 @@ func newStorage(isParallel bool) Storage { // - First you need to obtain a state object. // - Account values as well as storages can be accessed and modified through the object. // - Finally, call commit to return the changes of storage trie and update account data. +// +// NOTICE: For Parallel, there is lightCopy and deepCopy used for cloning object between +// slot DB and global DB, and it is not guaranteed to be happened after finalise(), so any +// field added into the stateObject must be handled in lightCopy and deepCopy. type stateObject struct { db *StateDB // The baseDB for parallel. dbItf StateDBer // The slotDB for parallel. @@ -321,7 +325,16 @@ func (s *stateObject) GetState(key common.Hash) common.Hash { return value } // Otherwise return the entry's original value - return s.GetCommittedState(key) + result := s.GetCommittedState(key) + // Record first read for conflict verify + if s.db.isParallel && s.db.parallel.isSlotDB { + addr := s.address + if s.db.parallel.kvReadsInSlot[addr] == nil { + s.db.parallel.kvReadsInSlot[addr] = newStorage(false) + } + s.db.parallel.kvReadsInSlot[addr].StoreValue(key, result) + } + return result } // GetCommittedState retrieves a value from the committed account storage trie. @@ -394,7 +407,9 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { s.db.setError(err) return common.Hash{} } + s.db.trieParallelLock.Lock() val, err := tr.GetStorage(s.address, key.Bytes()) + s.db.trieParallelLock.Unlock() if metrics.EnabledExpensive { s.db.StorageReads += time.Since(start) } @@ -405,7 +420,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { value.SetBytes(val) } s.originStorage.StoreValue(key, value) - return value } @@ -430,7 +444,7 @@ func (s *stateObject) SetState(key, value common.Hash) { prevalue: prev, }) - if s.db.parallel.isSlotDB { + if s.db.isParallel && s.db.parallel.isSlotDB { s.db.parallel.kvChangesInSlot[s.address][key] = struct{}{} // should be moved to here, after `s.db.GetState()` } s.setState(key, value) @@ -502,6 +516,16 @@ func (s *stateObject) finaliseRWSet() { // this function will return the mutated storage trie, or nil if there is no // storage change at all. func (s *stateObject) updateTrie() (Trie, error) { + maindb := s.db + if s.db.isParallel && s.db.parallel.isSlotDB { + // we need to fixup the origin storage with the mainDB. otherwise the changes maybe problem since the origin + // is wrong. + maindb = s.db.parallel.baseStateDB + // TODO: consider delete as it is dup with accountMux and storageMux + maindb.accountStorageParallelLock.Lock() + defer maindb.accountStorageParallelLock.Unlock() + } + // Make sure all dirty slots are finalized into the pending storage area s.finalise(false) @@ -511,7 +535,7 @@ func (s *stateObject) updateTrie() (Trie, error) { } // Track the amount of time wasted on updating the storage trie if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now()) + defer func(start time.Time) { maindb.StorageUpdates += time.Since(start) }(time.Now()) } // The snapshot storage map for the object var ( @@ -521,10 +545,9 @@ func (s *stateObject) updateTrie() (Trie, error) { ) tr, err := s.getTrie() if err != nil { - s.db.setError(err) + maindb.setError(err) return nil, err } - // Insert all the pending storage updates into the trie usedStorage := make([][]byte, 0, s.pendingStorage.Length()) dirtyStorage := make(map[common.Hash][]byte) @@ -552,14 +575,14 @@ func (s *stateObject) updateTrie() (Trie, error) { for key, value := range dirtyStorage { if len(value) == 0 { if err := tr.DeleteStorage(s.address, key[:]); err != nil { - s.db.setError(err) + maindb.setError(err) } - s.db.StorageDeleted += 1 + maindb.StorageDeleted += 1 } else { if err := tr.UpdateStorage(s.address, key[:], value); err != nil { - s.db.setError(err) + maindb.setError(err) } - s.db.StorageUpdated += 1 + maindb.StorageUpdated += 1 } // Cache the items for preloading usedStorage = append(usedStorage, common.CopyBytes(key[:])) @@ -569,20 +592,20 @@ func (s *stateObject) updateTrie() (Trie, error) { wg.Add(1) go func() { defer wg.Done() - s.db.StorageMux.Lock() + maindb.StorageMux.Lock() // The snapshot storage map for the object - storage = s.db.storages[s.addrHash] + storage = maindb.storages[s.addrHash] if storage == nil { storage = make(map[common.Hash][]byte, len(dirtyStorage)) - s.db.storages[s.addrHash] = storage + maindb.storages[s.addrHash] = storage } // Cache the original value of mutated storage slots - origin = s.db.storagesOrigin[s.address] + origin = maindb.storagesOrigin[s.address] if origin == nil { origin = make(map[common.Hash][]byte) - s.db.storagesOrigin[s.address] = origin + maindb.storagesOrigin[s.address] = origin } - s.db.StorageMux.Unlock() + maindb.StorageMux.Unlock() for key, value := range dirtyStorage { khash := crypto.HashData(hasher, key[:]) @@ -609,26 +632,89 @@ func (s *stateObject) updateTrie() (Trie, error) { }() wg.Wait() - if s.db.prefetcher != nil { - s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage) + if maindb.prefetcher != nil { + maindb.prefetcher.used(s.addrHash, s.data.Root, usedStorage) } s.pendingStorage = newStorage(s.isParallel) // reset pending map return tr, nil + /* + s.pendingStorage.Range(func(keyItf, valueItf interface{}) bool { + key := keyItf.(common.Hash) + value := valueItf.(common.Hash) + // Skip noop changes, persist actual changes + originalValue, _ := s.originStorage.GetValue(key) + if value == originalValue { + return true + } + + prev, _ := s.originStorage.GetValue(key) + s.originStorage.StoreValue(key, value) + + var encoded []byte // rlp-encoded value to be used by the snapshot + if (value == common.Hash{}) { + if err := tr.DeleteStorage(s.address, key[:]); err != nil { + maindb.setError(err) + } + maindb.StorageDeleted += 1 + } else { + // Encoding []byte cannot fail, ok to ignore the error. + trimmed := common.TrimLeftZeroes(value[:]) + encoded, _ = rlp.EncodeToBytes(trimmed) + if err := tr.UpdateStorage(s.address, key[:], trimmed); err != nil { + maindb.setError(err) + } + maindb.StorageUpdated += 1 + } + // Cache the mutated storage slots until commit + if storage == nil { + if storage = maindb.storages[s.addrHash]; storage == nil { + storage = make(map[common.Hash][]byte) + maindb.storages[s.addrHash] = storage + } + } + + khash := crypto.HashData(maindb.hasher, key[:]) + storage[khash] = encoded // encoded will be nil if it's deleted + + // Cache the original value of mutated storage slots + if origin == nil { + if origin = maindb.storagesOrigin[s.address]; origin == nil { + origin = make(map[common.Hash][]byte) + maindb.storagesOrigin[s.address] = origin + } + } + // Track the original value of slot only if it's mutated first time + if _, ok := origin[khash]; !ok { + if prev == (common.Hash{}) { + origin[khash] = nil // nil if it was not present previously + } else { + // Encoding []byte cannot fail, ok to ignore the error. + b, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(prev[:])) + origin[khash] = b + } + } + // Cache the items for preloading + usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure + return true + }) + if maindb.prefetcher != nil { + maindb.prefetcher.used(s.addrHash, s.data.Root, usedStorage) + } + s.pendingStorage = newStorage(s.isParallel) // reset pending map + + return tr, nil + */ } // updateRoot flushes all cached storage mutations to trie, recalculating the // new storage trie root. func (s *stateObject) updateRoot() { - // If node runs in no trie mode, set root to empty. - defer func() { - if s.db.db.NoTries() { - s.data.Root = types.EmptyRootHash - } - }() - // Flush cached storage mutations into trie, short circuit if any error // is occurred or there is not change in the trie. + // TODO: The trieParallelLock seems heavy, can we remove it? + s.db.trieParallelLock.Lock() tr, err := s.updateTrie() + s.db.trieParallelLock.Unlock() if err != nil || tr == nil { return } @@ -707,45 +793,42 @@ func (s *stateObject) ReturnGas(gas *uint256.Int) {} func (s *stateObject) lightCopy(db *ParallelStateDB) *stateObject { object := newObject(db, s.isParallel, s.address, &s.data) + if s.trie != nil { + s.db.trieParallelLock.Lock() + object.trie = db.db.CopyTrie(s.trie) + s.db.trieParallelLock.Unlock() + } object.code = s.code object.selfDestructed = s.selfDestructed // should be false object.dirtyCode = s.dirtyCode // it is not used in slot, but keep it is ok object.deleted = s.deleted // should be false - // we must copy because it is possible that s comes from unconfirmedDB and hence storage is necessary. - // otherwise there is problem that the light copied obj is in dirty and addrStateChangeInSlot is marked, but - // GetState get empty from storages and load from mainDB, which is inconsistent with real execution. - // Moreover, as the wrong object already in dirty, no KVStateRead recorded. and hence can not identified in - // conflict detection. - // example: block contains tx1 tx2 - // after execution of tx1, it store object@theAddr in unconfirmedDB. - // at tx2, it first AddBalance of theAddr, which cause lightcopy and store in dirty, and mark the addrStateChangeInSlot - // Then the GetState(theAddr) find addrStateChangeInSlot and get obj in dirty, but the slot is empty so it load from - // mainDB, and return is inconsistent with unconfirmedDB of Tx1 result. (and as object in dirty, it doesn't mark KVStateRead.) - if object.address.Hex() == "0x864BbDA5C698aC34b47a9ea3BD4228802cC5ce3b" { - fmt.Printf("Dav -- ligthCopy -- update storage :%s\n storages:\ndirty:", object.address.Hex()) - s.dirtyStorage.Range(func(key, value interface{}) bool { - fmt.Printf("key: %s, value: %s\n", - key.(common.Hash), value.(common.Hash)) - return true - }) - fmt.Printf("\npending:\n") - s.pendingStorage.Range(func(key, value interface{}) bool { - fmt.Printf("key: %s, value: %s\n", - key.(common.Hash), value.(common.Hash)) - return true - }) - } - - object.dirtyStorage = s.dirtyStorage.Copy() + object.dirtyBalance = s.dirtyBalance + object.dirtyNonce = s.dirtyNonce + object.dirtyCodeHash = s.dirtyCodeHash + + // object generated by lightCopy() is supposed to be used in the slot. + // and the origin storage will be filled at GetState() etc. + // the dirty and pending will be recorded in the execution for new changes. + // so no need to do the copy. + // moreover, copy storage here is tricky, as the stateDB is changed concurrently with + // the slot execution, and the snap is updated only at Commit stage. + // so the origin may different between the time NOW and the time of merge, so the conflict check is vital to avoid + // the problem. fortunately, the KVRead will record this and compare it with mainDB. + + //object.dirtyStorage = s.dirtyStorage.Copy() + s.db.accountStorageParallelLock.RLock() object.originStorage = s.originStorage.Copy() object.pendingStorage = s.pendingStorage.Copy() - + s.db.accountStorageParallelLock.RUnlock() return object } +// deepCopy happens only at global serial execution stage. +// E.g. prepareForParallel and merge (copy slotObj to mainDB) +// otherwise the origin/dirty/pending storages may cause incorrect issue. func (s *stateObject) deepCopy(db *StateDB) *stateObject { - obj := &stateObject{ + object := &stateObject{ db: db.getBaseStateDB(), dbItf: db, address: s.address, @@ -755,27 +838,23 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { isParallel: s.isParallel, } if s.trie != nil { - obj.trie = db.db.CopyTrie(s.trie) + s.db.trieParallelLock.Lock() + object.trie = db.db.CopyTrie(s.trie) + s.db.trieParallelLock.Unlock() } - obj.code = s.code - obj.dirtyStorage = s.dirtyStorage.Copy() - obj.originStorage = s.originStorage.Copy() - obj.pendingStorage = s.pendingStorage.Copy() - obj.selfDestructed = s.selfDestructed - obj.dirtyCode = s.dirtyCode - obj.deleted = s.deleted - // dirty states - if s.dirtyNonce != nil { - obj.dirtyNonce = new(uint64) - *obj.dirtyNonce = *s.dirtyNonce - } - if s.dirtyBalance != nil { - obj.dirtyBalance = new(uint256.Int).Set(s.dirtyBalance) - } - obj.dirtyCodeHash = s.dirtyCodeHash + object.code = s.code + object.dirtyStorage = s.dirtyStorage.Copy() + object.originStorage = s.originStorage.Copy() + object.pendingStorage = s.pendingStorage.Copy() + object.selfDestructed = s.selfDestructed + object.dirtyCode = s.dirtyCode + object.deleted = s.deleted + object.dirtyBalance = s.dirtyBalance + object.dirtyNonce = s.dirtyNonce + object.dirtyCodeHash = s.dirtyCodeHash - return obj + return object } func (s *stateObject) MergeSlotObject(db Database, dirtyObjs *stateObject, keys StateKeys) { @@ -801,16 +880,13 @@ func (s *stateObject) Code() []byte { if s.code != nil { return s.code } - if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { return nil } - code, err := s.db.db.ContractCode(s.address, common.BytesToHash(s.CodeHash())) if err != nil { s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err)) } - s.code = code return code } @@ -886,3 +962,23 @@ func (s *stateObject) Nonce() uint64 { func (s *stateObject) Root() common.Hash { return s.data.Root } + +// fixUpOriginAndResetPendingStorage is used for slot object only, the target is to fix up the origin storage of the +// object with the latest mainDB. And reset the pendingStorage as the execution recorded the changes in dirty and the +// dirties will be merged to pending at finalise. so the current pendingStorage contains obsoleted info mainly from +// lightCopy() +func (s *stateObject) fixUpOriginAndResetPendingStorage() { + if s.db.isParallel && s.db.parallel.isSlotDB { + mainDB := s.db.parallel.baseStateDB + origObj := mainDB.getStateObject(s.address) + mainDB.accountStorageParallelLock.RLock() + if origObj != nil && origObj.originStorage.Length() != 0 { + s.originStorage = origObj.originStorage.Copy() + } + // isParallel is unnecessary since the pendingStorage for slotObject will be used serially from now on. + if s.pendingStorage.Length() > 0 { + s.pendingStorage = newStorage(false) + } + mainDB.accountStorageParallelLock.RUnlock() + } +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 684a904b5f..a87cb5f67c 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,7 +18,6 @@ package state import ( - "bytes" "errors" "fmt" "runtime" @@ -91,9 +90,9 @@ func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { // When a state object is stored into s.parallel.stateObjects, // it belongs to base StateDB, it is confirmed and valid. // TODO-dav: remove the lock/unlock? - stateObject.db.storeParallelLock.Lock() + stateObject.db.parallelStateAccessLock.Lock() s.parallel.stateObjects.Store(addr, stateObject) - stateObject.db.storeParallelLock.Unlock() + stateObject.db.parallelStateAccessLock.Unlock() } else { s.stateObjects[addr] = stateObject } @@ -183,12 +182,14 @@ type StateDB struct { snaps *snapshot.Tree // Nil if snapshot is not available snap snapshot.Snapshot // Nil if snapshot is not available - storeParallelLock sync.RWMutex - snapParallelLock sync.RWMutex // for parallel mode, for main StateDB, slot will read snapshot, while processor will write. - trieParallelLock sync.Mutex // for parallel mode, for getting states/objects from trie, to handle trie tracer. - snapDestructs map[common.Address]struct{} - snapAccounts map[common.Address][]byte - snapStorage map[common.Address]map[string][]byte + parallelStateAccessLock sync.RWMutex + snapParallelLock sync.RWMutex // for parallel mode, for main StateDB, slot will read snapshot, while processor will write. + trieParallelLock sync.Mutex // for parallel mode of trie, mostly for get states/objects from trie, lock required to handle trie tracer. + // TODO: is it possible to remove this accountStorageParallelLock? + accountStorageParallelLock sync.RWMutex // for global state account/storage read (copyForSlot) and write (Intermediate) + snapDestructs map[common.Address]struct{} + snapAccounts map[common.Address][]byte + snapStorage map[common.Address]map[string][]byte // originalRoot is the pre-state root, before any changes were made. // It will be updated when the Commit is called. @@ -489,7 +490,9 @@ func (s *StateDB) GetBalance(addr common.Address) (ret *uint256.Int) { defer func() { s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), ret) }() + object := s.getStateObject(addr) + if object != nil { return object.Balance() } @@ -670,7 +673,6 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // TODO(rjl493456442) this function should only be supported by 'unwritable' // state and all mutations made should all be discarded afterwards. if _, ok := s.queryStateObjectsDestruct(addr); !ok { - fmt.Printf("Dav -- setStorage - stateObjectsDestruct[%s] = nil\n", addr) s.tagStateObjectsDestruct(addr, nil) } stateObject := s.getOrNewStateObject(addr) @@ -741,6 +743,9 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common // updateStateObject writes the given object to the trie. func (s *StateDB) updateStateObject(obj *stateObject) { + if !(s.isParallel && s.parallel.isSlotDB) { + s.accountStorageParallelLock.Lock() + } if !s.noTrie { // Track the amount of time wasted on updating the account from the trie if metrics.EnabledExpensive { @@ -772,6 +777,10 @@ func (s *StateDB) updateStateObject(obj *stateObject) { s.accountsOrigin[obj.address] = types.SlimAccountRLP(*obj.origin) } } + + if !(s.isParallel && s.parallel.isSlotDB) { + s.accountStorageParallelLock.Unlock() + } } // deleteStateObject removes the given object from the state trie. @@ -809,12 +818,9 @@ func (s *StateDB) GetStateObjectFromSnapshotOrTrie(addr common.Address) (data *t func (s *StateDB) SnapHasAccount(addr common.Address) (exist bool) { if s.snap == nil { - fmt.Printf("Dav -- Test Snap have account snap is nil\n") return false } - acc, _ := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())) - fmt.Printf("Dav -- Test Snap have account, root %s, have? %v\n", s.snap.Root(), acc != nil) return acc != nil } @@ -850,6 +856,7 @@ func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *t if acc == nil { return nil, false } + data = &types.StateAccount{ Nonce: acc.Nonce, Balance: acc.Balance, @@ -914,6 +921,7 @@ func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *t // destructed object instead of wiping all knowledge about the state object. func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { s.RecordRead(types.AccountStateKey(addr, types.AccountSelf), struct{}{}) + // Prefer live objects if any is available if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { return obj @@ -933,13 +941,12 @@ func (s *StateDB) setStateObject(object *stateObject) { if s.isParallel { // When a state object is stored into s.parallel.stateObjects, // it belongs to base StateDB, it is confirmed and valid. - s.storeParallelLock.Lock() + s.parallelStateAccessLock.Lock() s.parallel.stateObjects.Store(object.address, object) - s.storeParallelLock.Unlock() + s.parallelStateAccessLock.Unlock() } else { s.stateObjects[object.Address()] = object } - } // getOrNewStateObject retrieves a state object or create a new state object if nil. @@ -976,8 +983,6 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { // account and storage data should be cleared as well. Note, it must // be done here, otherwise the destruction event of "original account" // will be lost. - s.snapParallelLock.Lock() // fixme: with new dispatch policy, the ending Tx could running, while the block have processed. - _, prevdestruct := s.queryStateObjectsDestruct(prev.address) if !prevdestruct { s.tagStateObjectsDestruct(prev.address, prev.origin) @@ -1000,14 +1005,6 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { delete(s.storages, prev.addrHash) delete(s.accountsOrigin, prev.address) delete(s.storagesOrigin, prev.address) - - if s.parallel.isSlotDB { - s.parallel.accountsDeletedRecord = append(s.parallel.accountsDeletedRecord, prev.addrHash) - s.parallel.storagesDeleteRecord = append(s.parallel.storagesDeleteRecord, prev.addrHash) - s.parallel.accountsOriginDeleteRecord = append(s.parallel.accountsOriginDeleteRecord, prev.address) - s.parallel.storagesOriginDeleteRecord = append(s.parallel.storagesOriginDeleteRecord, prev.address) - } - s.snapParallelLock.Unlock() } newobj.created = true @@ -1112,7 +1109,6 @@ func (s *StateDB) copyInternal(doPrefetch bool) *StateDB { } // Deep copy the destruction markers. for addr, value := range s.stateObjectsDestruct { - // fmt.Printf("Dav -- copyInternal - stateObjectsDestruct[%s] = (%p) : %v \n", addr, value, value) state.stateObjectsDestruct[addr] = value } for addr, value := range s.stateObjectsDestructDirty { @@ -1392,12 +1388,12 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { // } // copy parallel stateObjects - s.storeParallelLock.Lock() + s.parallelStateAccessLock.Lock() s.parallel.stateObjects.Range(func(addr any, stateObj any) bool { state.parallel.stateObjects.StoreStateObject(addr.(common.Address), stateObj.(*stateObject).lightCopy(state)) return true }) - s.storeParallelLock.Unlock() + s.parallelStateAccessLock.Unlock() if s.snaps != nil { // In order for the miner to be able to use and make additions // to the snapshot tree, we need to copy that as well. @@ -1433,6 +1429,15 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { // state.prefetcher = s.prefetcher } + s.accountStorageParallelLock.RLock() + // Deep copy the state changes made in the scope of block + // along with their original values. + state.accounts = copySet(s.accounts) + state.storages = copy2DSet(s.storages) + state.accountsOrigin = copySet(state.accountsOrigin) + state.storagesOrigin = copy2DSet(state.storagesOrigin) + s.accountStorageParallelLock.RUnlock() + return state } @@ -1584,19 +1589,37 @@ func (s *StateDB) AccountsIntermediateRoot() { // first, giving the account prefetches just a few more milliseconds of time // to pull useful data from disk. for addr := range s.stateObjectsPending { - if obj := s.stateObjects[addr]; !obj.deleted { - wg.Add(1) - tasks <- func() { - defer wg.Done() - obj.updateRoot() - - // Cache the data until commit. Note, this update mechanism is not symmetric - // to the deletion, because whereas it is enough to track account updates - // at commit time, deletions need tracking at transaction boundary level to - // ensure we capture state clearing. - s.AccountMux.Lock() - s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data) - s.AccountMux.Unlock() + if s.parallel.isSlotDB { + if obj := s.parallel.dirtiedStateObjectsInSlot[addr]; !obj.deleted { + wg.Add(1) + tasks <- func() { + defer wg.Done() + obj.updateRoot() + + // Cache the data until commit. Note, this update mechanism is not symmetric + // to the deletion, because whereas it is enough to track account updates + // at commit time, deletions need tracking at transaction boundary level to + // ensure we capture state clearing. + s.AccountMux.Lock() + s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data) + s.AccountMux.Unlock() + } + } + } else { + if obj, _ := s.getStateObjectFromStateObjects(addr); !obj.deleted { + wg.Add(1) + tasks <- func() { + defer wg.Done() + obj.updateRoot() + + // Cache the data until commit. Note, this update mechanism is not symmetric + // to the deletion, because whereas it is enough to track account updates + // at commit time, deletions need tracking at transaction boundary level to + // ensure we capture state clearing. + s.AccountMux.Lock() + s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data) + s.AccountMux.Unlock() + } } } } @@ -1622,23 +1645,6 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { r = s.trie.Hash() } } - // Although naively it makes sense to retrieve the account trie and then do - // the contract storage and account updates sequentially, that short circuits - // the account prefetcher. Instead, let's process all the storage updates - // first, giving the account prefetches just a few more milliseconds of time - // to pull useful data from disk. - for addr := range s.stateObjectsPending { - var obj *stateObject - if s.parallel.isSlotDB { - if obj = s.parallel.dirtiedStateObjectsInSlot[addr]; !obj.deleted { - obj.updateRoot() - } - } else { - if obj, _ = s.getStateObjectFromStateObjects(addr); !obj.deleted { - obj.updateRoot() - } - } - } // Now we're about to start to write changes to the trie. The trie is so far // _untouched_. We can check with the prefetcher, if it can give us a trie // which has the same root, but also has some content loaded into it. @@ -2022,7 +2028,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er } for addr := range s.stateObjectsDirty { - if obj := s.stateObjects[addr]; !obj.deleted { + if obj, _ := s.getStateObjectFromStateObjects(addr); !obj.deleted { tasks <- func() { // Write any storage changes in the state object to its storage trie if !s.noTrie { @@ -2106,7 +2112,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er } codeWriter := s.db.DiskDB().NewBatch() for addr := range s.stateObjectsDirty { - if obj := s.stateObjects[addr]; !obj.deleted { + if obj, _ := s.getStateObjectFromStateObjects(addr); !obj.deleted { // Write any contract code associated with the state object if obj.code != nil && obj.dirtyCode { rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code) @@ -2267,7 +2273,7 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre func (s *StateDB) convertAccountSet(set map[common.Address]*types.StateAccount) map[common.Hash]struct{} { ret := make(map[common.Hash]struct{}, len(set)) for addr := range set { - obj, exist := s.stateObjects[addr] + obj, exist := s.getStateObjectFromStateObjects(addr) if !exist { ret[crypto.Keccak256Hash(addr[:])] = struct{}{} } else { @@ -2292,6 +2298,9 @@ func (s *StateDB) GetSnap() snapshot.Snapshot { } func (s *StateDB) BeforeTxTransition() { + if s.isParallel && s.parallel.isSlotDB { + return + } log.Debug("BeforeTxTransition", "mvStates", s.mvStates == nil, "rwSet", s.rwSet == nil) if s.mvStates == nil { return @@ -2302,6 +2311,9 @@ func (s *StateDB) BeforeTxTransition() { } func (s *StateDB) BeginTxStat(index int) { + if s.isParallel && s.parallel.isSlotDB { + return + } if s.mvStates == nil { return } @@ -2309,6 +2321,9 @@ func (s *StateDB) BeginTxStat(index int) { } func (s *StateDB) StopTxStat(usedGas uint64) { + if s.isParallel && s.parallel.isSlotDB { + return + } if s.mvStates == nil { return } @@ -2319,6 +2334,9 @@ func (s *StateDB) StopTxStat(usedGas uint64) { } func (s *StateDB) RecordRead(key types.RWKey, val interface{}) { + if s.isParallel && s.parallel.isSlotDB { + return + } if s.mvStates == nil || s.rwSet == nil { return } @@ -2329,6 +2347,9 @@ func (s *StateDB) RecordRead(key types.RWKey, val interface{}) { } func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) { + if s.isParallel && s.parallel.isSlotDB { + return + } if s.mvStates == nil || s.rwSet == nil { return } @@ -2336,11 +2357,17 @@ func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) { } func (s *StateDB) ResetMVStates(txCount int) { + if s.isParallel && s.parallel.isSlotDB { + return + } s.mvStates = types.NewMVStates(txCount) s.rwSet = nil } func (s *StateDB) FinaliseRWSet() error { + if s.isParallel && s.parallel.isSlotDB { + return nil + } if s.mvStates == nil || s.rwSet == nil { return nil } @@ -2350,7 +2377,7 @@ func (s *StateDB) FinaliseRWSet() error { s.RecordWrite(types.AccountStateKey(addr, types.AccountSuicide), struct{}{}) } for addr := range s.journal.dirties { - obj, exist := s.stateObjects[addr] + obj, exist := s.getStateObjectFromStateObjects(addr) if !exist { continue } @@ -2378,22 +2405,36 @@ func (s *StateDB) FinaliseRWSet() error { } func (s *StateDB) queryStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) { - if acc, ok := s.stateObjectsDestructDirty[addr]; ok { - return acc, ok + if !(s.isParallel && s.parallel.isSlotDB) { + if acc, ok := s.stateObjectsDestructDirty[addr]; ok { + return acc, ok + } } acc, ok := s.stateObjectsDestruct[addr] return acc, ok } func (s *StateDB) tagStateObjectsDestruct(addr common.Address, acc *types.StateAccount) { - s.stateObjectsDestructDirty[addr] = acc + if !(s.isParallel && s.parallel.isSlotDB) { + s.stateObjectsDestructDirty[addr] = acc + return + } + s.stateObjectsDestruct[addr] = acc + return } func (s *StateDB) deleteStateObjectsDestruct(addr common.Address) { - delete(s.stateObjectsDestructDirty, addr) + if !(s.isParallel && s.parallel.isSlotDB) { + delete(s.stateObjectsDestructDirty, addr) + return + } + delete(s.stateObjectsDestruct, addr) } func (s *StateDB) MVStates2TxDAG() (types.TxDAG, map[int]*types.ExeStat) { + if s.isParallel && s.parallel.isSlotDB { + return nil, nil + } if s.mvStates == nil { return types.NewEmptyTxDAG(), nil } @@ -2402,10 +2443,16 @@ func (s *StateDB) MVStates2TxDAG() (types.TxDAG, map[int]*types.ExeStat) { } func (s *StateDB) MVStates() *types.MVStates { + if s.isParallel && s.parallel.isSlotDB { + return nil + } return s.mvStates } func (s *StateDB) RecordSystemTxRWSet(index int) { + if s.isParallel && s.parallel.isSlotDB { + return + } if s.mvStates == nil { return } @@ -2538,6 +2585,7 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip // fixme: should not delete, would cause unconfirmed DB incorrect? // delete(slotDb.parallel.dirtiedStateObjectsInSlot, addr) // transfer ownership, fixme: shared read? if dirtyObj.deleted { + s.accountStorageParallelLock.Lock() // remove the addr from snapAccounts&snapStorage only when object is deleted. // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts @@ -2547,6 +2595,7 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip delete(s.storages, dirtyObj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.accountsOrigin, dirtyObj.address) // Clear out any previously updated account data (may be recreated via a resurrect) delete(s.storagesOrigin, dirtyObj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + s.accountStorageParallelLock.Unlock() } } else { // addr already in main DB, do merge: balance, KV, code, State(create, suicide) @@ -2569,14 +2618,6 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip // deepCopy here, which causes issue in root calculation. newMainObj = dirtyObj.deepCopy(s) - // Merge Storages. Only merge ones doesn't exist, since dirtyObj is newer than mainObj - mainObj.originStorage.Range(func(key, value interface{}) bool { - if _, found := newMainObj.originStorage.GetValue(key.(common.Hash)); !found { - newMainObj.originStorage.StoreValue(key.(common.Hash), value.(common.Hash)) - } - return true - }) - mainObj.pendingStorage.Range(func(key, value interface{}) bool { if _, found := newMainObj.pendingStorage.GetValue(key.(common.Hash)); !found { newMainObj.pendingStorage.StoreValue(key.(common.Hash), value.(common.Hash)) @@ -2599,26 +2640,33 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip // remove the addr from snapAccounts&snapStorage only when object is deleted. // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts + s.snapParallelLock.Lock() delete(s.snapAccounts, addr) delete(s.snapStorage, addr) + s.snapParallelLock.Unlock() + s.AccountMux.Lock() delete(s.accounts, dirtyObj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) - delete(s.storages, dirtyObj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.accountsOrigin, dirtyObj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + s.AccountMux.Unlock() + s.StorageMux.Lock() + delete(s.storages, dirtyObj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.storagesOrigin, dirtyObj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) + s.StorageMux.Unlock() } } else { // deepCopy a temporary *stateObject for safety, since slot could read the address, // dispatch should avoid overwrite the StateObject directly otherwise, it could // crash for: concurrent map iteration and map write - + // As there is dirtyBalance, Nonce and codehash, we keep it to mainObj and leave the merging work + // to "mainObj.finalise()", just in case that newMainObj.delete == true and somewhere potentially + // access the Nonce, balance or codehash later. if _, balanced := slotDb.parallel.balanceChangesInSlot[addr]; balanced { - newMainObj.setBalance(dirtyObj.Balance()) + newMainObj.dirtyBalance = dirtyObj.dirtyBalance + newMainObj.data.Balance = dirtyObj.data.Balance } if _, coded := slotDb.parallel.codeChangesInSlot[addr]; coded { - if bytes.Equal(dirtyObj.data.CodeHash, types.EmptyCodeHash.Bytes()) { // addr.Hex() == "0x0000000000000000000000000000000000000100" { - fmt.Printf("Dav -- MergeSlotDB - codeChangeInSlot - setObjectCodeHash to Empty, addr: %s\n", addr) - } newMainObj.code = dirtyObj.code + newMainObj.dirtyCodeHash = dirtyObj.dirtyCodeHash newMainObj.data.CodeHash = dirtyObj.data.CodeHash newMainObj.dirtyCode = true } @@ -2627,7 +2675,8 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip } if _, nonced := slotDb.parallel.nonceChangesInSlot[addr]; nonced { // dirtyObj.Nonce() should not be less than newMainObj - newMainObj.setNonce(dirtyObj.Nonce()) + newMainObj.data.Nonce = dirtyObj.data.Nonce + newMainObj.dirtyNonce = dirtyObj.dirtyNonce } newMainObj.deleted = dirtyObj.deleted } @@ -2656,7 +2705,6 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip } } // slotDb.logs: logs will be kept in receipts, no need to do merge - for hash, preimage := range slotDb.preimages { s.preimages[hash] = preimage } @@ -2664,63 +2712,6 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip s.accessList = slotDb.accessList.Copy() } - // handle accounts, storages and origins - for _, addr := range slotDb.parallel.accountsDeletedRecord { - if _, ok := s.accounts[addr]; ok { - delete(s.accounts, addr) - } - } - for addr, val := range slotDb.accounts { - s.accounts[addr] = val - } - - // storages - for _, addr := range slotDb.parallel.storagesDeleteRecord { - if _, ok := s.storages[addr]; ok { - delete(s.storages, addr) - } - } - - for addr, slotStMap := range slotDb.storages { - mainStMap := s.storages[addr] - if mainStMap == nil { - mainStMap = make(map[common.Hash][]byte) - } - for k, v := range slotStMap { - mainStMap[k] = v - } - s.storages[addr] = mainStMap - } - - // accountsOrigin - for _, addr := range slotDb.parallel.accountsOriginDeleteRecord { - if _, ok := s.accountsOrigin[addr]; ok { - delete(s.accountsOrigin, addr) - } - } - - for addr, val := range slotDb.accountsOrigin { - s.accountsOrigin[addr] = val - } - - // storagesOrigin - for _, addr := range slotDb.parallel.storagesOriginDeleteRecord { - if _, ok := s.storagesOrigin[addr]; ok { - delete(s.storagesOrigin, addr) - } - } - - for addr, slotStOrgMap := range slotDb.storagesOrigin { - mainStOrgMap := s.storagesOrigin[addr] - if mainStOrgMap == nil { - mainStOrgMap = make(map[common.Hash][]byte) - } - for k, v := range slotStOrgMap { - mainStOrgMap[k] = v - } - s.storagesOrigin[addr] = mainStOrgMap - } - if slotDb.snaps != nil { for k := range slotDb.snapDestructs { // There could be a race condition for parallel transaction execution @@ -2739,22 +2730,3 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip func (s *StateDB) ParallelMakeUp(common.Address, []byte) { // do nothing, this API is for parallel mode } - -func (s *StateDB) PrintParallelStateObjects() { - if s.parallel.stateObjects == nil { - return - } - s.parallel.stateObjects.Range(func(a any, v any) bool { - fmt.Printf("Dav - .parallel.stateObjects addr %v, val: %v\n", a, v) - return true - }) -} - -func (s *StateDB) GetNonceFromBaseDB(addr common.Address) uint64 { - return s.getBaseStateDB().GetNonce(addr) -} - -// delete me! -func (s *StateDB) GetDB() Database { - return s.db -} diff --git a/core/types/block.go b/core/types/block.go index f4da10994a..47b4abec39 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -241,8 +241,6 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* } else { b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher) b.header.Bloom = CreateBloom(receipts) - //fmt.Printf("Dav -- NewBlock -- ReceptHash: %s\nRecepts: %v\nBloom: %s\n", b.header.ReceiptHash, receipts, hexutils.BytesToHex(b.header.Bloom.Bytes())) - //debug.PrintStack() } if len(uncles) == 0 { diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 64c1a4fc7d..d5b9ef8b39 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -7,7 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/holiman/uint256" - "slices" + "golang.org/x/exp/slices" "strings" "sync" ) diff --git a/core/vm/interface.go b/core/vm/interface.go index 1f0295bc3b..9f8b6ea19d 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -83,8 +83,6 @@ type StateDB interface { ParallelMakeUp(addr common.Address, input []byte) // todo -dav : delete following - PrintParallelStateObjects() - GetNonceFromBaseDB(addr common.Address) uint64 TxIndex() int // parallel DAG related diff --git a/tests/block_test_util.go b/tests/block_test_util.go index bc90d524a9..75b4ab76a7 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -156,14 +156,12 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po Tracer: tracer, }, nil, nil) if err != nil { - fmt.Printf("Dav -- Test - NewBlockChain fail, err: %s\n", err) return err } defer chain.Stop() validBlocks, err := t.insertBlocks(chain) if err != nil { - fmt.Printf("Dav -- Test - t.insertBlocks fail, err: %s\n", err) return err } // Import succeeded: regardless of whether the _test_ succeeds or not, schedule From e2517b3cd7f710893ec0b20794e51a70af1ccd75 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:38:40 +0800 Subject: [PATCH 05/72] TxDAG: support PEVM static dispatch; (#6) * dag: add merge execute path method; pevm: support dispatch with TxDAG; * dag: add merge execute path method; pevm: support dispatch with TxDAG; * dag: clean code; * statedb: fix some broken uts; * pevm: support disable slot steal; --------- Co-authored-by: galaio --- core/parallel_state_processor.go | 73 +++++++++++++++++++++---- core/state/statedb.go | 30 ++++++----- core/types/dag.go | 91 +++++++++++++++++++++++++++----- core/types/dag_test.go | 57 ++++++++++++++++++++ core/types/mvstates.go | 45 ++++++++-------- 5 files changed, 236 insertions(+), 60 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 0968f74e02..e0338f28c9 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -46,6 +46,7 @@ type ParallelStateProcessor struct { inConfirmStage2 bool targetStage2Count int // when executed txNUM reach it, enter stage2 RT confirm nextStage2TxIndex int + disableStealTx bool } func NewParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine, parallelNum int) *ParallelStateProcessor { @@ -174,6 +175,37 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { p.nextStage2TxIndex = 0 } +// doStaticDispatchV2 could dispatch by TxDAG metadata +// txReqs must order by TxIndex +// txDAG must convert to dependency relation +// 1. The TxDAG generates parallel execution merge paths that will ignore cross slot tx dep; +// 2. It will dispatch the most hungry slot for every isolate execution path; +// 3. TODO(galaio) it need to schedule the slow dep tx path properly; +// 4. TODO(galaio) it is unfriendly for cross slot deps, maybe we can delay dispatch when tx cross in slots, it may increase PEVM parallelism; +func (p *ParallelStateProcessor) doStaticDispatchV2(txReqs []*ParallelTxRequest, txDAG types.TxDAG) { + // only support PlainTxDAG dispatch now. + if txDAG == nil || txDAG.Type() != types.PlainTxDAGType { + p.doStaticDispatch(txReqs) + return + } + + // resolve isolate execution paths from TxDAG, it indicates the tx dispatch + paths := types.MergeTxDAGExecutionPaths(txDAG) + log.Info("doStaticDispatchV2 merge parallel execution paths", "slots", len(p.slotState), "paths", len(paths)) + + for _, path := range paths { + slotIndex := p.mostHungrySlot() + for _, index := range path { + txReqs[index].staticSlotIndex = slotIndex // txReq is better to be executed in this slot + slot := p.slotState[slotIndex] + slot.pendingTxReqList = append(slot.pendingTxReqList, txReqs[index]) + } + } + + // it's unnecessary to enable slot steal mechanism, opt the steal mechanism later; + p.disableStealTx = true +} + // Benefits of StaticDispatch: // // ** try best to make Txs with same From() in same slot @@ -197,14 +229,7 @@ func (p *ParallelStateProcessor) doStaticDispatch(txReqs []*ParallelTxRequest) { // not found, dispatch to most hungry slot if slotIndex == -1 { - var workload = len(p.slotState[0].pendingTxReqList) - slotIndex = 0 - for i, slot := range p.slotState { // can start from index 1 - if len(slot.pendingTxReqList) < workload { - slotIndex = i - workload = len(slot.pendingTxReqList) - } - } + slotIndex = p.mostHungrySlot() } // update fromSlotMap[txReq.msg.From] = slotIndex @@ -218,6 +243,24 @@ func (p *ParallelStateProcessor) doStaticDispatch(txReqs []*ParallelTxRequest) { } } +func (p *ParallelStateProcessor) mostHungrySlot() int { + var ( + workload = len(p.slotState[0].pendingTxReqList) + slotIndex = 0 + ) + for i, slot := range p.slotState { // can start from index 1 + if len(slot.pendingTxReqList) < workload { + slotIndex = i + workload = len(slot.pendingTxReqList) + } + // just return the first slot with 0 workload + if workload == 0 { + return slotIndex + } + } + return slotIndex +} + // do conflict detect func (p *ParallelStateProcessor) hasConflict(txResult *ParallelTxResult, isStage2 bool) bool { slotDB := txResult.slotDB @@ -465,7 +508,7 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { // fmt.Printf("Dav -- runInLoop, - loopbody tail - TxREQ: %d\n", txReq.txIndex) } // switched to the other slot. - if interrupted { + if interrupted || p.disableStealTx { continue } @@ -688,8 +731,18 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat p.targetStage2Count = p.targetStage2Count - stage2AheadNum } + var ( + txDAG types.TxDAG + err error + ) + if len(block.TxDAG()) != 0 { + txDAG, err = types.DecodeTxDAG(block.TxDAG()) + if err != nil { + return nil, nil, 0, err + } + } // From now on, entering parallel execution. - p.doStaticDispatch(p.allTxReqs) // todo: put txReqs in unit? + p.doStaticDispatchV2(p.allTxReqs, txDAG) // todo: put txReqs in unit? // after static dispatch, we notify the slot to work. for _, slot := range p.slotState { diff --git a/core/state/statedb.go b/core/state/statedb.go index a87cb5f67c..b11bf723c9 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1031,6 +1031,15 @@ func (s *StateDB) CreateAccount(addr common.Address) { newObj.setBalance(new(uint256.Int).Set(preBalance)) // new big.Int for newObj } +// CopyWithMvStates will copy state with MVStates +func (s *StateDB) CopyWithMvStates(doPrefetch bool) *StateDB { + state := s.copyInternal(doPrefetch) + if s.mvStates != nil { + state.mvStates = s.mvStates + } + return state +} + // Copy creates a deep, independent copy of the state. // Snapshots of the copied state cannot be applied to the copy. func (s *StateDB) Copy() *StateDB { @@ -1150,11 +1159,6 @@ func (s *StateDB) copyInternal(doPrefetch bool) *StateDB { state.prefetcher = s.prefetcher.copy() } - // parallel EVM related - if s.mvStates != nil { - state.mvStates = s.mvStates - } - return state } @@ -2371,9 +2375,15 @@ func (s *StateDB) FinaliseRWSet() error { if s.mvStates == nil || s.rwSet == nil { return nil } + ver := types.StateVersion{ + TxIndex: s.txIndex, + } + if ver != s.rwSet.Version() { + return errors.New("you finalize a wrong ver of RWSet") + } + // finalise stateObjectsDestruct - for addr, acc := range s.stateObjectsDestructDirty { - s.stateObjectsDestruct[addr] = acc + for addr := range s.stateObjectsDestructDirty { s.RecordWrite(types.AccountStateKey(addr, types.AccountSuicide), struct{}{}) } for addr := range s.journal.dirties { @@ -2394,12 +2404,6 @@ func (s *StateDB) FinaliseRWSet() error { obj.finaliseRWSet() } } - ver := types.StateVersion{ - TxIndex: s.txIndex, - } - if ver != s.rwSet.Version() { - return errors.New("you finalize a wrong ver of RWSet") - } return s.mvStates.FulfillRWSet(s.rwSet, s.es) } diff --git a/core/types/dag.go b/core/types/dag.go index a4d111458b..b407c2bc77 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -28,6 +28,7 @@ type TxDAG interface { DelayGasDistribution() bool // TxDep query TxDeps from TxDAG + // TODO(galaio): txDAG must convert to dependency relation TxDep(int) TxDep // TxCount return tx count @@ -136,7 +137,7 @@ func NewPlainTxDAG(txLen int) *PlainTxDAG { func (d *PlainTxDAG) String() string { builder := strings.Builder{} - exePaths := travelExecutionPaths(d) + exePaths := travelTxDAGExecutionPaths(d) for _, path := range exePaths { builder.WriteString(fmt.Sprintf("%v\n", path)) } @@ -151,15 +152,67 @@ func (d *PlainTxDAG) Size() int { return len(enc) } -func travelExecutionPaths(d TxDAG) [][]uint64 { +// MergeTxDAGExecutionPaths will merge duplicate tx path for scheduling parallel. +// Any tx cannot exist in >= 2 paths. +func MergeTxDAGExecutionPaths(d TxDAG) [][]uint64 { + mergeMap := make(map[uint64][]uint64, d.TxCount()) + txMap := make(map[uint64]uint64, d.TxCount()) + for i := d.TxCount() - 1; i >= 0; i-- { + index, merge := uint64(i), uint64(i) + deps := d.TxDep(i).TxIndexes + if oldIdx, exist := findTxPathIndex(deps, index, txMap); exist { + merge = oldIdx + } + for _, tx := range deps { + txMap[tx] = merge + } + txMap[index] = merge + } + + // result by index order + for f, t := range txMap { + if mergeMap[t] == nil { + mergeMap[t] = make([]uint64, 0) + } + mergeMap[t] = append(mergeMap[t], f) + } + mergePaths := make([][]uint64, 0, len(mergeMap)) + for i := 0; i < d.TxCount(); i++ { + path, ok := mergeMap[uint64(i)] + if !ok { + continue + } + slices.Sort(path) + mergePaths = append(mergePaths, path) + } + + return mergePaths +} + +func findTxPathIndex(path []uint64, cur uint64, txMap map[uint64]uint64) (uint64, bool) { + if old, ok := txMap[cur]; ok { + return old, true + } + + for _, index := range path { + if old, ok := txMap[index]; ok { + return old, true + } + } + + return 0, false +} + +// travelTxDAGExecutionPaths will print all tx execution path +func travelTxDAGExecutionPaths(d TxDAG) [][]uint64 { txCount := d.TxCount() deps := make([]TxDep, txCount) for i := 0; i < txCount; i++ { dep := d.TxDep(i) if dep.Relation == 0 { deps[i] = dep + continue } - // recover to relation 0 for j := 0; j < i; j++ { if !dep.Exist(j) { @@ -171,7 +224,7 @@ func travelExecutionPaths(d TxDAG) [][]uint64 { exePaths := make([][]uint64, 0) // travel tx deps with BFS for i := uint64(0); i < uint64(txCount); i++ { - exePaths = append(exePaths, travelTargetPath(deps, i)) + exePaths = append(exePaths, travelTxDAGTargetPath(deps, i)) } return exePaths } @@ -199,6 +252,17 @@ func (d *TxDep) Exist(i int) bool { return false } +func (d *TxDep) Count() int { + return len(d.TxIndexes) +} + +func (d *TxDep) Last() int { + if d.Count() == 0 { + return -1 + } + return int(d.TxIndexes[len(d.TxIndexes)-1]) +} + var ( longestTimeTimer = metrics.NewRegisteredTimer("dag/longesttime", nil) longestGasTimer = metrics.NewRegisteredTimer("dag/longestgas", nil) @@ -225,7 +289,7 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) string { // sb.WriteString(fmt.Sprintf("%v: %v\n", i, dep.TxIndexes)) //} //sb.WriteString("Parallel Execution Path:\n") - paths := travelExecutionPaths(dag) + paths := travelTxDAGExecutionPaths(dag) // Attention: this is based on best schedule, it will reduce a lot by executing previous txs in parallel // It assumes that there is no parallel thread limit txCount := dag.TxCount() @@ -327,23 +391,24 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) string { return sb.String() } -func travelTargetPath(deps []TxDep, from uint64) []uint64 { - q := make([]uint64, 0, len(deps)) +// travelTxDAGTargetPath will print target execution path +func travelTxDAGTargetPath(deps []TxDep, from uint64) []uint64 { + queue := make([]uint64, 0, len(deps)) path := make([]uint64, 0, len(deps)) - q = append(q, from) + queue = append(queue, from) path = append(path, from) - for len(q) > 0 { - t := make([]uint64, 0, len(deps)) - for _, i := range q { + for len(queue) > 0 { + next := make([]uint64, 0, len(deps)) + for _, i := range queue { for _, dep := range deps[i].TxIndexes { if !slices.Contains(path, dep) { path = append(path, dep) - t = append(t, dep) + next = append(next, dep) } } } - q = t + queue = next } slices.Sort(path) return path diff --git a/core/types/dag_test.go b/core/types/dag_test.go index e86fff110c..bf12324246 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -1,6 +1,7 @@ package types import ( + "github.com/cometbft/cometbft/libs/rand" "testing" "time" @@ -172,6 +173,36 @@ func TestIsEqualRWVal(t *testing.T) { } } +func TestMergeTxDAGExecutionPaths_Simple(t *testing.T) { + paths := MergeTxDAGExecutionPaths(mockSimpleDAG()) + require.Equal(t, [][]uint64{ + {0, 3, 4}, + {1, 2, 5, 6, 7}, + {8, 9}, + }, paths) +} + +func TestMergeTxDAGExecutionPaths_Random(t *testing.T) { + dag := mockRandomDAG(10000) + paths := MergeTxDAGExecutionPaths(dag) + txMap := make(map[uint64]uint64, dag.TxCount()) + for _, path := range paths { + for _, index := range path { + old, ok := txMap[index] + require.False(t, ok, index, path, old) + txMap[index] = path[0] + } + } + require.Equal(t, dag.TxCount(), len(txMap)) +} + +func BenchmarkMergeTxDAGExecutionPaths(b *testing.B) { + dag := mockRandomDAG(100000) + for i := 0; i < b.N; i++ { + MergeTxDAGExecutionPaths(dag) + } +} + func mockSimpleDAG() TxDAG { dag := NewPlainTxDAG(10) dag.TxDeps[0].TxIndexes = []uint64{} @@ -187,6 +218,32 @@ func mockSimpleDAG() TxDAG { return dag } +func mockRandomDAG(txLen int) TxDAG { + dag := NewPlainTxDAG(txLen) + for i := 0; i < txLen; i++ { + var deps []uint64 + if i == 0 || rand.Bool() { + dag.TxDeps[i].TxIndexes = deps + continue + } + depCnt := rand.Int()%i + 1 + for j := 0; j < depCnt; j++ { + var dep uint64 + if j > 0 && deps[j-1]+1 == uint64(i) { + break + } + if j > 0 { + dep = uint64(rand.Int())%(uint64(i)-deps[j-1]-1) + deps[j-1] + 1 + } else { + dep = uint64(rand.Int() % i) + } + deps = append(deps, dep) + } + dag.TxDeps[i].TxIndexes = deps + } + return dag +} + func mockSystemTxDAG() TxDAG { dag := NewPlainTxDAG(12) dag.TxDeps[0].TxIndexes = []uint64{} diff --git a/core/types/mvstates.go b/core/types/mvstates.go index d5b9ef8b39..4db7586727 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -355,6 +355,22 @@ func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { s.stats[index] = stat } + s.resolveDepsCache(index, rwSet) + // append to pending write set + for k, v := range rwSet.writeSet { + // TODO(galaio): this action is only for testing, it can be removed in production mode. + // ignore no changed write record + checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet) + if _, exist := s.pendingWriteSet[k]; !exist { + s.pendingWriteSet[k] = NewPendingWrites() + } + s.pendingWriteSet[k].Append(NewPendingWrite(rwSet.ver, v)) + } + s.rwSets[index] = rwSet + return nil +} + +func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { // analysis dep, if the previous transaction is not executed/validated, re-analysis is required if _, ok := s.depsCache[index]; !ok { s.depsCache[index] = NewTxDeps(0) @@ -365,6 +381,8 @@ func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { if _, ok := s.rwSets[prev]; !ok { continue } + // TODO: check if there are RW with system address for gas delay calculation + // check if there has written op before i if checkDependency(s.rwSets[prev].writeSet, rwSet.readSet) { s.depsCache[index].add(prev) // clear redundancy deps compared with prev @@ -375,19 +393,6 @@ func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { } } } - - // append to pending write set - for k, v := range rwSet.writeSet { - // TODO(galaio): this action is only for testing, it can be removed in production mode. - // ignore no changed write record - checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet) - if _, exist := s.pendingWriteSet[k]; !exist { - s.pendingWriteSet[k] = NewPendingWrites() - } - s.pendingWriteSet[k].Append(NewPendingWrite(rwSet.ver, v)) - } - s.rwSets[index] = rwSet - return nil } func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*ReadRecord, writeSet map[RWKey]*WriteRecord) bool { @@ -423,18 +428,10 @@ func (s *MVStates) ResolveTxDAG() TxDAG { txDAG.TxDeps[i].Relation = 1 continue } - if s.depsCache[i] != nil { - txDAG.TxDeps[i].TxIndexes = s.depsCache[i].toArray() - continue - } - readSet := rwSets[i].ReadSet() - // TODO: check if there are RW with system address - // check if there has written op before i - for j := 0; j < i; j++ { - if checkDependency(rwSets[j].writeSet, readSet) { - txDAG.TxDeps[i].AppendDep(j) - } + if s.depsCache[i] == nil { + s.resolveDepsCache(i, rwSets[i]) } + txDAG.TxDeps[i].TxIndexes = s.depsCache[i].toArray() } return txDAG From d3edc53c0a194f4878059e1b547a3eac5f60dd6c Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:30:46 +0800 Subject: [PATCH 06/72] fix UT test and contention issue (#7) * fix several UT with racing issues * fix incorrect nonce balance codehash issue case: TestEIP1559 / TestDeleteThenCreate * Fix ExecutionSpec tests mainly root caused by balance not updated to dirty correctly. also fix similar issue with nonce and codehash. * fix TestBlockChain testcase issue TestBlockchain/ValidBlocks/bcStateTests/refundReset.json * fix concurrent racing issue of state.accounts. fix incorrect use of s.accountStorageParallelLock, it is designed to be used for dirty/pending/original storages, not the accounts and storages. Use statedb.AccountMux and statedb.StorageMux for accounts/storages lock. * fix issue of DAOTransactions handle the issue of updateObject of mainDB object touched by DAO transaction. --------- Co-authored-by: Sunny --- core/state/parallel_statedb.go | 20 +++++++++++++------- core/state/state_object.go | 6 ++++-- core/state/statedb.go | 34 +++++++++++++++++++++++++++------- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index b1eaf9d778..5a28753e19 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1487,12 +1487,15 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then // resurrect an account; but the snapshotter needs both events. - mainDB.accountStorageParallelLock.Lock() + mainDB.AccountMux.Lock() delete(mainDB.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) - delete(mainDB.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(mainDB.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + mainDB.AccountMux.Unlock() + + mainDB.StorageMux.Lock() + delete(mainDB.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(mainDB.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) - mainDB.accountStorageParallelLock.Unlock() + mainDB.StorageMux.Unlock() } else { obj.finalise(true) // Prefetch slots in the background } @@ -1545,13 +1548,16 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then // resurrect an account; but the snapshotter needs both events. - mainDB.accountStorageParallelLock.Lock() + mainDB.AccountMux.Lock() delete(mainDB.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) - delete(mainDB.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(mainDB.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + mainDB.AccountMux.Unlock() + mainDB.StorageMux.Lock() + delete(mainDB.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(mainDB.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) - mainDB.accountStorageParallelLock.Unlock() + mainDB.StorageMux.Unlock() + // todo: The following record seems unnecessary. if s.parallel.isSlotDB { s.parallel.accountsDeletedRecord = append(s.parallel.accountsDeletedRecord, obj.addrHash) s.parallel.storagesDeleteRecord = append(s.parallel.storagesDeleteRecord, obj.addrHash) @@ -1652,7 +1658,7 @@ func (s *ParallelStateDB) IntermediateRootForSlotDB(deleteEmptyObjects bool, mai if s.TxIndex() == 0 && len(mainDB.stateObjectsPending) > 0 { usedAddrs = make([][]byte, 0, len(s.stateObjectsPending)+len(mainDB.stateObjectsPending)) for addr := range mainDB.stateObjectsPending { - if obj, _ := s.getStateObjectFromStateObjects(addr); obj.deleted { + if obj, _ := mainDB.getStateObjectFromStateObjects(addr); obj.deleted { mainDB.deleteStateObject(obj) mainDB.AccountDeleted += 1 } else { diff --git a/core/state/state_object.go b/core/state/state_object.go index 061d61aa90..d9af47c164 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -518,10 +518,10 @@ func (s *stateObject) finaliseRWSet() { func (s *stateObject) updateTrie() (Trie, error) { maindb := s.db if s.db.isParallel && s.db.parallel.isSlotDB { - // we need to fixup the origin storage with the mainDB. otherwise the changes maybe problem since the origin + // we need to fixup the origin storage with the mainDB. otherwise the changes maybe problematic since the origin // is wrong. maindb = s.db.parallel.baseStateDB - // TODO: consider delete as it is dup with accountMux and storageMux + // For dirty/pending/origin Storage access and update. maindb.accountStorageParallelLock.Lock() defer maindb.accountStorageParallelLock.Unlock() } @@ -844,6 +844,8 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { } object.code = s.code + + // The lock is unnecessary since deepCopy only invoked at global phase. No concurrent racing. object.dirtyStorage = s.dirtyStorage.Copy() object.originStorage = s.originStorage.Copy() object.pendingStorage = s.pendingStorage.Copy() diff --git a/core/state/statedb.go b/core/state/statedb.go index b11bf723c9..09631f7047 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -753,14 +753,18 @@ func (s *StateDB) updateStateObject(obj *stateObject) { } // Encode the account and update the account trie addr := obj.Address() + s.trieParallelLock.Lock() if err := s.trie.UpdateAccount(addr, &obj.data); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err)) } if obj.dirtyCode { s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) } + s.trieParallelLock.Unlock() } + s.AccountMux.Lock() + defer s.AccountMux.Unlock() // Cache the data until commit. Note, this update mechanism is not symmetric // to the deletion, because whereas it is enough to track account updates // at commit time, deletions need tracking at transaction boundary level to @@ -1001,10 +1005,14 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { prevAccountOrigin: prevAccount, prevStorageOrigin: s.storagesOrigin[prev.address], }) + s.AccountMux.Lock() delete(s.accounts, prev.addrHash) - delete(s.storages, prev.addrHash) delete(s.accountsOrigin, prev.address) + s.AccountMux.Unlock() + s.StorageMux.Lock() + delete(s.storages, prev.addrHash) delete(s.storagesOrigin, prev.address) + s.StorageMux.Unlock() } newobj.created = true @@ -1125,10 +1133,15 @@ func (s *StateDB) copyInternal(doPrefetch bool) *StateDB { } // Deep copy the state changes made in the scope of block // along with their original values. + s.AccountMux.Lock() state.accounts = copySet(s.accounts) - state.storages = copy2DSet(s.storages) state.accountsOrigin = copySet(state.accountsOrigin) + s.AccountMux.Unlock() + + s.StorageMux.Lock() + state.storages = copy2DSet(s.storages) state.storagesOrigin = copy2DSet(state.storagesOrigin) + s.StorageMux.Unlock() // Deep copy the logs occurred in the scope of block for hash, logs := range s.logs { @@ -1433,14 +1446,16 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { // state.prefetcher = s.prefetcher } - s.accountStorageParallelLock.RLock() // Deep copy the state changes made in the scope of block // along with their original values. + s.AccountMux.Lock() state.accounts = copySet(s.accounts) - state.storages = copy2DSet(s.storages) state.accountsOrigin = copySet(state.accountsOrigin) + s.AccountMux.Unlock() + s.StorageMux.Lock() + state.storages = copy2DSet(s.storages) state.storagesOrigin = copy2DSet(state.storagesOrigin) - s.accountStorageParallelLock.RUnlock() + s.StorageMux.Unlock() return state } @@ -2593,13 +2608,18 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip // remove the addr from snapAccounts&snapStorage only when object is deleted. // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts + s.snapParallelLock.Lock() delete(s.snapAccounts, addr) delete(s.snapStorage, addr) + s.snapParallelLock.Unlock() + s.AccountMux.Lock() delete(s.accounts, dirtyObj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) - delete(s.storages, dirtyObj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.accountsOrigin, dirtyObj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + s.AccountMux.Unlock() + s.StorageMux.Lock() + delete(s.storages, dirtyObj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.storagesOrigin, dirtyObj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) - s.accountStorageParallelLock.Unlock() + s.StorageMux.Unlock() } } else { // addr already in main DB, do merge: balance, KV, code, State(create, suicide) From 74e2c96c86be17fe9a7b6864bad036fbb590a100 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 18 Jul 2024 17:10:51 +0800 Subject: [PATCH 07/72] Fix: dead lock issue --- core/state/statedb.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 09631f7047..741bbe54b8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2604,7 +2604,6 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip // fixme: should not delete, would cause unconfirmed DB incorrect? // delete(slotDb.parallel.dirtiedStateObjectsInSlot, addr) // transfer ownership, fixme: shared read? if dirtyObj.deleted { - s.accountStorageParallelLock.Lock() // remove the addr from snapAccounts&snapStorage only when object is deleted. // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts From 1e09e32101dadb2a50dfd20ea1bbe8d22bcf1cd3 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 18 Jul 2024 15:24:12 +0800 Subject: [PATCH 08/72] Fix: avoid update the stateObjects at conflict check phase There can be a issue that the object updated by mainDB.GetNonce etc is obseleted. The fix use statedb.getStateObjectNoUpdate to avoid touching the stateObjects of mainDB. Case: TestBlockchain/ValidBlocks/bcEIP1559/intrinsic.json --- core/state/parallel_statedb.go | 34 +++++++++++++++++++++++------ core/state/state_object.go | 9 ++++---- core/state/statedb.go | 39 +++++++++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 5a28753e19..3d4ac2d264 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1285,7 +1285,13 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } } } - nonceMain := mainDB.GetNonce(addr) + + /* can not use mainDB.GetNonce() because we do not want to record the stateObject */ + var nonceMain uint64 = 0 + mainObj := mainDB.getStateObjectNoUpdate(addr) + if mainObj != nil { + nonceMain = mainObj.Nonce() + } if nonceSlot != nonceMain { log.Debug("IsSlotDBReadsValid nonce read is invalid", "addr", addr, "nonceSlot", nonceSlot, "nonceMain", nonceMain, "SlotIndex", slotDB.parallel.SlotIndex, @@ -1304,7 +1310,12 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } } - balanceMain := mainDB.GetBalance(addr) + balanceMain := common.U2560 + mainObj := mainDB.getStateObjectNoUpdate(addr) + if mainObj != nil { + balanceMain = mainObj.Balance() + } + if balanceSlot.Cmp(balanceMain) != 0 { log.Debug("IsSlotDBReadsValid balance read is invalid", "addr", addr, "balanceSlot", balanceSlot, "balanceMain", balanceMain, "SlotIndex", slotDB.parallel.SlotIndex, @@ -1390,7 +1401,11 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { // check code for addr, codeSlot := range slotDB.parallel.codeReadsInSlot { - codeMain := mainDB.GetCode(addr) + var codeMain []byte = nil + object := mainDB.getStateObjectNoUpdate(addr) + if object != nil { + codeMain = object.Code() + } if !bytes.Equal(codeSlot, codeMain) { log.Debug("IsSlotDBReadsValid code read is invalid", "addr", addr, "len codeSlot", len(codeSlot), "len codeMain", len(codeMain), "SlotIndex", slotDB.parallel.SlotIndex, @@ -1400,7 +1415,11 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } // check codeHash for addr, codeHashSlot := range slotDB.parallel.codeHashReadsInSlot { - codeHashMain := mainDB.GetCodeHash(addr) + codeHashMain := common.Hash{} + object := mainDB.getStateObjectNoUpdate(addr) + if object != nil { + codeHashMain = common.BytesToHash(object.CodeHash()) + } if !bytes.Equal(codeHashSlot.Bytes(), codeHashMain.Bytes()) { log.Debug("IsSlotDBReadsValid codehash read is invalid", "addr", addr, "codeHashSlot", codeHashSlot, "codeHashMain", codeHashMain, "SlotIndex", slotDB.parallel.SlotIndex, @@ -1411,7 +1430,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { // addr state check for addr, stateSlot := range slotDB.parallel.addrStateReadsInSlot { stateMain := false // addr not exist - if mainDB.getStateObject(addr) != nil { + if mainDB.getStateObjectNoUpdate(addr) != nil { stateMain = true // addr exist in main DB } if stateSlot != stateMain { @@ -1424,7 +1443,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } // snapshot destructs check for addr, destructRead := range slotDB.parallel.addrSnapDestructsReadsInSlot { - mainObj := mainDB.getStateObject(addr) + mainObj := mainDB.getStateObjectNoUpdate(addr) if mainObj == nil { log.Debug("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", "addr", addr, "destruct", destructRead, @@ -1491,7 +1510,7 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S delete(mainDB.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) delete(mainDB.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) mainDB.AccountMux.Unlock() - + mainDB.StorageMux.Lock() delete(mainDB.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(mainDB.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) @@ -1573,6 +1592,7 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S obj.finalise(true) // Prefetch slots in the background } else { obj.fixUpOriginAndResetPendingStorage() + obj.finalise(false) } } diff --git a/core/state/state_object.go b/core/state/state_object.go index d9af47c164..059eaf3baf 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -258,7 +258,7 @@ func newObject(dbItf StateDBer, isParallel bool, address common.Address, acct *t address: address, addrHash: crypto.Keccak256Hash(address[:]), origin: origin, - data: *acct, + data: *acct.Copy(), isParallel: isParallel, originStorage: newStorage(isParallel), pendingStorage: newStorage(isParallel), @@ -267,7 +267,8 @@ func newObject(dbItf StateDBer, isParallel bool, address common.Address, acct *t } // dirty data when create a new account - if acct == nil { + + if created { s.dirtyBalance = new(uint256.Int).Set(acct.Balance) s.dirtyNonce = new(uint64) *s.dirtyNonce = acct.Nonce @@ -834,7 +835,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { address: s.address, addrHash: s.addrHash, origin: s.origin, - data: s.data, + data: *s.data.Copy(), isParallel: s.isParallel, } if s.trie != nil { @@ -972,7 +973,7 @@ func (s *stateObject) Root() common.Hash { func (s *stateObject) fixUpOriginAndResetPendingStorage() { if s.db.isParallel && s.db.parallel.isSlotDB { mainDB := s.db.parallel.baseStateDB - origObj := mainDB.getStateObject(s.address) + origObj := mainDB.getStateObjectNoUpdate(s.address) mainDB.accountStorageParallelLock.RLock() if origObj != nil && origObj.originStorage.Length() != 0 { s.originStorage = origObj.originStorage.Copy() diff --git a/core/state/statedb.go b/core/state/statedb.go index 741bbe54b8..49d168175d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -76,6 +76,8 @@ func (s *StateObjectSyncMap) StoreStateObject(addr common.Address, stateObject * func (s *StateDB) loadStateObj(addr common.Address) (*stateObject, bool) { if s.isParallel { + s.parallelStateAccessLock.Lock() + defer s.parallelStateAccessLock.Unlock() ret, ok := s.parallel.stateObjects.LoadStateObject(addr) return ret, ok } @@ -90,9 +92,9 @@ func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { // When a state object is stored into s.parallel.stateObjects, // it belongs to base StateDB, it is confirmed and valid. // TODO-dav: remove the lock/unlock? - stateObject.db.parallelStateAccessLock.Lock() - s.parallel.stateObjects.Store(addr, stateObject) - stateObject.db.parallelStateAccessLock.Unlock() + s.parallelStateAccessLock.Lock() + s.parallel.stateObjects.StoreStateObject(addr, stateObject) + s.parallelStateAccessLock.Unlock() } else { s.stateObjects[addr] = stateObject } @@ -816,6 +818,33 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { return nil } +// getStateObjectNoUpdate is similar with getStateObject except that it does not +// update stateObjects records. +func (s *StateDB) getStateObjectNoUpdate(addr common.Address) *stateObject { + obj := s.getDeletedStateObjectNoUpdate(addr) + if obj != nil && !obj.deleted { + return obj + } + return nil +} + +func (s *StateDB) getDeletedStateObjectNoUpdate(addr common.Address) *stateObject { + s.RecordRead(types.AccountStateKey(addr, types.AccountSelf), struct{}{}) + + // Prefer live objects if any is available + if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { + return obj + } + + data, ok := s.getStateObjectFromSnapshotOrTrie(addr) + if !ok { + return nil + } + // Insert into the live set + obj := newObject(s, s.isParallel, addr, data) + return obj +} + func (s *StateDB) GetStateObjectFromSnapshotOrTrie(addr common.Address) (data *types.StateAccount, ok bool) { return s.getStateObjectFromSnapshotOrTrie(addr) } @@ -878,11 +907,11 @@ func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *t // If snapshot unavailable or reading from it failed, load from the database if data == nil { + s.trieParallelLock.Lock() + defer s.trieParallelLock.Unlock() var trie Trie if s.isParallel { // hold lock for parallel - s.trieParallelLock.Lock() - defer s.trieParallelLock.Unlock() if s.parallel.isSlotDB { if s.parallel.baseStateDB == nil { return nil, false From 2c9e42cf679891c02fe0d03c32f6cca4f735240b Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:58:15 +0800 Subject: [PATCH 09/72] TxDAG: support TxDAG transfer, it can be used in QA performance testing; (#10) * txdag: support txdag transfer in extra; * txdag: support txdag transfer in extra; --------- Co-authored-by: galaio --- beacon/engine/types.go | 8 +++++--- core/blockchain.go | 10 +++++++++- core/parallel_state_processor.go | 8 ++++++++ miner/worker.go | 31 ++++++++++++++++++++++++++++++- 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 9a3ea8d077..aef01e7f5f 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -217,9 +217,11 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, if err != nil { return nil, err } - if len(params.ExtraData) > 32 { - return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) - } + + // TODO(galaio): need hardfork, skip check + //if len(params.ExtraData) > 32 { + // return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) + //} if len(params.LogsBloom) != 256 { return nil, fmt.Errorf("invalid logsBloom length: %v", len(params.LogsBloom)) } diff --git a/core/blockchain.go b/core/blockchain.go index caba259bed..31511af72d 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1887,7 +1887,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if err != nil { return it.index, err } - log.Info("Insert chain", "block", block.NumberU64(), "txDAG", txDAG) + log.Info("Insert chain", "block", block.NumberU64(), "txDAG", txDAG.Type()) + } + // TODO(galaio): need hardfork + if bc.chainConfig.Optimism != nil && len(block.Header().Extra) > 0 { + txDAG, err := types.DecodeTxDAG(block.Header().Extra) + if err != nil { + return it.index, err + } + log.Info("Insert chain", "block", block.NumberU64(), "txDAG", txDAG.Type()) } // Enable prefetching to pull in trie node paths while processing transactions diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index e0338f28c9..9fe363f151 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -741,6 +741,14 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat return nil, nil, 0, err } } + // TODO(galaio): need hardfork + if p.bc.chainConfig.Optimism != nil && len(block.Header().Extra) > 0 { + txDAG, err = types.DecodeTxDAG(block.Header().Extra) + if err != nil { + return nil, nil, 0, err + } + log.Info("dispatch chain with", "block", block.NumberU64(), "txDAG", txDAG.Type()) + } // From now on, entering parallel execution. p.doStaticDispatchV2(p.allTxReqs, txDAG) // todo: put txReqs in unit? diff --git a/miner/worker.go b/miner/worker.go index c93751c378..572a50c2c0 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1336,6 +1336,25 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { return &newPayloadResult{err: fmt.Errorf("empty block root")} } + // Because the TxDAG appends after sidecar, so we only enable after cancun + if w.chainConfig.IsCancun(block.Number(), block.Time()) && w.chainConfig.Optimism == nil { + txDAG, _ := work.state.MVStates2TxDAG() + rawTxDAG, err := types.EncodeTxDAG(txDAG) + if err != nil { + return &newPayloadResult{err: err} + } + block = block.WithTxDAG(rawTxDAG) + } + + // TODO(galaio): need hardfork + if w.chainConfig.Optimism != nil { + txDAG, _ := work.state.MVStates2TxDAG() + rawTxDAG, err := types.EncodeTxDAG(txDAG) + if err != nil { + return &newPayloadResult{err: err} + } + block.Header().Extra = rawTxDAG + } assembleBlockTimer.UpdateSince(start) log.Debug("assembleBlockTimer", "duration", common.PrettyDuration(time.Since(start)), "parentHash", genParams.parentHash) @@ -1443,7 +1462,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti } // Because the TxDAG appends after sidecar, so we only enable after cancun - if w.chainConfig.IsCancun(env.header.Number, env.header.Time) { + if w.chainConfig.IsCancun(env.header.Number, env.header.Time) && w.chainConfig.Optimism == nil { for i := len(env.txs); i < len(block.Transactions()); i++ { env.state.RecordSystemTxRWSet(i) } @@ -1455,6 +1474,16 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti block = block.WithTxDAG(rawTxDAG) } + // TODO(galaio): need hardfork + if w.chainConfig.Optimism != nil { + txDAG, _ := env.state.MVStates2TxDAG() + rawTxDAG, err := types.EncodeTxDAG(txDAG) + if err != nil { + return err + } + block.Header().Extra = rawTxDAG + } + // If we're post merge, just ignore if !w.isTTDReached(block.Header()) { select { From 732090a31057bfbfad53d576883dddf4875f234c Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:58:56 +0800 Subject: [PATCH 10/72] txdag: support write & read TxDAG from file; (#9) txdag: record txdag metrics; txdag: opt txdag flag name; Co-authored-by: galaio --- cmd/geth/main.go | 2 + cmd/utils/flags.go | 21 ++++++ core/blockchain.go | 123 ++++++++++++++++++++++++++++--- core/blockchain_insert.go | 2 +- core/blockchain_test.go | 32 ++++++++ core/parallel_state_processor.go | 30 ++++++-- core/state/statedb.go | 11 +++ core/state_processor.go | 26 ++++--- core/types/dag.go | 5 +- core/types/dag_test.go | 3 +- core/types/mvstates.go | 5 +- eth/backend.go | 3 + eth/ethconfig/config.go | 2 + miner/miner.go | 3 +- miner/worker.go | 17 +++-- 15 files changed, 244 insertions(+), 41 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 6ed003061c..23fe516b9b 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -171,6 +171,8 @@ var ( utils.RollupSuperchainUpgradesFlag, utils.ParallelTxFlag, utils.ParallelTxNumFlag, + utils.ParallelTxDAGFlag, + utils.ParallelTxDAGFileFlag, configFileFlag, utils.LogDebugFlag, utils.LogBacktraceAtFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2974662beb..616ea4ea15 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1106,6 +1106,19 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Category: flags.VMCategory, } + ParallelTxDAGFlag = &cli.BoolFlag{ + Name: "parallel.txdag", + Usage: "Enable the experimental parallel TxDAG generation, only valid in full sync mode (default = false)", + Category: flags.VMCategory, + } + + ParallelTxDAGFileFlag = &cli.StringFlag{ + Name: "parallel.txdagfile", + Usage: "It indicates the TxDAG file path", + Value: "./parallel-txdag-output.csv", + Category: flags.VMCategory, + } + VMOpcodeOptimizeFlag = &cli.BoolFlag{ Name: "vm.opcode.optimize", Usage: "enable opcode optimization", @@ -2017,6 +2030,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.ParallelTxNum = parallelNum } + if ctx.IsSet(ParallelTxDAGFlag.Name) { + cfg.EnableParallelTxDAG = ctx.Bool(ParallelTxDAGFlag.Name) + } + + if ctx.IsSet(ParallelTxDAGFileFlag.Name) { + cfg.ParallelTxDAGFile = ctx.String(ParallelTxDAGFileFlag.Name) + } + if ctx.IsSet(VMOpcodeOptimizeFlag.Name) { cfg.EnableOpcodeOptimizing = ctx.Bool(VMOpcodeOptimizeFlag.Name) if cfg.EnableOpcodeOptimizing { diff --git a/core/blockchain.go b/core/blockchain.go index 31511af72d..f9af61b269 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -18,11 +18,16 @@ package core import ( + "bufio" + "bytes" + "encoding/hex" "errors" "fmt" "io" "math/big" + "os" "runtime" + "strconv" "strings" "sync" "sync/atomic" @@ -91,6 +96,9 @@ var ( triedbCommitExternalTimer = metrics.NewRegisteredTimer("chain/triedb/commit/external", nil) innerExecutionTimer = metrics.NewRegisteredTimer("chain/inner/execution", nil) + txDAGGenerateTimer = metrics.NewRegisteredTimer("chain/block/txdag/gen", nil) + txDAGDispatchTimer = metrics.NewRegisteredTimer("chain/block/txdag/dispatch", nil) + blockGasUsedGauge = metrics.NewRegisteredGauge("chain/block/gas/used", nil) mgaspsGauge = metrics.NewRegisteredGauge("chain/mgas/ps", nil) @@ -297,6 +305,9 @@ type BlockChain struct { forker *ForkChoice vmConfig vm.Config parallelExecution bool + enableTxDAG bool + txDAGWriteCh chan TxDAGOutputItem + txDAGMapping map[uint64]types.TxDAG } // NewBlockChain returns a fully initialised block chain using information @@ -1881,16 +1892,16 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } - // TODO(galaio): use txDAG in some accelerate scenarios. - if len(block.TxDAG()) > 0 { - txDAG, err := types.DecodeTxDAG(block.TxDAG()) - if err != nil { - return it.index, err - } - log.Info("Insert chain", "block", block.NumberU64(), "txDAG", txDAG.Type()) - } + // TODO(galaio): use txDAG in some accelerate scenarios, like state pre-fetcher. + //if bc.enableTxDAG && len(block.TxDAG()) > 0 { + // txDAG, err := types.DecodeTxDAG(block.TxDAG()) + // if err != nil { + // return it.index, err + // } + // log.Info("Insert chain", "block", block.NumberU64(), "txDAG", txDAG) + //} // TODO(galaio): need hardfork - if bc.chainConfig.Optimism != nil && len(block.Header().Extra) > 0 { + if bc.enableTxDAG && bc.chainConfig.Optimism != nil && len(block.Header().Extra) > 0 { txDAG, err := types.DecodeTxDAG(block.Header().Extra) if err != nil { return it.index, err @@ -1952,8 +1963,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) 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) - blockExecutionTimer.Update(ptime) // The time spent on block execution - blockValidationTimer.Update(vtime) // The time spent on block validation + txDAGGenerateTimer.Update(statedb.TxDAGGenerate) + blockExecutionTimer.Update(ptime) // The time spent on block execution + blockValidationTimer.Update(vtime) // The time spent on block validation innerExecutionTimer.Update(DebugInnerExecutionDuration) @@ -2689,3 +2701,92 @@ func createDelFn(bc *BlockChain) func(db ethdb.KeyValueWriter, hash common.Hash, func (bc *BlockChain) HeaderChainForceSetHead(headNumber uint64) { bc.hc.SetHead(headNumber, nil, createDelFn(bc)) } + +func (bc *BlockChain) TxDAGEnabled() bool { + return bc.enableTxDAG +} + +func (bc *BlockChain) EnableTxDAGGeneration(output string) { + bc.enableTxDAG = true + if len(output) == 0 { + return + } + // read TxDAG file, and cache in mem + var err error + bc.txDAGMapping, err = readTxDAGMappingFromFile(output) + if err != nil { + log.Error("read TxDAG err", err) + } + + // write handler + bc.txDAGWriteCh = make(chan TxDAGOutputItem, 10000) + go func() { + writeHandle, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) + if err != nil { + log.Error("OpenFile when open the txDAG output file", "file", output) + return + } + defer writeHandle.Close() + for { + select { + case <-bc.quit: + return + case item := <-bc.txDAGWriteCh: + if err := writeTxDAGToFile(writeHandle, item); err != nil { + log.Error("encode TxDAG err in OutputHandler", "err", err) + continue + } + } + } + }() +} + +type TxDAGOutputItem struct { + blockNumber uint64 + txDAG types.TxDAG +} + +func writeTxDAGToFile(writeHandle *os.File, item TxDAGOutputItem) error { + var buf bytes.Buffer + buf.WriteString(strconv.FormatUint(item.blockNumber, 10)) + buf.WriteByte(',') + enc, err := types.EncodeTxDAG(item.txDAG) + if err != nil { + return err + } + buf.WriteString(hex.EncodeToString(enc)) + buf.WriteByte('\n') + _, err = writeHandle.Write(buf.Bytes()) + return err +} + +func readTxDAGMappingFromFile(output string) (map[uint64]types.TxDAG, error) { + file, err := os.Open(output) + if err != nil { + return nil, err + } + defer file.Close() + + mapping := make(map[uint64]types.TxDAG) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + tokens := strings.Split(scanner.Text(), ",") + if len(tokens) != 2 { + return nil, errors.New("txDAG output contain wrong size") + } + num, err := strconv.Atoi(tokens[0]) + if err != nil { + return nil, err + } + enc, err := hex.DecodeString(tokens[1]) + if err != nil { + return nil, err + } + txDAG, err := types.DecodeTxDAG(enc) + if err != nil { + return nil, err + } + mapping[uint64(num)] = txDAG + } + return mapping, nil +} diff --git a/core/blockchain_insert.go b/core/blockchain_insert.go index 82a480d0be..2667323c1e 100644 --- a/core/blockchain_insert.go +++ b/core/blockchain_insert.go @@ -60,7 +60,7 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn "blocks", st.processed, "txs", txs, "mgas", float64(st.usedGas) / 1000000, "elapsed", common.PrettyDuration(elapsed), "mgasps", float64(st.usedGas) * 1000 / float64(elapsed), } - mgaspsGauge.Update(int64(st.usedGas)*1000/int64(elapsed)) + mgaspsGauge.Update(int64(st.usedGas) * 1000 / int64(elapsed)) if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute { context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 9f799181d9..b4d5a1381b 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -22,10 +22,13 @@ import ( "math/big" "math/rand" "os" + "path/filepath" "sync" "testing" "time" + "github.com/stretchr/testify/require" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" @@ -4717,3 +4720,32 @@ func TestEIP3651(t *testing.T) { t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) } } + +func TestTxDAGFile_ReadWrite(t *testing.T) { + path := filepath.Join(os.TempDir(), "test.csv") + except := map[uint64]types.TxDAG{ + 0: types.NewEmptyTxDAG(), + 1: makeEmptyPlainTxDAG(1), + 2: makeEmptyPlainTxDAG(2), + } + writeFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) + require.NoError(t, err) + for num, dag := range except { + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: dag})) + } + writeFile.Close() + + actual, err := readTxDAGMappingFromFile(path) + require.NoError(t, err) + for num, dag := range except { + require.Equal(t, dag, actual[num]) + } +} + +func makeEmptyPlainTxDAG(cnt int) *types.PlainTxDAG { + dag := types.NewPlainTxDAG(cnt) + for i := range dag.TxDeps { + dag.TxDeps[i].TxIndexes = make([]uint64, 0) + } + return dag +} diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 9fe363f151..7cbfe4cad9 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -3,6 +3,11 @@ package core import ( "errors" "fmt" + "runtime" + "sync" + "sync/atomic" + "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -11,10 +16,8 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" - "runtime" - "sync" - "sync/atomic" ) const ( @@ -189,6 +192,11 @@ func (p *ParallelStateProcessor) doStaticDispatchV2(txReqs []*ParallelTxRequest, return } + if metrics.EnabledExpensive { + defer func(start time.Time) { + txDAGDispatchTimer.Update(time.Since(start)) + }(time.Now()) + } // resolve isolate execution paths from TxDAG, it indicates the tx dispatch paths := types.MergeTxDAGExecutionPaths(txDAG) log.Info("doStaticDispatchV2 merge parallel execution paths", "slots", len(p.slotState), "paths", len(paths)) @@ -735,14 +743,20 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat txDAG types.TxDAG err error ) - if len(block.TxDAG()) != 0 { - txDAG, err = types.DecodeTxDAG(block.TxDAG()) - if err != nil { - return nil, nil, 0, err + if p.bc.enableTxDAG { + if len(block.TxDAG()) != 0 { + txDAG, err = types.DecodeTxDAG(block.TxDAG()) + if err != nil { + return nil, nil, 0, err + } + } + // load cache txDAG from file + if txDAG == nil && len(p.bc.txDAGMapping) > 0 { + txDAG = p.bc.txDAGMapping[block.NumberU64()] } } // TODO(galaio): need hardfork - if p.bc.chainConfig.Optimism != nil && len(block.Header().Extra) > 0 { + if p.bc.enableTxDAG && p.bc.chainConfig.Optimism != nil && len(block.Header().Extra) > 0 { txDAG, err = types.DecodeTxDAG(block.Header().Extra) if err != nil { return nil, nil, 0, err diff --git a/core/state/statedb.go b/core/state/statedb.go index 49d168175d..9f63253865 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -271,6 +271,7 @@ type StateDB struct { TrieDBCommits time.Duration TrieCommits time.Duration CodeCommits time.Duration + TxDAGGenerate time.Duration AccountUpdated int StorageUpdated int @@ -2419,6 +2420,11 @@ func (s *StateDB) FinaliseRWSet() error { if s.mvStates == nil || s.rwSet == nil { return nil } + if metrics.EnabledExpensive { + defer func(start time.Time) { + s.TxDAGGenerate += time.Since(start) + }(time.Now()) + } ver := types.StateVersion{ TxIndex: s.txIndex, } @@ -2486,6 +2492,11 @@ func (s *StateDB) MVStates2TxDAG() (types.TxDAG, map[int]*types.ExeStat) { if s.mvStates == nil { return types.NewEmptyTxDAG(), nil } + if metrics.EnabledExpensive { + defer func(start time.Time) { + s.TxDAGGenerate += time.Since(start) + }(time.Now()) + } return s.mvStates.ResolveTxDAG(), s.mvStates.Stats() } diff --git a/core/state_processor.go b/core/state_processor.go index 85441dbc6b..632e3df5ff 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -29,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" ) @@ -91,7 +90,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } statedb.MarkFullProcessed() - statedb.ResetMVStates(len(block.Transactions())) + if p.bc.enableTxDAG { + statedb.ResetMVStates(len(block.Transactions())) + } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { statedb.BeginTxStat(i) @@ -121,13 +122,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) - // TODO(galaio): append dag into block body, TxDAGPerformance will print metrics when profile is enabled - // compare input TxDAG when it enable in consensus - dag, exrStats := statedb.MVStates2TxDAG() - types.EvaluateTxDAGPerformance(dag, exrStats) - //fmt.Print(types.EvaluateTxDAGPerformance(dag, exrStats)) - log.Info("Process result", "block", block.NumberU64(), "txDAG", dag) - + if p.bc.enableTxDAG { + // TODO(galaio): append dag into block body, TxDAGPerformance will print metrics when profile is enabled + // compare input TxDAG when it enable in consensus + dag, exrStats := statedb.MVStates2TxDAG() + fmt.Print(types.EvaluateTxDAGPerformance(dag, exrStats)) + //log.Info("Process result", "block", block.NumberU64(), "txDAG", dag) + // try write txDAG into file + if p.bc.txDAGWriteCh != nil && dag != nil { + p.bc.txDAGWriteCh <- TxDAGOutputItem{ + blockNumber: block.NumberU64(), + txDAG: dag, + } + } + } return receipts, allLogs, *usedGas, nil } diff --git a/core/types/dag.go b/core/types/dag.go index b407c2bc77..dbd7cf9ea8 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -4,11 +4,12 @@ import ( "bytes" "errors" "fmt" + "strings" + "time" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "golang.org/x/exp/slices" - "strings" - "time" ) // TxDAGType Used to extend TxDAG and customize a new DAG structure diff --git a/core/types/dag_test.go b/core/types/dag_test.go index bf12324246..bfda2de6e3 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -1,10 +1,11 @@ package types import ( - "github.com/cometbft/cometbft/libs/rand" "testing" "time" + "github.com/cometbft/cometbft/libs/rand" + "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" "github.com/stretchr/testify/require" diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 4db7586727..2584807467 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -4,12 +4,13 @@ import ( "encoding/hex" "errors" "fmt" + "strings" + "sync" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/holiman/uint256" "golang.org/x/exp/slices" - "strings" - "sync" ) const ( diff --git a/eth/backend.go b/eth/backend.go index a8ed4fe836..3fb51c4afb 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -272,6 +272,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } + if config.EnableParallelTxDAG { + eth.blockchain.EnableTxDAGGeneration(config.ParallelTxDAGFile) + } if chainConfig := eth.blockchain.Config(); chainConfig.Optimism != nil { // config.Genesis.Config.ChainID cannot be used because it's based on CLI flags only, thus default to mainnet L1 config.NetworkId = chainConfig.ChainID.Uint64() // optimism defaults eth network ID to chain ID eth.networkID = config.NetworkId diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 77080f5870..3f9624dc7c 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -221,6 +221,8 @@ type Config struct { ParallelTxMode bool // Whether to execute transaction in parallel mode when do full sync ParallelTxNum int // Number of slot for transaction execution EnableOpcodeOptimizing bool + EnableParallelTxDAG bool + ParallelTxDAGFile string } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/miner/miner.go b/miner/miner.go index b65b226238..8dbe58c45e 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -62,7 +62,8 @@ var ( snapshotAccountReadTimer = metrics.NewRegisteredTimer("miner/snapshot/account/reads", nil) snapshotStorageReadTimer = metrics.NewRegisteredTimer("miner/snapshot/storage/reads", nil) - waitPayloadTimer = metrics.NewRegisteredTimer("miner/wait/payload", nil) + waitPayloadTimer = metrics.NewRegisteredTimer("miner/wait/payload", nil) + txDAGGenerateTimer = metrics.NewRegisteredTimer("miner/txdag/gen", nil) isBuildBlockInterruptCounter = metrics.NewRegisteredCounter("miner/build/interrupt", nil) ) diff --git a/miner/worker.go b/miner/worker.go index 572a50c2c0..4f96679d3f 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1179,6 +1179,7 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err w.mu.RUnlock() start := time.Now() +<<<<<<< HEAD // Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees filter := txpool.PendingFilter{ @@ -1196,6 +1197,10 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err filter.OnlyPlainTxs, filter.OnlyBlobTxs = false, true pendingBlobTxs := w.eth.TxPool().Pending(filter) + if w.chain.TxDAGEnabled() { + env.state.ResetMVStates(0) + } + packFromTxpoolTimer.UpdateSince(start) log.Debug("packFromTxpoolTimer", "duration", common.PrettyDuration(time.Since(start)), "hash", env.header.Hash()) @@ -1203,8 +1208,6 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err localPlainTxs, remotePlainTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingPlainTxs localBlobTxs, remoteBlobTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingBlobTxs - env.state.ResetMVStates(0) - for _, account := range w.eth.TxPool().Locals() { if txs := remotePlainTxs[account]; len(txs) > 0 { delete(remotePlainTxs, account) @@ -1337,7 +1340,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { } // Because the TxDAG appends after sidecar, so we only enable after cancun - if w.chainConfig.IsCancun(block.Number(), block.Time()) && w.chainConfig.Optimism == nil { + if w.chain.TxDAGEnabled() && w.chainConfig.IsCancun(block.Number(), block.Time()) && w.chainConfig.Optimism == nil { txDAG, _ := work.state.MVStates2TxDAG() rawTxDAG, err := types.EncodeTxDAG(txDAG) if err != nil { @@ -1347,7 +1350,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { } // TODO(galaio): need hardfork - if w.chainConfig.Optimism != nil { + if w.chain.TxDAGEnabled() && w.chainConfig.Optimism != nil { txDAG, _ := work.state.MVStates2TxDAG() rawTxDAG, err := types.EncodeTxDAG(txDAG) if err != nil { @@ -1355,6 +1358,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { } block.Header().Extra = rawTxDAG } + assembleBlockTimer.UpdateSince(start) log.Debug("assembleBlockTimer", "duration", common.PrettyDuration(time.Since(start)), "parentHash", genParams.parentHash) @@ -1366,6 +1370,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { storageUpdateTimer.Update(work.state.StorageUpdates) // Storage updates are complete(in FinalizeAndAssemble) accountHashTimer.Update(work.state.AccountHashes) // Account hashes are complete(in FinalizeAndAssemble) storageHashTimer.Update(work.state.StorageHashes) // Storage hashes are complete(in FinalizeAndAssemble) + txDAGGenerateTimer.Update(work.state.TxDAGGenerate) innerExecutionTimer.Update(core.DebugInnerExecutionDuration) @@ -1462,7 +1467,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti } // Because the TxDAG appends after sidecar, so we only enable after cancun - if w.chainConfig.IsCancun(env.header.Number, env.header.Time) && w.chainConfig.Optimism == nil { + if w.chain.TxDAGEnabled() && w.chainConfig.IsCancun(env.header.Number, env.header.Time) && w.chainConfig.Optimism == nil { for i := len(env.txs); i < len(block.Transactions()); i++ { env.state.RecordSystemTxRWSet(i) } @@ -1475,7 +1480,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti } // TODO(galaio): need hardfork - if w.chainConfig.Optimism != nil { + if w.chain.TxDAGEnabled() && w.chainConfig.Optimism != nil { txDAG, _ := env.state.MVStates2TxDAG() rawTxDAG, err := types.EncodeTxDAG(txDAG) if err != nil { From ada5f6212a62af0f54d7c5c9d060e17ff5f4b43c Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:39:34 +0800 Subject: [PATCH 11/72] FIX: redundancy execution and incorrect merge of dirty object (#12) Fix 3 issues: - re-execution happens only to new version of baseDB to remove redundancy execution. And remove the retry with same baseIndex that is conflicted. - incorrect merge dirty objects in addrStateChangeInSlot, which cause incorrect data.root copied with obseleted stateDB, this fix handle the created, stateChanged and deleted object separately. - stateObject.GetCommitedState check mainDB of the object delete. Co-authored-by: Sunny --- core/parallel_state_processor.go | 110 +++++++++++++++++++++---------- core/state/parallel_statedb.go | 11 +++- core/state/state_object.go | 48 +++++++++----- core/state/statedb.go | 83 +++++++++++++---------- 4 files changed, 162 insertions(+), 90 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 7cbfe4cad9..05a1f9bdff 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -105,9 +105,10 @@ type ParallelTxRequest struct { usedGas *uint64 curTxChan chan int systemAddrRedo bool - runnable int32 // 0: not runnable, 1: runnable + runnable int32 // 0: not runnable, executing, 1: runnable, on hold, can be scheduled executedNum atomic.Int32 retryNum int32 + conflictIndex atomic.Int32 } // to create and start the execution slot goroutines @@ -302,9 +303,16 @@ func (p *ParallelStateProcessor) switchSlot(slotIndex int) { } func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxRequest) *ParallelTxResult { + mIndex := p.mergedTxIndex.Load() + conflictIndex := txReq.conflictIndex.Load() + if mIndex <= conflictIndex { + // The conflicted TX has not been finished executing, skip execution. + // the transaction failed at check(nonce or balance), actually it has not been executed yet. + atomic.CompareAndSwapInt32(&txReq.runnable, 0, 1) + return nil + } execNum := txReq.executedNum.Add(1) - slotDB := state.NewSlotDB(txReq.baseStateDB, txReq.txIndex, int(p.mergedTxIndex.Load()), p.unconfirmedDBs) - + slotDB := state.NewSlotDB(txReq.baseStateDB, txReq.txIndex, int(mIndex), p.unconfirmedDBs) blockContext := NewEVMBlockContext(txReq.block.Header(), p.bc, nil, p.config, slotDB) // can share blockContext within a block for efficiency txContext := NewEVMTxContext(txReq.msg) vmenv := vm.NewEVM(blockContext, txContext, slotDB, p.config, txReq.vmConfig) @@ -341,6 +349,21 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR p.unconfirmedDBs.Store(txReq.txIndex, slotDB) } else { // the transaction failed at check(nonce or balance), actually it has not been executed yet. + // the error here can be both expected and unexpected + // expected - the execution is correct and the error is normal result + // unexpected - the execution is incorrectly accessed the state because of parallelization. + // In both case, rerun with next version of stateDB, it is a waste and buggy to rerun with same + // version of stateDB. + // Therefore, treat it as conflict and rerun, leave the result to conflict check. + // Load conflict as it maybe updated by conflict checker or other execution slots. + // use old mIndex so that we can try the new one that is updated by other thread of merging + // during execution. + conflictIndex = txReq.conflictIndex.Load() + if conflictIndex < mIndex { + if txReq.conflictIndex.CompareAndSwap(conflictIndex, mIndex) { + log.Debug("Update conflictIndex in execution because of error, new conflictIndex: %d", conflictIndex) + } + } atomic.CompareAndSwapInt32(&txReq.runnable, 0, 1) // the error could be caused by unconfirmed balance reference, // the balance could insufficient to pay its gas limit, which cause it preCheck.buyGas() failed @@ -397,6 +420,14 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo valid := p.toConfirmTxIndexResult(targetResult, isStage2) if !valid { staticSlotIndex := targetResult.txReq.staticSlotIndex // it is better to run the TxReq in its static dispatch slot + conflictBase := targetResult.slotDB.BaseTxIndex() + conflictIndex := targetResult.txReq.conflictIndex.Load() + if conflictIndex < int32(conflictBase) { + if targetResult.txReq.conflictIndex.CompareAndSwap(conflictIndex, int32(conflictBase)) { + // updated successfully + log.Debug("Update conflict index", "conflictIndex", conflictIndex, "conflictBase", conflictBase) + } + } if isStage2 { atomic.CompareAndSwapInt32(&targetResult.txReq.runnable, 0, 1) // needs redo p.debugConflictRedoNum++ @@ -406,30 +437,32 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo } if len(p.pendingConfirmResults[targetTxIndex]) == 0 { // this is the last result to check, and it is not valid - blockTxCount := targetResult.txReq.block.Transactions().Len() // This means that the tx has been executed more than blockTxCount times, so it exits with the error. // TODO-dav: p.mergedTxIndex+2 may be more reasonable? - this is buggy for expected exit - if targetResult.txReq.txIndex == int(p.mergedTxIndex.Load())+1 { - // txReq is the next to merge - if atomic.LoadInt32(&targetResult.txReq.retryNum) <= int32(blockTxCount)+3000 { - atomic.AddInt32(&targetResult.txReq.retryNum, 1) - // conflict retry - } else { - // retry many times and still conflict, either the tx is expected to be wrong, or something wrong. - if targetResult.err != nil { - if true { // TODO: delete the printf - fmt.Printf("!!!!!!!!!!! Parallel execution exited with error!!!!!, txIndex:%d, err: %v\n", targetResult.txReq.txIndex, targetResult.err) - } - return targetResult + if targetResult.txReq.txIndex == int(p.mergedTxIndex.Load())+1 && + targetResult.slotDB.BaseTxIndex() == int(p.mergedTxIndex.Load()) { + /* + // txReq is the next to merge + if atomic.LoadInt32(&targetResult.txReq.retryNum) <= int32(blockTxCount)+3000 { + atomic.AddInt32(&targetResult.txReq.retryNum, 1) + // conflict retry } else { - // abnormal exit with conflict error, need check the parallel algorithm - targetResult.err = ErrParallelUnexpectedConflict - if true { - fmt.Printf("!!!!!!!!!!! Parallel execution exited unexpected conflict!!!!!, txIndex:%d\n", targetResult.txReq.txIndex) - } - return targetResult + */ + // retry many times and still conflict, either the tx is expected to be wrong, or something wrong. + if targetResult.err != nil { + if false { // TODO: delete the printf + fmt.Printf("!!!!!!!!!!! Parallel execution exited with error!!!!!, txIndex:%d, err: %v\n", targetResult.txReq.txIndex, targetResult.err) } + return targetResult + } else { + // abnormal exit with conflict error, need check the parallel algorithm + targetResult.err = ErrParallelUnexpectedConflict + if false { + fmt.Printf("!!!!!!!!!!! Parallel execution exited unexpected conflict!!!!!, txIndex:%d\n", targetResult.txReq.txIndex) + } + return targetResult } + //} } atomic.CompareAndSwapInt32(&targetResult.txReq.runnable, 0, 1) // needs redo p.debugConflictRedoNum++ @@ -508,11 +541,13 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { } if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { // not swapped: txReq.runnable == 0 - //fmt.Printf("Dav -- runInLoop, - not runnable - TxREQ: %d\n", txReq.txIndex) continue } - // fmt.Printf("Dav -- runInLoop, - executeInSlot - TxREQ: %d\n", txReq.txIndex) - p.txResultChan <- p.executeInSlot(slotIndex, txReq) + res := p.executeInSlot(slotIndex, txReq) + if res == nil { + continue + } + p.txResultChan <- res // fmt.Printf("Dav -- runInLoop, - loopbody tail - TxREQ: %d\n", txReq.txIndex) } // switched to the other slot. @@ -531,7 +566,6 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { if atomic.LoadInt32(&curSlot.activatedType) != slotType { interrupted = true // fmt.Printf("Dav -- stealLoop, - activatedType - TxREQ: %d\n", stealTxReq.txIndex) - break } @@ -542,7 +576,11 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { continue } // fmt.Printf("Dav -- stealLoop, - executeInSlot - TxREQ: %d\n", stealTxReq.txIndex) - p.txResultChan <- p.executeInSlot(slotIndex, stealTxReq) + res := p.executeInSlot(slotIndex, stealTxReq) + if res == nil { + continue + } + p.txResultChan <- res // fmt.Printf("Dav -- stealLoop, - loopbody tail - TxREQ: %d\n", stealTxReq.txIndex) } } @@ -625,15 +663,20 @@ func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *Ga var root []byte header := result.txReq.block.Header() - if p.config.IsByzantium(header.Number) { - result.slotDB.FinaliseForParallel(true, statedb) - } else { - root = result.slotDB.IntermediateRootForSlotDB(p.config.IsEIP158(header.Number), statedb).Bytes() - } - result.receipt.PostState = root + + isByzantium := p.config.IsByzantium(header.Number) + isEIP158 := p.config.IsEIP158(header.Number) + result.slotDB.FinaliseForParallel(isByzantium || isEIP158, statedb) + // merge slotDB into mainDB statedb.MergeSlotDB(result.slotDB, result.receipt, resultTxIndex) + // Do IntermediateRoot after mergeSlotDB. + if !isByzantium { + root = statedb.IntermediateRoot(isEIP158).Bytes() + } + result.receipt.PostState = root + if resultTxIndex != int(p.mergedTxIndex.Load())+1 { log.Error("ProcessParallel tx result out of order", "resultTxIndex", resultTxIndex, "p.mergedTxIndex", p.mergedTxIndex.Load()) @@ -729,6 +772,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat retryNum: 0, } txReq.executedNum.Store(0) + txReq.conflictIndex.Store(-2) p.allTxReqs = append(p.allTxReqs, txReq) } // set up stage2 enter criteria diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 3d4ac2d264..73bac5abbc 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -94,11 +94,13 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash } } valMain := mainDB.GetState(addr, key) + if !bytes.Equal(val.Bytes(), valMain.Bytes()) { log.Debug("hasKvConflict is invalid", "addr", addr, "key", key, "valSlot", val, "valMain", valMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return true // return false, Range will be terminated. } return false @@ -656,8 +658,6 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common // it could be suicided within this SlotDB? // it should be able to get state from suicided address within a Tx: // e.g. within a transaction: call addr:suicide -> get state: should be ok - // return common.Hash{} - log.Info("ParallelStateDB GetState suicided", "addr", addr, "hash", hash) if dirtyObj == nil { log.Error("ParallelStateDB GetState access untouched object after create, may check create2") @@ -1285,7 +1285,6 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } } } - /* can not use mainDB.GetNonce() because we do not want to record the stateObject */ var nonceMain uint64 = 0 mainObj := mainDB.getStateObjectNoUpdate(addr) @@ -1296,6 +1295,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { log.Debug("IsSlotDBReadsValid nonce read is invalid", "addr", addr, "nonceSlot", nonceSlot, "nonceMain", nonceMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false } } @@ -1395,6 +1395,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } } } + if isStage2 { // stage2 skip check code, or state, since they are likely unchanged. return true } @@ -1596,7 +1597,11 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S } } + if obj.created { + s.parallel.createdObjectRecord[addr] = struct{}{} + } obj.created = false + s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} diff --git a/core/state/state_object.go b/core/state/state_object.go index 059eaf3baf..8fd477397d 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -349,23 +349,33 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { return value } - // Add-Dav: - // Need to confirm the object is not destructed in unconfirmed db and resurrected in this tx. - // otherwise there is an issue for cases like: - // B0: TX0 --> createAccount @addr1 -- merged into DB - // B1: Tx1 and Tx2 - // Tx1 account@addr1, setState(key0), setState(key1) selfDestruct -- unconfirmed - // Tx2 recreate account@addr2, setState(key0) -- executing - // TX2 GetState(addr2, key1) --- - // key1 is never set after recurrsect, and should not return state in trie as it destructed in unconfirmed - // TODO - dav: do we need try storages from unconfirmedDB? - currently not because conflict detection need it for get from mainDB. - obj, exist := s.dbItf.GetStateObjectFromUnconfirmedDB(s.address) - if exist { - if obj.deleted || obj.selfDestructed { - return common.Hash{} + if s.db.isParallel && s.db.parallel.isSlotDB { + // Add-Dav: + // Need to confirm the object is not destructed in unconfirmed db and resurrected in this tx. + // otherwise there is an issue for cases like: + // B0: TX0 --> createAccount @addr1 -- merged into DB + // B1: Tx1 and Tx2 + // Tx1 account@addr1, setState(key0), setState(key1) selfDestruct -- unconfirmed + // Tx2 recreate account@addr2, setState(key0) -- executing + // TX2 GetState(addr2, key1) --- + // key1 is never set after recurrsect, and should not return state in trie as it destructed in unconfirmed + // TODO - dav: do we need try storages from unconfirmedDB? - currently not because conflict detection need it for get from mainDB. + obj, exist := s.dbItf.GetStateObjectFromUnconfirmedDB(s.address) + if exist { + if obj.deleted || obj.selfDestructed { + return common.Hash{} + } } - } + // also test whether the object is in mainDB and deleted. + pdb := s.db.parallel.baseStateDB + obj, exist = pdb.getStateObjectFromStateObjects(s.address) + if exist { + if obj.deleted || obj.selfDestructed { + return common.Hash{} + } + } + } // If the object was destructed in *this* block (and potentially resurrected), // the storage has been cleared out, and we should *not* consult the previous // database about any storage values. The only possible alternatives are: @@ -526,7 +536,6 @@ func (s *stateObject) updateTrie() (Trie, error) { maindb.accountStorageParallelLock.Lock() defer maindb.accountStorageParallelLock.Unlock() } - // Make sure all dirty slots are finalized into the pending storage area s.finalise(false) @@ -549,6 +558,7 @@ func (s *stateObject) updateTrie() (Trie, error) { maindb.setError(err) return nil, err } + // Insert all the pending storage updates into the trie usedStorage := make([][]byte, 0, s.pendingStorage.Length()) dirtyStorage := make(map[common.Hash][]byte) @@ -594,6 +604,7 @@ func (s *stateObject) updateTrie() (Trie, error) { go func() { defer wg.Done() maindb.StorageMux.Lock() + defer maindb.StorageMux.Unlock() // The snapshot storage map for the object storage = maindb.storages[s.addrHash] if storage == nil { @@ -606,7 +617,6 @@ func (s *stateObject) updateTrie() (Trie, error) { origin = make(map[common.Hash][]byte) maindb.storagesOrigin[s.address] = origin } - maindb.StorageMux.Unlock() for key, value := range dirtyStorage { khash := crypto.HashData(hasher, key[:]) @@ -636,6 +646,7 @@ func (s *stateObject) updateTrie() (Trie, error) { if maindb.prefetcher != nil { maindb.prefetcher.used(s.addrHash, s.data.Root, usedStorage) } + s.pendingStorage = newStorage(s.isParallel) // reset pending map return tr, nil /* @@ -714,8 +725,9 @@ func (s *stateObject) updateRoot() { // is occurred or there is not change in the trie. // TODO: The trieParallelLock seems heavy, can we remove it? s.db.trieParallelLock.Lock() + defer s.db.trieParallelLock.Unlock() + tr, err := s.updateTrie() - s.db.trieParallelLock.Unlock() if err != nil || tr == nil { return } diff --git a/core/state/statedb.go b/core/state/statedb.go index 9f63253865..f6552b6926 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -152,6 +152,8 @@ type ParallelState struct { accountsOriginDeleteRecord []common.Address storagesOriginDeleteRecord []common.Address + createdObjectRecord map[common.Address]struct{} + // Transaction will pay gas fee to system address. // Parallel execution will clear system address's balance at first, in order to maintain transaction's // gas fee value. Normal transaction will access system address twice, otherwise it means the transaction @@ -1354,6 +1356,11 @@ func (s *StateDB) PutSyncPool() { } addressToStructPool.Put(s.snapDestructs) + for key := range s.parallel.createdObjectRecord { + delete(s.parallel.createdObjectRecord, key) + } + addressToStructPool.Put(s.parallel.createdObjectRecord) + for key := range s.snapAccounts { delete(s.snapAccounts, key) } @@ -1406,6 +1413,7 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { storagesDeleteRecord: make([]common.Hash, 10), accountsOriginDeleteRecord: make([]common.Address, 10), storagesOriginDeleteRecord: make([]common.Address, 10), + createdObjectRecord: addressToStructPool.Get().(map[common.Address]struct{}), } state := &ParallelStateDB{ StateDB: StateDB{ @@ -2625,17 +2633,9 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip continue } mainObj, exist := s.loadStateObj(addr) - if !exist || mainObj.deleted { - - // fixme: it is also state change - // addr not exist on main DB, do ownership transfer - // dirtyObj.db = s - // dirtyObj.finalise(true) // true: prefetch on dispatcher + // addr not exist on main DB, the object is created in the merging tx. mainObj = dirtyObj.deepCopy(s) - /* if addr == WBNBAddress && slotDb.wbnbMakeUpBalance != nil { - mainObj.setBalance(slotDb.wbnbMakeUpBalance) - }*/ if !dirtyObj.deleted { mainObj.finalise(true) } @@ -2665,40 +2665,51 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip // can not do copy or ownership transfer directly, since dirtyObj could have outdated // data(maybe updated within the conflict window) var newMainObj = mainObj // we don't need to copy the object since the storages are thread safe - if _, ok := slotDb.parallel.addrStateChangesInSlot[addr]; ok { - // there are 3 kinds of state change: + if createdOrChanged, ok := slotDb.parallel.addrStateChangesInSlot[addr]; ok { + // there are 4 kinds of state change: // 1.Suicide // 2.Empty Delete // 3.createObject // a: AddBalance,SetState to a non-exist or deleted(suicide, empty delete) address. // b: CreateAccount: like DAO the fork, regenerate an account carry its balance without KV - // For these state change, do ownership transfer for efficiency: - // dirtyObj.db = s - // newMainObj = dirtyObj - - // The deepCopy() here introduces issue that the pendingStorage may not empty until block validation. - // so the pendingStorage filled by the execution of previous txs in same block may get overwritten by - // deepCopy here, which causes issue in root calculation. - newMainObj = dirtyObj.deepCopy(s) - - mainObj.pendingStorage.Range(func(key, value interface{}) bool { - if _, found := newMainObj.pendingStorage.GetValue(key.(common.Hash)); !found { - newMainObj.pendingStorage.StoreValue(key.(common.Hash), value.(common.Hash)) - } - return true - }) - - // TODO - dav: check - the dirtyStorage should be always empty for mainObj as it should be moved to - // pendingStorage by Finalise in execution phase. - mainObj.dirtyStorage.Range(func(key, value interface{}) bool { - if _, found := newMainObj.dirtyStorage.GetValue(key.(common.Hash)); !found { - newMainObj.dirtyStorage.StoreValue(key.(common.Hash), value.(common.Hash)) + // 4. setState. + // For these state change + if createdOrChanged { + // Need to differentiate the case of createObject and setState, since the mainDB at this moment contains + // the latest update of the object, which cause the object.data.root newer then the dirtyObject. so + // the deepCopy() here can not be used for setState as it introduces issue that the pendingStorage + // may not empty until block validation. so the pendingStorage filled by the execution of previous txs + // in same block may get overwritten by deepCopy here, which causes issue in root calculation. + if _, created := s.parallel.createdObjectRecord[addr]; created { + newMainObj = dirtyObj.deepCopy(s) + } else { + // Merge the dirtyObject with mainObject + if _, balanced := slotDb.parallel.balanceChangesInSlot[addr]; balanced { + newMainObj.dirtyBalance = dirtyObj.dirtyBalance + newMainObj.data.Balance = dirtyObj.data.Balance + } + if _, coded := slotDb.parallel.codeChangesInSlot[addr]; coded { + newMainObj.code = dirtyObj.code + newMainObj.dirtyCodeHash = dirtyObj.dirtyCodeHash + newMainObj.data.CodeHash = dirtyObj.data.CodeHash + newMainObj.dirtyCode = true + } + if keys, stated := slotDb.parallel.kvChangesInSlot[addr]; stated { + newMainObj.MergeSlotObject(s.db, dirtyObj, keys) + } + if _, nonced := slotDb.parallel.nonceChangesInSlot[addr]; nonced { + // dirtyObj.Nonce() should not be less than newMainObj + newMainObj.data.Nonce = dirtyObj.data.Nonce + newMainObj.dirtyNonce = dirtyObj.dirtyNonce + } + newMainObj.deleted = dirtyObj.deleted } - return true - }) + } else { + // The object is deleted in the TX. + newMainObj = dirtyObj.deepCopy(s) + } - // should not delete, would cause unconfirmed DB incorrect. - // delete(slotDb.parallel.dirtiedStateObjectsInSlot, addr) // transfer ownership, fixme: shared read? + // All cases with addrStateChange set to true/false can be deleted. so handle it here. if dirtyObj.deleted { // remove the addr from snapAccounts&snapStorage only when object is deleted. // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for From 6b51dcc6d67ad2e879aa8138b713aa7fd27f2d72 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:53:24 +0800 Subject: [PATCH 12/72] pevm: support delay gas fee calculation & Uts; (#11) * pevm: support delay gas fee calculation; txdag: check gas fee receiver; tests: support PEVM+TxDAG UTs; * txdag: skip some cost time operation; tests: fix some broken UTs; --------- Co-authored-by: galaio --- cmd/evm/blockrunner.go | 2 +- core/blockchain.go | 4 +-- core/parallel_state_processor.go | 34 +++++++++++++++--- core/state/statedb.go | 39 ++++++++++++++------- core/state/statedb_test.go | 2 +- core/state_processor.go | 12 ++++--- core/state_transition.go | 45 +++++++++++++++++++++--- core/types/dag.go | 39 +++++---------------- core/types/dag_test.go | 6 ++-- core/types/mvstates.go | 11 ++++-- eth/backend.go | 2 +- miner/worker.go | 9 +++-- tests/block_test.go | 60 +++++++++++++++++++++++++++++--- tests/block_test_util.go | 8 +++-- 14 files changed, 193 insertions(+), 80 deletions(-) diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index c5d836e0ea..196e86d591 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -92,7 +92,7 @@ func blockTestCmd(ctx *cli.Context) error { fmt.Println(string(state.Dump(nil))) } } - }); err != nil { + }, "", true); err != nil { return fmt.Errorf("test %v: %w", name, err) } } diff --git a/core/blockchain.go b/core/blockchain.go index f9af61b269..bf18593300 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2706,7 +2706,7 @@ func (bc *BlockChain) TxDAGEnabled() bool { return bc.enableTxDAG } -func (bc *BlockChain) EnableTxDAGGeneration(output string) { +func (bc *BlockChain) SetupTxDAGGeneration(output string) { bc.enableTxDAG = true if len(output) == 0 { return @@ -2715,7 +2715,7 @@ func (bc *BlockChain) EnableTxDAGGeneration(output string) { var err error bc.txDAGMapping, err = readTxDAGMappingFromFile(output) if err != nil { - log.Error("read TxDAG err", err) + log.Error("read TxDAG err", "err", err) } // write handler diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 05a1f9bdff..93c56f5c31 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -50,6 +50,7 @@ type ParallelStateProcessor struct { targetStage2Count int // when executed txNUM reach it, enter stage2 RT confirm nextStage2TxIndex int disableStealTx bool + delayGasFee bool // it is provided by TxDAG } func NewParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine, parallelNum int) *ParallelStateProcessor { @@ -187,6 +188,8 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { // 3. TODO(galaio) it need to schedule the slow dep tx path properly; // 4. TODO(galaio) it is unfriendly for cross slot deps, maybe we can delay dispatch when tx cross in slots, it may increase PEVM parallelism; func (p *ParallelStateProcessor) doStaticDispatchV2(txReqs []*ParallelTxRequest, txDAG types.TxDAG) { + p.disableStealTx = false + p.delayGasFee = false // only support PlainTxDAG dispatch now. if txDAG == nil || txDAG.Type() != types.PlainTxDAGType { p.doStaticDispatch(txReqs) @@ -213,6 +216,7 @@ func (p *ParallelStateProcessor) doStaticDispatchV2(txReqs []*ParallelTxRequest, // it's unnecessary to enable slot steal mechanism, opt the steal mechanism later; p.disableStealTx = true + p.delayGasFee = true } // Benefits of StaticDispatch: @@ -331,7 +335,7 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR slotDB.SetTxContext(txReq.tx.Hash(), txReq.txIndex) - evm, result, err := applyTransactionStageExecution(txReq.msg, gpSlot, slotDB, vmenv) + evm, result, err := applyTransactionStageExecution(txReq.msg, gpSlot, slotDB, vmenv, p.delayGasFee) txResult := ParallelTxResult{ executedIndex: execNum, slotIndex: slotIndex, @@ -660,7 +664,19 @@ func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *Ga } resultTxIndex := result.txReq.txIndex - + delayGasFee := result.result.delayFees + // add delayed gas fee + if delayGasFee != nil { + if delayGasFee.TipFee != nil { + result.slotDB.AddBalance(delayGasFee.Coinbase, delayGasFee.TipFee) + } + if delayGasFee.BaseFee != nil { + result.slotDB.AddBalance(params.OptimismBaseFeeRecipient, delayGasFee.BaseFee) + } + if delayGasFee.L1Fee != nil { + result.slotDB.AddBalance(params.OptimismL1FeeRecipient, delayGasFee.L1Fee) + } + } var root []byte header := result.txReq.block.Header() @@ -669,7 +685,7 @@ func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *Ga result.slotDB.FinaliseForParallel(isByzantium || isEIP158, statedb) // merge slotDB into mainDB - statedb.MergeSlotDB(result.slotDB, result.receipt, resultTxIndex) + statedb.MergeSlotDB(result.slotDB, result.receipt, resultTxIndex, result.result.delayFees) // Do IntermediateRoot after mergeSlotDB. if !isByzantium { @@ -887,13 +903,21 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat return receipts, allLogs, *usedGas, nil } -func applyTransactionStageExecution(msg *Message, gp *GasPool, statedb *state.ParallelStateDB, evm *vm.EVM) (*vm.EVM, *ExecutionResult, error) { +func applyTransactionStageExecution(msg *Message, gp *GasPool, statedb *state.ParallelStateDB, evm *vm.EVM, delayGasFee bool) (*vm.EVM, *ExecutionResult, error) { // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) // Apply the transaction to the current state (included in the env). - result, err := ApplyMessage(evm, msg, gp) + var ( + result *ExecutionResult + err error + ) + if delayGasFee { + result, err = ApplyMessageDelayGasFee(evm, msg, gp) + } else { + result, err = ApplyMessage(evm, msg, gp) + } if err != nil { return nil, nil, err diff --git a/core/state/statedb.go b/core/state/statedb.go index f6552b6926..ac2b2a9c15 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -60,6 +60,13 @@ type StateObjectSyncMap struct { sync.Map } +type DelayedGasFee struct { + BaseFee *uint256.Int + TipFee *uint256.Int + L1Fee *uint256.Int + Coinbase common.Address +} + func (s *StateObjectSyncMap) LoadStateObject(addr common.Address) (*stateObject, bool) { so, ok := s.Load(addr) if !ok { @@ -239,9 +246,10 @@ type StateDB struct { logSize uint // parallel EVM related - rwSet *types.RWSet - mvStates *types.MVStates - es *types.ExeStat + rwSet *types.RWSet + mvStates *types.MVStates + stat *types.ExeStat + rwRecordFlag bool // Preimages occurred seen by VM in the scope of block. preimages map[common.Hash][]byte @@ -2365,6 +2373,7 @@ func (s *StateDB) BeforeTxTransition() { s.rwSet = types.NewRWSet(types.StateVersion{ TxIndex: s.txIndex, }) + s.rwRecordFlag = true } func (s *StateDB) BeginTxStat(index int) { @@ -2374,7 +2383,9 @@ func (s *StateDB) BeginTxStat(index int) { if s.mvStates == nil { return } - s.es = types.NewExeStat(index).Begin() + if metrics.EnabledExpensive { + s.stat = types.NewExeStat(index).Begin() + } } func (s *StateDB) StopTxStat(usedGas uint64) { @@ -2385,8 +2396,8 @@ func (s *StateDB) StopTxStat(usedGas uint64) { return } // record stat first - if s.es != nil { - s.es.Done().WithGas(usedGas).WithRead(len(s.rwSet.ReadSet())) + if metrics.EnabledExpensive && s.stat != nil { + s.stat.Done().WithGas(usedGas).WithRead(len(s.rwSet.ReadSet())) } } @@ -2394,7 +2405,7 @@ func (s *StateDB) RecordRead(key types.RWKey, val interface{}) { if s.isParallel && s.parallel.isSlotDB { return } - if s.mvStates == nil || s.rwSet == nil { + if !s.rwRecordFlag { return } // TODO: read from MVStates, record with ver @@ -2407,7 +2418,7 @@ func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) { if s.isParallel && s.parallel.isSlotDB { return } - if s.mvStates == nil || s.rwSet == nil { + if !s.rwRecordFlag { return } s.rwSet.RecordWrite(key, val) @@ -2419,13 +2430,14 @@ func (s *StateDB) ResetMVStates(txCount int) { } s.mvStates = types.NewMVStates(txCount) s.rwSet = nil + s.rwRecordFlag = false } func (s *StateDB) FinaliseRWSet() error { if s.isParallel && s.parallel.isSlotDB { return nil } - if s.mvStates == nil || s.rwSet == nil { + if !s.rwRecordFlag { return nil } if metrics.EnabledExpensive { @@ -2463,7 +2475,8 @@ func (s *StateDB) FinaliseRWSet() error { } } - return s.mvStates.FulfillRWSet(s.rwSet, s.es) + s.rwRecordFlag = false + return s.mvStates.FulfillRWSet(s.rwSet, s.stat) } func (s *StateDB) queryStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) { @@ -2493,7 +2506,7 @@ func (s *StateDB) deleteStateObjectsDestruct(addr common.Address) { delete(s.stateObjectsDestruct, addr) } -func (s *StateDB) MVStates2TxDAG() (types.TxDAG, map[int]*types.ExeStat) { +func (s *StateDB) ResolveTxDAG(gasFeeReceivers []common.Address) (types.TxDAG, map[int]*types.ExeStat) { if s.isParallel && s.parallel.isSlotDB { return nil, nil } @@ -2506,7 +2519,7 @@ func (s *StateDB) MVStates2TxDAG() (types.TxDAG, map[int]*types.ExeStat) { }(time.Now()) } - return s.mvStates.ResolveTxDAG(), s.mvStates.Stats() + return s.mvStates.ResolveTxDAG(gasFeeReceivers), s.mvStates.Stats() } func (s *StateDB) MVStates() *types.MVStates { @@ -2595,7 +2608,7 @@ func (s *StateDB) AddrPrefetch(slotDb *ParallelStateDB) { // MergeSlotDB is for Parallel execution mode, when the transaction has been // finalized(dirty -> pending) on execution slot, the execution results should be // merged back to the main StateDB. -func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receipt, txIndex int) *StateDB { +func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receipt, txIndex int, fees *DelayedGasFee) *StateDB { s.SetTxContext(slotDb.thash, slotDb.txIndex) for s.nextRevisionId < slotDb.nextRevisionId { diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 0ffeca0e22..6660482d6c 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1539,7 +1539,7 @@ func TestMergeSlotDB(t *testing.T) { newSlotDb.SelfDestruct(addr) newSlotDb.Finalise(true) - changeList := oldSlotDb.MergeSlotDB(newSlotDb, &types.Receipt{}, 0) + changeList := oldSlotDb.MergeSlotDB(newSlotDb, &types.Receipt{}, 0, nil) if ok := changeList.getDeletedStateObject(addr); ok == nil || !ok.selfDestructed { t.Fatalf("address should exist in StateObjectSuicided") diff --git a/core/state_processor.go b/core/state_processor.go index 632e3df5ff..83b473e253 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -22,6 +22,8 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -125,10 +127,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if p.bc.enableTxDAG { // TODO(galaio): append dag into block body, TxDAGPerformance will print metrics when profile is enabled // compare input TxDAG when it enable in consensus - dag, exrStats := statedb.MVStates2TxDAG() - fmt.Print(types.EvaluateTxDAGPerformance(dag, exrStats)) - //log.Info("Process result", "block", block.NumberU64(), "txDAG", dag) - // try write txDAG into file + dag, extraStats := statedb.ResolveTxDAG([]common.Address{context.Coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + log.Debug("Process TxDAG result", "block", block.NumberU64(), "txDAG", dag) + if metrics.EnabledExpensive { + types.EvaluateTxDAGPerformance(dag, extraStats) + } + // try to write txDAG into file if p.bc.txDAGWriteCh != nil && dag != nil { p.bc.txDAGWriteCh <- TxDAGOutputItem{ blockNumber: block.NumberU64(), diff --git a/core/state_transition.go b/core/state_transition.go index 065c260b71..286193f8d6 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -18,6 +18,7 @@ package core import ( "fmt" + "github.com/ethereum/go-ethereum/core/state" "math" "math/big" "time" @@ -41,6 +42,7 @@ type ExecutionResult struct { RefundedGas uint64 // Total gas refunded after execution Err error // Any error encountered during the execution(listed in core/vm/errors.go) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) + delayFees *state.DelayedGasFee } // Unwrap returns the internal evm error which allows us for further @@ -197,6 +199,12 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err return NewStateTransition(evm, msg, gp).TransitionDb() } +func ApplyMessageDelayGasFee(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) { + transition := NewStateTransition(evm, msg, gp) + transition.delayGasFee = true + return transition.TransitionDb() +} + // StateTransition represents a state transition. // // == The State Transitioning Model @@ -226,6 +234,7 @@ type StateTransition struct { initialGas uint64 state vm.StateDB evm *vm.EVM + delayGasFee bool } // NewStateTransition initialises and returns a new state transition object. @@ -563,12 +572,18 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { }, nil } + var ( + tipFee *uint256.Int + baseFee *uint256.Int + l1Fee *uint256.Int + ) effectiveTip := msg.GasPrice if rules.IsLondon { effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)) } effectiveTipU256, _ := uint256.FromBig(effectiveTip) + // delay gas fee calculation, provide from TxDAG if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 { // Skip fee payment when NoBaseFee is set and the fee fields // are 0. This avoids a negative effectiveTip being applied to @@ -576,7 +591,11 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { } else { fee := new(uint256.Int).SetUint64(st.gasUsed()) fee.Mul(fee, effectiveTipU256) - st.state.AddBalance(st.evm.Context.Coinbase, fee) + if st.delayGasFee { + tipFee = fee + } else { + st.state.AddBalance(st.evm.Context.Coinbase, fee) + } } // Check that we are post bedrock to enable op-geth to be able to create pseudo pre-bedrock blocks (these are pre-bedrock, but don't follow l2 geth rules) @@ -587,15 +606,27 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { if overflow { return nil, fmt.Errorf("optimism gas cost overflows U256: %d", gasCost) } - st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256) + if st.delayGasFee { + baseFee = amtU256 + } else { + st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256) + } if st.msg.GasPrice.Cmp(big.NewInt(0)) == 0 && st.evm.ChainConfig().IsWright(st.evm.Context.Time) { - st.state.AddBalance(params.OptimismL1FeeRecipient, uint256.NewInt(0)) + if st.delayGasFee { + baseFee = uint256.NewInt(0) + } else { + st.state.AddBalance(params.OptimismBaseFeeRecipient, uint256.NewInt(0)) + } } else if l1Cost := st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time); l1Cost != nil { amtU256, overflow = uint256.FromBig(l1Cost) if overflow { return nil, fmt.Errorf("optimism l1 cost overflows U256: %d", l1Cost) } - st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256) + if st.delayGasFee { + baseFee = amtU256 + } else { + st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256) + } } } return &ExecutionResult{ @@ -603,6 +634,12 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { RefundedGas: gasRefund, Err: vmerr, ReturnData: ret, + delayFees: &state.DelayedGasFee{ + TipFee: tipFee, + BaseFee: baseFee, + L1Fee: l1Fee, + Coinbase: st.evm.Context.Coinbase, + }, }, nil } diff --git a/core/types/dag.go b/core/types/dag.go index dbd7cf9ea8..1cbd87e431 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -29,7 +29,6 @@ type TxDAG interface { DelayGasDistribution() bool // TxDep query TxDeps from TxDAG - // TODO(galaio): txDAG must convert to dependency relation TxDep(int) TxDep // TxCount return tx count @@ -269,27 +268,18 @@ var ( longestGasTimer = metrics.NewRegisteredTimer("dag/longestgas", nil) serialTimeTimer = metrics.NewRegisteredTimer("dag/serialtime", nil) totalTxMeter = metrics.NewRegisteredMeter("dag/txcnt", nil) - totalNoDepMeter = metrics.NewRegisteredMeter("dag/nodepcntcnt", nil) - total2DepMeter = metrics.NewRegisteredMeter("dag/2depcntcnt", nil) - total4DepMeter = metrics.NewRegisteredMeter("dag/4depcntcnt", nil) - total8DepMeter = metrics.NewRegisteredMeter("dag/8depcntcnt", nil) - total16DepMeter = metrics.NewRegisteredMeter("dag/16depcntcnt", nil) - total32DepMeter = metrics.NewRegisteredMeter("dag/32depcntcnt", nil) + totalNoDepMeter = metrics.NewRegisteredMeter("dag/nodepcnt", nil) + total2DepMeter = metrics.NewRegisteredMeter("dag/2depcnt", nil) + total4DepMeter = metrics.NewRegisteredMeter("dag/4depcnt", nil) + total8DepMeter = metrics.NewRegisteredMeter("dag/8depcnt", nil) + total16DepMeter = metrics.NewRegisteredMeter("dag/16depcnt", nil) + total32DepMeter = metrics.NewRegisteredMeter("dag/32depcnt", nil) ) -func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) string { +func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) { if len(stats) != dag.TxCount() || dag.TxCount() == 0 { - return "" + return } - sb := strings.Builder{} - //sb.WriteString("TxDAG:\n") - //for i, dep := range dag.TxDeps { - // if stats[i].mustSerialFlag { - // continue - // } - // sb.WriteString(fmt.Sprintf("%v: %v\n", i, dep.TxIndexes)) - //} - //sb.WriteString("Parallel Execution Path:\n") paths := travelTxDAGExecutionPaths(dag) // Attention: this is based on best schedule, it will reduce a lot by executing previous txs in parallel // It assumes that there is no parallel thread limit @@ -347,8 +337,6 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) string { txGases[i] += stats[i].usedGas txReads[i] += stats[i].readCount - //sb.WriteString(fmt.Sprintf("Tx%v, %.2fms|%vgas|%vreads\npath: %v\n", i, float64(txTimes[i].Microseconds())/1000, txGases[i], txReads[i], path)) - //sb.WriteString(fmt.Sprintf("%v: %v\n", i, path)) // try to find max gas if txGases[i] > maxGas { maxGas = txGases[i] @@ -360,8 +348,6 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) string { } } - sb.WriteString(fmt.Sprintf("LargestGasPath: %.2fms|%vgas|%vreads\npath: %v\n", float64(txTimes[maxGasIndex].Microseconds())/1000, txGases[maxGasIndex], txReads[maxGasIndex], paths[maxGasIndex])) - sb.WriteString(fmt.Sprintf("LongestTimePath: %.2fms|%vgas|%vreads\npath: %v\n", float64(txTimes[maxTimeIndex].Microseconds())/1000, txGases[maxTimeIndex], txReads[maxTimeIndex], paths[maxTimeIndex])) longestTimeTimer.Update(txTimes[maxTimeIndex]) longestGasTimer.Update(txTimes[maxGasIndex]) // serial path @@ -380,16 +366,7 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) string { sGas += stat.usedGas sRead += stat.readCount } - if sTime == 0 { - return "" - } - sb.WriteString(fmt.Sprintf("SerialPath: %.2fms|%vgas|%vreads\npath: %v\n", float64(sTime.Microseconds())/1000, sGas, sRead, sPath)) - maxParaTime := txTimes[maxTimeIndex] - sb.WriteString(fmt.Sprintf("Estimated saving: %.2fms, %.2f%%, %.2fX, noDepCnt: %v|%.2f%%\n", - float64((sTime-maxParaTime).Microseconds())/1000, float64(sTime-maxParaTime)/float64(sTime)*100, - float64(sTime)/float64(maxParaTime), noDepdencyCount, float64(noDepdencyCount)/float64(txCount)*100)) serialTimeTimer.Update(sTime) - return sb.String() } // travelTxDAGTargetPath will print target execution path diff --git a/core/types/dag_test.go b/core/types/dag_test.go index bfda2de6e3..b83da5f5fb 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -33,7 +33,7 @@ func TestEvaluateTxDAG(t *testing.T) { stats[i].WithSerialFlag() } } - t.Log(EvaluateTxDAGPerformance(dag, stats)) + EvaluateTxDAGPerformance(dag, stats) } func TestSimpleMVStates2TxDAG(t *testing.T) { @@ -50,7 +50,7 @@ func TestSimpleMVStates2TxDAG(t *testing.T) { ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) - dag := ms.ResolveTxDAG() + dag := ms.ResolveTxDAG(nil) require.Equal(t, mockSimpleDAG(), dag) t.Log(dag) } @@ -71,7 +71,7 @@ func TestSystemTxMVStates2TxDAG(t *testing.T) { ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithSerialFlag() ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithSerialFlag() - dag := ms.ResolveTxDAG() + dag := ms.ResolveTxDAG(nil) require.Equal(t, mockSystemTxDAG(), dag) t.Log(dag) } diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 2584807467..5b7580df1b 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -339,7 +339,7 @@ func (s *MVStates) ReadState(key RWKey) (interface{}, bool) { } // FulfillRWSet it can execute as async, and rwSet & stat must guarantee read-only -// TODO(galaio): try to generate TxDAG, when fulfill RWSet +// try to generate TxDAG, when fulfill RWSet // TODO(galaio): support flag to stat execution as optional func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { log.Debug("FulfillRWSet", "s.len", len(s.rwSets), "cur", rwSet.ver.TxIndex, "reads", len(rwSet.readSet), "writes", len(rwSet.writeSet)) @@ -382,7 +382,6 @@ func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { if _, ok := s.rwSets[prev]; !ok { continue } - // TODO: check if there are RW with system address for gas delay calculation // check if there has written op before i if checkDependency(s.rwSets[prev].writeSet, rwSet.readSet) { s.depsCache[index].add(prev) @@ -420,10 +419,16 @@ func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*ReadRecord, w } // ResolveTxDAG generate TxDAG from RWSets -func (s *MVStates) ResolveTxDAG() TxDAG { +func (s *MVStates) ResolveTxDAG(gasFeeReceivers []common.Address) TxDAG { rwSets := s.RWSets() txDAG := NewPlainTxDAG(len(rwSets)) for i := len(rwSets) - 1; i >= 0; i-- { + // check if there are RW with gas fee receiver for gas delay calculation + for _, addr := range gasFeeReceivers { + if _, ok := rwSets[i].readSet[AccountStateKey(addr, AccountSelf)]; ok { + return NewEmptyTxDAG() + } + } txDAG.TxDeps[i].TxIndexes = []uint64{} if rwSets[i].mustSerial { txDAG.TxDeps[i].Relation = 1 diff --git a/eth/backend.go b/eth/backend.go index 3fb51c4afb..a48492fb02 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -273,7 +273,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { return nil, err } if config.EnableParallelTxDAG { - eth.blockchain.EnableTxDAGGeneration(config.ParallelTxDAGFile) + eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile) } if chainConfig := eth.blockchain.Config(); chainConfig.Optimism != nil { // config.Genesis.Config.ChainID cannot be used because it's based on CLI flags only, thus default to mainnet L1 config.NetworkId = chainConfig.ChainID.Uint64() // optimism defaults eth network ID to chain ID diff --git a/miner/worker.go b/miner/worker.go index 4f96679d3f..21de3c7838 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1179,7 +1179,6 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err w.mu.RUnlock() start := time.Now() -<<<<<<< HEAD // Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees filter := txpool.PendingFilter{ @@ -1341,7 +1340,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { // Because the TxDAG appends after sidecar, so we only enable after cancun if w.chain.TxDAGEnabled() && w.chainConfig.IsCancun(block.Number(), block.Time()) && w.chainConfig.Optimism == nil { - txDAG, _ := work.state.MVStates2TxDAG() + txDAG, _ := work.state.ResolveTxDAG([]common.Address{work.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) rawTxDAG, err := types.EncodeTxDAG(txDAG) if err != nil { return &newPayloadResult{err: err} @@ -1351,7 +1350,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { // TODO(galaio): need hardfork if w.chain.TxDAGEnabled() && w.chainConfig.Optimism != nil { - txDAG, _ := work.state.MVStates2TxDAG() + txDAG, _ := work.state.ResolveTxDAG([]common.Address{work.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) rawTxDAG, err := types.EncodeTxDAG(txDAG) if err != nil { return &newPayloadResult{err: err} @@ -1471,7 +1470,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti for i := len(env.txs); i < len(block.Transactions()); i++ { env.state.RecordSystemTxRWSet(i) } - txDAG, _ := env.state.MVStates2TxDAG() + txDAG, _ := env.state.ResolveTxDAG([]common.Address{env.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) rawTxDAG, err := types.EncodeTxDAG(txDAG) if err != nil { return err @@ -1481,7 +1480,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti // TODO(galaio): need hardfork if w.chain.TxDAGEnabled() && w.chainConfig.Optimism != nil { - txDAG, _ := env.state.MVStates2TxDAG() + txDAG, _ := env.state.ResolveTxDAG([]common.Address{env.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) rawTxDAG, err := types.EncodeTxDAG(txDAG) if err != nil { return err diff --git a/tests/block_test.go b/tests/block_test.go index ac11974e66..5103b4467d 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -17,7 +17,10 @@ package tests import ( + "fmt" "math/rand" + "os" + "path/filepath" "runtime" "testing" @@ -26,6 +29,38 @@ import ( "github.com/ethereum/go-ethereum/common" ) +func TestBlockchainWithTxDAG(t *testing.T) { + bt := new(testMatcher) + // General state tests are 'exported' as blockchain tests, but we can run them natively. + // For speedier CI-runs, the line below can be uncommented, so those are skipped. + // For now, in hardfork-times (Berlin), we run the tests both as StateTests and + // as blockchain tests, since the latter also covers things like receipt root + bt.skipLoad(`^GeneralStateTests/`) + + // Skip random failures due to selfish mining test + bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) + + // Slow tests + bt.slow(`.*bcExploitTest/DelegateCallSpam.json`) + bt.slow(`.*bcExploitTest/ShanghaiLove.json`) + bt.slow(`.*bcExploitTest/SuicideIssue.json`) + bt.slow(`.*/bcForkStressTest/`) + bt.slow(`.*/bcGasPricerTest/RPC_API_Test.json`) + bt.slow(`.*/bcWalletTest/`) + + // Very slow test + bt.skipLoad(`.*/stTimeConsuming/.*`) + // test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range, + // using 4.6 TGas + bt.skipLoad(`.*randomStatetest94.json.*`) + + bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { + if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { + t.Skip("test (randomly) skipped on 32-bit windows") + } + execBlockTestWithTxDAG(t, bt, test) + }) +} func TestBlockchain(t *testing.T) { bt := new(testMatcher) // General state tests are 'exported' as blockchain tests, but we can run them natively. @@ -74,20 +109,37 @@ func TestExecutionSpecBlocktests(t *testing.T) { }) } +func execBlockTestWithTxDAG(t *testing.T, bt *testMatcher, test *BlockTest) { + txDAGFile := filepath.Join(os.TempDir(), fmt.Sprintf("test_txdag_%s.csv", t.Name())) + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, false)); err != nil { + t.Errorf("test in path mode with snapshotter failed: %v", err) + return + } + + // run again with dagFile + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, true)); err != nil { + t.Errorf("test in path mode with snapshotter failed: %v", err) + return + } + + // clean + os.Remove(txDAGFile) +} + func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { - if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil, "", true)); err != nil { t.Errorf("test in hash mode without snapshotter failed: %v", err) return } - if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil, "", true)); err != nil { t.Errorf("test in hash mode with snapshotter failed: %v", err) return } - if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil, "", true)); err != nil { t.Errorf("test in path mode without snapshotter failed: %v", err) return } - if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, "", true)); err != nil { t.Errorf("test in path mode with snapshotter failed: %v", err) return } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 75b4ab76a7..e6b18cc2b6 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -109,7 +109,7 @@ type btHeaderMarshaling struct { ExcessBlobGas *math.HexOrDecimal64 } -func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain)) (result error) { +func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain), dagFile string, enableParallel bool) (result error) { config, ok := Forks[t.json.Network] if !ok { return UnsupportedForkError{t.json.Network} @@ -151,7 +151,7 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po cache.SnapshotWait = true } chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{ - EnableParallelExec: true, + EnableParallelExec: enableParallel, ParallelTxNum: 4, Tracer: tracer, }, nil, nil) @@ -159,7 +159,9 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po return err } defer chain.Stop() - + if len(dagFile) > 0 { + chain.SetupTxDAGGeneration(dagFile) + } validBlocks, err := t.insertBlocks(chain) if err != nil { return err From 23d66cc1ac1c2468bfe1bbb2c6f965d2baed53e4 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Mon, 29 Jul 2024 15:53:36 +0800 Subject: [PATCH 13/72] FIX: issue in fixUpOriginAndResetPendingStorage (#14) The originStorage will miss some loading in txn execution,do merge rather than simple copy This fix also use stateObject specific lock for storage update, rather than the one in stateDB. Co-authored-by: Sunny --- core/state/state_object.go | 30 ++++++++++++++++++++++-------- core/state/statedb.go | 15 +++++---------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 8fd477397d..64a1f5f97f 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -172,7 +172,8 @@ type stateObject struct { // isParallel indicates this state object is used in parallel mode, in which mode the // storage would be sync.Map instead of map - isParallel bool + isParallel bool + storageRecordsLock sync.RWMutex // for pending/dirty/origin storage read (lightCopy) and write (Intermediate/FixupOrigin) originStorage Storage // Storage cache of original entries to dedup rewrites pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block @@ -533,8 +534,8 @@ func (s *stateObject) updateTrie() (Trie, error) { // is wrong. maindb = s.db.parallel.baseStateDB // For dirty/pending/origin Storage access and update. - maindb.accountStorageParallelLock.Lock() - defer maindb.accountStorageParallelLock.Unlock() + s.storageRecordsLock.Lock() + defer s.storageRecordsLock.Unlock() } // Make sure all dirty slots are finalized into the pending storage area s.finalise(false) @@ -830,10 +831,10 @@ func (s *stateObject) lightCopy(db *ParallelStateDB) *stateObject { // the problem. fortunately, the KVRead will record this and compare it with mainDB. //object.dirtyStorage = s.dirtyStorage.Copy() - s.db.accountStorageParallelLock.RLock() + s.storageRecordsLock.RLock() object.originStorage = s.originStorage.Copy() object.pendingStorage = s.pendingStorage.Copy() - s.db.accountStorageParallelLock.RUnlock() + s.storageRecordsLock.RUnlock() return object } @@ -986,14 +987,27 @@ func (s *stateObject) fixUpOriginAndResetPendingStorage() { if s.db.isParallel && s.db.parallel.isSlotDB { mainDB := s.db.parallel.baseStateDB origObj := mainDB.getStateObjectNoUpdate(s.address) - mainDB.accountStorageParallelLock.RLock() + s.storageRecordsLock.RLock() if origObj != nil && origObj.originStorage.Length() != 0 { - s.originStorage = origObj.originStorage.Copy() + originStorage := origObj.originStorage.Copy() + // During the tx execution, the originStorage can be updated with GetCommittedState() + // But is never get updated for the already existed one as there is no finalise called in execution. + // so here get the latest object in MainDB, and update the object storage with + s.originStorage.Range(func(keyItf, valueItf interface{}) bool { + key := keyItf.(common.Hash) + value := valueItf.(common.Hash) + // Skip noop changes, persist actual changes + if _, ok := originStorage.GetValue(key); !ok { + originStorage.StoreValue(key, value) + } + return true + }) + s.originStorage = originStorage } // isParallel is unnecessary since the pendingStorage for slotObject will be used serially from now on. if s.pendingStorage.Length() > 0 { s.pendingStorage = newStorage(false) } - mainDB.accountStorageParallelLock.RUnlock() + s.storageRecordsLock.RUnlock() } } diff --git a/core/state/statedb.go b/core/state/statedb.go index ac2b2a9c15..6c764494c9 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -196,11 +196,9 @@ type StateDB struct { parallelStateAccessLock sync.RWMutex snapParallelLock sync.RWMutex // for parallel mode, for main StateDB, slot will read snapshot, while processor will write. trieParallelLock sync.Mutex // for parallel mode of trie, mostly for get states/objects from trie, lock required to handle trie tracer. - // TODO: is it possible to remove this accountStorageParallelLock? - accountStorageParallelLock sync.RWMutex // for global state account/storage read (copyForSlot) and write (Intermediate) - snapDestructs map[common.Address]struct{} - snapAccounts map[common.Address][]byte - snapStorage map[common.Address]map[string][]byte + snapDestructs map[common.Address]struct{} + snapAccounts map[common.Address][]byte + snapStorage map[common.Address]map[string][]byte // originalRoot is the pre-state root, before any changes were made. // It will be updated when the Commit is called. @@ -757,7 +755,8 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common // updateStateObject writes the given object to the trie. func (s *StateDB) updateStateObject(obj *stateObject) { if !(s.isParallel && s.parallel.isSlotDB) { - s.accountStorageParallelLock.Lock() + obj.storageRecordsLock.Lock() + defer obj.storageRecordsLock.Unlock() } if !s.noTrie { // Track the amount of time wasted on updating the account from the trie @@ -794,10 +793,6 @@ func (s *StateDB) updateStateObject(obj *stateObject) { s.accountsOrigin[obj.address] = types.SlimAccountRLP(*obj.origin) } } - - if !(s.isParallel && s.parallel.isSlotDB) { - s.accountStorageParallelLock.Unlock() - } } // deleteStateObject removes the given object from the state trie. From 4f213c52eb379375ff4347a0348d55df0cb24bc2 Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 30 Jul 2024 10:41:59 +0800 Subject: [PATCH 14/72] Fix: racying issue in fixUpOriginAndResetPendingStorage --- core/state/state_object.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 64a1f5f97f..6e47cd1fe2 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -987,9 +987,12 @@ func (s *stateObject) fixUpOriginAndResetPendingStorage() { if s.db.isParallel && s.db.parallel.isSlotDB { mainDB := s.db.parallel.baseStateDB origObj := mainDB.getStateObjectNoUpdate(s.address) - s.storageRecordsLock.RLock() + s.storageRecordsLock.Lock() if origObj != nil && origObj.originStorage.Length() != 0 { + // There can be racing issue with CopyForSlot/LightCopy + origObj.storageRecordsLock.RLock() originStorage := origObj.originStorage.Copy() + origObj.storageRecordsLock.RUnlock() // During the tx execution, the originStorage can be updated with GetCommittedState() // But is never get updated for the already existed one as there is no finalise called in execution. // so here get the latest object in MainDB, and update the object storage with @@ -1008,6 +1011,6 @@ func (s *stateObject) fixUpOriginAndResetPendingStorage() { if s.pendingStorage.Length() > 0 { s.pendingStorage = newStorage(false) } - s.storageRecordsLock.RUnlock() + s.storageRecordsLock.Unlock() } } From a48f50230cc63e6359b13ef6256934849e490614 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:36:56 +0800 Subject: [PATCH 15/72] txdag: remove legacy TxDAG transfer logic; (#16) Co-authored-by: galaio --- beacon/engine/types.go | 8 ++--- core/block_validator.go | 7 ----- core/blockchain.go | 20 ++----------- core/parallel_state_processor.go | 18 ++---------- core/types/block.go | 21 -------------- eth/handler_eth.go | 3 -- eth/protocols/eth/peer.go | 1 - eth/protocols/eth/protocol.go | 3 -- miner/worker.go | 50 +++++--------------------------- 9 files changed, 16 insertions(+), 115 deletions(-) diff --git a/beacon/engine/types.go b/beacon/engine/types.go index aef01e7f5f..9a3ea8d077 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -217,11 +217,9 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, if err != nil { return nil, err } - - // TODO(galaio): need hardfork, skip check - //if len(params.ExtraData) > 32 { - // return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) - //} + if len(params.ExtraData) > 32 { + return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) + } if len(params.LogsBloom) != 256 { return nil, fmt.Errorf("invalid logsBloom length: %v", len(params.LogsBloom)) } diff --git a/core/block_validator.go b/core/block_validator.go index f2446927cc..538cea51b0 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -156,13 +156,6 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if ancestorErr != nil { return ancestorErr } - - // TODO(galaio): add more TxDAG hash when TxDAG in consensus, txDAG check here - if len(block.TxDAG()) > 0 { - if _, err := types.DecodeTxDAG(block.TxDAG()); err != nil { - return errors.New("wrong TxDAG in block body") - } - } return nil } diff --git a/core/blockchain.go b/core/blockchain.go index bf18593300..00886276d1 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1892,22 +1892,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } - // TODO(galaio): use txDAG in some accelerate scenarios, like state pre-fetcher. - //if bc.enableTxDAG && len(block.TxDAG()) > 0 { - // txDAG, err := types.DecodeTxDAG(block.TxDAG()) - // if err != nil { - // return it.index, err - // } - // log.Info("Insert chain", "block", block.NumberU64(), "txDAG", txDAG) - //} - // TODO(galaio): need hardfork - if bc.enableTxDAG && bc.chainConfig.Optimism != nil && len(block.Header().Extra) > 0 { - txDAG, err := types.DecodeTxDAG(block.Header().Extra) - if err != nil { - return it.index, err - } - log.Info("Insert chain", "block", block.NumberU64(), "txDAG", txDAG.Type()) - } + // TODO(galaio): load TxDAG from block, use txDAG in some accelerate scenarios, like state pre-fetcher. + //if bc.enableTxDAG {} // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") @@ -2719,13 +2705,13 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string) { } // write handler - bc.txDAGWriteCh = make(chan TxDAGOutputItem, 10000) go func() { writeHandle, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) if err != nil { log.Error("OpenFile when open the txDAG output file", "file", output) return } + bc.txDAGWriteCh = make(chan TxDAGOutputItem, 10000) defer writeHandle.Close() for { select { diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 93c56f5c31..77d86902af 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -801,28 +801,14 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat var ( txDAG types.TxDAG - err error ) if p.bc.enableTxDAG { - if len(block.TxDAG()) != 0 { - txDAG, err = types.DecodeTxDAG(block.TxDAG()) - if err != nil { - return nil, nil, 0, err - } - } - // load cache txDAG from file + // TODO(galaio): load TxDAG from block + // or load cache txDAG from file if txDAG == nil && len(p.bc.txDAGMapping) > 0 { txDAG = p.bc.txDAGMapping[block.NumberU64()] } } - // TODO(galaio): need hardfork - if p.bc.enableTxDAG && p.bc.chainConfig.Optimism != nil && len(block.Header().Extra) > 0 { - txDAG, err = types.DecodeTxDAG(block.Header().Extra) - if err != nil { - return nil, nil, 0, err - } - log.Info("dispatch chain with", "block", block.NumberU64(), "txDAG", txDAG.Type()) - } // From now on, entering parallel execution. p.doStaticDispatchV2(p.allTxReqs, txDAG) // todo: put txReqs in unit? diff --git a/core/types/block.go b/core/types/block.go index 47b4abec39..b32931b054 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -197,8 +197,6 @@ type Block struct { uncles []*Header transactions Transactions withdrawals Withdrawals - // TODO(galaio): package txDAG in consensus later - txDAG []byte // caches hash atomic.Value @@ -425,10 +423,6 @@ func (b *Block) SanityCheck() error { return b.header.SanityCheck() } -func (b *Block) TxDAG() []byte { - return b.txDAG -} - type writeCounter uint64 func (c *writeCounter) Write(b []byte) (int, error) { @@ -458,7 +452,6 @@ func (b *Block) WithSeal(header *Header) *Block { transactions: b.transactions, uncles: b.uncles, withdrawals: b.withdrawals, - txDAG: b.txDAG, } } @@ -469,7 +462,6 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { transactions: make([]*Transaction, len(transactions)), uncles: make([]*Header, len(uncles)), withdrawals: b.withdrawals, - txDAG: b.txDAG, } copy(block.transactions, transactions) for i := range uncles { @@ -484,7 +476,6 @@ func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block { header: b.header, transactions: b.transactions, uncles: b.uncles, - txDAG: b.txDAG, } if withdrawals != nil { block.withdrawals = make([]*Withdrawal, len(withdrawals)) @@ -493,18 +484,6 @@ func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block { return block } -// WithTxDAG returns a block containing the given txDAG. -func (b *Block) WithTxDAG(txDAG []byte) *Block { - block := &Block{ - header: b.header, - transactions: b.transactions, - uncles: b.uncles, - withdrawals: b.withdrawals, - txDAG: txDAG, - } - return block -} - // Hash returns the keccak256 hash of b's header. // The hash is computed on the first call and cached thereafter. func (b *Block) Hash() common.Hash { diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 47d63691cd..4ceaf3adaf 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -81,9 +81,6 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { return h.handleBlockAnnounces(peer, hashes, numbers) case *eth.NewBlockPacket: - if len(packet.TxDAG) != 0 { - packet.Block = packet.Block.WithTxDAG(packet.TxDAG) - } return h.handleBlockBroadcast(peer, packet.Block, packet.TD) case *eth.NewPooledTransactionHashesPacket: diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 265de7a2a9..ffd78b0594 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -282,7 +282,6 @@ func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{ Block: block, TD: td, - TxDAG: block.TxDAG(), }) } diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 1da4cda9a9..47e8d97244 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -187,7 +187,6 @@ type BlockHeadersRLPPacket struct { type NewBlockPacket struct { Block *types.Block TD *big.Int - TxDAG []byte `rlp:"optional"` } // sanityCheck verifies that the values are reasonable, as a DoS protection @@ -238,8 +237,6 @@ type BlockBody struct { Transactions []*types.Transaction // Transactions contained within a block Uncles []*types.Header // Uncles contained within a block Withdrawals []*types.Withdrawal `rlp:"optional"` // Withdrawals contained within a block - // TODO(galio): add block body later - //TxDAGs [][]byte `rlp:"optional"` // TxDAGs contained within a block } // Unpack retrieves the transactions and uncles from the range packet and returns diff --git a/miner/worker.go b/miner/worker.go index 21de3c7838..702a576600 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1338,25 +1338,14 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { return &newPayloadResult{err: fmt.Errorf("empty block root")} } - // Because the TxDAG appends after sidecar, so we only enable after cancun - if w.chain.TxDAGEnabled() && w.chainConfig.IsCancun(block.Number(), block.Time()) && w.chainConfig.Optimism == nil { - txDAG, _ := work.state.ResolveTxDAG([]common.Address{work.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) - rawTxDAG, err := types.EncodeTxDAG(txDAG) - if err != nil { - return &newPayloadResult{err: err} - } - block = block.WithTxDAG(rawTxDAG) - } - - // TODO(galaio): need hardfork - if w.chain.TxDAGEnabled() && w.chainConfig.Optimism != nil { - txDAG, _ := work.state.ResolveTxDAG([]common.Address{work.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) - rawTxDAG, err := types.EncodeTxDAG(txDAG) - if err != nil { - return &newPayloadResult{err: err} - } - block.Header().Extra = rawTxDAG - } + // TODO(galaio): fulfill TxDAG to mined block + //if w.chain.TxDAGEnabled() && w.chainConfig.Optimism != nil { + // txDAG, _ := work.state.ResolveTxDAG([]common.Address{work.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + // rawTxDAG, err := types.EncodeTxDAG(txDAG) + // if err != nil { + // return &newPayloadResult{err: err} + // } + //} assembleBlockTimer.UpdateSince(start) log.Debug("assembleBlockTimer", "duration", common.PrettyDuration(time.Since(start)), "parentHash", genParams.parentHash) @@ -1465,29 +1454,6 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti return err } - // Because the TxDAG appends after sidecar, so we only enable after cancun - if w.chain.TxDAGEnabled() && w.chainConfig.IsCancun(env.header.Number, env.header.Time) && w.chainConfig.Optimism == nil { - for i := len(env.txs); i < len(block.Transactions()); i++ { - env.state.RecordSystemTxRWSet(i) - } - txDAG, _ := env.state.ResolveTxDAG([]common.Address{env.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) - rawTxDAG, err := types.EncodeTxDAG(txDAG) - if err != nil { - return err - } - block = block.WithTxDAG(rawTxDAG) - } - - // TODO(galaio): need hardfork - if w.chain.TxDAGEnabled() && w.chainConfig.Optimism != nil { - txDAG, _ := env.state.ResolveTxDAG([]common.Address{env.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) - rawTxDAG, err := types.EncodeTxDAG(txDAG) - if err != nil { - return err - } - block.Header().Extra = rawTxDAG - } - // If we're post merge, just ignore if !w.isTTDReached(block.Header()) { select { From 947370f04c81cb8a8ffe34e36f84d27429ee41be Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Wed, 31 Jul 2024 14:41:07 +0800 Subject: [PATCH 16/72] txdag: opt txdag logic & clean todos; (#17) txdag: opt rw record flag; txdag: opt some logic; Co-authored-by: galaio --- core/state/journal.go | 2 +- core/state/state_object.go | 2 +- core/state/statedb.go | 32 +++-- core/state_processor.go | 2 +- core/types/dag.go | 48 ++++--- core/types/dag_test.go | 194 ++++------------------------- core/types/mvstates.go | 137 ++++++++++++-------- core/types/mvstates_test.go | 241 ++++++++++++++++++++++++++++++++++++ 8 files changed, 399 insertions(+), 259 deletions(-) create mode 100644 core/types/mvstates_test.go diff --git a/core/state/journal.go b/core/state/journal.go index 23e1a6c48c..4f61acdd3e 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -183,7 +183,7 @@ func (ch resetObjectChange) revert(dber StateDBer) { if !ch.prevdestruct { s.snapParallelLock.Lock() - s.deleteStateObjectsDestruct(ch.prev.address) + s.removeStateObjectsDestruct(ch.prev.address) s.snapParallelLock.Unlock() } if ch.prevAccount != nil { diff --git a/core/state/state_object.go b/core/state/state_object.go index 6e47cd1fe2..d9129c4913 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -385,7 +385,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // 2) we don't have new values, and can deliver empty response back //if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { s.db.snapParallelLock.RLock() - if _, destructed := s.db.queryStateObjectsDestruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? + if _, destructed := s.db.getStateObjectsDegetstruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? s.db.snapParallelLock.RUnlock() return common.Hash{} } diff --git a/core/state/statedb.go b/core/state/statedb.go index 6c764494c9..fecbd4ca55 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -244,10 +244,9 @@ type StateDB struct { logSize uint // parallel EVM related - rwSet *types.RWSet - mvStates *types.MVStates - stat *types.ExeStat - rwRecordFlag bool + rwSet *types.RWSet + mvStates *types.MVStates + stat *types.ExeStat // Preimages occurred seen by VM in the scope of block. preimages map[common.Hash][]byte @@ -683,8 +682,8 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // // TODO(rjl493456442) this function should only be supported by 'unwritable' // state and all mutations made should all be discarded afterwards. - if _, ok := s.queryStateObjectsDestruct(addr); !ok { - s.tagStateObjectsDestruct(addr, nil) + if _, ok := s.getStateObjectsDegetstruct(addr); !ok { + s.setStateObjectsDestruct(addr, nil) } stateObject := s.getOrNewStateObject(addr) for k, v := range storage { @@ -1022,9 +1021,9 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { // account and storage data should be cleared as well. Note, it must // be done here, otherwise the destruction event of "original account" // will be lost. - _, prevdestruct := s.queryStateObjectsDestruct(prev.address) + _, prevdestruct := s.getStateObjectsDegetstruct(prev.address) if !prevdestruct { - s.tagStateObjectsDestruct(prev.address, prev.origin) + s.setStateObjectsDestruct(prev.address, prev.origin) } // There may be some cached account/storage data already since IntermediateRoot // will be called for each transaction before byzantium fork which will always @@ -2368,7 +2367,6 @@ func (s *StateDB) BeforeTxTransition() { s.rwSet = types.NewRWSet(types.StateVersion{ TxIndex: s.txIndex, }) - s.rwRecordFlag = true } func (s *StateDB) BeginTxStat(index int) { @@ -2400,10 +2398,9 @@ func (s *StateDB) RecordRead(key types.RWKey, val interface{}) { if s.isParallel && s.parallel.isSlotDB { return } - if !s.rwRecordFlag { + if s.rwSet == nil || s.rwSet.RWRecordDone() { return } - // TODO: read from MVStates, record with ver s.rwSet.RecordRead(key, types.StateVersion{ TxIndex: -1, }, val) @@ -2413,7 +2410,7 @@ func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) { if s.isParallel && s.parallel.isSlotDB { return } - if !s.rwRecordFlag { + if s.rwSet == nil || s.rwSet.RWRecordDone() { return } s.rwSet.RecordWrite(key, val) @@ -2425,14 +2422,13 @@ func (s *StateDB) ResetMVStates(txCount int) { } s.mvStates = types.NewMVStates(txCount) s.rwSet = nil - s.rwRecordFlag = false } func (s *StateDB) FinaliseRWSet() error { if s.isParallel && s.parallel.isSlotDB { return nil } - if !s.rwRecordFlag { + if s.rwSet == nil || s.rwSet.RWRecordDone() { return nil } if metrics.EnabledExpensive { @@ -2470,11 +2466,11 @@ func (s *StateDB) FinaliseRWSet() error { } } - s.rwRecordFlag = false + s.rwSet.SetRWRecordDone() return s.mvStates.FulfillRWSet(s.rwSet, s.stat) } -func (s *StateDB) queryStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) { +func (s *StateDB) getStateObjectsDegetstruct(addr common.Address) (*types.StateAccount, bool) { if !(s.isParallel && s.parallel.isSlotDB) { if acc, ok := s.stateObjectsDestructDirty[addr]; ok { return acc, ok @@ -2484,7 +2480,7 @@ func (s *StateDB) queryStateObjectsDestruct(addr common.Address) (*types.StateAc return acc, ok } -func (s *StateDB) tagStateObjectsDestruct(addr common.Address, acc *types.StateAccount) { +func (s *StateDB) setStateObjectsDestruct(addr common.Address, acc *types.StateAccount) { if !(s.isParallel && s.parallel.isSlotDB) { s.stateObjectsDestructDirty[addr] = acc return @@ -2493,7 +2489,7 @@ func (s *StateDB) tagStateObjectsDestruct(addr common.Address, acc *types.StateA return } -func (s *StateDB) deleteStateObjectsDestruct(addr common.Address) { +func (s *StateDB) removeStateObjectsDestruct(addr common.Address) { if !(s.isParallel && s.parallel.isSlotDB) { delete(s.stateObjectsDestructDirty, addr) return diff --git a/core/state_processor.go b/core/state_processor.go index 83b473e253..c86d5dc35d 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -125,9 +125,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) if p.bc.enableTxDAG { - // TODO(galaio): append dag into block body, TxDAGPerformance will print metrics when profile is enabled // compare input TxDAG when it enable in consensus dag, extraStats := statedb.ResolveTxDAG([]common.Address{context.Coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + // TODO(galaio): check TxDAG correctness? log.Debug("Process TxDAG result", "block", block.NumberU64(), "txDAG", dag) if metrics.EnabledExpensive { types.EvaluateTxDAGPerformance(dag, extraStats) diff --git a/core/types/dag.go b/core/types/dag.go index 1cbd87e431..801ec476a2 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -33,6 +33,9 @@ type TxDAG interface { // TxCount return tx count TxCount() int + + // SetTxDep at the last one + SetTxDep(int, TxDep) error } func EncodeTxDAG(dag TxDAG) ([]byte, error) { @@ -99,6 +102,10 @@ func (d *EmptyTxDAG) TxCount() int { return 0 } +func (d *EmptyTxDAG) SetTxDep(int, TxDep) error { + return nil +} + func (d *EmptyTxDAG) String() string { return "None" } @@ -129,6 +136,18 @@ func (d *PlainTxDAG) TxCount() int { return len(d.TxDeps) } +func (d *PlainTxDAG) SetTxDep(i int, dep TxDep) error { + if i < 0 || i > len(d.TxDeps) { + return fmt.Errorf("SetTxDep with wrong index: %d", i) + } + if i < len(d.TxDeps) { + d.TxDeps[i] = dep + return nil + } + d.TxDeps = append(d.TxDeps, dep) + return nil +} + func NewPlainTxDAG(txLen int) *PlainTxDAG { return &PlainTxDAG{ TxDeps: make([]TxDep, txLen), @@ -285,23 +304,23 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) { // It assumes that there is no parallel thread limit txCount := dag.TxCount() var ( - maxGasIndex int - maxGas uint64 - maxTimeIndex int - maxTime time.Duration - txTimes = make([]time.Duration, txCount) - txGases = make([]uint64, txCount) - txReads = make([]int, txCount) - noDepdencyCount int + maxGasIndex int + maxGas uint64 + maxTimeIndex int + maxTime time.Duration + txTimes = make([]time.Duration, txCount) + txGases = make([]uint64, txCount) + txReads = make([]int, txCount) + noDepCnt int ) totalTxMeter.Mark(int64(txCount)) for i, path := range paths { - if stats[i].mustSerialFlag { + if stats[i].mustSerial { continue } if len(path) <= 1 { - noDepdencyCount++ + noDepCnt++ totalNoDepMeter.Mark(1) } if len(path) <= 3 { @@ -358,7 +377,7 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) { sPath []int ) for i, stat := range stats { - if stat.mustSerialFlag { + if stat.mustSerial { continue } sPath = append(sPath, i) @@ -399,8 +418,9 @@ type ExeStat struct { readCount int startTime time.Time costTime time.Duration - // TODO: consider system tx, gas fee issues, may need to use different flag - mustSerialFlag bool + + // some flags + mustSerial bool } func NewExeStat(txIndex int) *ExeStat { @@ -420,7 +440,7 @@ func (s *ExeStat) Done() *ExeStat { } func (s *ExeStat) WithSerialFlag() *ExeStat { - s.mustSerialFlag = true + s.mustSerial = true return s } diff --git a/core/types/dag_test.go b/core/types/dag_test.go index b83da5f5fb..1c0b334a8d 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -7,7 +7,6 @@ import ( "github.com/cometbft/cometbft/libs/rand" "github.com/ethereum/go-ethereum/common" - "github.com/holiman/uint256" "github.com/stretchr/testify/require" ) @@ -17,6 +16,31 @@ var ( ) func TestTxDAG(t *testing.T) { + dag := mockSimpleDAG() + require.NoError(t, dag.SetTxDep(9, TxDep{ + Relation: 1, + TxIndexes: nil, + })) + require.NoError(t, dag.SetTxDep(10, TxDep{ + Relation: 1, + TxIndexes: nil, + })) + require.Error(t, dag.SetTxDep(12, TxDep{ + Relation: 1, + TxIndexes: nil, + })) + dag = NewEmptyTxDAG() + require.NoError(t, dag.SetTxDep(0, TxDep{ + Relation: 1, + TxIndexes: nil, + })) + require.NoError(t, dag.SetTxDep(11, TxDep{ + Relation: 1, + TxIndexes: nil, + })) +} + +func TestTxDAG_SetTxDep(t *testing.T) { dag := mockSimpleDAG() t.Log(dag) dag = mockSystemTxDAG() @@ -36,144 +60,6 @@ func TestEvaluateTxDAG(t *testing.T) { EvaluateTxDAGPerformance(dag, stats) } -func TestSimpleMVStates2TxDAG(t *testing.T) { - ms := NewMVStates(10) - - ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) - ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) - ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) - ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) - ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) - ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) - ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) - ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}) - ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) - ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) - - dag := ms.ResolveTxDAG(nil) - require.Equal(t, mockSimpleDAG(), dag) - t.Log(dag) -} - -func TestSystemTxMVStates2TxDAG(t *testing.T) { - ms := NewMVStates(12) - - ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) - ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) - ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) - ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) - ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) - ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) - ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) - ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}) - ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) - ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) - ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithSerialFlag() - ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithSerialFlag() - - dag := ms.ResolveTxDAG(nil) - require.Equal(t, mockSystemTxDAG(), dag) - t.Log(dag) -} - -func TestIsEqualRWVal(t *testing.T) { - tests := []struct { - key RWKey - src interface{} - compared interface{} - isEqual bool - }{ - { - key: AccountStateKey(mockAddr, AccountNonce), - src: uint64(0), - compared: uint64(0), - isEqual: true, - }, - { - key: AccountStateKey(mockAddr, AccountNonce), - src: uint64(0), - compared: uint64(1), - isEqual: false, - }, - { - key: AccountStateKey(mockAddr, AccountBalance), - src: new(uint256.Int).SetUint64(1), - compared: new(uint256.Int).SetUint64(1), - isEqual: true, - }, - { - key: AccountStateKey(mockAddr, AccountBalance), - src: nil, - compared: new(uint256.Int).SetUint64(1), - isEqual: false, - }, - { - key: AccountStateKey(mockAddr, AccountBalance), - src: (*uint256.Int)(nil), - compared: new(uint256.Int).SetUint64(1), - isEqual: false, - }, - { - key: AccountStateKey(mockAddr, AccountBalance), - src: (*uint256.Int)(nil), - compared: (*uint256.Int)(nil), - isEqual: true, - }, - { - key: AccountStateKey(mockAddr, AccountCodeHash), - src: []byte{1}, - compared: []byte{1}, - isEqual: true, - }, - { - key: AccountStateKey(mockAddr, AccountCodeHash), - src: nil, - compared: []byte{1}, - isEqual: false, - }, - { - key: AccountStateKey(mockAddr, AccountCodeHash), - src: ([]byte)(nil), - compared: []byte{1}, - isEqual: false, - }, - { - key: AccountStateKey(mockAddr, AccountCodeHash), - src: ([]byte)(nil), - compared: ([]byte)(nil), - isEqual: true, - }, - { - key: AccountStateKey(mockAddr, AccountSuicide), - src: struct{}{}, - compared: struct{}{}, - isEqual: false, - }, - { - key: AccountStateKey(mockAddr, AccountSuicide), - src: nil, - compared: struct{}{}, - isEqual: false, - }, - { - key: StorageStateKey(mockAddr, mockHash), - src: mockHash, - compared: mockHash, - isEqual: true, - }, - { - key: StorageStateKey(mockAddr, mockHash), - src: nil, - compared: mockHash, - isEqual: false, - }, - } - - for i, item := range tests { - require.Equal(t, item.isEqual, isEqualRWVal(item.key, item.src, item.compared), i) - } -} - func TestMergeTxDAGExecutionPaths_Simple(t *testing.T) { paths := MergeTxDAGExecutionPaths(mockSimpleDAG()) require.Equal(t, [][]uint64{ @@ -268,36 +154,6 @@ func mockSystemTxDAG() TxDAG { return dag } -func mockRWSet(index int, read []string, write []string) *RWSet { - ver := StateVersion{ - TxIndex: index, - } - set := NewRWSet(ver) - for _, k := range read { - key := RWKey{} - if len(k) > len(key) { - k = k[:len(key)] - } - copy(key[:], k) - set.readSet[key] = &ReadRecord{ - StateVersion: ver, - Val: struct{}{}, - } - } - for _, k := range write { - key := RWKey{} - if len(k) > len(key) { - k = k[:len(key)] - } - copy(key[:], k) - set.writeSet[key] = &WriteRecord{ - Val: struct{}{}, - } - } - - return set -} - func TestTxDAG_Encode_Decode(t *testing.T) { expected := TxDAG(&EmptyTxDAG{}) enc, err := EncodeTxDAG(expected) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 5b7580df1b..85d3886470 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -7,6 +7,8 @@ import ( "strings" "sync" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/holiman/uint256" @@ -86,37 +88,26 @@ func (key *RWKey) Addr() common.Address { // if TxIndex equals to -1, it means the state read from DB. type StateVersion struct { TxIndex int - // TODO(galaio): used for multi ver state + // Tx incarnation used for multi ver state TxIncarnation int } -// ReadRecord keep read value & its version -type ReadRecord struct { - StateVersion - Val interface{} -} - -// WriteRecord keep latest state value & change count -type WriteRecord struct { - Val interface{} -} - // RWSet record all read & write set in txs // Attention: this is not a concurrent safety structure type RWSet struct { ver StateVersion - readSet map[RWKey]*ReadRecord - writeSet map[RWKey]*WriteRecord + readSet map[RWKey]*RWItem + writeSet map[RWKey]*RWItem - // some flags - mustSerial bool + rwRecordDone bool + mustSerial bool } func NewRWSet(ver StateVersion) *RWSet { return &RWSet{ ver: ver, - readSet: make(map[RWKey]*ReadRecord), - writeSet: make(map[RWKey]*WriteRecord), + readSet: make(map[RWKey]*RWItem), + writeSet: make(map[RWKey]*RWItem), } } @@ -125,16 +116,17 @@ func (s *RWSet) RecordRead(key RWKey, ver StateVersion, val interface{}) { if _, exist := s.readSet[key]; exist { return } - s.readSet[key] = &ReadRecord{ - StateVersion: ver, - Val: val, + s.readSet[key] = &RWItem{ + Ver: ver, + Val: val, } } func (s *RWSet) RecordWrite(key RWKey, val interface{}) { wr, exist := s.writeSet[key] if !exist { - s.writeSet[key] = &WriteRecord{ + s.writeSet[key] = &RWItem{ + Ver: s.ver, Val: val, } return @@ -146,11 +138,11 @@ func (s *RWSet) Version() StateVersion { return s.ver } -func (s *RWSet) ReadSet() map[RWKey]*ReadRecord { +func (s *RWSet) ReadSet() map[RWKey]*RWItem { return s.readSet } -func (s *RWSet) WriteSet() map[RWKey]*WriteRecord { +func (s *RWSet) WriteSet() map[RWKey]*RWItem { return s.writeSet } @@ -159,6 +151,14 @@ func (s *RWSet) WithSerialFlag() *RWSet { return s } +func (s *RWSet) RWRecordDone() bool { + return s.rwRecordDone +} + +func (s *RWSet) SetRWRecordDone() { + s.rwRecordDone = true +} + func (s *RWSet) String() string { builder := strings.Builder{} builder.WriteString(fmt.Sprintf("tx: %v, inc: %v\nreadSet: [", s.ver.TxIndex, s.ver.TxIncarnation)) @@ -219,37 +219,37 @@ func equalUint256(s, c *uint256.Int) bool { return s == c } -type PendingWrite struct { +type RWItem struct { Ver StateVersion Val interface{} } -func NewPendingWrite(ver StateVersion, wr *WriteRecord) *PendingWrite { - return &PendingWrite{ +func NewRWItem(ver StateVersion, val interface{}) *RWItem { + return &RWItem{ Ver: ver, - Val: wr.Val, + Val: val, } } -func (w *PendingWrite) TxIndex() int { +func (w *RWItem) TxIndex() int { return w.Ver.TxIndex } -func (w *PendingWrite) TxIncarnation() int { +func (w *RWItem) TxIncarnation() int { return w.Ver.TxIncarnation } type PendingWrites struct { - list []*PendingWrite + list []*RWItem } func NewPendingWrites() *PendingWrites { return &PendingWrites{ - list: make([]*PendingWrite, 0), + list: make([]*RWItem, 0), } } -func (w *PendingWrites) Append(pw *PendingWrite) { +func (w *PendingWrites) Append(pw *RWItem) { if i, found := w.SearchTxIndex(pw.TxIndex()); found { w.list[i] = pw return @@ -279,7 +279,7 @@ func (w *PendingWrites) SearchTxIndex(txIndex int) (int, bool) { return i, i < n && w.list[i].TxIndex() == txIndex } -func (w *PendingWrites) FindLastWrite(txIndex int) *PendingWrite { +func (w *PendingWrites) FindLastWrite(txIndex int) *RWItem { var i, _ = w.SearchTxIndex(txIndex) for j := i - 1; j >= 0; j-- { if w.list[j].TxIndex() < txIndex { @@ -291,8 +291,9 @@ func (w *PendingWrites) FindLastWrite(txIndex int) *PendingWrite { } type MVStates struct { - rwSets map[int]*RWSet - pendingWriteSet map[RWKey]*PendingWrites + rwSets map[int]*RWSet + pendingWriteSet map[RWKey]*PendingWrites + nextFinaliseIndex int // dependency map cache for generating TxDAG // depsCache[i].exist(j) means j->i, and i > j @@ -333,21 +334,27 @@ func (s *MVStates) RWSet(index int) *RWSet { return s.rwSets[index] } -// ReadState TODO(galaio): read state from MVStates -func (s *MVStates) ReadState(key RWKey) (interface{}, bool) { - return nil, false +// ReadState read state from MVStates +func (s *MVStates) ReadState(txIndex int, key RWKey) *RWItem { + s.lock.RLock() + defer s.lock.RUnlock() + + wset, ok := s.pendingWriteSet[key] + if !ok { + return nil + } + return wset.FindLastWrite(txIndex) } // FulfillRWSet it can execute as async, and rwSet & stat must guarantee read-only // try to generate TxDAG, when fulfill RWSet -// TODO(galaio): support flag to stat execution as optional func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { - log.Debug("FulfillRWSet", "s.len", len(s.rwSets), "cur", rwSet.ver.TxIndex, "reads", len(rwSet.readSet), "writes", len(rwSet.writeSet)) + log.Debug("FulfillRWSet", "total", len(s.rwSets), "cur", rwSet.ver.TxIndex, "reads", len(rwSet.readSet), "writes", len(rwSet.writeSet)) s.lock.Lock() defer s.lock.Unlock() index := rwSet.ver.TxIndex - if s := s.rwSets[index]; s != nil { - return errors.New("refill a exist RWSet") + if index < s.nextFinaliseIndex { + return errors.New("fulfill a finalized RWSet") } if stat != nil { if stat.txIndex != index { @@ -356,26 +363,46 @@ func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { s.stats[index] = stat } + if metrics.EnabledExpensive { + for k := range rwSet.writeSet { + // this action is only for testing, it runs when enable expensive metrics. + checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet) + } + } s.resolveDepsCache(index, rwSet) + s.rwSets[index] = rwSet + return nil +} + +// Finalise it will put target write set into pending writes. +func (s *MVStates) Finalise(index int) error { + log.Debug("Finalise", "total", len(s.rwSets), "index", index) + s.lock.Lock() + defer s.lock.Unlock() + + rwSet := s.rwSets[index] + if rwSet == nil { + return fmt.Errorf("finalise a non-exist RWSet, index: %d", index) + } + + if index != s.nextFinaliseIndex { + return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) + } + // append to pending write set for k, v := range rwSet.writeSet { - // TODO(galaio): this action is only for testing, it can be removed in production mode. - // ignore no changed write record - checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet) if _, exist := s.pendingWriteSet[k]; !exist { s.pendingWriteSet[k] = NewPendingWrites() } - s.pendingWriteSet[k].Append(NewPendingWrite(rwSet.ver, v)) + s.pendingWriteSet[k].Append(v) } - s.rwSets[index] = rwSet + s.nextFinaliseIndex++ return nil } func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { // analysis dep, if the previous transaction is not executed/validated, re-analysis is required - if _, ok := s.depsCache[index]; !ok { - s.depsCache[index] = NewTxDeps(0) - } + s.depsCache[index] = NewTxDeps(0) for prev := 0; prev < index; prev++ { // if there are some parallel execution or system txs, it will fulfill in advance // it's ok, and try re-generate later @@ -395,11 +422,11 @@ func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { } } -func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*ReadRecord, writeSet map[RWKey]*WriteRecord) bool { +func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*RWItem, writeSet map[RWKey]*RWItem) bool { var ( readOk bool writeOk bool - r *WriteRecord + r *RWItem ) if k.IsAccountSuicide() { @@ -411,7 +438,7 @@ func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*ReadRecord, w r, writeOk = writeSet[k] if readOk != writeOk { // check if it's correct? read nil, write non-nil - log.Info("checkRWSetInconsistent find inconsistent", "tx", index, "k", k.String(), "read", readOk, "write", writeOk, "val", r.Val) + log.Warn("checkRWSetInconsistent find inconsistent", "tx", index, "k", k.String(), "read", readOk, "write", writeOk, "val", r.Val) return true } @@ -443,7 +470,7 @@ func (s *MVStates) ResolveTxDAG(gasFeeReceivers []common.Address) TxDAG { return txDAG } -func checkDependency(writeSet map[RWKey]*WriteRecord, readSet map[RWKey]*ReadRecord) bool { +func checkDependency(writeSet map[RWKey]*RWItem, readSet map[RWKey]*RWItem) bool { // check tx dependency, only check key, skip version for k, _ := range writeSet { // check suicide, add read address flag, it only for check suicide quickly, and cannot for other scenarios. diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go new file mode 100644 index 0000000000..9d4422bf1f --- /dev/null +++ b/core/types/mvstates_test.go @@ -0,0 +1,241 @@ +package types + +import ( + "testing" + + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +func TestMVStates_BasicUsage(t *testing.T) { + ms := NewMVStates(0) + require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(0, []interface{}{"0x00", 0}, []interface{}{"0x00", 0}), nil)) + require.Nil(t, ms.ReadState(0, str2key("0x00"))) + require.NoError(t, ms.Finalise(0)) + require.Error(t, ms.Finalise(0)) + require.Error(t, ms.FulfillRWSet(mockRWSetWithVal(0, nil, nil), nil)) + require.Nil(t, ms.ReadState(0, str2key("0x00"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 0}, 0), ms.ReadState(1, str2key("0x00"))) + + require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(1, []interface{}{"0x01", 1}, []interface{}{"0x01", 1}), nil)) + require.Nil(t, ms.ReadState(1, str2key("0x01"))) + require.NoError(t, ms.Finalise(1)) + require.Nil(t, ms.ReadState(0, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadState(2, str2key("0x01"))) + + require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(2, []interface{}{"0x02", 2, "0x01", 1}, []interface{}{"0x01", 2, "0x02", 2}), nil)) + require.NoError(t, ms.Finalise(2)) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadState(2, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 2}, 2), ms.ReadState(3, str2key("0x01"))) + + require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(3, []interface{}{"0x03", 3, "0x00", 0, "0x01", 2}, []interface{}{"0x00", 3, "0x01", 3, "0x03", 3}), nil)) + require.Nil(t, ms.ReadState(3, str2key("0x03"))) + require.NoError(t, ms.Finalise(3)) + require.Nil(t, ms.ReadState(0, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadState(2, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 2}, 2), ms.ReadState(3, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 3}, 3), ms.ReadState(4, str2key("0x01"))) + require.Nil(t, ms.ReadState(0, str2key("0x00"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 3}, 3), ms.ReadState(5, str2key("0x00"))) +} + +func TestSimpleMVStates2TxDAG(t *testing.T) { + ms := NewMVStates(10) + + ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) + ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) + ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) + ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) + ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) + ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) + ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) + ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}) + ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) + ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) + + dag := ms.ResolveTxDAG(nil) + require.Equal(t, mockSimpleDAG(), dag) + t.Log(dag) +} + +func TestSystemTxMVStates2TxDAG(t *testing.T) { + ms := NewMVStates(12) + + ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) + ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) + ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) + ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) + ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) + ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) + ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) + ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}) + ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) + ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) + ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithSerialFlag() + ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithSerialFlag() + + dag := ms.ResolveTxDAG(nil) + require.Equal(t, mockSystemTxDAG(), dag) + t.Log(dag) +} + +func TestIsEqualRWVal(t *testing.T) { + tests := []struct { + key RWKey + src interface{} + compared interface{} + isEqual bool + }{ + { + key: AccountStateKey(mockAddr, AccountNonce), + src: uint64(0), + compared: uint64(0), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountNonce), + src: uint64(0), + compared: uint64(1), + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: new(uint256.Int).SetUint64(1), + compared: new(uint256.Int).SetUint64(1), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: nil, + compared: new(uint256.Int).SetUint64(1), + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: (*uint256.Int)(nil), + compared: new(uint256.Int).SetUint64(1), + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: (*uint256.Int)(nil), + compared: (*uint256.Int)(nil), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: []byte{1}, + compared: []byte{1}, + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: nil, + compared: []byte{1}, + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: ([]byte)(nil), + compared: []byte{1}, + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: ([]byte)(nil), + compared: ([]byte)(nil), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountSuicide), + src: struct{}{}, + compared: struct{}{}, + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountSuicide), + src: nil, + compared: struct{}{}, + isEqual: false, + }, + { + key: StorageStateKey(mockAddr, mockHash), + src: mockHash, + compared: mockHash, + isEqual: true, + }, + { + key: StorageStateKey(mockAddr, mockHash), + src: nil, + compared: mockHash, + isEqual: false, + }, + } + + for i, item := range tests { + require.Equal(t, item.isEqual, isEqualRWVal(item.key, item.src, item.compared), i) + } +} + +func mockRWSet(index int, read []string, write []string) *RWSet { + ver := StateVersion{ + TxIndex: index, + } + set := NewRWSet(ver) + for _, k := range read { + set.readSet[str2key(k)] = &RWItem{ + Ver: ver, + Val: struct{}{}, + } + } + for _, k := range write { + set.writeSet[str2key(k)] = &RWItem{ + Ver: ver, + Val: struct{}{}, + } + } + + return set +} + +func mockRWSetWithVal(index int, read []interface{}, write []interface{}) *RWSet { + ver := StateVersion{ + TxIndex: index, + } + set := NewRWSet(ver) + + if len(read)%2 != 0 { + panic("wrong read size") + } + if len(write)%2 != 0 { + panic("wrong write size") + } + + for i := 0; i < len(read); { + set.readSet[str2key(read[i].(string))] = &RWItem{ + Ver: StateVersion{ + TxIndex: index - 1, + }, + Val: read[i+1], + } + i += 2 + } + for i := 0; i < len(write); { + set.writeSet[str2key(write[i].(string))] = &RWItem{ + Ver: ver, + Val: write[i+1], + } + i += 2 + } + + return set +} + +func str2key(k string) RWKey { + key := RWKey{} + if len(k) > len(key) { + k = k[:len(key)] + } + copy(key[:], k) + return key +} From 5963131f3c4a292257f9160d1ad3be185408ad59 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Fri, 2 Aug 2024 11:45:38 +0800 Subject: [PATCH 17/72] refine lock and fix racying issue (#18) Co-authored-by: Sunny --- core/state/journal.go | 4 ++-- core/state/parallel_statedb.go | 4 +++- core/state/state_object.go | 9 ++++++--- core/state/statedb.go | 11 +++++++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/core/state/journal.go b/core/state/journal.go index 4f61acdd3e..33a373a798 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -182,9 +182,9 @@ func (ch resetObjectChange) revert(dber StateDBer) { } if !ch.prevdestruct { - s.snapParallelLock.Lock() + s.stateObjectDestructLock.Lock() s.removeStateObjectsDestruct(ch.prev.address) - s.snapParallelLock.Unlock() + s.stateObjectDestructLock.Unlock() } if ch.prevAccount != nil { s.accounts[ch.prev.addrHash] = ch.prevAccount diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 73bac5abbc..ff0d05a5f9 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1500,10 +1500,11 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S // We need to maintain account deletions explicitly (will remain // set indefinitely). Note only the first occurred self-destruct // event is tracked. + mainDB.stateObjectDestructLock.Lock() if _, ok := mainDB.stateObjectsDestruct[obj.address]; !ok { mainDB.stateObjectsDestruct[obj.address] = obj.origin } - + mainDB.stateObjectDestructLock.Unlock() // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then // resurrect an account; but the snapshotter needs both events. @@ -1561,6 +1562,7 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S // We need to maintain account deletions explicitly (will remain // set indefinitely). Note only the first occurred self-destruct // event is tracked. + // This is the thread local one, no need to acquire the stateObjectsDestructLock. if _, ok := s.stateObjectsDestruct[obj.address]; !ok { s.stateObjectsDestruct[obj.address] = obj.origin } diff --git a/core/state/state_object.go b/core/state/state_object.go index d9129c4913..67e1b06205 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -384,12 +384,12 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // have been handles via pendingStorage above. // 2) we don't have new values, and can deliver empty response back //if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { - s.db.snapParallelLock.RLock() + s.db.stateObjectDestructLock.RLock() if _, destructed := s.db.getStateObjectsDegetstruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? - s.db.snapParallelLock.RUnlock() + s.db.stateObjectDestructLock.RUnlock() return common.Hash{} } - s.db.snapParallelLock.RUnlock() + s.db.stateObjectDestructLock.RUnlock() // If no live objects are available, attempt to use snapshots var ( @@ -469,6 +469,8 @@ func (s *stateObject) setState(key, value common.Hash) { // finalise moves all dirty storage slots into the pending area to be hashed or // committed later. It is invoked at the end of every transaction. func (s *stateObject) finalise(prefetch bool) { + s.storageRecordsLock.Lock() + defer s.storageRecordsLock.Unlock() slotsToPrefetch := make([][]byte, 0, s.dirtyStorage.Length()) s.dirtyStorage.Range(func(key, value interface{}) bool { s.pendingStorage.StoreValue(key.(common.Hash), value.(common.Hash)) @@ -479,6 +481,7 @@ func (s *stateObject) finalise(prefetch bool) { } return true }) + if s.dirtyNonce != nil { s.data.Nonce = *s.dirtyNonce s.dirtyNonce = nil diff --git a/core/state/statedb.go b/core/state/statedb.go index fecbd4ca55..5ecba6532a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -110,7 +110,9 @@ func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { // deleteStateObj is the entry for deleting state object to stateObjects in StateDB or stateObjects in parallel func (s *StateDB) deleteStateObj(addr common.Address) { if s.isParallel { + s.parallelStateAccessLock.Lock() s.parallel.stateObjects.Delete(addr) + s.parallelStateAccessLock.Unlock() } else { delete(s.stateObjects, addr) } @@ -196,6 +198,7 @@ type StateDB struct { parallelStateAccessLock sync.RWMutex snapParallelLock sync.RWMutex // for parallel mode, for main StateDB, slot will read snapshot, while processor will write. trieParallelLock sync.Mutex // for parallel mode of trie, mostly for get states/objects from trie, lock required to handle trie tracer. + stateObjectDestructLock sync.RWMutex // for parallel mode, used in mainDB for mergeSlot and conflict check. snapDestructs map[common.Address]struct{} snapAccounts map[common.Address][]byte snapStorage map[common.Address]map[string][]byte @@ -1021,10 +1024,12 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { // account and storage data should be cleared as well. Note, it must // be done here, otherwise the destruction event of "original account" // will be lost. + s.stateObjectDestructLock.Lock() _, prevdestruct := s.getStateObjectsDegetstruct(prev.address) if !prevdestruct { s.setStateObjectsDestruct(prev.address, prev.origin) } + s.stateObjectDestructLock.Unlock() // There may be some cached account/storage data already since IntermediateRoot // will be called for each transaction before byzantium fork which will always // cache the latest account/storage data. @@ -1536,6 +1541,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) // finalise stateObjectsDestruct + // The finalise of stateDB is called at verify & commit phase, which is global, no need to acquire the lock. for addr, acc := range s.stateObjectsDestructDirty { s.stateObjectsDestruct[addr] = acc } @@ -1570,6 +1576,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // We need to maintain account deletions explicitly (will remain // set indefinitely). Note only the first occurred self-destruct // event is tracked. + // The finalise of stateDB is called at verify & commit phase, which is global, no need to acquire the lock. if _, ok := s.stateObjectsDestruct[obj.address]; !ok { s.stateObjectsDestruct[obj.address] = obj.origin } @@ -1941,6 +1948,7 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A return incomplete, nil } + // Commit phase, no need to acquire lock. for addr, prev := range s.stateObjectsDestruct { // The original account was non-existing, and it's marked as destructed // in the scope of block. It can be case (a) or (b). @@ -2600,6 +2608,7 @@ func (s *StateDB) AddrPrefetch(slotDb *ParallelStateDB) { // finalized(dirty -> pending) on execution slot, the execution results should be // merged back to the main StateDB. func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receipt, txIndex int, fees *DelayedGasFee) *StateDB { + s.SetTxContext(slotDb.thash, slotDb.txIndex) for s.nextRevisionId < slotDb.nextRevisionId { @@ -2777,11 +2786,13 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip } } + s.stateObjectDestructLock.Lock() for addr := range slotDb.stateObjectsDestruct { if acc, exist := s.stateObjectsDestruct[addr]; !exist { s.stateObjectsDestruct[addr] = acc } } + s.stateObjectDestructLock.Unlock() // slotDb.logs: logs will be kept in receipts, no need to do merge for hash, preimage := range slotDb.preimages { s.preimages[hash] = preimage From ea8d9ad98d0cab68ee43d386e286cb70d26b84d4 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:59:58 +0800 Subject: [PATCH 18/72] txdag: opt TxDAG rwset collecting & generating; (#19) * txdag: opt some logic; txdag: opt rw set collect logic; * pevm: opt logs; * txdag: opt txdag encoding, reduce rlp size; --------- Co-authored-by: galaio --- core/blockchain.go | 5 +- core/blockchain_test.go | 16 ++- core/parallel_state_processor.go | 7 +- core/state/statedb.go | 15 +- core/state_processor.go | 26 ++-- core/state_transition.go | 20 ++- core/types/dag.go | 77 ++++++---- core/types/dag_test.go | 234 ++++++++++++++++++++++++++----- core/types/mvstates.go | 37 +++-- core/types/mvstates_test.go | 32 ++++- 10 files changed, 373 insertions(+), 96 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 00886276d1..2daedfd74f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2693,6 +2693,7 @@ func (bc *BlockChain) TxDAGEnabled() bool { } func (bc *BlockChain) SetupTxDAGGeneration(output string) { + log.Info("node enable TxDAG feature", "output", output) bc.enableTxDAG = true if len(output) == 0 { return @@ -2703,10 +2704,11 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string) { if err != nil { log.Error("read TxDAG err", "err", err) } + log.Info("load TxDAG from file", "output", output, "count", len(bc.txDAGMapping)) // write handler go func() { - writeHandle, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) + writeHandle, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) if err != nil { log.Error("OpenFile when open the txDAG output file", "file", output) return @@ -2746,6 +2748,7 @@ func writeTxDAGToFile(writeHandle *os.File, item TxDAGOutputItem) error { return err } +// TODO(galaio): support load with segments, every segment 100000 blocks? func readTxDAGMappingFromFile(output string) (map[uint64]types.TxDAG, error) { file, err := os.Open(output) if err != nil { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index b4d5a1381b..d3fd06751f 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4728,18 +4728,32 @@ func TestTxDAGFile_ReadWrite(t *testing.T) { 1: makeEmptyPlainTxDAG(1), 2: makeEmptyPlainTxDAG(2), } - writeFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) + writeFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) require.NoError(t, err) for num, dag := range except { require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: dag})) } writeFile.Close() + except2 := map[uint64]types.TxDAG{ + 3: types.NewEmptyTxDAG(), + 4: makeEmptyPlainTxDAG(4), + } + writeFile, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) + require.NoError(t, err) + for num, dag := range except2 { + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: dag})) + } + writeFile.Close() + actual, err := readTxDAGMappingFromFile(path) require.NoError(t, err) for num, dag := range except { require.Equal(t, dag, actual[num]) } + for num, dag := range except2 { + require.Equal(t, dag, actual[num]) + } } func makeEmptyPlainTxDAG(cnt int) *types.PlainTxDAG { diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 77d86902af..cc50e1b82e 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -203,7 +203,7 @@ func (p *ParallelStateProcessor) doStaticDispatchV2(txReqs []*ParallelTxRequest, } // resolve isolate execution paths from TxDAG, it indicates the tx dispatch paths := types.MergeTxDAGExecutionPaths(txDAG) - log.Info("doStaticDispatchV2 merge parallel execution paths", "slots", len(p.slotState), "paths", len(paths)) + log.Debug("doStaticDispatchV2 merge parallel execution paths", "slots", len(p.slotState), "paths", len(paths)) for _, path := range paths { slotIndex := p.mostHungrySlot() @@ -866,12 +866,13 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat p.doCleanUp() // len(commonTxs) could be 0, such as: https://bscscan.com/block/14580486 - if len(commonTxs) > 0 { + if len(commonTxs) > 0 && p.debugConflictRedoNum > 0 { log.Info("ProcessParallel tx all done", "block", header.Number, "usedGas", *usedGas, "txNum", txNum, "len(commonTxs)", len(commonTxs), "conflictNum", p.debugConflictRedoNum, - "redoRate(%)", 100*(p.debugConflictRedoNum)/len(commonTxs)) + "redoRate(%)", 100*(p.debugConflictRedoNum)/len(commonTxs), + "txDAG", txDAG) } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. diff --git a/core/state/statedb.go b/core/state/statedb.go index 5ecba6532a..8ee3df823e 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2505,7 +2505,7 @@ func (s *StateDB) removeStateObjectsDestruct(addr common.Address) { delete(s.stateObjectsDestruct, addr) } -func (s *StateDB) ResolveTxDAG(gasFeeReceivers []common.Address) (types.TxDAG, map[int]*types.ExeStat) { +func (s *StateDB) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (types.TxDAG, error) { if s.isParallel && s.parallel.isSlotDB { return nil, nil } @@ -2518,7 +2518,18 @@ func (s *StateDB) ResolveTxDAG(gasFeeReceivers []common.Address) (types.TxDAG, m }(time.Now()) } - return s.mvStates.ResolveTxDAG(gasFeeReceivers), s.mvStates.Stats() + return s.mvStates.ResolveTxDAG(txCnt, gasFeeReceivers) +} + +func (s *StateDB) ResolveStats() map[int]*types.ExeStat { + if s.isParallel && s.parallel.isSlotDB { + return nil + } + if s.mvStates == nil { + return nil + } + + return s.mvStates.Stats() } func (s *StateDB) MVStates() *types.MVStates { diff --git a/core/state_processor.go b/core/state_processor.go index c86d5dc35d..7ac2d59497 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -126,18 +126,22 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if p.bc.enableTxDAG { // compare input TxDAG when it enable in consensus - dag, extraStats := statedb.ResolveTxDAG([]common.Address{context.Coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) - // TODO(galaio): check TxDAG correctness? - log.Debug("Process TxDAG result", "block", block.NumberU64(), "txDAG", dag) - if metrics.EnabledExpensive { - types.EvaluateTxDAGPerformance(dag, extraStats) - } - // try to write txDAG into file - if p.bc.txDAGWriteCh != nil && dag != nil { - p.bc.txDAGWriteCh <- TxDAGOutputItem{ - blockNumber: block.NumberU64(), - txDAG: dag, + dag, err := statedb.ResolveTxDAG(len(block.Transactions()), []common.Address{context.Coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + if err == nil { + // TODO(galaio): check TxDAG correctness? + log.Debug("Process TxDAG result", "block", block.NumberU64(), "txDAG", dag) + if metrics.EnabledExpensive { + types.EvaluateTxDAGPerformance(dag, statedb.ResolveStats()) + } + // try to write txDAG into file + if p.bc.txDAGWriteCh != nil && dag != nil { + p.bc.txDAGWriteCh <- TxDAGOutputItem{ + blockNumber: block.NumberU64(), + txDAG: dag, + } } + } else { + log.Error("ResolveTxDAG err", "block", block.NumberU64(), "tx", len(block.Transactions()), "err", err) } } return receipts, allLogs, *usedGas, nil diff --git a/core/state_transition.go b/core/state_transition.go index 286193f8d6..9542d43a53 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -19,6 +19,7 @@ package core import ( "fmt" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/log" "math" "math/big" "time" @@ -441,6 +442,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.msg.IsSystemTx && !st.evm.ChainConfig().IsRegolith(st.evm.Context.Time) { gasUsed = 0 } + // just record error tx here + if ferr := st.state.FinaliseRWSet(); ferr != nil { + log.Error("finalise error deposit tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex()) + } result = &ExecutionResult{ UsedGas: gasUsed, Err: fmt.Errorf("failed deposit: %w", err), @@ -448,6 +453,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } err = nil } + if err != nil { + // just record error tx here + if ferr := st.state.FinaliseRWSet(); ferr != nil { + log.Error("finalise error tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex()) + } + } return result, err } @@ -530,6 +541,11 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { } DebugInnerExecutionDuration += time.Since(start) + // stop record rw set in here, skip gas fee distribution + if ferr := st.state.FinaliseRWSet(); ferr != nil { + log.Error("finalise tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex()) + } + // if deposit: skip refunds, skip tipping coinbase // Regolith changes this behaviour to report the actual gasUsed instead of always reporting all gas used. if st.msg.IsDepositTx && !rules.IsOptimismRegolith { @@ -545,10 +561,6 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { ReturnData: ret, }, nil } - // stop record rw set in here, skip gas fee distribution - if err := st.state.FinaliseRWSet(); err != nil { - return nil, err - } // Note for deposit tx there is no ETH refunded for unused gas, but that's taken care of by the fact that gasPrice // is always 0 for deposit tx. So calling refundGas will ensure the gasUsed accounting is correct without actually diff --git a/core/types/dag.go b/core/types/dag.go index 801ec476a2..f5ec17735f 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -18,6 +18,11 @@ const ( PlainTxDAGType ) +var ( + TxDAGRelation0 uint8 = 0 + TxDAGRelation1 uint8 = 1 +) + type TxDAG interface { // Type return TxDAG type Type() byte @@ -93,8 +98,8 @@ func (d *EmptyTxDAG) DelayGasDistribution() bool { func (d *EmptyTxDAG) TxDep(int) TxDep { return TxDep{ - Relation: 1, TxIndexes: nil, + Relation: &TxDAGRelation1, } } @@ -156,9 +161,12 @@ func NewPlainTxDAG(txLen int) *PlainTxDAG { func (d *PlainTxDAG) String() string { builder := strings.Builder{} - exePaths := travelTxDAGExecutionPaths(d) - for _, path := range exePaths { - builder.WriteString(fmt.Sprintf("%v\n", path)) + for _, txDep := range d.TxDeps { + if txDep.Relation == nil || txDep.RelationEqual(TxDAGRelation0) { + builder.WriteString(fmt.Sprintf("%v\n", txDep.TxIndexes)) + continue + } + builder.WriteString(fmt.Sprintf("%d: %v\n", *txDep.Relation, txDep.TxIndexes)) } return builder.String() } @@ -174,11 +182,12 @@ func (d *PlainTxDAG) Size() int { // MergeTxDAGExecutionPaths will merge duplicate tx path for scheduling parallel. // Any tx cannot exist in >= 2 paths. func MergeTxDAGExecutionPaths(d TxDAG) [][]uint64 { - mergeMap := make(map[uint64][]uint64, d.TxCount()) - txMap := make(map[uint64]uint64, d.TxCount()) - for i := d.TxCount() - 1; i >= 0; i-- { + nd := convert2PlainTxDAGWithRelation0(d) + mergeMap := make(map[uint64][]uint64, nd.TxCount()) + txMap := make(map[uint64]uint64, nd.TxCount()) + for i := nd.TxCount() - 1; i >= 0; i-- { index, merge := uint64(i), uint64(i) - deps := d.TxDep(i).TxIndexes + deps := nd.TxDep(i).TxIndexes if oldIdx, exist := findTxPathIndex(deps, index, txMap); exist { merge = oldIdx } @@ -196,7 +205,7 @@ func MergeTxDAGExecutionPaths(d TxDAG) [][]uint64 { mergeMap[t] = append(mergeMap[t], f) } mergePaths := make([][]uint64, 0, len(mergeMap)) - for i := 0; i < d.TxCount(); i++ { + for i := 0; i < nd.TxCount(); i++ { path, ok := mergeMap[uint64(i)] if !ok { continue @@ -224,37 +233,46 @@ func findTxPathIndex(path []uint64, cur uint64, txMap map[uint64]uint64) (uint64 // travelTxDAGExecutionPaths will print all tx execution path func travelTxDAGExecutionPaths(d TxDAG) [][]uint64 { - txCount := d.TxCount() - deps := make([]TxDep, txCount) - for i := 0; i < txCount; i++ { + nd := convert2PlainTxDAGWithRelation0(d) + + exePaths := make([][]uint64, 0) + // travel tx deps with BFS + for i := uint64(0); i < uint64(nd.TxCount()); i++ { + exePaths = append(exePaths, travelTxDAGTargetPath(nd.TxDeps, i)) + } + return exePaths +} + +func convert2PlainTxDAGWithRelation0(d TxDAG) *PlainTxDAG { + if d.TxCount() == 0 { + return NewPlainTxDAG(0) + } + nd := NewPlainTxDAG(d.TxCount()) + for i := 0; i < d.TxCount(); i++ { dep := d.TxDep(i) - if dep.Relation == 0 { - deps[i] = dep + if dep.RelationEqual(TxDAGRelation0) { + nd.SetTxDep(i, dep) continue } + np := TxDep{} // recover to relation 0 for j := 0; j < i; j++ { - if !dep.Exist(j) { - deps[i].AppendDep(j) + if !dep.Exist(j) && j != i { + np.AppendDep(j) } } + nd.SetTxDep(i, np) } - - exePaths := make([][]uint64, 0) - // travel tx deps with BFS - for i := uint64(0); i < uint64(txCount); i++ { - exePaths = append(exePaths, travelTxDAGTargetPath(deps, i)) - } - return exePaths + return nd } // TxDep store the current tx dependency relation with other txs type TxDep struct { + TxIndexes []uint64 // It describes the Relation with below txs - // 0: this tx depends on below txs + // 0: this tx depends on below txs, it can be ignored and not be encoded in rlp encoder. // 1: this transaction does not depend on below txs, all other previous txs depend on - Relation uint8 - TxIndexes []uint64 + Relation *uint8 `rlp:"optional"` } func (d *TxDep) AppendDep(i int) { @@ -282,6 +300,13 @@ func (d *TxDep) Last() int { return int(d.TxIndexes[len(d.TxIndexes)-1]) } +func (d *TxDep) RelationEqual(rel uint8) bool { + if d.Relation == nil { + return TxDAGRelation0 == rel + } + return *d.Relation == rel +} + var ( longestTimeTimer = metrics.NewRegisteredTimer("dag/longesttime", nil) longestGasTimer = metrics.NewRegisteredTimer("dag/longestgas", nil) diff --git a/core/types/dag_test.go b/core/types/dag_test.go index 1c0b334a8d..ead8b1cda5 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -1,6 +1,7 @@ package types import ( + "encoding/hex" "testing" "time" @@ -15,32 +16,32 @@ var ( mockHash = common.HexToHash("0xdc13f8d7bdb8ec4de02cd4a50a1aa2ab73ec8814e0cdb550341623be3dd8ab7a") ) -func TestTxDAG(t *testing.T) { +func TestTxDAG_SetTxDep(t *testing.T) { dag := mockSimpleDAG() require.NoError(t, dag.SetTxDep(9, TxDep{ - Relation: 1, + Relation: &TxDAGRelation1, TxIndexes: nil, })) require.NoError(t, dag.SetTxDep(10, TxDep{ - Relation: 1, + Relation: &TxDAGRelation1, TxIndexes: nil, })) require.Error(t, dag.SetTxDep(12, TxDep{ - Relation: 1, + Relation: &TxDAGRelation1, TxIndexes: nil, })) dag = NewEmptyTxDAG() require.NoError(t, dag.SetTxDep(0, TxDep{ - Relation: 1, + Relation: &TxDAGRelation1, TxIndexes: nil, })) require.NoError(t, dag.SetTxDep(11, TxDep{ - Relation: 1, + Relation: &TxDAGRelation1, TxIndexes: nil, })) } -func TestTxDAG_SetTxDep(t *testing.T) { +func TestTxDAG(t *testing.T) { dag := mockSimpleDAG() t.Log(dag) dag = mockSystemTxDAG() @@ -53,7 +54,8 @@ func TestEvaluateTxDAG(t *testing.T) { for i := 0; i < dag.TxCount(); i++ { stats[i] = NewExeStat(i).WithGas(uint64(i)).WithRead(i) stats[i].costTime = time.Duration(i) - if dag.TxDep(i).Relation == 1 { + txDep := dag.TxDep(i) + if txDep.RelationEqual(TxDAGRelation1) { stats[i].WithSerialFlag() } } @@ -61,12 +63,36 @@ func TestEvaluateTxDAG(t *testing.T) { } func TestMergeTxDAGExecutionPaths_Simple(t *testing.T) { - paths := MergeTxDAGExecutionPaths(mockSimpleDAG()) - require.Equal(t, [][]uint64{ - {0, 3, 4}, - {1, 2, 5, 6, 7}, - {8, 9}, - }, paths) + tests := []struct { + d TxDAG + expect [][]uint64 + }{ + { + d: mockSimpleDAG(), + expect: [][]uint64{ + {0, 3, 4}, + {1, 2, 5, 6, 7}, + {8, 9}, + }, + }, + { + d: mockSimpleDAGWithLargeDeps(), + expect: [][]uint64{ + {5, 6}, + {0, 1, 2, 3, 4, 7, 8, 9}, + }, + }, + { + d: mockSystemTxDAGWithLargeDeps(), + expect: [][]uint64{ + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, + }, + }, + } + for i, item := range tests { + paths := MergeTxDAGExecutionPaths(item.d) + require.Equal(t, item.expect, paths, i) + } } func TestMergeTxDAGExecutionPaths_Random(t *testing.T) { @@ -105,10 +131,29 @@ func mockSimpleDAG() TxDAG { return dag } +func mockSimpleDAGWithLargeDeps() TxDAG { + dag := NewPlainTxDAG(10) + dag.TxDeps[0].TxIndexes = []uint64{} + dag.TxDeps[1].TxIndexes = []uint64{} + dag.TxDeps[2].TxIndexes = []uint64{} + dag.TxDeps[3].TxIndexes = []uint64{0} + dag.TxDeps[4].TxIndexes = []uint64{0} + dag.TxDeps[5].TxIndexes = []uint64{} + dag.TxDeps[6].TxIndexes = []uint64{5} + dag.TxDeps[7].TxIndexes = []uint64{2, 4} + dag.TxDeps[8].TxIndexes = []uint64{} + //dag.TxDeps[9].TxIndexes = []uint64{0, 1, 3, 4, 8} + dag.TxDeps[9] = TxDep{ + Relation: &TxDAGRelation1, + TxIndexes: []uint64{2, 5, 6, 7}, + } + return dag +} + func mockRandomDAG(txLen int) TxDAG { dag := NewPlainTxDAG(txLen) for i := 0; i < txLen; i++ { - var deps []uint64 + deps := make([]uint64, 0) if i == 0 || rand.Bool() { dag.TxDeps[i].TxIndexes = deps continue @@ -144,31 +189,154 @@ func mockSystemTxDAG() TxDAG { dag.TxDeps[8].TxIndexes = []uint64{} dag.TxDeps[9].TxIndexes = []uint64{8} dag.TxDeps[10] = TxDep{ - Relation: 1, + Relation: &TxDAGRelation1, TxIndexes: []uint64{}, } dag.TxDeps[11] = TxDep{ - Relation: 1, + Relation: &TxDAGRelation1, + TxIndexes: []uint64{}, + } + return dag +} + +func mockSystemTxDAG2() TxDAG { + dag := NewPlainTxDAG(12) + dag.TxDeps[0] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{}, + } + dag.TxDeps[1] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{}, + } + dag.TxDeps[2] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{}, + } + dag.TxDeps[3] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{0}, + } + dag.TxDeps[4] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{0}, + } + dag.TxDeps[5] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{1, 2}, + } + dag.TxDeps[6] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{2, 5}, + } + dag.TxDeps[7] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{6}, + } + dag.TxDeps[8] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{}, + } + dag.TxDeps[9] = TxDep{ + Relation: &TxDAGRelation0, + TxIndexes: []uint64{8}, + } + dag.TxDeps[10] = TxDep{ + Relation: &TxDAGRelation1, + TxIndexes: []uint64{}, + } + dag.TxDeps[11] = TxDep{ + Relation: &TxDAGRelation1, + TxIndexes: []uint64{}, + } + return dag +} + +func mockSystemTxDAGWithLargeDeps() TxDAG { + dag := NewPlainTxDAG(12) + dag.TxDeps[0].TxIndexes = []uint64{} + dag.TxDeps[1].TxIndexes = []uint64{} + dag.TxDeps[2].TxIndexes = []uint64{} + dag.TxDeps[3].TxIndexes = []uint64{0} + dag.TxDeps[4].TxIndexes = []uint64{0} + dag.TxDeps[5].TxIndexes = []uint64{1, 2} + dag.TxDeps[6].TxIndexes = []uint64{2, 5} + dag.TxDeps[7].TxIndexes = []uint64{0, 1, 3, 5, 6} + dag.TxDeps[8].TxIndexes = []uint64{} + //dag.TxDeps[9].TxIndexes = []uint64{0, 1, 2, 3, 4, 8} + dag.TxDeps[9] = TxDep{ + Relation: &TxDAGRelation1, + TxIndexes: []uint64{5, 6, 7, 10, 11}, + } + dag.TxDeps[10] = TxDep{ + Relation: &TxDAGRelation1, + TxIndexes: []uint64{}, + } + dag.TxDeps[11] = TxDep{ + Relation: &TxDAGRelation1, TxIndexes: []uint64{}, } return dag } func TestTxDAG_Encode_Decode(t *testing.T) { - expected := TxDAG(&EmptyTxDAG{}) - enc, err := EncodeTxDAG(expected) - require.NoError(t, err) - actual, err := DecodeTxDAG(enc) - require.NoError(t, err) - require.Equal(t, expected, actual) - - expected = mockSimpleDAG() - enc, err = EncodeTxDAG(expected) - require.NoError(t, err) - actual, err = DecodeTxDAG(enc) - require.NoError(t, err) - require.Equal(t, expected, actual) - enc[0] = 2 - _, err = DecodeTxDAG(enc) - require.Error(t, err) + tests := []struct { + expect TxDAG + }{ + { + expect: TxDAG(&EmptyTxDAG{}), + }, + { + expect: mockSimpleDAG(), + }, + { + expect: mockRandomDAG(100), + }, + { + expect: mockSystemTxDAG(), + }, + { + expect: mockSystemTxDAG2(), + }, + { + expect: mockSystemTxDAGWithLargeDeps(), + }, + } + for i, item := range tests { + enc, err := EncodeTxDAG(item.expect) + t.Log(hex.EncodeToString(enc)) + require.NoError(t, err, i) + actual, err := DecodeTxDAG(enc) + require.NoError(t, err, i) + require.Equal(t, item.expect, actual, i) + if i%2 == 0 { + enc[0] = 2 + _, err = DecodeTxDAG(enc) + require.Error(t, err) + } + } +} + +func TestDecodeTxDAG(t *testing.T) { + tests := []struct { + enc string + err bool + }{ + {"00c0", false}, + {"01dddcc1c0c1c0c1c0c2c180c2c180c3c20102c3c20205c2c106c1c0c2c108", false}, + {"01e3e2c1c0c1c0c1c0c2c180c2c180c3c20102c3c20205c2c106c1c0c2c108c2c001c2c001", false}, + {"0132e212", true}, + {"01dfdec280c0c280c0c380c101c380c102c380c103c380c104c380c105c380c106", true}, + } + for i, item := range tests { + enc, err := hex.DecodeString(item.enc) + require.NoError(t, err, i) + txDAG, err := DecodeTxDAG(enc) + if item.err { + require.Error(t, err, i) + continue + } + require.NoError(t, err, i) + t.Log(txDAG) + } } diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 85d3886470..bfcaf13613 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -446,28 +446,43 @@ func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*RWItem, write } // ResolveTxDAG generate TxDAG from RWSets -func (s *MVStates) ResolveTxDAG(gasFeeReceivers []common.Address) TxDAG { - rwSets := s.RWSets() - txDAG := NewPlainTxDAG(len(rwSets)) - for i := len(rwSets) - 1; i >= 0; i-- { +func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (TxDAG, error) { + s.lock.RLock() + defer s.lock.RUnlock() + if len(s.rwSets) != txCnt { + return nil, fmt.Errorf("wrong rwSet count, expect: %v, actual: %v", txCnt, len(s.rwSets)) + } + txDAG := NewPlainTxDAG(len(s.rwSets)) + for i := txCnt - 1; i >= 0; i-- { // check if there are RW with gas fee receiver for gas delay calculation for _, addr := range gasFeeReceivers { - if _, ok := rwSets[i].readSet[AccountStateKey(addr, AccountSelf)]; ok { - return NewEmptyTxDAG() + if _, ok := s.rwSets[i].readSet[AccountStateKey(addr, AccountSelf)]; ok { + return NewEmptyTxDAG(), nil } } txDAG.TxDeps[i].TxIndexes = []uint64{} - if rwSets[i].mustSerial { - txDAG.TxDeps[i].Relation = 1 + if s.rwSets[i].mustSerial { + txDAG.TxDeps[i].Relation = &TxDAGRelation1 continue } if s.depsCache[i] == nil { - s.resolveDepsCache(i, rwSets[i]) + s.resolveDepsCache(i, s.rwSets[i]) + } + deps := s.depsCache[i].toArray() + if len(deps) <= (txCnt-1)/2 { + txDAG.TxDeps[i].TxIndexes = deps + continue + } + // if tx deps larger than half of txs, then convert to relation1 + txDAG.TxDeps[i].Relation = &TxDAGRelation1 + for j := uint64(0); j < uint64(txCnt); j++ { + if !slices.Contains(deps, j) && j != uint64(i) { + txDAG.TxDeps[i].TxIndexes = append(txDAG.TxDeps[i].TxIndexes, j) + } } - txDAG.TxDeps[i].TxIndexes = s.depsCache[i].toArray() } - return txDAG + return txDAG, nil } func checkDependency(writeSet map[RWKey]*RWItem, readSet map[RWKey]*RWItem) bool { diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 9d4422bf1f..9dcd83a6ba 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -39,7 +39,7 @@ func TestMVStates_BasicUsage(t *testing.T) { require.Equal(t, NewRWItem(StateVersion{TxIndex: 3}, 3), ms.ReadState(5, str2key("0x00"))) } -func TestSimpleMVStates2TxDAG(t *testing.T) { +func TestMVStates_SimpleResolveTxDAG(t *testing.T) { ms := NewMVStates(10) ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) @@ -53,12 +53,13 @@ func TestSimpleMVStates2TxDAG(t *testing.T) { ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) - dag := ms.ResolveTxDAG(nil) + dag, err := ms.ResolveTxDAG(10, nil) + require.NoError(t, err) require.Equal(t, mockSimpleDAG(), dag) t.Log(dag) } -func TestSystemTxMVStates2TxDAG(t *testing.T) { +func TestMVStates_SystemTxResolveTxDAG(t *testing.T) { ms := NewMVStates(12) ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) @@ -74,11 +75,34 @@ func TestSystemTxMVStates2TxDAG(t *testing.T) { ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithSerialFlag() ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithSerialFlag() - dag := ms.ResolveTxDAG(nil) + dag, err := ms.ResolveTxDAG(12, nil) + require.NoError(t, err) require.Equal(t, mockSystemTxDAG(), dag) t.Log(dag) } +func TestMVStates_SystemTxWithLargeDepsResolveTxDAG(t *testing.T) { + ms := NewMVStates(12) + + ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) + ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) + ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) + ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) + ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) + ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) + ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) + ms.rwSets[7] = mockRWSet(7, []string{"0x00", "0x01", "0x03", "0x05", "0x06", "0x07"}, []string{"0x07"}) + ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) + ms.rwSets[9] = mockRWSet(9, []string{"0x00", "0x01", "0x02", "0x03", "0x04", "0x08", "0x09"}, []string{"0x09"}) + ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithSerialFlag() + ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithSerialFlag() + + dag, err := ms.ResolveTxDAG(12, nil) + require.NoError(t, err) + require.Equal(t, mockSystemTxDAGWithLargeDeps(), dag) + t.Log(dag) +} + func TestIsEqualRWVal(t *testing.T) { tests := []struct { key RWKey From eaf7405c986060704561cf004203b09d6246e5a4 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Mon, 5 Aug 2024 09:47:53 +0800 Subject: [PATCH 19/72] contention issue fix (#21) * remove finalise * fix: update maindb txIndex after merge slotDB otherwise there can be issue that txIndex is load before the change in mergeSlotDB. * Fix: avoid update mainDB nonce in executeInSlot It should use slotDB, otherwise it cause the stateObjects change in mainDB, which cause racing issue. * Fix: remove stateDB update during conflict check stateDB.getState() will update the stateDB's stateObjects, which should not be updated for the purpose of state read for conflict check. --------- Co-authored-by: Sunny --- core/parallel_state_processor.go | 4 +- core/state/parallel_statedb.go | 6 +- core/state/state_object.go | 95 +++++++++++++++++++++++++++++++- core/state/statedb.go | 18 ++++-- 4 files changed, 111 insertions(+), 12 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index cc50e1b82e..b99d7de5e0 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -330,7 +330,7 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR on := txReq.tx.Nonce() if txReq.msg.IsDepositTx && p.config.IsOptimismRegolith(vmenv.Context.Time) { - on = txReq.baseStateDB.GetNonce(txReq.msg.From) + on = slotDB.GetNonce(txReq.msg.From) } slotDB.SetTxContext(txReq.tx.Hash(), txReq.txIndex) @@ -365,7 +365,7 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR conflictIndex = txReq.conflictIndex.Load() if conflictIndex < mIndex { if txReq.conflictIndex.CompareAndSwap(conflictIndex, mIndex) { - log.Debug("Update conflictIndex in execution because of error, new conflictIndex: %d", conflictIndex) + log.Debug(fmt.Sprintf("Update conflictIndex in execution because of error: %s, new conflictIndex: %d", err.Error(), conflictIndex)) } } atomic.CompareAndSwapInt32(&txReq.runnable, 0, 1) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index ff0d05a5f9..c3a049a750 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -93,7 +93,7 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash } } } - valMain := mainDB.GetState(addr, key) + valMain := mainDB.GetStateNoUpdate(addr, key) if !bytes.Equal(val.Bytes(), valMain.Bytes()) { log.Debug("hasKvConflict is invalid", "addr", addr, @@ -1238,6 +1238,8 @@ func (s *ParallelStateDB) getKVFromUnconfirmedDB(addr common.Address, key common if obj.deleted || obj.selfDestructed { return common.Hash{}, true } + // The dirty object in unconfirmed DB will never be finalised and changed after execution. + // So no storageRecordsLock requried. if val, exist := obj.dirtyStorage.GetValue(key); exist { return val, true } @@ -1594,8 +1596,8 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S if !s.isParallel || !s.parallel.isSlotDB { obj.finalise(true) // Prefetch slots in the background } else { + // don't do finalise() here as to keep dirtyObjects unchanged in dirtyStorages, which avoid contention issue. obj.fixUpOriginAndResetPendingStorage() - obj.finalise(false) } } diff --git a/core/state/state_object.go b/core/state/state_object.go index 67e1b06205..161d2a81cb 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -861,8 +861,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { } object.code = s.code - - // The lock is unnecessary since deepCopy only invoked at global phase. No concurrent racing. + // The lock is unnecessary since deepCopy only invoked at global phase and with dirty object that never changed. object.dirtyStorage = s.dirtyStorage.Copy() object.originStorage = s.originStorage.Copy() object.pendingStorage = s.pendingStorage.Copy() @@ -883,6 +882,17 @@ func (s *stateObject) MergeSlotObject(db Database, dirtyObjs *stateObject, keys // But here, it should be ok, since the KV should be changed and valid in the SlotDB, s.setState(key, dirtyObjs.GetState(key)) } + + // The dirtyObject may have new state accessed from Snap and Trie, so merge the origins. + dirtyObjs.originStorage.Range(func(keyItf, valueItf interface{}) bool { + key := keyItf.(common.Hash) + value := valueItf.(common.Hash) + // Skip noop changes, persist actual changes + if _, ok := s.originStorage.GetValue(key); !ok { + s.originStorage.StoreValue(key, value) + } + return true + }) } // @@ -982,6 +992,87 @@ func (s *stateObject) Root() common.Hash { return s.data.Root } +// GetStateNoUpdate retrieves a value from the account storage trie, but never update the stateDB cache +func (s *stateObject) GetStateNoUpdate(key common.Hash) common.Hash { + // If we have a dirty value for this state entry, return it + value, dirty := s.dirtyStorage.GetValue(key) + if dirty { + return value + } + // Otherwise return the entry's original value + result := s.GetCommittedStateNoUpdate(key) + return result +} + +// GetCommittedStateNoUpdate retrieves a value from the committed account storage trie, but never update the +// stateDB cache (object.originStorage) +func (s *stateObject) GetCommittedStateNoUpdate(key common.Hash) common.Hash { + // If we have a pending write or clean cached, return that + // if value, pending := s.pendingStorage[key]; pending { + if value, pending := s.pendingStorage.GetValue(key); pending { + return value + } + if value, cached := s.originStorage.GetValue(key); cached { + return value + } + + // If the object was destructed in *this* block (and potentially resurrected), + // the storage has been cleared out, and we should *not* consult the previous + // database about any storage values. The only possible alternatives are: + // 1) resurrect happened, and new slot values were set -- those should + // have been handles via pendingStorage above. + // 2) we don't have new values, and can deliver empty response back + //if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { + s.db.stateObjectDestructLock.RLock() + if _, destructed := s.db.getStateObjectsDegetstruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? + s.db.stateObjectDestructLock.RUnlock() + return common.Hash{} + } + s.db.stateObjectDestructLock.RUnlock() + + // If no live objects are available, attempt to use snapshots + var ( + enc []byte + err error + value common.Hash + ) + if s.db.snap != nil { + start := time.Now() + enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes())) + if metrics.EnabledExpensive { + s.db.SnapshotStorageReads += time.Since(start) + } + if len(enc) > 0 { + _, content, _, err := rlp.Split(enc) + if err != nil { + s.db.setError(err) + } + value.SetBytes(content) + } + } + // If the snapshot is unavailable or reading from it fails, load from the database. + if s.db.snap == nil || err != nil { + start := time.Now() + tr, err := s.getTrie() + if err != nil { + s.db.setError(err) + return common.Hash{} + } + s.db.trieParallelLock.Lock() + val, err := tr.GetStorage(s.address, key.Bytes()) + s.db.trieParallelLock.Unlock() + if metrics.EnabledExpensive { + s.db.StorageReads += time.Since(start) + } + if err != nil { + s.db.setError(err) + return common.Hash{} + } + value.SetBytes(val) + } + return value +} + // fixUpOriginAndResetPendingStorage is used for slot object only, the target is to fix up the origin storage of the // object with the latest mainDB. And reset the pendingStorage as the execution recorded the changes in dirty and the // dirties will be merged to pending at finalise. so the current pendingStorage contains obsoleted info mainly from diff --git a/core/state/statedb.go b/core/state/statedb.go index 8ee3df823e..7fbcef7171 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -826,6 +826,15 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { return nil } +// GetStateNoUpdate retrieves a value from the given account's storage trie, but do not update the db.stateObjects cache. +func (s *StateDB) GetStateNoUpdate(addr common.Address, hash common.Hash) (ret common.Hash) { + object := s.getStateObjectNoUpdate(addr) + if object != nil { + return object.GetStateNoUpdate(hash) + } + return common.Hash{} +} + // getStateObjectNoUpdate is similar with getStateObject except that it does not // update stateObjects records. func (s *StateDB) getStateObjectNoUpdate(addr common.Address) *stateObject { @@ -837,8 +846,6 @@ func (s *StateDB) getStateObjectNoUpdate(addr common.Address) *stateObject { } func (s *StateDB) getDeletedStateObjectNoUpdate(addr common.Address) *stateObject { - s.RecordRead(types.AccountStateKey(addr, types.AccountSelf), struct{}{}) - // Prefer live objects if any is available if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { return obj @@ -848,7 +855,6 @@ func (s *StateDB) getDeletedStateObjectNoUpdate(addr common.Address) *stateObjec if !ok { return nil } - // Insert into the live set obj := newObject(s, s.isParallel, addr, data) return obj } @@ -2593,6 +2599,7 @@ func (s *StateDB) AddrPrefetch(slotDb *ParallelStateDB) { if obj.deleted { continue } + obj.storageRecordsLock.RLock() // copied from obj.finalise(true) slotsToPrefetch := make([][]byte, 0, obj.dirtyStorage.Length()) obj.dirtyStorage.Range(func(key, value interface{}) bool { @@ -2603,6 +2610,7 @@ func (s *StateDB) AddrPrefetch(slotDb *ParallelStateDB) { } return true }) + obj.storageRecordsLock.RUnlock() if s.prefetcher != nil && len(slotsToPrefetch) > 0 { s.prefetcher.prefetch(obj.addrHash, obj.data.Root, obj.address, slotsToPrefetch) } @@ -2620,8 +2628,6 @@ func (s *StateDB) AddrPrefetch(slotDb *ParallelStateDB) { // merged back to the main StateDB. func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receipt, txIndex int, fees *DelayedGasFee) *StateDB { - s.SetTxContext(slotDb.thash, slotDb.txIndex) - for s.nextRevisionId < slotDb.nextRevisionId { if len(slotDb.validRevisions) > 0 { r := slotDb.validRevisions[s.nextRevisionId] @@ -2823,7 +2829,7 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip s.snapParallelLock.Unlock() } } - + s.SetTxContext(slotDb.thash, slotDb.txIndex) return s } From 92b02c9e5fbf1b78bd1da7e5e6e16163ed7761b9 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:50:18 +0800 Subject: [PATCH 20/72] txdag: support multi flags, and supported in pevm; (#22) * txdag: add excluded flag; mvstates: generate txdag with excluded flag; * pevm: support txdag with excluded tx; * blockchain: opt txdag file mode; * pevm: fix dispatch bugs; * pevm: opt txdag dispatch; --------- Co-authored-by: galaio --- core/blockchain.go | 5 +- core/blockchain_test.go | 15 +- core/parallel_state_processor.go | 122 ++++++++-------- core/state/statedb.go | 18 ++- core/state_processor.go | 4 + core/state_transition.go | 6 +- core/types/dag.go | 133 ++++++++++++----- core/types/dag_test.go | 241 ++++++++++++++++++------------- core/types/mvstates.go | 28 ++-- core/types/mvstates_test.go | 8 +- tests/block_test.go | 24 ++- 11 files changed, 366 insertions(+), 238 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 2daedfd74f..dafd11afe1 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -97,7 +97,6 @@ var ( innerExecutionTimer = metrics.NewRegisteredTimer("chain/inner/execution", nil) txDAGGenerateTimer = metrics.NewRegisteredTimer("chain/block/txdag/gen", nil) - txDAGDispatchTimer = metrics.NewRegisteredTimer("chain/block/txdag/dispatch", nil) blockGasUsedGauge = metrics.NewRegisteredGauge("chain/block/gas/used", nil) mgaspsGauge = metrics.NewRegisteredGauge("chain/mgas/ps", nil) @@ -2708,9 +2707,9 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string) { // write handler go func() { - writeHandle, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) + writeHandle, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { - log.Error("OpenFile when open the txDAG output file", "file", output) + log.Error("OpenFile when open the txDAG output file", "file", output, "err", err) return } bc.txDAGWriteCh = make(chan TxDAGOutputItem, 10000) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index d3fd06751f..97932eaca3 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4723,12 +4723,15 @@ func TestEIP3651(t *testing.T) { func TestTxDAGFile_ReadWrite(t *testing.T) { path := filepath.Join(os.TempDir(), "test.csv") + defer func() { + os.Remove(path) + }() except := map[uint64]types.TxDAG{ 0: types.NewEmptyTxDAG(), 1: makeEmptyPlainTxDAG(1), - 2: makeEmptyPlainTxDAG(2), + 2: makeEmptyPlainTxDAG(2, types.NonDependentRelFlag), } - writeFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) + writeFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) require.NoError(t, err) for num, dag := range except { require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: dag})) @@ -4737,9 +4740,9 @@ func TestTxDAGFile_ReadWrite(t *testing.T) { except2 := map[uint64]types.TxDAG{ 3: types.NewEmptyTxDAG(), - 4: makeEmptyPlainTxDAG(4), + 4: makeEmptyPlainTxDAG(4, types.NonDependentRelFlag, types.ExcludedTxFlag), } - writeFile, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) + writeFile, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) require.NoError(t, err) for num, dag := range except2 { require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: dag})) @@ -4756,10 +4759,10 @@ func TestTxDAGFile_ReadWrite(t *testing.T) { } } -func makeEmptyPlainTxDAG(cnt int) *types.PlainTxDAG { +func makeEmptyPlainTxDAG(cnt int, flags ...uint8) *types.PlainTxDAG { dag := types.NewPlainTxDAG(cnt) for i := range dag.TxDeps { - dag.TxDeps[i].TxIndexes = make([]uint64, 0) + dag.TxDeps[i] = types.NewTxDep(make([]uint64, 0), flags...) } return dag } diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index b99d7de5e0..27e1b5eb05 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -6,7 +6,6 @@ import ( "runtime" "sync" "sync/atomic" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" @@ -16,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" ) @@ -49,7 +47,6 @@ type ParallelStateProcessor struct { inConfirmStage2 bool targetStage2Count int // when executed txNUM reach it, enter stage2 RT confirm nextStage2TxIndex int - disableStealTx bool delayGasFee bool // it is provided by TxDAG } @@ -180,45 +177,6 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { p.nextStage2TxIndex = 0 } -// doStaticDispatchV2 could dispatch by TxDAG metadata -// txReqs must order by TxIndex -// txDAG must convert to dependency relation -// 1. The TxDAG generates parallel execution merge paths that will ignore cross slot tx dep; -// 2. It will dispatch the most hungry slot for every isolate execution path; -// 3. TODO(galaio) it need to schedule the slow dep tx path properly; -// 4. TODO(galaio) it is unfriendly for cross slot deps, maybe we can delay dispatch when tx cross in slots, it may increase PEVM parallelism; -func (p *ParallelStateProcessor) doStaticDispatchV2(txReqs []*ParallelTxRequest, txDAG types.TxDAG) { - p.disableStealTx = false - p.delayGasFee = false - // only support PlainTxDAG dispatch now. - if txDAG == nil || txDAG.Type() != types.PlainTxDAGType { - p.doStaticDispatch(txReqs) - return - } - - if metrics.EnabledExpensive { - defer func(start time.Time) { - txDAGDispatchTimer.Update(time.Since(start)) - }(time.Now()) - } - // resolve isolate execution paths from TxDAG, it indicates the tx dispatch - paths := types.MergeTxDAGExecutionPaths(txDAG) - log.Debug("doStaticDispatchV2 merge parallel execution paths", "slots", len(p.slotState), "paths", len(paths)) - - for _, path := range paths { - slotIndex := p.mostHungrySlot() - for _, index := range path { - txReqs[index].staticSlotIndex = slotIndex // txReq is better to be executed in this slot - slot := p.slotState[slotIndex] - slot.pendingTxReqList = append(slot.pendingTxReqList, txReqs[index]) - } - } - - // it's unnecessary to enable slot steal mechanism, opt the steal mechanism later; - p.disableStealTx = true - p.delayGasFee = true -} - // Benefits of StaticDispatch: // // ** try best to make Txs with same From() in same slot @@ -555,7 +513,7 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { // fmt.Printf("Dav -- runInLoop, - loopbody tail - TxREQ: %d\n", txReq.txIndex) } // switched to the other slot. - if interrupted || p.disableStealTx { + if interrupted { continue } @@ -742,11 +700,8 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat misc.EnsureCreate2Deployer(p.config, block.Time(), statedb) - txNum := len(block.Transactions()) - p.resetState(txNum, statedb) - - // Iterate over and process the individual transactions - commonTxs := make([]*types.Transaction, 0, txNum) + allTxs := block.Transactions() + p.resetState(len(allTxs), statedb) var ( // with parallel mode, vmenv will be created inside of slot @@ -758,10 +713,47 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat if beaconRoot := block.BeaconRoot(); beaconRoot != nil { ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } - statedb.MarkFullProcessed() + + var ( + txDAG types.TxDAG + ) + if p.bc.enableTxDAG { + // TODO(galaio): load TxDAG from block + // or load cache txDAG from file + if txDAG == nil && len(p.bc.txDAGMapping) > 0 { + txDAG = p.bc.txDAGMapping[block.NumberU64()] + } + if txDAG != nil && txDAG.TxCount() != len(block.Transactions()) { + log.Warn("parallel process cannot apply the TxDAG with wrong txs length", + "block", block.NumberU64(), "txs", len(block.Transactions()), "txdag", txDAG.TxCount()) + txDAG = nil + } + // TODO(galaio): check TxDAG validation & excludedTxs in head and continuous + // we only support this format + // convert to normal plain txdag + //parallelIndex := -1 + //if txDAG != nil && txDAG.Type() == types.PlainTxDAGType { + // for i := range allTxs { + // if !txDAG.TxDep(i).CheckFlag(types.ExcludedTxFlag) { + // if parallelIndex == -1 { + // parallelIndex = i + // } + // continue + // } + // if i > 0 && !txDAG.TxDep(i-1).CheckFlag(types.ExcludedTxFlag) { + // return nil, nil, 0, errors.New("cannot support non-continuous excludedTxs") + // } + // } + //} + } + + txNum := len(allTxs) + latestExcludedTx := -1 + // Iterate over and process the individual transactions + commonTxs := make([]*types.Transaction, 0, txNum) // var txReqs []*ParallelTxRequest - for i, tx := range block.Transactions() { + for i, tx := range allTxs { // can be moved it into slot for efficiency, but signer is not concurrent safe // Parallel Execution 1.0&2.0 is for full sync mode, Nonce PreCheck is not necessary // And since we will do out-of-order execution, the Nonce PreCheck could fail. @@ -771,6 +763,15 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } + // find the latestDepTx from TxDAG or latestExcludedTx + latestDepTx := -1 + if txDAG != nil && txDAG.TxDep(i).Count() > 0 { + latestDepTx = int(txDAG.TxDep(i).TxIndexes[txDAG.TxDep(i).Count()-1]) + } + if latestDepTx < latestExcludedTx { + latestDepTx = latestExcludedTx + } + // parallel start, wrap an exec message, which will be dispatched to a slot txReq := &ParallelTxRequest{ txIndex: i, @@ -789,7 +790,13 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat } txReq.executedNum.Store(0) txReq.conflictIndex.Store(-2) + if latestDepTx >= 0 { + txReq.conflictIndex.Store(int32(latestDepTx)) + } p.allTxReqs = append(p.allTxReqs, txReq) + if txDAG != nil && txDAG.TxDep(i).CheckFlag(types.ExcludedTxFlag) { + latestExcludedTx = i + } } // set up stage2 enter criteria p.targetStage2Count = len(p.allTxReqs) @@ -799,18 +806,11 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat p.targetStage2Count = p.targetStage2Count - stage2AheadNum } - var ( - txDAG types.TxDAG - ) - if p.bc.enableTxDAG { - // TODO(galaio): load TxDAG from block - // or load cache txDAG from file - if txDAG == nil && len(p.bc.txDAGMapping) > 0 { - txDAG = p.bc.txDAGMapping[block.NumberU64()] - } + p.delayGasFee = false + p.doStaticDispatch(p.allTxReqs) + if txDAG != nil && txDAG.DelayGasFeeDistribution() { + p.delayGasFee = true } - // From now on, entering parallel execution. - p.doStaticDispatchV2(p.allTxReqs, txDAG) // todo: put txReqs in unit? // after static dispatch, we notify the slot to work. for _, slot := range p.slotState { diff --git a/core/state/statedb.go b/core/state/statedb.go index 7fbcef7171..4e5344245d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2412,7 +2412,7 @@ func (s *StateDB) RecordRead(key types.RWKey, val interface{}) { if s.isParallel && s.parallel.isSlotDB { return } - if s.rwSet == nil || s.rwSet.RWRecordDone() { + if s.rwSet == nil { return } s.rwSet.RecordRead(key, types.StateVersion{ @@ -2424,7 +2424,7 @@ func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) { if s.isParallel && s.parallel.isSlotDB { return } - if s.rwSet == nil || s.rwSet.RWRecordDone() { + if s.rwSet == nil { return } s.rwSet.RecordWrite(key, val) @@ -2442,9 +2442,11 @@ func (s *StateDB) FinaliseRWSet() error { if s.isParallel && s.parallel.isSlotDB { return nil } - if s.rwSet == nil || s.rwSet.RWRecordDone() { + if s.rwSet == nil { return nil } + rwSet := s.rwSet + stat := s.stat if metrics.EnabledExpensive { defer func(start time.Time) { s.TxDAGGenerate += time.Since(start) @@ -2453,7 +2455,7 @@ func (s *StateDB) FinaliseRWSet() error { ver := types.StateVersion{ TxIndex: s.txIndex, } - if ver != s.rwSet.Version() { + if ver != rwSet.Version() { return errors.New("you finalize a wrong ver of RWSet") } @@ -2480,8 +2482,10 @@ func (s *StateDB) FinaliseRWSet() error { } } - s.rwSet.SetRWRecordDone() - return s.mvStates.FulfillRWSet(s.rwSet, s.stat) + // reset stateDB + s.rwSet = nil + s.stat = nil + return s.mvStates.FulfillRWSet(rwSet, stat) } func (s *StateDB) getStateObjectsDegetstruct(addr common.Address) (*types.StateAccount, bool) { @@ -2554,7 +2558,7 @@ func (s *StateDB) RecordSystemTxRWSet(index int) { } s.mvStates.FulfillRWSet(types.NewRWSet(types.StateVersion{ TxIndex: index, - }).WithSerialFlag(), types.NewExeStat(index).WithSerialFlag()) + }).WithExcludedTxFlag(), types.NewExeStat(index).WithSerialFlag()) } // copySet returns a deep-copied set. diff --git a/core/state_processor.go b/core/state_processor.go index 7ac2d59497..8721fe62c8 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -109,6 +109,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } + // if systemTx or depositTx, tag it + if tx.IsSystemTx() || tx.IsDepositTx() { + statedb.RecordSystemTxRWSet(i) + } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) if metrics.EnabledExpensive { diff --git a/core/state_transition.go b/core/state_transition.go index 9542d43a53..571080faf8 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -418,6 +418,10 @@ func (st *StateTransition) preCheck() error { // However if any consensus issue encountered, return the error directly with // nil evm execution result. func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { + // start record rw set in here + if !st.msg.IsSystemTx && !st.msg.IsDepositTx { + st.state.BeforeTxTransition() + } if mint := st.msg.Mint; mint != nil { mintU256, overflow := uint256.FromBig(mint) if overflow { @@ -463,8 +467,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { - // start record rw set in here - st.state.BeforeTxTransition() // First check this message satisfies all consensus rules before // applying the message. The rules include these clauses // diff --git a/core/types/dag.go b/core/types/dag.go index f5ec17735f..79e00b3680 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -19,8 +19,12 @@ const ( ) var ( - TxDAGRelation0 uint8 = 0 - TxDAGRelation1 uint8 = 1 + // NonDependentRelFlag indicates that the txs described is non-dependent + // and is used to reduce storage when there are a large number of dependencies. + NonDependentRelFlag uint8 = 0x01 + // ExcludedTxFlag indicates that the tx is excluded from TxDAG, user should execute them in sequence. + // These excluded transactions should be consecutive in the head or tail. + ExcludedTxFlag uint8 = 0x02 ) type TxDAG interface { @@ -30,11 +34,11 @@ type TxDAG interface { // Inner return inner instance Inner() interface{} - // DelayGasDistribution check if delay the distribution of GasFee - DelayGasDistribution() bool + // DelayGasFeeDistribution check if delay the distribution of GasFee + DelayGasFeeDistribution() bool // TxDep query TxDeps from TxDAG - TxDep(int) TxDep + TxDep(int) *TxDep // TxCount return tx count TxCount() int @@ -92,15 +96,17 @@ func (d *EmptyTxDAG) Inner() interface{} { return d } -func (d *EmptyTxDAG) DelayGasDistribution() bool { +func (d *EmptyTxDAG) DelayGasFeeDistribution() bool { return false } -func (d *EmptyTxDAG) TxDep(int) TxDep { - return TxDep{ +func (d *EmptyTxDAG) TxDep(int) *TxDep { + dep := TxDep{ TxIndexes: nil, - Relation: &TxDAGRelation1, + Flags: new(uint8), } + dep.SetFlag(NonDependentRelFlag) + return &dep } func (d *EmptyTxDAG) TxCount() int { @@ -112,7 +118,7 @@ func (d *EmptyTxDAG) SetTxDep(int, TxDep) error { } func (d *EmptyTxDAG) String() string { - return "None" + return "EmptyTxDAG" } // PlainTxDAG indicate how to use the dependency of txs, and delay the distribution of GasFee @@ -129,12 +135,12 @@ func (d *PlainTxDAG) Inner() interface{} { return d } -func (d *PlainTxDAG) DelayGasDistribution() bool { +func (d *PlainTxDAG) DelayGasFeeDistribution() bool { return true } -func (d *PlainTxDAG) TxDep(i int) TxDep { - return d.TxDeps[i] +func (d *PlainTxDAG) TxDep(i int) *TxDep { + return &d.TxDeps[i] } func (d *PlainTxDAG) TxCount() int { @@ -162,11 +168,11 @@ func NewPlainTxDAG(txLen int) *PlainTxDAG { func (d *PlainTxDAG) String() string { builder := strings.Builder{} for _, txDep := range d.TxDeps { - if txDep.Relation == nil || txDep.RelationEqual(TxDAGRelation0) { - builder.WriteString(fmt.Sprintf("%v\n", txDep.TxIndexes)) + if txDep.Flags != nil { + builder.WriteString(fmt.Sprintf("%v|%v\n", txDep.TxIndexes, *txDep.Flags)) continue } - builder.WriteString(fmt.Sprintf("%d: %v\n", *txDep.Relation, txDep.TxIndexes)) + builder.WriteString(fmt.Sprintf("%v\n", txDep.TxIndexes)) } return builder.String() } @@ -181,13 +187,18 @@ func (d *PlainTxDAG) Size() int { // MergeTxDAGExecutionPaths will merge duplicate tx path for scheduling parallel. // Any tx cannot exist in >= 2 paths. -func MergeTxDAGExecutionPaths(d TxDAG) [][]uint64 { - nd := convert2PlainTxDAGWithRelation0(d) +func MergeTxDAGExecutionPaths(d TxDAG, from, to uint64) ([][]uint64, error) { + if from > to || to >= uint64(d.TxCount()) { + return nil, fmt.Errorf("input wrong from: %v, to: %v, txCnt:%v", from, to, d.TxCount()) + } + nd := convert2PlainTxDAG(d) mergeMap := make(map[uint64][]uint64, nd.TxCount()) txMap := make(map[uint64]uint64, nd.TxCount()) - for i := nd.TxCount() - 1; i >= 0; i-- { + for i := int(to); i >= int(from); i-- { index, merge := uint64(i), uint64(i) deps := nd.TxDep(i).TxIndexes + // drop the out range txs + deps = depExcludeTxRange(deps, from, to) if oldIdx, exist := findTxPathIndex(deps, index, txMap); exist { merge = oldIdx } @@ -202,11 +213,14 @@ func MergeTxDAGExecutionPaths(d TxDAG) [][]uint64 { if mergeMap[t] == nil { mergeMap[t] = make([]uint64, 0) } + if f < from || f > to { + continue + } mergeMap[t] = append(mergeMap[t], f) } mergePaths := make([][]uint64, 0, len(mergeMap)) - for i := 0; i < nd.TxCount(); i++ { - path, ok := mergeMap[uint64(i)] + for i := from; i <= to; i++ { + path, ok := mergeMap[i] if !ok { continue } @@ -214,7 +228,25 @@ func MergeTxDAGExecutionPaths(d TxDAG) [][]uint64 { mergePaths = append(mergePaths, path) } - return mergePaths + return mergePaths, nil +} + +// depExcludeTxRange drop all from~to items, and deps is ordered. +func depExcludeTxRange(deps []uint64, from uint64, to uint64) []uint64 { + if len(deps) == 0 { + return deps + } + start, end := 0, len(deps)-1 + for start < len(deps) && deps[start] < from { + start++ + } + for end >= 0 && deps[end] > to { + end-- + } + if start > end { + return nil + } + return deps[start : end+1] } func findTxPathIndex(path []uint64, cur uint64, txMap map[uint64]uint64) (uint64, bool) { @@ -233,7 +265,7 @@ func findTxPathIndex(path []uint64, cur uint64, txMap map[uint64]uint64) (uint64 // travelTxDAGExecutionPaths will print all tx execution path func travelTxDAGExecutionPaths(d TxDAG) [][]uint64 { - nd := convert2PlainTxDAGWithRelation0(d) + nd := convert2PlainTxDAG(d) exePaths := make([][]uint64, 0) // travel tx deps with BFS @@ -243,19 +275,21 @@ func travelTxDAGExecutionPaths(d TxDAG) [][]uint64 { return exePaths } -func convert2PlainTxDAGWithRelation0(d TxDAG) *PlainTxDAG { +// convert2PlainTxDAG will convert to PlainTxDAG with dependency txs +func convert2PlainTxDAG(d TxDAG) *PlainTxDAG { if d.TxCount() == 0 { return NewPlainTxDAG(0) } nd := NewPlainTxDAG(d.TxCount()) for i := 0; i < d.TxCount(); i++ { dep := d.TxDep(i) - if dep.RelationEqual(TxDAGRelation0) { - nd.SetTxDep(i, dep) + if !dep.CheckFlag(NonDependentRelFlag) { + nd.SetTxDep(i, *dep) continue } - np := TxDep{} - // recover to relation 0 + // recover to dependency relation txs + np := TxDep{Flags: dep.Flags} + np.ClearFlag(NonDependentRelFlag) for j := 0; j < i; j++ { if !dep.Exist(j) && j != i { np.AppendDep(j) @@ -269,10 +303,22 @@ func convert2PlainTxDAGWithRelation0(d TxDAG) *PlainTxDAG { // TxDep store the current tx dependency relation with other txs type TxDep struct { TxIndexes []uint64 - // It describes the Relation with below txs - // 0: this tx depends on below txs, it can be ignored and not be encoded in rlp encoder. - // 1: this transaction does not depend on below txs, all other previous txs depend on - Relation *uint8 `rlp:"optional"` + // Flags may has multi flag meaning, ref NonDependentRelFlag, ExcludedTxFlag. + Flags *uint8 `rlp:"optional"` +} + +func NewTxDep(indexes []uint64, flags ...uint8) TxDep { + dep := TxDep{ + TxIndexes: indexes, + } + if len(flags) == 0 { + return dep + } + dep.Flags = new(uint8) + for _, flag := range flags { + dep.SetFlag(flag) + } + return dep } func (d *TxDep) AppendDep(i int) { @@ -300,11 +346,26 @@ func (d *TxDep) Last() int { return int(d.TxIndexes[len(d.TxIndexes)-1]) } -func (d *TxDep) RelationEqual(rel uint8) bool { - if d.Relation == nil { - return TxDAGRelation0 == rel +func (d *TxDep) CheckFlag(flag uint8) bool { + var flags uint8 + if d.Flags != nil { + flags = *d.Flags + } + return flags&flag == flag +} + +func (d *TxDep) SetFlag(flag uint8) { + if d.Flags == nil { + d.Flags = new(uint8) + } + *d.Flags |= flag +} + +func (d *TxDep) ClearFlag(flag uint8) { + if d.Flags == nil { + return } - return *d.Relation == rel + *d.Flags &= ^flag } var ( diff --git a/core/types/dag_test.go b/core/types/dag_test.go index ead8b1cda5..499b914340 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -18,27 +18,12 @@ var ( func TestTxDAG_SetTxDep(t *testing.T) { dag := mockSimpleDAG() - require.NoError(t, dag.SetTxDep(9, TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: nil, - })) - require.NoError(t, dag.SetTxDep(10, TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: nil, - })) - require.Error(t, dag.SetTxDep(12, TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: nil, - })) + require.NoError(t, dag.SetTxDep(9, NewTxDep(nil, NonDependentRelFlag))) + require.NoError(t, dag.SetTxDep(10, NewTxDep(nil, NonDependentRelFlag))) + require.Error(t, dag.SetTxDep(12, NewTxDep(nil, NonDependentRelFlag))) dag = NewEmptyTxDAG() - require.NoError(t, dag.SetTxDep(0, TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: nil, - })) - require.NoError(t, dag.SetTxDep(11, TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: nil, - })) + require.NoError(t, dag.SetTxDep(0, NewTxDep(nil, NonDependentRelFlag))) + require.NoError(t, dag.SetTxDep(11, NewTxDep(nil, NonDependentRelFlag))) } func TestTxDAG(t *testing.T) { @@ -55,7 +40,7 @@ func TestEvaluateTxDAG(t *testing.T) { stats[i] = NewExeStat(i).WithGas(uint64(i)).WithRead(i) stats[i].costTime = time.Duration(i) txDep := dag.TxDep(i) - if txDep.RelationEqual(TxDAGRelation1) { + if txDep.CheckFlag(NonDependentRelFlag) { stats[i].WithSerialFlag() } } @@ -65,10 +50,14 @@ func TestEvaluateTxDAG(t *testing.T) { func TestMergeTxDAGExecutionPaths_Simple(t *testing.T) { tests := []struct { d TxDAG + from uint64 + to uint64 expect [][]uint64 }{ { - d: mockSimpleDAG(), + d: mockSimpleDAG(), + from: 0, + to: 9, expect: [][]uint64{ {0, 3, 4}, {1, 2, 5, 6, 7}, @@ -76,28 +65,63 @@ func TestMergeTxDAGExecutionPaths_Simple(t *testing.T) { }, }, { - d: mockSimpleDAGWithLargeDeps(), + d: mockSimpleDAG(), + from: 1, + to: 1, + expect: [][]uint64{ + {1}, + }, + }, + { + d: mockSimpleDAGWithLargeDeps(), + from: 0, + to: 9, expect: [][]uint64{ {5, 6}, {0, 1, 2, 3, 4, 7, 8, 9}, }, }, { - d: mockSystemTxDAGWithLargeDeps(), + d: mockSystemTxDAGWithLargeDeps(), + from: 0, + to: 11, + expect: [][]uint64{ + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + {10}, + {11}, + }, + }, + { + d: mockSimpleDAGWithLargeDeps(), + from: 5, + to: 8, expect: [][]uint64{ - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, + {5, 6}, + {7}, + {8}, + }, + }, + { + d: mockSimpleDAGWithLargeDeps(), + from: 5, + to: 9, + expect: [][]uint64{ + {5, 6}, + {7}, + {8, 9}, }, }, } for i, item := range tests { - paths := MergeTxDAGExecutionPaths(item.d) + paths, err := MergeTxDAGExecutionPaths(item.d, item.from, item.to) + require.NoError(t, err) require.Equal(t, item.expect, paths, i) } } func TestMergeTxDAGExecutionPaths_Random(t *testing.T) { dag := mockRandomDAG(10000) - paths := MergeTxDAGExecutionPaths(dag) + paths, _ := MergeTxDAGExecutionPaths(dag, 0, uint64(dag.TxCount()-1)) txMap := make(map[uint64]uint64, dag.TxCount()) for _, path := range paths { for _, index := range path { @@ -112,7 +136,7 @@ func TestMergeTxDAGExecutionPaths_Random(t *testing.T) { func BenchmarkMergeTxDAGExecutionPaths(b *testing.B) { dag := mockRandomDAG(100000) for i := 0; i < b.N; i++ { - MergeTxDAGExecutionPaths(dag) + MergeTxDAGExecutionPaths(dag, 0, uint64(dag.TxCount()-1)) } } @@ -143,10 +167,7 @@ func mockSimpleDAGWithLargeDeps() TxDAG { dag.TxDeps[7].TxIndexes = []uint64{2, 4} dag.TxDeps[8].TxIndexes = []uint64{} //dag.TxDeps[9].TxIndexes = []uint64{0, 1, 3, 4, 8} - dag.TxDeps[9] = TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: []uint64{2, 5, 6, 7}, - } + dag.TxDeps[9] = NewTxDep([]uint64{2, 5, 6, 7}, NonDependentRelFlag) return dag } @@ -188,67 +209,25 @@ func mockSystemTxDAG() TxDAG { dag.TxDeps[7].TxIndexes = []uint64{6} dag.TxDeps[8].TxIndexes = []uint64{} dag.TxDeps[9].TxIndexes = []uint64{8} - dag.TxDeps[10] = TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: []uint64{}, - } - dag.TxDeps[11] = TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: []uint64{}, - } + dag.TxDeps[10] = NewTxDep([]uint64{}, ExcludedTxFlag) + dag.TxDeps[11] = NewTxDep([]uint64{}, ExcludedTxFlag) return dag } func mockSystemTxDAG2() TxDAG { dag := NewPlainTxDAG(12) - dag.TxDeps[0] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{}, - } - dag.TxDeps[1] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{}, - } - dag.TxDeps[2] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{}, - } - dag.TxDeps[3] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{0}, - } - dag.TxDeps[4] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{0}, - } - dag.TxDeps[5] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{1, 2}, - } - dag.TxDeps[6] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{2, 5}, - } - dag.TxDeps[7] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{6}, - } - dag.TxDeps[8] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{}, - } - dag.TxDeps[9] = TxDep{ - Relation: &TxDAGRelation0, - TxIndexes: []uint64{8}, - } - dag.TxDeps[10] = TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: []uint64{}, - } - dag.TxDeps[11] = TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: []uint64{}, - } + dag.TxDeps[0] = NewTxDep([]uint64{}) + dag.TxDeps[1] = NewTxDep([]uint64{}) + dag.TxDeps[2] = NewTxDep([]uint64{}) + dag.TxDeps[3] = NewTxDep([]uint64{0}) + dag.TxDeps[4] = NewTxDep([]uint64{0}) + dag.TxDeps[5] = NewTxDep([]uint64{1, 2}) + dag.TxDeps[6] = NewTxDep([]uint64{2, 5}) + dag.TxDeps[7] = NewTxDep([]uint64{6}) + dag.TxDeps[8] = NewTxDep([]uint64{}) + dag.TxDeps[9] = NewTxDep([]uint64{8}) + dag.TxDeps[10] = NewTxDep([]uint64{}, NonDependentRelFlag) + dag.TxDeps[11] = NewTxDep([]uint64{}, NonDependentRelFlag) return dag } @@ -264,18 +243,9 @@ func mockSystemTxDAGWithLargeDeps() TxDAG { dag.TxDeps[7].TxIndexes = []uint64{0, 1, 3, 5, 6} dag.TxDeps[8].TxIndexes = []uint64{} //dag.TxDeps[9].TxIndexes = []uint64{0, 1, 2, 3, 4, 8} - dag.TxDeps[9] = TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: []uint64{5, 6, 7, 10, 11}, - } - dag.TxDeps[10] = TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: []uint64{}, - } - dag.TxDeps[11] = TxDep{ - Relation: &TxDAGRelation1, - TxIndexes: []uint64{}, - } + dag.TxDeps[9] = NewTxDep([]uint64{5, 6, 7, 10, 11}, NonDependentRelFlag) + dag.TxDeps[10] = NewTxDep([]uint64{}, ExcludedTxFlag) + dag.TxDeps[11] = NewTxDep([]uint64{}, ExcludedTxFlag) return dag } @@ -327,6 +297,7 @@ func TestDecodeTxDAG(t *testing.T) { {"01e3e2c1c0c1c0c1c0c2c180c2c180c3c20102c3c20205c2c106c1c0c2c108c2c001c2c001", false}, {"0132e212", true}, {"01dfdec280c0c280c0c380c101c380c102c380c103c380c104c380c105c380c106", true}, + {"01cdccc280c0c280c0c280c0c280c0", true}, } for i, item := range tests { enc, err := hex.DecodeString(item.enc) @@ -340,3 +311,73 @@ func TestDecodeTxDAG(t *testing.T) { t.Log(txDAG) } } + +func TestTxDep_Flags(t *testing.T) { + dep := NewTxDep(nil) + dep.ClearFlag(NonDependentRelFlag) + dep.SetFlag(NonDependentRelFlag) + dep.SetFlag(ExcludedTxFlag) + compared := NewTxDep(nil, NonDependentRelFlag, ExcludedTxFlag) + require.Equal(t, dep, compared) + require.Equal(t, NonDependentRelFlag|ExcludedTxFlag, *dep.Flags) + dep.ClearFlag(ExcludedTxFlag) + require.Equal(t, NonDependentRelFlag, *dep.Flags) + require.True(t, dep.CheckFlag(NonDependentRelFlag)) + require.False(t, dep.CheckFlag(ExcludedTxFlag)) +} + +func TestDepExcludeTxRange(t *testing.T) { + tests := []struct { + src []uint64 + from uint64 + to uint64 + expect []uint64 + }{ + { + src: nil, + from: 0, + to: 4, + expect: nil, + }, + { + src: []uint64{}, + from: 0, + to: 4, + expect: []uint64{}, + }, + { + src: []uint64{0, 1, 2, 3, 4}, + from: 4, + to: 4, + expect: []uint64{4}, + }, + { + src: []uint64{0, 1, 2, 3, 4}, + from: 1, + to: 3, + expect: []uint64{1, 2, 3}, + }, + { + src: []uint64{0, 1, 2, 3, 4}, + from: 5, + to: 6, + expect: nil, + }, + { + src: []uint64{2, 3, 4}, + from: 0, + to: 1, + expect: nil, + }, + { + src: []uint64{0, 1, 2, 3, 4}, + from: 0, + to: 4, + expect: []uint64{0, 1, 2, 3, 4}, + }, + } + + for i, item := range tests { + require.Equal(t, item.expect, depExcludeTxRange(item.src, item.from, item.to), i) + } +} diff --git a/core/types/mvstates.go b/core/types/mvstates.go index bfcaf13613..9816c6ecfa 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -99,8 +99,9 @@ type RWSet struct { readSet map[RWKey]*RWItem writeSet map[RWKey]*RWItem + // some flags rwRecordDone bool - mustSerial bool + excludedTx bool } func NewRWSet(ver StateVersion) *RWSet { @@ -146,19 +147,11 @@ func (s *RWSet) WriteSet() map[RWKey]*RWItem { return s.writeSet } -func (s *RWSet) WithSerialFlag() *RWSet { - s.mustSerial = true +func (s *RWSet) WithExcludedTxFlag() *RWSet { + s.excludedTx = true return s } -func (s *RWSet) RWRecordDone() bool { - return s.rwRecordDone -} - -func (s *RWSet) SetRWRecordDone() { - s.rwRecordDone = true -} - func (s *RWSet) String() string { builder := strings.Builder{} builder.WriteString(fmt.Sprintf("tx: %v, inc: %v\nreadSet: [", s.ver.TxIndex, s.ver.TxIncarnation)) @@ -403,12 +396,19 @@ func (s *MVStates) Finalise(index int) error { func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { // analysis dep, if the previous transaction is not executed/validated, re-analysis is required s.depsCache[index] = NewTxDeps(0) + if rwSet.excludedTx { + return + } for prev := 0; prev < index; prev++ { // if there are some parallel execution or system txs, it will fulfill in advance // it's ok, and try re-generate later if _, ok := s.rwSets[prev]; !ok { continue } + // if prev tx is tagged ExcludedTxFlag, just skip the check + if s.rwSets[prev].excludedTx { + continue + } // check if there has written op before i if checkDependency(s.rwSets[prev].writeSet, rwSet.readSet) { s.depsCache[index].add(prev) @@ -461,8 +461,8 @@ func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (Tx } } txDAG.TxDeps[i].TxIndexes = []uint64{} - if s.rwSets[i].mustSerial { - txDAG.TxDeps[i].Relation = &TxDAGRelation1 + if s.rwSets[i].excludedTx { + txDAG.TxDeps[i].SetFlag(ExcludedTxFlag) continue } if s.depsCache[i] == nil { @@ -474,7 +474,7 @@ func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (Tx continue } // if tx deps larger than half of txs, then convert to relation1 - txDAG.TxDeps[i].Relation = &TxDAGRelation1 + txDAG.TxDeps[i].SetFlag(NonDependentRelFlag) for j := uint64(0); j < uint64(txCnt); j++ { if !slices.Contains(deps, j) && j != uint64(i) { txDAG.TxDeps[i].TxIndexes = append(txDAG.TxDeps[i].TxIndexes, j) diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 9dcd83a6ba..10ee095861 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -72,8 +72,8 @@ func TestMVStates_SystemTxResolveTxDAG(t *testing.T) { ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}) ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) - ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithSerialFlag() - ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithSerialFlag() + ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithExcludedTxFlag() + ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithExcludedTxFlag() dag, err := ms.ResolveTxDAG(12, nil) require.NoError(t, err) @@ -94,8 +94,8 @@ func TestMVStates_SystemTxWithLargeDepsResolveTxDAG(t *testing.T) { ms.rwSets[7] = mockRWSet(7, []string{"0x00", "0x01", "0x03", "0x05", "0x06", "0x07"}, []string{"0x07"}) ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) ms.rwSets[9] = mockRWSet(9, []string{"0x00", "0x01", "0x02", "0x03", "0x04", "0x08", "0x09"}, []string{"0x09"}) - ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithSerialFlag() - ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithSerialFlag() + ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithExcludedTxFlag() + ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithExcludedTxFlag() dag, err := ms.ResolveTxDAG(12, nil) require.NoError(t, err) diff --git a/tests/block_test.go b/tests/block_test.go index 5103b4467d..97cb66d3f2 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -18,18 +18,18 @@ package tests import ( "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "math/rand" "os" "path/filepath" "runtime" + "sync/atomic" "testing" - - "github.com/ethereum/go-ethereum/core/rawdb" - - "github.com/ethereum/go-ethereum/common" ) func TestBlockchainWithTxDAG(t *testing.T) { + //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) bt := new(testMatcher) // General state tests are 'exported' as blockchain tests, but we can run them natively. // For speedier CI-runs, the line below can be uncommented, so those are skipped. @@ -60,6 +60,18 @@ func TestBlockchainWithTxDAG(t *testing.T) { } execBlockTestWithTxDAG(t, bt, test) }) + //bt := new(testMatcher) + //path := filepath.Join(blockTestDir, "ValidBlocks", "bcEIP1559", "intrinsic.json") + //_, name := filepath.Split(path) + //t.Run(name, func(t *testing.T) { + // bt.runTestFile(t, path, name, func(t *testing.T, name string, test *BlockTest) { + // if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { + // t.Skip("test (randomly) skipped on 32-bit windows") + // } + // execBlockTestWithTxDAG(t, bt, test) + // //execBlockTest(t, bt, test) + // }) + //}) } func TestBlockchain(t *testing.T) { bt := new(testMatcher) @@ -109,8 +121,10 @@ func TestExecutionSpecBlocktests(t *testing.T) { }) } +var txDAGFileCounter atomic.Uint64 + func execBlockTestWithTxDAG(t *testing.T, bt *testMatcher, test *BlockTest) { - txDAGFile := filepath.Join(os.TempDir(), fmt.Sprintf("test_txdag_%s.csv", t.Name())) + txDAGFile := filepath.Join(os.TempDir(), fmt.Sprintf("test_txdag_%v.csv", txDAGFileCounter.Add(1))) if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, false)); err != nil { t.Errorf("test in path mode with snapshotter failed: %v", err) return From bfff8f8e98135cc27734b442afe0ab14cae70147 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:45:17 +0800 Subject: [PATCH 21/72] pevm: opt slot trigger mechanism; (#24) * pevm: opt slot trigger mechanism; * txdag: opt execute stat; * pevm: opt slot trigger mechanism; * txdag: add txdag more validation logic; --------- Co-authored-by: galaio --- core/parallel_state_processor.go | 38 ++++++++++++++------------------ core/state/statedb.go | 6 ++++- core/types/dag.go | 36 ++++++++++++++++++++++++++++++ tests/block_test.go | 1 - 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 27e1b5eb05..018e66f19a 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -267,7 +267,7 @@ func (p *ParallelStateProcessor) switchSlot(slotIndex int) { func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxRequest) *ParallelTxResult { mIndex := p.mergedTxIndex.Load() conflictIndex := txReq.conflictIndex.Load() - if mIndex <= conflictIndex { + if mIndex < conflictIndex { // The conflicted TX has not been finished executing, skip execution. // the transaction failed at check(nonce or balance), actually it has not been executed yet. atomic.CompareAndSwapInt32(&txReq.runnable, 0, 1) @@ -657,6 +657,19 @@ func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *Ga } p.mergedTxIndex.Store(int32(resultTxIndex)) + // trigger all slot to run left conflicted txs + for _, slot := range p.slotState { + var wakeupChan chan struct{} + if slot.activatedType == parallelPrimarySlot { + wakeupChan = slot.primaryWakeUpChan + } else { + wakeupChan = slot.shadowWakeUpChan + } + select { + case wakeupChan <- struct{}{}: + default: + } + } return result } @@ -724,28 +737,11 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat if txDAG == nil && len(p.bc.txDAGMapping) > 0 { txDAG = p.bc.txDAGMapping[block.NumberU64()] } - if txDAG != nil && txDAG.TxCount() != len(block.Transactions()) { - log.Warn("parallel process cannot apply the TxDAG with wrong txs length", - "block", block.NumberU64(), "txs", len(block.Transactions()), "txdag", txDAG.TxCount()) + if err := types.ValidateTxDAG(txDAG, len(block.Transactions())); err != nil { + log.Warn("pevm cannot apply wrong txdag", + "block", block.NumberU64(), "txs", len(block.Transactions()), "err", err) txDAG = nil } - // TODO(galaio): check TxDAG validation & excludedTxs in head and continuous - // we only support this format - // convert to normal plain txdag - //parallelIndex := -1 - //if txDAG != nil && txDAG.Type() == types.PlainTxDAGType { - // for i := range allTxs { - // if !txDAG.TxDep(i).CheckFlag(types.ExcludedTxFlag) { - // if parallelIndex == -1 { - // parallelIndex = i - // } - // continue - // } - // if i > 0 && !txDAG.TxDep(i-1).CheckFlag(types.ExcludedTxFlag) { - // return nil, nil, 0, errors.New("cannot support non-continuous excludedTxs") - // } - // } - //} } txNum := len(allTxs) diff --git a/core/state/statedb.go b/core/state/statedb.go index 4e5344245d..dfadc0231b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2404,7 +2404,11 @@ func (s *StateDB) StopTxStat(usedGas uint64) { } // record stat first if metrics.EnabledExpensive && s.stat != nil { - s.stat.Done().WithGas(usedGas).WithRead(len(s.rwSet.ReadSet())) + s.stat.Done().WithGas(usedGas) + rwSet := s.mvStates.RWSet(s.txIndex) + if rwSet != nil { + s.stat.WithRead(len(rwSet.ReadSet())) + } } } diff --git a/core/types/dag.go b/core/types/dag.go index 79e00b3680..76d543aff6 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -78,6 +78,42 @@ func DecodeTxDAG(enc []byte) (TxDAG, error) { } } +func ValidateTxDAG(d TxDAG, txCnt int) error { + if d == nil { + return nil + } + + switch d.Type() { + case EmptyTxDAGType: + return nil + case PlainTxDAGType: + return ValidatePlainTxDAG(d, txCnt) + default: + return fmt.Errorf("unsupported TxDAG type: %v", d.Type()) + } +} + +func ValidatePlainTxDAG(d TxDAG, txCnt int) error { + if d.TxCount() != txCnt { + return fmt.Errorf("PlainTxDAG contains wrong txs count, expect: %v, actual: %v", txCnt, d.TxCount()) + } + for i := 0; i < txCnt; i++ { + dep := d.TxDep(i) + if dep == nil { + return fmt.Errorf("PlainTxDAG contains nil txdep, tx: %v", i) + } + for j, tx := range dep.TxIndexes { + if tx >= uint64(i) || tx >= uint64(txCnt) { + return fmt.Errorf("PlainTxDAG contains the exceed range dependency, tx: %v", i) + } + if j > 0 && dep.TxIndexes[j] <= dep.TxIndexes[j-1] { + return fmt.Errorf("PlainTxDAG contains unordered dependency, tx: %v", i) + } + } + } + return nil +} + // EmptyTxDAG indicate that execute txs in sequence // It means no transactions or need timely distribute transaction fees // it only keep partial serial execution when tx cannot delay the distribution or just execute txs in sequence diff --git a/tests/block_test.go b/tests/block_test.go index 97cb66d3f2..ae0290862a 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -69,7 +69,6 @@ func TestBlockchainWithTxDAG(t *testing.T) { // t.Skip("test (randomly) skipped on 32-bit windows") // } // execBlockTestWithTxDAG(t, bt, test) - // //execBlockTest(t, bt, test) // }) //}) } From ec799b0fd5f2a7650b491858ca75ec449a6c9443 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Wed, 7 Aug 2024 09:56:23 +0800 Subject: [PATCH 22/72] fix addBalance for delayGasFee (#25) Make the change into the merged mainDB instead of slotDB to avoid the concurrency issue Co-authored-by: Sunny --- core/parallel_state_processor.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 018e66f19a..26f0637c37 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -622,28 +622,30 @@ func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *Ga } resultTxIndex := result.txReq.txIndex + + var root []byte + header := result.txReq.block.Header() + + isByzantium := p.config.IsByzantium(header.Number) + isEIP158 := p.config.IsEIP158(header.Number) + result.slotDB.FinaliseForParallel(isByzantium || isEIP158, statedb) + + // merge slotDB into mainDB + statedb.MergeSlotDB(result.slotDB, result.receipt, resultTxIndex, result.result.delayFees) + delayGasFee := result.result.delayFees // add delayed gas fee if delayGasFee != nil { if delayGasFee.TipFee != nil { - result.slotDB.AddBalance(delayGasFee.Coinbase, delayGasFee.TipFee) + statedb.AddBalance(delayGasFee.Coinbase, delayGasFee.TipFee) } if delayGasFee.BaseFee != nil { - result.slotDB.AddBalance(params.OptimismBaseFeeRecipient, delayGasFee.BaseFee) + statedb.AddBalance(params.OptimismBaseFeeRecipient, delayGasFee.BaseFee) } if delayGasFee.L1Fee != nil { - result.slotDB.AddBalance(params.OptimismL1FeeRecipient, delayGasFee.L1Fee) + statedb.AddBalance(params.OptimismL1FeeRecipient, delayGasFee.L1Fee) } } - var root []byte - header := result.txReq.block.Header() - - isByzantium := p.config.IsByzantium(header.Number) - isEIP158 := p.config.IsEIP158(header.Number) - result.slotDB.FinaliseForParallel(isByzantium || isEIP158, statedb) - - // merge slotDB into mainDB - statedb.MergeSlotDB(result.slotDB, result.receipt, resultTxIndex, result.result.delayFees) // Do IntermediateRoot after mergeSlotDB. if !isByzantium { From 4c41ce42e0330ce0a10683cb367fe226e58b1c93 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Thu, 8 Aug 2024 10:29:08 +0800 Subject: [PATCH 23/72] txdag: opt read txdag file and validation logic; (#26) * txdag: support new txdep resolve method; pevm: avoid read txdag file when generating; * pevm: support read txdag file in const size; * txdag: reduce mem alloc and async resolve tx dependency; --------- Co-authored-by: galaio --- core/blockchain.go | 129 ++++++++++++++++++++++++------- core/blockchain_test.go | 19 +++-- core/parallel_state_processor.go | 8 +- core/state/statedb.go | 3 +- core/types/dag.go | 46 +++++++++-- core/types/dag_test.go | 2 +- core/types/mvstates.go | 9 ++- eth/backend.go | 2 +- tests/block_test_util.go | 2 +- 9 files changed, 168 insertions(+), 52 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index dafd11afe1..daf6d3edcb 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -306,7 +306,7 @@ type BlockChain struct { parallelExecution bool enableTxDAG bool txDAGWriteCh chan TxDAGOutputItem - txDAGMapping map[uint64]types.TxDAG + txDAGReader *TxDAGFileReader } // NewBlockChain returns a fully initialised block chain using information @@ -1063,6 +1063,9 @@ func (bc *BlockChain) stopWithoutSaving() { // Stop stops the blockchain service. If any imports are currently in progress // it will abort them using the procInterrupt. func (bc *BlockChain) Stop() { + if bc.txDAGReader != nil { + bc.txDAGReader.Close() + } bc.stopWithoutSaving() // Ensure that the entirety of the state snapshot is journaled to disk. @@ -2691,19 +2694,27 @@ func (bc *BlockChain) TxDAGEnabled() bool { return bc.enableTxDAG } -func (bc *BlockChain) SetupTxDAGGeneration(output string) { +func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { log.Info("node enable TxDAG feature", "output", output) bc.enableTxDAG = true if len(output) == 0 { return } // read TxDAG file, and cache in mem - var err error - bc.txDAGMapping, err = readTxDAGMappingFromFile(output) - if err != nil { - log.Error("read TxDAG err", "err", err) + if readFile { + var err error + bc.txDAGReader, err = NewTxDAGFileReader(output) + if err != nil { + log.Error("read TxDAG err", "err", err) + } + // startup with latest block + curHeader := bc.CurrentHeader() + if curHeader != nil { + bc.txDAGReader.TxDAG(curHeader.Number.Uint64()) + } + log.Info("load TxDAG from file", "output", output, "latest", bc.txDAGReader.Latest()) + return } - log.Info("load TxDAG from file", "output", output, "count", len(bc.txDAGMapping)) // write handler go func() { @@ -2747,34 +2758,98 @@ func writeTxDAGToFile(writeHandle *os.File, item TxDAGOutputItem) error { return err } -// TODO(galaio): support load with segments, every segment 100000 blocks? -func readTxDAGMappingFromFile(output string) (map[uint64]types.TxDAG, error) { +const TxDAGCacheSize = 200000 + +type TxDAGFileReader struct { + file *os.File + scanner *bufio.Scanner + cache map[uint64]types.TxDAG + latest uint64 + lock sync.RWMutex +} + +func NewTxDAGFileReader(output string) (*TxDAGFileReader, error) { file, err := os.Open(output) if err != nil { return nil, err } - defer file.Close() - - mapping := make(map[uint64]types.TxDAG) scanner := bufio.NewScanner(file) - for scanner.Scan() { - tokens := strings.Split(scanner.Text(), ",") - if len(tokens) != 2 { - return nil, errors.New("txDAG output contain wrong size") - } - num, err := strconv.Atoi(tokens[0]) - if err != nil { - return nil, err + return &TxDAGFileReader{ + file: file, + scanner: scanner, + }, nil +} + +func (t *TxDAGFileReader) Close() { + t.lock.Lock() + defer t.lock.Unlock() + t.closeFile() +} + +func (t *TxDAGFileReader) closeFile() { + if t.scanner != nil { + t.scanner = nil + } + if t.file != nil { + t.file.Close() + t.file = nil + } +} + +func (t *TxDAGFileReader) Latest() uint64 { + t.lock.RLock() + defer t.lock.RUnlock() + return t.latest +} + +func (t *TxDAGFileReader) TxDAG(expect uint64) types.TxDAG { + t.lock.Lock() + defer t.lock.Unlock() + + if t.cache != nil && t.latest >= expect { + return t.cache[expect] + } + + t.cache = make(map[uint64]types.TxDAG, TxDAGCacheSize) + counter := 0 + for t.scanner != nil && t.scanner.Scan() { + if counter > TxDAGCacheSize { + break } - enc, err := hex.DecodeString(tokens[1]) + num, dag, err := readTxDAGItemFromLine(t.scanner.Text()) if err != nil { - return nil, err + log.Error("query TxDAG error found and read aborted", "err", err) + t.closeFile() + break } - txDAG, err := types.DecodeTxDAG(enc) - if err != nil { - return nil, err + // skip lower blocks + if expect > num { + continue } - mapping[uint64(num)] = txDAG + t.cache[num] = dag + t.latest = num + counter++ + } + + return t.cache[expect] +} + +func readTxDAGItemFromLine(line string) (uint64, types.TxDAG, error) { + tokens := strings.Split(line, ",") + if len(tokens) != 2 { + return 0, nil, errors.New("txDAG output contain wrong size") + } + num, err := strconv.Atoi(tokens[0]) + if err != nil { + return 0, nil, err + } + enc, err := hex.DecodeString(tokens[1]) + if err != nil { + return 0, nil, err + } + txDAG, err := types.DecodeTxDAG(enc) + if err != nil { + return 0, nil, err } - return mapping, nil + return uint64(num), txDAG, nil } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 97932eaca3..c5aacf4283 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4741,21 +4741,28 @@ func TestTxDAGFile_ReadWrite(t *testing.T) { except2 := map[uint64]types.TxDAG{ 3: types.NewEmptyTxDAG(), 4: makeEmptyPlainTxDAG(4, types.NonDependentRelFlag, types.ExcludedTxFlag), + 5: makeEmptyPlainTxDAG(5, types.NonDependentRelFlag, types.ExcludedTxFlag), } writeFile, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) require.NoError(t, err) for num, dag := range except2 { + if num == 5 { + writeFile.WriteString("num,txdag\n") + continue + } require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: dag})) } writeFile.Close() - actual, err := readTxDAGMappingFromFile(path) + reader, err := NewTxDAGFileReader(path) require.NoError(t, err) - for num, dag := range except { - require.Equal(t, dag, actual[num]) - } - for num, dag := range except2 { - require.Equal(t, dag, actual[num]) + for i := 0; i < 5; i++ { + num := uint64(i) + if except[num] != nil { + require.Equal(t, except[num], reader.TxDAG(num)) + continue + } + require.Equal(t, except2[num], reader.TxDAG(num)) } } diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 26f0637c37..56172e578f 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -736,8 +736,8 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat if p.bc.enableTxDAG { // TODO(galaio): load TxDAG from block // or load cache txDAG from file - if txDAG == nil && len(p.bc.txDAGMapping) > 0 { - txDAG = p.bc.txDAGMapping[block.NumberU64()] + if txDAG == nil && p.bc.txDAGReader != nil { + txDAG = p.bc.txDAGReader.TxDAG(block.NumberU64()) } if err := types.ValidateTxDAG(txDAG, len(block.Transactions())); err != nil { log.Warn("pevm cannot apply wrong txdag", @@ -763,8 +763,8 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat // find the latestDepTx from TxDAG or latestExcludedTx latestDepTx := -1 - if txDAG != nil && txDAG.TxDep(i).Count() > 0 { - latestDepTx = int(txDAG.TxDep(i).TxIndexes[txDAG.TxDep(i).Count()-1]) + if dep := types.TxDependency(txDAG, i); len(dep) > 0 { + latestDepTx = int(dep[len(dep)-1]) } if latestDepTx < latestExcludedTx { latestDepTx = latestExcludedTx diff --git a/core/state/statedb.go b/core/state/statedb.go index dfadc0231b..afed70c72f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2488,7 +2488,6 @@ func (s *StateDB) FinaliseRWSet() error { // reset stateDB s.rwSet = nil - s.stat = nil return s.mvStates.FulfillRWSet(rwSet, stat) } @@ -2562,7 +2561,7 @@ func (s *StateDB) RecordSystemTxRWSet(index int) { } s.mvStates.FulfillRWSet(types.NewRWSet(types.StateVersion{ TxIndex: index, - }).WithExcludedTxFlag(), types.NewExeStat(index).WithSerialFlag()) + }).WithExcludedTxFlag(), types.NewExeStat(index).WithExcludedTxFlag()) } // copySet returns a deep-copied set. diff --git a/core/types/dag.go b/core/types/dag.go index 76d543aff6..fb6e374bd2 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -25,6 +25,7 @@ var ( // ExcludedTxFlag indicates that the tx is excluded from TxDAG, user should execute them in sequence. // These excluded transactions should be consecutive in the head or tail. ExcludedTxFlag uint8 = 0x02 + TxDepFlagMask = NonDependentRelFlag | ExcludedTxFlag ) type TxDAG interface { @@ -110,10 +111,37 @@ func ValidatePlainTxDAG(d TxDAG, txCnt int) error { return fmt.Errorf("PlainTxDAG contains unordered dependency, tx: %v", i) } } + if dep.Flags != nil && *dep.Flags & ^TxDepFlagMask > 0 { + return fmt.Errorf("PlainTxDAG contains unknown flags, flags: %v", *dep.Flags) + } } return nil } +func TxDependency(d TxDAG, i int) []uint64 { + if d == nil || i < 0 || i >= d.TxCount() { + return []uint64{} + } + dep := d.TxDep(i) + if dep.CheckFlag(ExcludedTxFlag) { + txs := make([]uint64, 0, i) + for j := 0; j < i; j++ { + txs = append(txs, uint64(j)) + } + return txs + } + if dep.CheckFlag(NonDependentRelFlag) { + txs := make([]uint64, 0, d.TxCount()-dep.Count()) + for j := 0; j < i; j++ { + if !dep.Exist(j) && j != i { + txs = append(txs, uint64(j)) + } + } + return txs + } + return dep.TxIndexes +} + // EmptyTxDAG indicate that execute txs in sequence // It means no transactions or need timely distribute transaction fees // it only keep partial serial execution when tx cannot delay the distribution or just execute txs in sequence @@ -438,7 +466,7 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) { totalTxMeter.Mark(int64(txCount)) for i, path := range paths { - if stats[i].mustSerial { + if stats[i].excludedTx { continue } if len(path) <= 1 { @@ -499,7 +527,7 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) { sPath []int ) for i, stat := range stats { - if stat.mustSerial { + if stat.excludedTx { continue } sPath = append(sPath, i) @@ -512,13 +540,15 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) { // travelTxDAGTargetPath will print target execution path func travelTxDAGTargetPath(deps []TxDep, from uint64) []uint64 { - queue := make([]uint64, 0, len(deps)) - path := make([]uint64, 0, len(deps)) + var ( + queue []uint64 + path []uint64 + ) queue = append(queue, from) path = append(path, from) for len(queue) > 0 { - next := make([]uint64, 0, len(deps)) + var next []uint64 for _, i := range queue { for _, dep := range deps[i].TxIndexes { if !slices.Contains(path, dep) { @@ -542,7 +572,7 @@ type ExeStat struct { costTime time.Duration // some flags - mustSerial bool + excludedTx bool } func NewExeStat(txIndex int) *ExeStat { @@ -561,8 +591,8 @@ func (s *ExeStat) Done() *ExeStat { return s } -func (s *ExeStat) WithSerialFlag() *ExeStat { - s.mustSerial = true +func (s *ExeStat) WithExcludedTxFlag() *ExeStat { + s.excludedTx = true return s } diff --git a/core/types/dag_test.go b/core/types/dag_test.go index 499b914340..6edb10cc3b 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -41,7 +41,7 @@ func TestEvaluateTxDAG(t *testing.T) { stats[i].costTime = time.Duration(i) txDep := dag.TxDep(i) if txDep.CheckFlag(NonDependentRelFlag) { - stats[i].WithSerialFlag() + stats[i].WithExcludedTxFlag() } } EvaluateTxDAGPerformance(dag, stats) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 9816c6ecfa..c789dbeabc 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -362,8 +362,13 @@ func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet) } } - s.resolveDepsCache(index, rwSet) s.rwSets[index] = rwSet + // async resolve dependency + go func() { + s.lock.Lock() + defer s.lock.Unlock() + s.resolveDepsCache(index, rwSet) + }() return nil } @@ -473,7 +478,7 @@ func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (Tx txDAG.TxDeps[i].TxIndexes = deps continue } - // if tx deps larger than half of txs, then convert to relation1 + // if tx deps larger than half of txs, then convert with NonDependentRelFlag txDAG.TxDeps[i].SetFlag(NonDependentRelFlag) for j := uint64(0); j < uint64(txCnt); j++ { if !slices.Contains(deps, j) && j != uint64(i) { diff --git a/eth/backend.go b/eth/backend.go index a48492fb02..03e354bb87 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -273,7 +273,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { return nil, err } if config.EnableParallelTxDAG { - eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile) + eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile, config.ParallelTxMode) } if chainConfig := eth.blockchain.Config(); chainConfig.Optimism != nil { // config.Genesis.Config.ChainID cannot be used because it's based on CLI flags only, thus default to mainnet L1 config.NetworkId = chainConfig.ChainID.Uint64() // optimism defaults eth network ID to chain ID diff --git a/tests/block_test_util.go b/tests/block_test_util.go index e6b18cc2b6..643f719c35 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -160,7 +160,7 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po } defer chain.Stop() if len(dagFile) > 0 { - chain.SetupTxDAGGeneration(dagFile) + chain.SetupTxDAGGeneration(dagFile, enableParallel) } validBlocks, err := t.insertBlocks(chain) if err != nil { From 879bf457797a017da271efcfe20e251730bc1b09 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:28:31 +0800 Subject: [PATCH 24/72] pevm: opt read large txdag logic and add conflict metrics; (#29) * pevm: add some parallel tx metrics; * pevm: opt read large txdag logic; --------- Co-authored-by: galaio --- core/blockchain.go | 37 ++++++++++++++++++++++---------- core/blockchain_test.go | 26 ++++++++++++++++++++++ core/parallel_state_processor.go | 7 +++++- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index daf6d3edcb..e0078cf966 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -98,6 +98,9 @@ var ( txDAGGenerateTimer = metrics.NewRegisteredTimer("chain/block/txdag/gen", nil) + parallelTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/txs", nil) + parallelConflictTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/conflicttxs", nil) + blockGasUsedGauge = metrics.NewRegisteredGauge("chain/block/gas/used", nil) mgaspsGauge = metrics.NewRegisteredGauge("chain/mgas/ps", nil) @@ -2711,8 +2714,8 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { curHeader := bc.CurrentHeader() if curHeader != nil { bc.txDAGReader.TxDAG(curHeader.Number.Uint64()) + log.Info("load TxDAG from file", "output", output, "block", curHeader.Number, "latest", bc.txDAGReader.Latest()) } - log.Info("load TxDAG from file", "output", output, "latest", bc.txDAGReader.Latest()) return } @@ -2758,7 +2761,7 @@ func writeTxDAGToFile(writeHandle *os.File, item TxDAGOutputItem) error { return err } -const TxDAGCacheSize = 200000 +var TxDAGCacheSize = 10000 type TxDAGFileReader struct { file *os.File @@ -2774,6 +2777,7 @@ func NewTxDAGFileReader(output string) (*TxDAGFileReader, error) { return nil, err } scanner := bufio.NewScanner(file) + scanner.Buffer(make([]byte, 5*1024*1024), 5*1024*1024) return &TxDAGFileReader{ file: file, scanner: scanner, @@ -2809,18 +2813,21 @@ func (t *TxDAGFileReader) TxDAG(expect uint64) types.TxDAG { if t.cache != nil && t.latest >= expect { return t.cache[expect] } + if t.scanner == nil { + return nil + } + logTime := time.Now() t.cache = make(map[uint64]types.TxDAG, TxDAGCacheSize) - counter := 0 - for t.scanner != nil && t.scanner.Scan() { - if counter > TxDAGCacheSize { - break - } + for t.scanner.Scan() { num, dag, err := readTxDAGItemFromLine(t.scanner.Text()) if err != nil { - log.Error("query TxDAG error found and read aborted", "err", err) - t.closeFile() - break + log.Error("query TxDAG error", "latest", t.latest, "err", err) + continue + } + if time.Since(logTime) > 10*time.Second { + logTime = time.Now() + log.Info("try load TxDAG from file", "num", num, "expect", expect, "cached", len(t.cache)) } // skip lower blocks if expect > num { @@ -2828,9 +2835,17 @@ func (t *TxDAGFileReader) TxDAG(expect uint64) types.TxDAG { } t.cache[num] = dag t.latest = num - counter++ + if len(t.cache) >= TxDAGCacheSize { + break + } + } + if t.scanner.Err() != nil { + log.Error("scan TxDAG file got err", "expect", expect, "err", t.scanner.Err()) } + if time.Since(logTime) > 10*time.Second { + log.Info("try load TxDAG from file", "expect", expect, "cached", len(t.cache)) + } return t.cache[expect] } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index c5aacf4283..04b931f629 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4766,6 +4766,32 @@ func TestTxDAGFile_ReadWrite(t *testing.T) { } } +func TestTxDAGFile_LargeRead(t *testing.T) { + path := filepath.Join(os.TempDir(), "test.csv") + defer func() { + os.Remove(path) + }() + TxDAGCacheSize = 10 + totalSize := uint64(100) + except := map[uint64]types.TxDAG{} + for i := uint64(0); i < totalSize-1; i++ { + except[i] = makeEmptyPlainTxDAG(1) + } + except[totalSize-1] = makeEmptyPlainTxDAG(510 * 1024) + writeFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + require.NoError(t, err) + for num := uint64(0); num < totalSize; num++ { + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: except[num]})) + } + writeFile.Close() + + reader, err := NewTxDAGFileReader(path) + require.NoError(t, err) + for i := uint64(0); i < totalSize; i++ { + require.Equal(t, except[i], reader.TxDAG(i), i) + } +} + func makeEmptyPlainTxDAG(cnt int, flags ...uint8) *types.PlainTxDAG { dag := types.NewPlainTxDAG(cnt) for i := range dag.TxDeps { diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 56172e578f..4c0c8da3cb 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -3,6 +3,7 @@ package core import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/metrics" "runtime" "sync" "sync/atomic" @@ -870,7 +871,11 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat "len(commonTxs)", len(commonTxs), "conflictNum", p.debugConflictRedoNum, "redoRate(%)", 100*(p.debugConflictRedoNum)/len(commonTxs), - "txDAG", txDAG) + "txDAG", txDAG != nil) + } + if metrics.EnabledExpensive { + parallelTxNumMeter.Mark(int64(len(commonTxs))) + parallelConflictTxNumMeter.Mark(int64(p.debugConflictRedoNum)) } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. From f111c50b63d4bd1c26f9d5a6d83864eee0be18aa Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 7 Aug 2024 11:29:40 +0800 Subject: [PATCH 25/72] feat: avoid parallel process for block with few txs --- core/blockchain.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index e0078cf966..340f92f733 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -527,11 +527,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) } - if vmConfig.EnableParallelExec { - bc.EnableParallelProcessor(vmConfig.ParallelTxNum) - } else { - bc.processor = NewStateProcessor(chainConfig, bc, engine) - } + bc.processor = NewStateProcessor(chainConfig, bc, engine) // Start future block processor. bc.wg.Add(1) @@ -1904,6 +1900,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) statedb.StartPrefetcher("chain") activeState = statedb + txsCount := block.Transactions().Len() + if bc.vmConfig.EnableParallelExec && txsCount > 4 /* todo: use a parallelTxNum */ { + bc.EnableParallelProcessor(bc.vmConfig.ParallelTxNum) + log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) + } // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. // parallel mode has a pipeline, similar to this prefetch, to save CPU we disable this prefetch for parallel From 6a205beae00b0db6c022a85f4b6bc67f0f793f0c Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 7 Aug 2024 11:29:40 +0800 Subject: [PATCH 26/72] feat: avoid parallel process for block with few txs This change enable parallel execution when txs count > parallelTxNum/2 + 2, with lower bound as 4, as the parallel execution is slower than serial execution with single thread. --- core/blockchain.go | 59 +++++++++++++++++++++++--------- core/parallel_state_processor.go | 2 +- core/state_processor.go | 8 +++++ 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 340f92f733..204354270f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -310,6 +310,8 @@ type BlockChain struct { enableTxDAG bool txDAGWriteCh chan TxDAGOutputItem txDAGReader *TxDAGFileReader + serialProcessor Processor + parallelProcessor Processor } // NewBlockChain returns a fully initialised block chain using information @@ -527,8 +529,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) } - bc.processor = NewStateProcessor(chainConfig, bc, engine) - + if bc.vmConfig.EnableParallelExec { + bc.CreateParallelProcessor(bc.vmConfig.ParallelTxNum) + bc.CreateSerialProcessor(chainConfig, bc, engine) + } else { + bc.processor = NewStateProcessor(chainConfig, bc, engine) + } // Start future block processor. bc.wg.Add(1) go bc.updateFutureBlocks() @@ -1900,10 +1906,16 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) statedb.StartPrefetcher("chain") activeState = statedb - txsCount := block.Transactions().Len() - if bc.vmConfig.EnableParallelExec && txsCount > 4 /* todo: use a parallelTxNum */ { - bc.EnableParallelProcessor(bc.vmConfig.ParallelTxNum) - log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) + if bc.vmConfig.EnableParallelExec { + txsCount := block.Transactions().Len() + threshold := min(bc.vmConfig.ParallelTxNum/2+2, 4) + if txsCount >= threshold { + bc.UseParallelProcessor() + log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) + } else { + bc.UseSerialProcessor() + log.Debug("Disable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) + } } // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. @@ -2650,17 +2662,12 @@ func (bc *BlockChain) GetTrieFlushInterval() time.Duration { return time.Duration(bc.flushInterval.Load()) } -func (bc *BlockChain) EnableParallelProcessor(parallelNum int) (*BlockChain, error) { - /* - if bc.snaps == nil { - // disable parallel processor if snapshot is not enabled to avoid concurrent issue for SecureTrie - log.Info("parallel processor is not enabled since snapshot is not enabled") - return bc, nil - } - */ - bc.parallelExecution = true - bc.processor = NewParallelStateProcessor(bc.Config(), bc, bc.engine, parallelNum) - return bc, nil +func (bc *BlockChain) CreateParallelProcessor(parallelNum int) *BlockChain { + if bc.parallelProcessor == nil { + bc.parallelProcessor = newParallelStateProcessor(bc.Config(), bc, bc.engine, parallelNum) + bc.parallelExecution = true + } + return bc } func (bc *BlockChain) NoTries() bool { @@ -2743,6 +2750,24 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { }() } +func (bc *BlockChain) UseParallelProcessor() { + if bc.parallelProcessor != nil { + bc.parallelExecution = true + bc.processor = bc.parallelProcessor + } else { + bc.CreateParallelProcessor(bc.vmConfig.ParallelTxNum) + } +} + +func (bc *BlockChain) UseSerialProcessor() { + if bc.serialProcessor != nil { + bc.parallelExecution = false + bc.processor = bc.serialProcessor + } else { + bc.CreateSerialProcessor(bc.chainConfig, bc, bc.engine) + } +} + type TxDAGOutputItem struct { blockNumber uint64 txDAG types.TxDAG diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 4c0c8da3cb..962ba2d56a 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -51,7 +51,7 @@ type ParallelStateProcessor struct { delayGasFee bool // it is provided by TxDAG } -func NewParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine, parallelNum int) *ParallelStateProcessor { +func newParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine, parallelNum int) *ParallelStateProcessor { processor := &ParallelStateProcessor{ StateProcessor: *NewStateProcessor(config, bc, engine), parallelNum: parallelNum, diff --git a/core/state_processor.go b/core/state_processor.go index 8721fe62c8..84708e09d0 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -56,6 +56,14 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen } } +// CreateSerialProcessor create a new StateProcessor +func (bc *BlockChain) CreateSerialProcessor(config *params.ChainConfig, bc2 *BlockChain, engine consensus.Engine) { + if bc.serialProcessor == nil { + bc.serialProcessor = NewStateProcessor(config, bc2, engine) + bc.parallelExecution = false + } +} + // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. From 2f99b82e64a360c3e4b11a6d250f0f8e7cbbbf5e Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 8 Aug 2024 23:23:43 +0800 Subject: [PATCH 27/72] fix: avoid rewrite readsCache in slotDB This change avoid the rewrite of reads cache in slotDB. --- core/state/parallel_statedb.go | 31 ++++++++++++++++++++++--------- core/state/state_object.go | 4 +++- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index c3a049a750..4edd8bdeda 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -417,7 +417,9 @@ func (s *ParallelStateDB) GetBalance(addr common.Address) *uint256.Int { } balance = blc } - s.parallel.balanceReadsInSlot[addr] = balance + if _, ok := s.parallel.balanceReadsInSlot[addr]; !ok { + s.parallel.balanceReadsInSlot[addr] = balance + } // fixup dirties if dirtyObj != nil && dirtyObj.Balance() != balance { @@ -462,8 +464,9 @@ func (s *ParallelStateDB) GetNonce(addr common.Address) uint64 { } nonce = nc } - s.parallel.nonceReadsInSlot[addr] = nonce - + if _, ok := s.parallel.nonceReadsInSlot[addr]; !ok { + s.parallel.nonceReadsInSlot[addr] = nonce + } // fixup dirties if dirtyObj != nil && dirtyObj.Nonce() < nonce { dirtyObj.setNonce(nonce) @@ -503,8 +506,9 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { code = object.Code() } } - s.parallel.codeReadsInSlot[addr] = code - + if _, ok := s.parallel.codeReadsInSlot[addr]; !ok { + s.parallel.codeReadsInSlot[addr] = code + } // fixup dirties if dirtyObj != nil && !bytes.Equal(dirtyObj.code, code) { dirtyObj.code = code @@ -550,7 +554,9 @@ func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { } code = cc } - s.parallel.codeReadsInSlot[addr] = code + if _, ok := s.parallel.codeReadsInSlot[addr]; !ok { + s.parallel.codeReadsInSlot[addr] = code + } // fixup dirties if dirtyObj != nil { if !bytes.Equal(dirtyObj.code, code) { @@ -597,7 +603,9 @@ func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { codeHash = common.BytesToHash(object.CodeHash()) } } - s.parallel.codeHashReadsInSlot[addr] = codeHash + if _, ok := s.parallel.codeHashReadsInSlot[addr]; !ok { + s.parallel.codeHashReadsInSlot[addr] = codeHash + } // fill slots in dirty if exist. // A case for this: @@ -702,6 +710,10 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common if s.parallel.kvReadsInSlot[addr] == nil { s.parallel.kvReadsInSlot[addr] = newStorage(false) } + if _, ok := s.parallel.kvReadsInSlot[addr].GetValue(hash); !ok { + s.parallel.kvReadsInSlot[addr].StoreValue(hash, value) // update cache + } + return value } @@ -734,8 +746,9 @@ func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Has if s.parallel.kvReadsInSlot[addr] == nil { s.parallel.kvReadsInSlot[addr] = newStorage(false) } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, value) // update cache - + if _, ok := s.parallel.kvReadsInSlot[addr].GetValue(hash); !ok { + s.parallel.kvReadsInSlot[addr].StoreValue(hash, value) // update cache + } return value } diff --git a/core/state/state_object.go b/core/state/state_object.go index 161d2a81cb..19852bf6a7 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -334,7 +334,9 @@ func (s *stateObject) GetState(key common.Hash) common.Hash { if s.db.parallel.kvReadsInSlot[addr] == nil { s.db.parallel.kvReadsInSlot[addr] = newStorage(false) } - s.db.parallel.kvReadsInSlot[addr].StoreValue(key, result) + if _, ok := s.db.parallel.kvReadsInSlot[addr].GetValue(key); !ok { + s.db.parallel.kvReadsInSlot[addr].StoreValue(key, result) + } } return result } From 6117f5eaeb5ccadaf5e01c046449ee44ad887c47 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 8 Aug 2024 21:24:31 +0800 Subject: [PATCH 28/72] Fix: incorrect GetState of obsoleted data caused by createObject This change fix the issue that prevDestruct is not recorded correctly and the stateObjectDestruct is not recorded in slotDB's createObject. The incorrect record causes the GetState get obsoleted state as the stateObjectDestruct is not correct. --- core/state/journal.go | 7 ++++ core/state/parallel_statedb.go | 60 ++++++++++++++++++++++++++-------- core/state/state_object.go | 4 +-- core/state/statedb.go | 40 +++++++++++------------ 4 files changed, 75 insertions(+), 36 deletions(-) diff --git a/core/state/journal.go b/core/state/journal.go index 33a373a798..488e313f60 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -185,6 +185,13 @@ func (ch resetObjectChange) revert(dber StateDBer) { s.stateObjectDestructLock.Lock() s.removeStateObjectsDestruct(ch.prev.address) s.stateObjectDestructLock.Unlock() + if s.isParallel && s.parallel.isSlotDB { + s.snapParallelLock.Lock() + if _, ok := s.snapDestructs[ch.prev.address]; ok { + delete(s.snapDestructs, ch.prev.address) + } + s.snapParallelLock.Unlock() + } } if ch.prevAccount != nil { s.accounts[ch.prev.addrHash] = ch.prevAccount diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 4edd8bdeda..a0c00fe4ca 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -99,8 +100,7 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash log.Debug("hasKvConflict is invalid", "addr", addr, "key", key, "valSlot", val, "valMain", valMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainDB.TxIndex", mainDB.TxIndex()) return true // return false, Range will be terminated. } return false @@ -217,24 +217,50 @@ func (s *ParallelStateDB) getStateObjectNoSlot(addr common.Address) *stateObject // b.if it is existed in SlotDB, `revert` will recover to the `prev` in SlotDB // c.as `snapDestructs` it is the same func (s *ParallelStateDB) createObject(addr common.Address) (newobj *stateObject) { - prev := s.parallel.dirtiedStateObjectsInSlot[addr] - // TODO-dav: check + var prev *stateObject = nil + readFromDB := false + prevdestruct := false + if object, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + prev = object + } else { + object, ok = s.getStateObjectFromUnconfirmedDB(addr) + if ok { + prev = object + readFromDB = true + } else { + object = s.getDeletedStateObject(addr) // try to get from base db + if object != nil { + prev = object + readFromDB = true + } + } + } // There can be tx0 create an obj at addr0, tx1 destruct it, and tx2 recreate it use create2. // so if tx0 is finalized, and tx1 is unconfirmed, we have to check the states of unconfirmed, otherwise there // will be wrong behavior that we recreate an object that is already there. see. test "TestDeleteThenCreate" - var prevdestruct bool - if s.snap != nil && prev != nil { - s.snapParallelLock.Lock() - _, prevdestruct = s.snapDestructs[prev.address] - s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct + if prev != nil { + // check slot + _, prevdestruct = s.getStateObjectsDestruct(prev.address) + if !prevdestruct { - // To destroy the previous trie node first and update the trie tree - // with the new object on block commit. - s.snapDestructs[prev.address] = struct{}{} + // set Destruct so later accesses in this transaction will not touch the obsoleted state. + s.setStateObjectsDestruct(prev.address, prev.origin) + if readFromDB { + // check nonSlot + s.snapParallelLock.RLock() + _, prevdestruct = s.snapDestructs[prev.address] + s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct + s.snapParallelLock.RUnlock() + } + if !prevdestruct { + s.snapParallelLock.Lock() + s.snapDestructs[prev.address] = struct{}{} + s.snapParallelLock.Unlock() + } } - s.snapParallelLock.Unlock() } + newobj = newObject(s, s.isParallel, addr, nil) newobj.setNonce(0) // sets the object to dirty if prev == nil { @@ -1459,7 +1485,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } // snapshot destructs check for addr, destructRead := range slotDB.parallel.addrSnapDestructsReadsInSlot { - mainObj := mainDB.getStateObjectNoUpdate(addr) + mainObj := mainDB.getDeletedStateObjectNoUpdate(addr) if mainObj == nil { log.Debug("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", "addr", addr, "destruct", destructRead, @@ -1494,6 +1520,12 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) if s.TxIndex() == 0 && len(mainDB.journal.dirties) > 0 { + mainDB.stateObjectDestructLock.Lock() + for addr, acc := range mainDB.stateObjectsDestructDirty { + mainDB.stateObjectsDestruct[addr] = acc + } + mainDB.stateObjectsDestructDirty = make(map[common.Address]*types.StateAccount) + mainDB.stateObjectDestructLock.Unlock() for addr := range mainDB.journal.dirties { var obj *stateObject var exist bool diff --git a/core/state/state_object.go b/core/state/state_object.go index 19852bf6a7..0e8481131d 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -387,7 +387,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // 2) we don't have new values, and can deliver empty response back //if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { s.db.stateObjectDestructLock.RLock() - if _, destructed := s.db.getStateObjectsDegetstruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? + if _, destructed := s.db.getStateObjectsDestruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? s.db.stateObjectDestructLock.RUnlock() return common.Hash{} } @@ -1026,7 +1026,7 @@ func (s *stateObject) GetCommittedStateNoUpdate(key common.Hash) common.Hash { // 2) we don't have new values, and can deliver empty response back //if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { s.db.stateObjectDestructLock.RLock() - if _, destructed := s.db.getStateObjectsDegetstruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? + if _, destructed := s.db.getStateObjectsDestruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? s.db.stateObjectDestructLock.RUnlock() return common.Hash{} } diff --git a/core/state/statedb.go b/core/state/statedb.go index afed70c72f..e94295861f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -310,6 +310,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) trie: tr, originalRoot: root, snaps: snaps, + snapDestructs: make(map[common.Address]struct{}), accounts: make(map[common.Hash][]byte), storages: make(map[common.Hash]map[common.Hash][]byte), accountsOrigin: make(map[common.Address][]byte), @@ -345,6 +346,7 @@ func NewStateDBByTrie(tr Trie, db Database, snaps *snapshot.Tree) (*StateDB, err trie: tr, originalRoot: tr.Hash(), snaps: snaps, + snapDestructs: make(map[common.Address]struct{}), accounts: make(map[common.Hash][]byte), storages: make(map[common.Hash]map[common.Hash][]byte), accountsOrigin: make(map[common.Address][]byte), @@ -685,7 +687,7 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // // TODO(rjl493456442) this function should only be supported by 'unwritable' // state and all mutations made should all be discarded afterwards. - if _, ok := s.getStateObjectsDegetstruct(addr); !ok { + if _, ok := s.getStateObjectsDestruct(addr); !ok { s.setStateObjectsDestruct(addr, nil) } stateObject := s.getOrNewStateObject(addr) @@ -1031,7 +1033,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { // be done here, otherwise the destruction event of "original account" // will be lost. s.stateObjectDestructLock.Lock() - _, prevdestruct := s.getStateObjectsDegetstruct(prev.address) + _, prevdestruct := s.getStateObjectsDestruct(prev.address) if !prevdestruct { s.setStateObjectsDestruct(prev.address, prev.origin) } @@ -1110,6 +1112,7 @@ func (s *StateDB) copyInternal(doPrefetch bool) *StateDB { db: s.db, trie: s.db.CopyTrie(s.trie), originalRoot: s.originalRoot, + snapDestructs: make(map[common.Address]struct{}), accounts: make(map[common.Hash][]byte), storages: make(map[common.Hash]map[common.Hash][]byte), accountsOrigin: make(map[common.Address][]byte), @@ -1462,6 +1465,15 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { return true }) s.parallelStateAccessLock.Unlock() + + // deep copy needed + state.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) + s.snapParallelLock.RLock() + for k, v := range s.snapDestructs { + state.snapDestructs[k] = v + } + s.snapParallelLock.RUnlock() + if s.snaps != nil { // In order for the miner to be able to use and make additions // to the snapshot tree, we need to copy that as well. @@ -1469,13 +1481,6 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { // and force the miner to operate trie-backed only state.snaps = s.snaps state.snap = s.snap - // deep copy needed - state.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) - s.snapParallelLock.RLock() - for k, v := range s.snapDestructs { - state.snapDestructs[k] = v - } - s.snapParallelLock.RUnlock() // snapAccounts is useless in SlotDB, comment out and remove later // state.snapAccounts = make(map[common.Address][]byte) // snapAccountPool.Get().(map[common.Address][]byte) // for k, v := range s.snapAccounts { @@ -2491,7 +2496,7 @@ func (s *StateDB) FinaliseRWSet() error { return s.mvStates.FulfillRWSet(rwSet, stat) } -func (s *StateDB) getStateObjectsDegetstruct(addr common.Address) (*types.StateAccount, bool) { +func (s *StateDB) getStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) { if !(s.isParallel && s.parallel.isSlotDB) { if acc, ok := s.stateObjectsDestructDirty[addr]; ok { return acc, ok @@ -2825,17 +2830,12 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip s.accessList = slotDb.accessList.Copy() } - if slotDb.snaps != nil { - for k := range slotDb.snapDestructs { - // There could be a race condition for parallel transaction execution - // One transaction add balance 0 to an empty address, will delete it(delete empty is enabled). - // While another concurrent transaction could add a none-zero balance to it, make it not empty - // We fixed it by add an addr state read record for add balance 0 - s.snapParallelLock.Lock() - s.snapDestructs[k] = struct{}{} - s.snapParallelLock.Unlock() - } + for k := range slotDb.snapDestructs { + s.snapParallelLock.Lock() + s.snapDestructs[k] = struct{}{} + s.snapParallelLock.Unlock() } + s.SetTxContext(slotDb.thash, slotDb.txIndex) return s } From 828901f2b664dc2f5186232880e97a9fdca12fd0 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 11 Aug 2024 21:05:41 +0800 Subject: [PATCH 29/72] fix: DAG disable access unconfirmedDB --- core/parallel_state_processor.go | 4 ++- core/state/parallel_statedb.go | 47 ++++++++++++++++++++++++++++++-- core/state/state_object.go | 14 +++++----- core/state/statedb.go | 1 + core/state/statedb_test.go | 26 +++++++++--------- 5 files changed, 68 insertions(+), 24 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 962ba2d56a..3689fe0842 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -108,6 +108,7 @@ type ParallelTxRequest struct { executedNum atomic.Int32 retryNum int32 conflictIndex atomic.Int32 + useDAG bool } // to create and start the execution slot goroutines @@ -275,7 +276,7 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR return nil } execNum := txReq.executedNum.Add(1) - slotDB := state.NewSlotDB(txReq.baseStateDB, txReq.txIndex, int(mIndex), p.unconfirmedDBs) + slotDB := state.NewSlotDB(txReq.baseStateDB, txReq.txIndex, int(mIndex), p.unconfirmedDBs, txReq.useDAG) blockContext := NewEVMBlockContext(txReq.block.Header(), p.bc, nil, p.config, slotDB) // can share blockContext within a block for efficiency txContext := NewEVMTxContext(txReq.msg) vmenv := vm.NewEVM(blockContext, txContext, slotDB, p.config, txReq.vmConfig) @@ -786,6 +787,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat systemAddrRedo: false, // set to true, when systemAddr access is detected. runnable: 1, // 0: not runnable, 1: runnable retryNum: 0, + useDAG: txDAG != nil, } txReq.executedNum.Store(0) txReq.conflictIndex.Store(-2) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index a0c00fe4ca..84063ea9f0 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -84,6 +84,10 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash mainDB := slotDB.parallel.baseStateDB if isStage2 { // update slotDB's unconfirmed DB list and try + if slotDB.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return false + } if valUnconfirm, ok := slotDB.getKVFromUnconfirmedDB(addr, key); ok { if !bytes.Equal(val.Bytes(), valUnconfirm.Bytes()) { log.Debug("IsSlotDBReadsValid KV read is invalid in unconfirmed", "addr", addr, @@ -124,14 +128,14 @@ func StartKvCheckLoop() { // NewSlotDB creates a new State DB based on the provided StateDB. // With parallel, each execution slot would have its own StateDB. // This method must be called after the baseDB call PrepareParallel() -func NewSlotDB(db *StateDB, txIndex int, baseTxIndex int, unconfirmedDBs *sync.Map /*map[int]*ParallelStateDB*/) *ParallelStateDB { +func NewSlotDB(db *StateDB, txIndex int, baseTxIndex int, unconfirmedDBs *sync.Map, useDAG bool) *ParallelStateDB { slotDB := db.CopyForSlot() slotDB.txIndex = txIndex slotDB.originalRoot = db.originalRoot slotDB.parallel.baseStateDB = db slotDB.parallel.baseTxIndex = baseTxIndex slotDB.parallel.unconfirmedDBs = unconfirmedDBs - + slotDB.parallel.useDAG = useDAG return slotDB } @@ -304,7 +308,7 @@ func (s *ParallelStateDB) getDeletedStateObject(addr common.Address) *stateObjec } // GetOrNewStateObject retrieves a state object or create a new state object if nil. -// dirtyInSlot -> Unconfirmed DB -> main DB -> snapshot, no? create one +// dirtyInSlot -> Unconfirmed DB (if not DAG) -> main DB -> snapshot, no? create one func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *stateObject { var object *stateObject var ok bool @@ -1089,6 +1093,10 @@ func (s *ParallelStateDB) SubRefund(gas uint64) { // // Access from the unconfirmed DB with range&priority: txIndex -1(previous tx) -> baseTxIndex + 1 func (s *ParallelStateDB) getBalanceFromUnconfirmedDB(addr common.Address) *uint256.Int { + if s.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return nil + } for i := s.txIndex - 1; i >= 0 && i > s.BaseTxIndex(); i-- { db_, ok := s.parallel.unconfirmedDBs.Load(i) if !ok { @@ -1119,6 +1127,11 @@ func (s *ParallelStateDB) getBalanceFromUnconfirmedDB(addr common.Address) *uint // Similar to getBalanceFromUnconfirmedDB func (s *ParallelStateDB) getNonceFromUnconfirmedDB(addr common.Address) (uint64, bool) { + if s.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return 0, false + } + for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { db_, ok := s.parallel.unconfirmedDBs.Load(i) if !ok { @@ -1158,6 +1171,10 @@ func (s *ParallelStateDB) getNonceFromUnconfirmedDB(addr common.Address) (uint64 // Similar to getBalanceFromUnconfirmedDB // It is not only for code, but also codeHash and codeSize, we return the *stateObject for convenience. func (s *ParallelStateDB) getCodeFromUnconfirmedDB(addr common.Address) ([]byte, bool) { + if s.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return nil, false + } for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { db_, ok := s.parallel.unconfirmedDBs.Load(i) if !ok { @@ -1196,6 +1213,10 @@ func (s *ParallelStateDB) getCodeFromUnconfirmedDB(addr common.Address) ([]byte, // Similar to getCodeFromUnconfirmedDB // but differ when address is deleted or not exist func (s *ParallelStateDB) getCodeHashFromUnconfirmedDB(addr common.Address) (common.Hash, bool) { + if s.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return common.Hash{}, false + } for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { db_, ok := s.parallel.unconfirmedDBs.Load(i) if !ok { @@ -1236,6 +1257,10 @@ func (s *ParallelStateDB) getCodeHashFromUnconfirmedDB(addr common.Address) (com // Since the unconfirmed DB should have done Finalise() with `deleteEmptyObjects = true` // If the dirty address is empty or suicided, it will be marked as deleted, so we only need to return `deleted` or not. func (s *ParallelStateDB) getAddrStateFromUnconfirmedDB(addr common.Address, testEmpty bool) (bool, bool) { + if s.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return false, false + } // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { db_, ok := s.parallel.unconfirmedDBs.Load(i) @@ -1265,6 +1290,10 @@ func (s *ParallelStateDB) getAddrStateFromUnconfirmedDB(addr common.Address, tes } func (s *ParallelStateDB) getKVFromUnconfirmedDB(addr common.Address, key common.Hash) (common.Hash, bool) { + if s.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return common.Hash{}, false + } // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { db_, ok := s.parallel.unconfirmedDBs.Load(i) @@ -1292,6 +1321,10 @@ func (s *ParallelStateDB) GetStateObjectFromUnconfirmedDB(addr common.Address) ( } func (s *ParallelStateDB) getStateObjectFromUnconfirmedDB(addr common.Address) (*stateObject, bool) { + if s.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return nil, false + } // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) for i := s.txIndex - 1; i > s.BaseTxIndex(); i-- { db_, ok := s.parallel.unconfirmedDBs.Load(i) @@ -1317,6 +1350,10 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { // for nonce for addr, nonceSlot := range slotDB.parallel.nonceReadsInSlot { if isStage2 { // update slotDB's unconfirmed DB list and try + if slotDB.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return false + } if nonceUnconfirm, ok := slotDB.getNonceFromUnconfirmedDB(addr); ok { if nonceSlot != nonceUnconfirm { log.Debug("IsSlotDBReadsValid nonce read is invalid in unconfirmed", "addr", addr, @@ -1343,6 +1380,10 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { // balance for addr, balanceSlot := range slotDB.parallel.balanceReadsInSlot { if isStage2 { // update slotDB's unconfirmed DB list and try + if slotDB.parallel.useDAG { + // DAG never reads from unconfirmedDB, skip check. + return false + } if balanceUnconfirm := slotDB.getBalanceFromUnconfirmedDB(addr); balanceUnconfirm != nil { if balanceSlot.Cmp(balanceUnconfirm) == 0 { continue diff --git a/core/state/state_object.go b/core/state/state_object.go index 0e8481131d..6560c8752b 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -362,17 +362,17 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // Tx2 recreate account@addr2, setState(key0) -- executing // TX2 GetState(addr2, key1) --- // key1 is never set after recurrsect, and should not return state in trie as it destructed in unconfirmed - // TODO - dav: do we need try storages from unconfirmedDB? - currently not because conflict detection need it for get from mainDB. - obj, exist := s.dbItf.GetStateObjectFromUnconfirmedDB(s.address) - if exist { - if obj.deleted || obj.selfDestructed { - return common.Hash{} + if s.db.parallel.useDAG != true { + obj, exist := s.dbItf.GetStateObjectFromUnconfirmedDB(s.address) + if exist { + if obj.deleted || obj.selfDestructed { + return common.Hash{} + } } } - // also test whether the object is in mainDB and deleted. pdb := s.db.parallel.baseStateDB - obj, exist = pdb.getStateObjectFromStateObjects(s.address) + obj, exist := pdb.getStateObjectFromStateObjects(s.address) if exist { if obj.deleted || obj.selfDestructed { return common.Hash{} diff --git a/core/state/statedb.go b/core/state/statedb.go index e94295861f..0031b37e00 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -173,6 +173,7 @@ type ParallelState struct { // we may need to redo for some specific reasons, like we read the wrong state and need to panic in sequential mode in SubRefund needsRedo bool + useDAG bool } // StateDB structs within the ethereum protocol are used to store anything diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 6660482d6c..274854587f 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1205,7 +1205,7 @@ func TestSuicide(t *testing.T) { unconfirmedDBs := new(sync.Map) state.PrepareForParallel() - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) addr := common.BytesToAddress([]byte("so")) slotDb.SetBalance(addr, big.NewInt(1)) @@ -1239,7 +1239,7 @@ func TestSetAndGetState(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) unconfirmedDBs := new(sync.Map) state.PrepareForParallel() - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetState(addr, common.BytesToHash([]byte("test key")), common.BytesToHash([]byte("test store"))) if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; !ok { @@ -1276,7 +1276,7 @@ func TestSetAndGetCode(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; ok { t.Fatalf("address should not exist in dirtiedStateObjectsInSlot") } @@ -1311,7 +1311,7 @@ func TestGetCodeSize(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetCode(addr, []byte("test code")) codeSize := slotDb.GetCodeSize(addr) @@ -1333,7 +1333,7 @@ func TestGetCodeHash(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetCode(addr, []byte("test code")) @@ -1358,7 +1358,7 @@ func TestSetNonce(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetNonce(addr, 2) oldNonce := state.GetNonce(addr) @@ -1384,7 +1384,7 @@ func TestSetAndGetBalance(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetBalance(addr, big.NewInt(2)) @@ -1420,7 +1420,7 @@ func TestSubBalance(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SubBalance(addr, big.NewInt(1)) oldBalance := state.GetBalance(addr) @@ -1454,7 +1454,7 @@ func TestAddBalance(t *testing.T) { state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.AddBalance(addr, big.NewInt(1)) oldBalance := state.GetBalance(addr) @@ -1489,7 +1489,7 @@ func TestEmpty(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) empty := slotDb.Empty(addr) if empty { @@ -1509,7 +1509,7 @@ func TestExist(t *testing.T) { state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) exist := slotDb.Exist(addr) if !exist { @@ -1528,9 +1528,9 @@ func TestMergeSlotDB(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - oldSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + oldSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) - newSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs) + newSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) addr := systemAddress newSlotDb.SetBalance(addr, big.NewInt(2)) From e774f15da61562981186324a71bc0e5223952baf Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 12 Aug 2024 07:14:59 +0800 Subject: [PATCH 30/72] do not check special addr 0x1 for destruct --- core/state/parallel_statedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 84063ea9f0..c36aa371f2 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1537,7 +1537,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { slotDB.snapParallelLock.RLock() // fixme: this lock is not needed _, destructMain := mainDB.snapDestructs[addr] // addr not exist slotDB.snapParallelLock.RUnlock() - if destructRead != destructMain { + if destructRead != destructMain && addr.Hex() != "0x0000000000000000000000000000000000000001" { log.Debug("IsSlotDBReadsValid snapshot destructs read invalid", "addr", addr, "destructRead", destructRead, "destructMain", destructMain, "SlotIndex", slotDB.parallel.SlotIndex, From fcc558c43c57630a95735975f7bcff693fc9d73f Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 12 Aug 2024 10:36:19 +0800 Subject: [PATCH 31/72] fix log issue --- core/parallel_state_processor.go | 2 +- core/state/parallel_statedb.go | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 3689fe0842..13cb2964ac 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -449,7 +449,7 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo func (p *ParallelStateProcessor) toConfirmTxIndexResult(txResult *ParallelTxResult, isStage2 bool) bool { txReq := txResult.txReq if p.hasConflict(txResult, isStage2) { - log.Debug("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex) + log.Debug(fmt.Sprintf("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex)) return false } if isStage2 { // not its turn diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index c36aa371f2..0f6c170775 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -104,7 +104,8 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash log.Debug("hasKvConflict is invalid", "addr", addr, "key", key, "valSlot", val, "valMain", valMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainDB.TxIndex", mainDB.TxIndex()) + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, + "mainDB.TxIndex", mainDB.TxIndex()) return true // return false, Range will be terminated. } return false @@ -1372,7 +1373,8 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { if nonceSlot != nonceMain { log.Debug("IsSlotDBReadsValid nonce read is invalid", "addr", addr, "nonceSlot", nonceSlot, "nonceMain", nonceMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, + "mainIndex", mainDB.txIndex) return false } @@ -1401,7 +1403,8 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { if balanceSlot.Cmp(balanceMain) != 0 { log.Debug("IsSlotDBReadsValid balance read is invalid", "addr", addr, "balanceSlot", balanceSlot, "balanceMain", balanceMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, + "mainIndex", mainDB.txIndex) return false } } @@ -1492,7 +1495,8 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { if !bytes.Equal(codeSlot, codeMain) { log.Debug("IsSlotDBReadsValid code read is invalid", "addr", addr, "len codeSlot", len(codeSlot), "len codeMain", len(codeMain), "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, + "mainIndex", mainDB.txIndex) return false } } @@ -1506,7 +1510,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { if !bytes.Equal(codeHashSlot.Bytes(), codeHashMain.Bytes()) { log.Debug("IsSlotDBReadsValid codehash read is invalid", "addr", addr, "codeHashSlot", codeHashSlot, "codeHashMain", codeHashMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) return false } } @@ -1520,7 +1524,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { log.Debug("IsSlotDBReadsValid addrState read invalid(true: exist, false: not exist)", "addr", addr, "stateSlot", stateSlot, "stateMain", stateMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) return false } } @@ -1541,7 +1545,8 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { log.Debug("IsSlotDBReadsValid snapshot destructs read invalid", "addr", addr, "destructRead", destructRead, "destructMain", destructMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, + "mainIndex", mainDB.txIndex) return false } } From f38a91e4976800421f1c3ac96c21223a9a56f128 Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 13 Aug 2024 10:19:00 +0800 Subject: [PATCH 32/72] fix: false report of conflict --- core/state/parallel_statedb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 0f6c170775..8388a616e8 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1353,7 +1353,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { if isStage2 { // update slotDB's unconfirmed DB list and try if slotDB.parallel.useDAG { // DAG never reads from unconfirmedDB, skip check. - return false + return true } if nonceUnconfirm, ok := slotDB.getNonceFromUnconfirmedDB(addr); ok { if nonceSlot != nonceUnconfirm { @@ -1384,7 +1384,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { if isStage2 { // update slotDB's unconfirmed DB list and try if slotDB.parallel.useDAG { // DAG never reads from unconfirmedDB, skip check. - return false + return true } if balanceUnconfirm := slotDB.getBalanceFromUnconfirmedDB(addr); balanceUnconfirm != nil { if balanceSlot.Cmp(balanceUnconfirm) == 0 { From 3b0cf581d7d31f681b0ed68d1fa763e7b9d946a5 Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Tue, 13 Aug 2024 21:06:16 +0800 Subject: [PATCH 33/72] txDAG transfer (#28) * txDAG transfer * set flag of txDAG transaction to 'no dependency' * encode/decode txDAG data with ABI * set enable flag for txdag * set txDAG receiver to a special address * remove invalid flags --------- Co-authored-by: andyzhang2023 --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 17 +++++- core/blockchain.go | 4 ++ core/parallel_state_processor.go | 13 +++-- core/types/dag.go | 72 ++++++++++++++++++++++++++ core/types/dag_test.go | 14 +++++ miner/miner.go | 8 ++- miner/worker.go | 89 +++++++++++++++++++++++++++++++- 8 files changed, 211 insertions(+), 7 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 23fe516b9b..1e1df35afa 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -173,6 +173,7 @@ var ( utils.ParallelTxNumFlag, utils.ParallelTxDAGFlag, utils.ParallelTxDAGFileFlag, + utils.ParallelTxDAGSenderPrivFlag, configFileFlag, utils.LogDebugFlag, utils.LogBacktraceAtFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 616ea4ea15..a668e813f3 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -23,7 +23,6 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/ethereum/go-ethereum/core/txpool/bundlepool" "math" "math/big" "net" @@ -36,6 +35,8 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/core/txpool/bundlepool" + pcsclite "github.com/gballet/go-libpcsclite" gopsutil "github.com/shirou/gopsutil/mem" "github.com/urfave/cli/v2" @@ -1124,6 +1125,13 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Usage: "enable opcode optimization", Category: flags.VMCategory, } + + ParallelTxDAGSenderPrivFlag = &cli.StringFlag{ + Name: "parallel.txdagsenderpriv", + Usage: "private key of the sender who sends the TxDAG transactions", + Value: "", + Category: flags.VMCategory, + } ) var ( @@ -2038,6 +2046,13 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.ParallelTxDAGFile = ctx.String(ParallelTxDAGFileFlag.Name) } + if ctx.IsSet(ParallelTxDAGSenderPrivFlag.Name) { + priHex := ctx.String(ParallelTxDAGSenderPrivFlag.Name) + if cfg.Miner.ParallelTxDAGSenderPriv, err = crypto.HexToECDSA(priHex); err != nil { + Fatalf("Failed to parse txdag private key of %s, err: %v", ParallelTxDAGSenderPrivFlag.Name, err) + } + } + if ctx.IsSet(VMOpcodeOptimizeFlag.Name) { cfg.EnableOpcodeOptimizing = ctx.Bool(VMOpcodeOptimizeFlag.Name) if cfg.EnableOpcodeOptimizing { diff --git a/core/blockchain.go b/core/blockchain.go index 204354270f..92197d76d1 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2705,6 +2705,10 @@ func (bc *BlockChain) TxDAGEnabled() bool { return bc.enableTxDAG } +func (bc *BlockChain) TxDAGFileOpened() bool { + return bc.txDAGWriteCh != nil +} + func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { log.Info("node enable TxDAG feature", "output", output) bc.enableTxDAG = true diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 13cb2964ac..1f74e1f583 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -736,10 +736,17 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat txDAG types.TxDAG ) if p.bc.enableTxDAG { - // TODO(galaio): load TxDAG from block - // or load cache txDAG from file - if txDAG == nil && p.bc.txDAGReader != nil { + var err error + if p.bc.txDAGReader != nil { + // load cache txDAG from file first txDAG = p.bc.txDAGReader.TxDAG(block.NumberU64()) + + } else { + // load TxDAG from block + txDAG, err = types.GetTxDAG(block) + if err != nil { + log.Debug("pevm decode txdag failed", "block", block.NumberU64(), "err", err) + } } if err := types.ValidateTxDAG(txDAG, len(block.Transactions())); err != nil { log.Warn("pevm cannot apply wrong txdag", diff --git a/core/types/dag.go b/core/types/dag.go index fb6e374bd2..1815ffba30 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -7,11 +7,41 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "golang.org/x/exp/slices" ) +const TxDAGAbiJson = ` +[ + { + "type": "function", + "name": "setTxDAG", + "inputs": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } +] +` + +var TxDAGABI abi.ABI + +func init() { + var err error + // must be able to register the TxDAGABI + TxDAGABI, err = abi.JSON(strings.NewReader(TxDAGAbiJson)) + if err != nil { + panic(err) + } +} + // TxDAGType Used to extend TxDAG and customize a new DAG structure const ( EmptyTxDAGType byte = iota @@ -48,6 +78,37 @@ type TxDAG interface { SetTxDep(int, TxDep) error } +func DecodeTxDAGCalldata(data []byte) (TxDAG, error) { + // trim the method id before unpack + if len(data) < 4 { + return nil, fmt.Errorf("invalid txDAG calldata, len(data)=%d", len(data)) + } + calldata, err := TxDAGABI.Methods["setTxDAG"].Inputs.Unpack(data[4:]) + if err != nil { + return nil, fmt.Errorf("failed to call abi unpack, err: %v", err) + } + if len(calldata) <= 0 { + return nil, fmt.Errorf("invalid txDAG calldata, len(calldata)=%d", len(calldata)) + } + data, ok := calldata[0].([]byte) + if !ok { + return nil, fmt.Errorf("invalid txDAG calldata parameter") + } + return DecodeTxDAG(data) +} + +func EncodeTxDAGCalldata(dag TxDAG) ([]byte, error) { + data, err := EncodeTxDAG(dag) + if err != nil { + return nil, fmt.Errorf("failed to encode txDAG, err: %v", err) + } + data, err = TxDAGABI.Pack("setTxDAG", data) + if err != nil { + return nil, fmt.Errorf("failed to call abi pack, err: %v", err) + } + return data, nil +} + func EncodeTxDAG(dag TxDAG) ([]byte, error) { if dag == nil { return nil, errors.New("input nil TxDAG") @@ -118,6 +179,17 @@ func ValidatePlainTxDAG(d TxDAG, txCnt int) error { return nil } +// GetTxDAG return TxDAG bytes from block if there is any, or return nil if not exist +// the txDAG is stored in the calldata of the last transaction of the block +func GetTxDAG(block *Block) (TxDAG, error) { + txs := block.Transactions() + if txs.Len() <= 0 { + return nil, fmt.Errorf("no txdag found") + } + // get data from the last tx + return DecodeTxDAGCalldata(txs[txs.Len()-1].Data()) +} + func TxDependency(d TxDAG, i int) []uint64 { if d == nil || i < 0 || i >= d.TxCount() { return []uint64{} diff --git a/core/types/dag_test.go b/core/types/dag_test.go index 6edb10cc3b..d44a750c78 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -8,6 +8,7 @@ import ( "github.com/cometbft/cometbft/libs/rand" "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -16,6 +17,19 @@ var ( mockHash = common.HexToHash("0xdc13f8d7bdb8ec4de02cd4a50a1aa2ab73ec8814e0cdb550341623be3dd8ab7a") ) +func TestEncodeTxDAGCalldata(t *testing.T) { + tg := mockSimpleDAG() + data, err := EncodeTxDAGCalldata(tg) + assert.Equal(t, nil, err) + tg, err = DecodeTxDAGCalldata(data) + assert.Equal(t, nil, err) + assert.Equal(t, tg.TxDep(6).TxIndexes[0], uint64(2)) + assert.Equal(t, tg.TxDep(6).TxIndexes[1], uint64(5)) + + _, err = DecodeTxDAGCalldata(nil) + assert.NotEqual(t, nil, err) +} + func TestTxDAG_SetTxDep(t *testing.T) { dag := mockSimpleDAG() require.NoError(t, dag.SetTxDep(9, NewTxDep(nil, NonDependentRelFlag))) diff --git a/miner/miner.go b/miner/miner.go index 8dbe58c45e..08c60c186e 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -19,14 +19,16 @@ package miner import ( "context" + "crypto/ecdsa" "errors" "fmt" - "github.com/ethereum/go-ethereum/consensus/misc/eip1559" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "math/big" "sync" "time" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -108,6 +110,8 @@ type Config struct { EffectiveGasCeil uint64 // if non-zero, a gas ceiling to apply independent of the header's gaslimit value Mev MevConfig // Mev configuration + + ParallelTxDAGSenderPriv *ecdsa.PrivateKey // The private key for the parallel tx DAG sender } // DefaultConfig contains default settings for miner. diff --git a/miner/worker.go b/miner/worker.go index 702a576600..67c6d57d21 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -18,14 +18,16 @@ package miner import ( "context" + "crypto/ecdsa" "errors" "fmt" - mapset "github.com/deckarep/golang-set/v2" "math/big" "sync" "sync/atomic" "time" + mapset "github.com/deckarep/golang-set/v2" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -36,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -99,6 +102,10 @@ var ( txErrReplayMeter = metrics.NewRegisteredMeter("miner/tx/replay", nil) ) +var ( + DefaultTxDAGAddress = common.HexToAddress("0xda90000000000000000000000000000000000000") +) + // environment is the worker's current environment and holds all // information of the sealing block generation. type environment struct { @@ -905,10 +912,36 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac } var coalescedLogs []*types.Log + //append the tx DAG transaction to the block + appendTxDAG := func() { + // whether enable TxDAG + if !w.chain.TxDAGEnabled() { + return + } + // whether export to file + if w.chain.TxDAGFileOpened() { + return + } + // TODO this is a placeholder for the tx DAG data that will be generated by the stateDB + txForDAG, err := w.generateDAGTx(env.signer, env.tcount, env.coinbase) + if err != nil { + log.Warn("failed to generate DAG tx", "err", err) + return + } + logs, err := w.commitTransaction(env, txForDAG) + if err != nil { + log.Warn("failed to commit DAG tx", "err", err) + return + } + coalescedLogs = append(coalescedLogs, logs...) + env.tcount++ + } + for { // Check interruption signal and abort building if it's fired. if interrupt != nil { if signal := interrupt.Load(); signal != commitInterruptNone { + appendTxDAG() return signalToErr(signal) } } @@ -1007,6 +1040,7 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac txErrUnknownMeter.Mark(1) } } + appendTxDAG() if !w.isRunning() && len(coalescedLogs) > 0 { // We don't push the pendingLogsEvent while we are sealing. The reason is that // when we are sealing, the worker will regenerate a sealing block every 3 seconds. @@ -1025,6 +1059,59 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac return nil } +// generateDAGTx generates a DAG transaction for the block +func (w *worker) generateDAGTx(signer types.Signer, txIndex int, coinbase common.Address) (*types.Transaction, error) { + statedb, err := w.chain.State() + if err != nil { + return nil, fmt.Errorf("failed to get state db, err: %v", err) + } + + if signer == nil { + return nil, fmt.Errorf("current signer is nil") + } + + //privateKey, err := crypto.HexToECDSA(privateKeyHex) + sender := w.config.ParallelTxDAGSenderPriv + receiver := DefaultTxDAGAddress + if sender == nil { + return nil, fmt.Errorf("missing sender private key") + } + + // get txDAG data from the stateDB + txDAG, err := statedb.ResolveTxDAG(txIndex, []common.Address{coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + if txDAG == nil { + return nil, err + } + // txIndex is the index of this txDAG transaction + txDAG.SetTxDep(txIndex, types.TxDep{Flags: &types.NonDependentRelFlag}) + + publicKey := sender.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("error casting public key to ECDSA") + } + fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) + + // get nonce from the + nonce := statedb.GetNonce(fromAddress) + + data, err := types.EncodeTxDAGCalldata(txDAG) + if err != nil { + return nil, fmt.Errorf("failed to encode txDAG, err: %v", err) + } + + // Create the transaction + tx := types.NewTransaction(nonce, receiver, big.NewInt(0), 21100, big.NewInt(0), data) + + // Sign the transaction with the private key + signedTx, err := types.SignTx(tx, signer, sender) + if err != nil { + return nil, fmt.Errorf("failed to sign transaction, err: %v", err) + } + + return signedTx, nil +} + // generateParams wraps various of settings for generating sealing task. type generateParams struct { timestamp uint64 // The timestamp for sealing task From 6c0427b1f4da27f4a822f05c7f162b5394413224 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Tue, 13 Aug 2024 21:12:32 +0800 Subject: [PATCH 34/72] txdag: using pending writes to accelerate txdag generation, add more bench tests; (#30) * mvstate: using pending writes to accelerate txdag generation; * txdag: test snappy compress ratio; * txdag: add more bench tests; --------- Co-authored-by: galaio --- core/blockchain.go | 24 +++- core/state/statedb.go | 8 +- core/state_processor.go | 23 ---- core/types/dag.go | 48 ++----- core/types/dag_test.go | 39 +++++- core/types/mvstates.go | 129 ++++++++++++++---- core/types/mvstates_test.go | 253 ++++++++++++++++++++++++++++++------ 7 files changed, 391 insertions(+), 133 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 92197d76d1..f188d917d6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1955,6 +1955,28 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) followupInterrupt.Store(true) return it.index, err } + + if bc.enableTxDAG { + // compare input TxDAG when it enable in consensus + dag, err := statedb.ResolveTxDAG(len(block.Transactions()), []common.Address{block.Coinbase(), params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + if err == nil { + // TODO(galaio): check TxDAG correctness? + log.Debug("Process TxDAG result", "block", block.NumberU64(), "txDAG", dag) + if metrics.EnabledExpensive { + go types.EvaluateTxDAGPerformance(dag, statedb.ResolveStats()) + } + // try to write txDAG into file + if bc.txDAGWriteCh != nil && dag != nil { + bc.txDAGWriteCh <- TxDAGOutputItem{ + blockNumber: block.NumberU64(), + txDAG: dag, + } + } + } else { + log.Error("ResolveTxDAG err", "block", block.NumberU64(), "tx", len(block.Transactions()), "err", err) + } + } + vtime := time.Since(vstart) proctime := time.Since(start) // processing + validation @@ -2724,7 +2746,7 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { } // startup with latest block curHeader := bc.CurrentHeader() - if curHeader != nil { + if curHeader != nil && bc.txDAGReader != nil { bc.txDAGReader.TxDAG(curHeader.Number.Uint64()) log.Info("load TxDAG from file", "output", output, "block", curHeader.Number, "latest", bc.txDAGReader.Latest()) } diff --git a/core/state/statedb.go b/core/state/statedb.go index 0031b37e00..16500e2c4d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2444,7 +2444,7 @@ func (s *StateDB) ResetMVStates(txCount int) { if s.isParallel && s.parallel.isSlotDB { return } - s.mvStates = types.NewMVStates(txCount) + s.mvStates = types.NewMVStates(txCount).EnableAsyncDepGen() s.rwSet = nil } @@ -2494,7 +2494,11 @@ func (s *StateDB) FinaliseRWSet() error { // reset stateDB s.rwSet = nil - return s.mvStates.FulfillRWSet(rwSet, stat) + if err := s.mvStates.FulfillRWSet(rwSet, stat); err != nil { + return err + } + // just Finalise rwSet in serial execution + return s.mvStates.Finalise(s.txIndex) } func (s *StateDB) getStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) { diff --git a/core/state_processor.go b/core/state_processor.go index 84708e09d0..7a0252d36f 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -22,8 +22,6 @@ import ( "math/big" "time" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" @@ -135,27 +133,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) - - if p.bc.enableTxDAG { - // compare input TxDAG when it enable in consensus - dag, err := statedb.ResolveTxDAG(len(block.Transactions()), []common.Address{context.Coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) - if err == nil { - // TODO(galaio): check TxDAG correctness? - log.Debug("Process TxDAG result", "block", block.NumberU64(), "txDAG", dag) - if metrics.EnabledExpensive { - types.EvaluateTxDAGPerformance(dag, statedb.ResolveStats()) - } - // try to write txDAG into file - if p.bc.txDAGWriteCh != nil && dag != nil { - p.bc.txDAGWriteCh <- TxDAGOutputItem{ - blockNumber: block.NumberU64(), - txDAG: dag, - } - } - } else { - log.Error("ResolveTxDAG err", "block", block.NumberU64(), "tx", len(block.Transactions()), "err", err) - } - } return receipts, allLogs, *usedGas, nil } diff --git a/core/types/dag.go b/core/types/dag.go index 1815ffba30..e46f7bb897 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -196,11 +196,7 @@ func TxDependency(d TxDAG, i int) []uint64 { } dep := d.TxDep(i) if dep.CheckFlag(ExcludedTxFlag) { - txs := make([]uint64, 0, i) - for j := 0; j < i; j++ { - txs = append(txs, uint64(j)) - } - return txs + return []uint64{} } if dep.CheckFlag(NonDependentRelFlag) { txs := make([]uint64, 0, d.TxCount()-dep.Count()) @@ -327,12 +323,11 @@ func MergeTxDAGExecutionPaths(d TxDAG, from, to uint64) ([][]uint64, error) { if from > to || to >= uint64(d.TxCount()) { return nil, fmt.Errorf("input wrong from: %v, to: %v, txCnt:%v", from, to, d.TxCount()) } - nd := convert2PlainTxDAG(d) - mergeMap := make(map[uint64][]uint64, nd.TxCount()) - txMap := make(map[uint64]uint64, nd.TxCount()) + mergeMap := make(map[uint64][]uint64, d.TxCount()) + txMap := make(map[uint64]uint64, d.TxCount()) for i := int(to); i >= int(from); i-- { index, merge := uint64(i), uint64(i) - deps := nd.TxDep(i).TxIndexes + deps := TxDependency(d, i) // drop the out range txs deps = depExcludeTxRange(deps, from, to) if oldIdx, exist := findTxPathIndex(deps, index, txMap); exist { @@ -401,41 +396,14 @@ func findTxPathIndex(path []uint64, cur uint64, txMap map[uint64]uint64) (uint64 // travelTxDAGExecutionPaths will print all tx execution path func travelTxDAGExecutionPaths(d TxDAG) [][]uint64 { - nd := convert2PlainTxDAG(d) - exePaths := make([][]uint64, 0) // travel tx deps with BFS - for i := uint64(0); i < uint64(nd.TxCount()); i++ { - exePaths = append(exePaths, travelTxDAGTargetPath(nd.TxDeps, i)) + for i := uint64(0); i < uint64(d.TxCount()); i++ { + exePaths = append(exePaths, travelTxDAGTargetPath(d, i)) } return exePaths } -// convert2PlainTxDAG will convert to PlainTxDAG with dependency txs -func convert2PlainTxDAG(d TxDAG) *PlainTxDAG { - if d.TxCount() == 0 { - return NewPlainTxDAG(0) - } - nd := NewPlainTxDAG(d.TxCount()) - for i := 0; i < d.TxCount(); i++ { - dep := d.TxDep(i) - if !dep.CheckFlag(NonDependentRelFlag) { - nd.SetTxDep(i, *dep) - continue - } - // recover to dependency relation txs - np := TxDep{Flags: dep.Flags} - np.ClearFlag(NonDependentRelFlag) - for j := 0; j < i; j++ { - if !dep.Exist(j) && j != i { - np.AppendDep(j) - } - } - nd.SetTxDep(i, np) - } - return nd -} - // TxDep store the current tx dependency relation with other txs type TxDep struct { TxIndexes []uint64 @@ -611,7 +579,7 @@ func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) { } // travelTxDAGTargetPath will print target execution path -func travelTxDAGTargetPath(deps []TxDep, from uint64) []uint64 { +func travelTxDAGTargetPath(d TxDAG, from uint64) []uint64 { var ( queue []uint64 path []uint64 @@ -622,7 +590,7 @@ func travelTxDAGTargetPath(deps []TxDep, from uint64) []uint64 { for len(queue) > 0 { var next []uint64 for _, i := range queue { - for _, dep := range deps[i].TxIndexes { + for _, dep := range TxDependency(d, int(i)) { if !slices.Contains(path, dep) { path = append(path, dep) next = append(next, dep) diff --git a/core/types/dag_test.go b/core/types/dag_test.go index d44a750c78..11587c70ea 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/golang/snappy" + "github.com/cometbft/cometbft/libs/rand" "github.com/ethereum/go-ethereum/common" @@ -147,6 +149,14 @@ func TestMergeTxDAGExecutionPaths_Random(t *testing.T) { require.Equal(t, dag.TxCount(), len(txMap)) } +func TestTxDAG_Compression(t *testing.T) { + dag := mockRandomDAG(10000) + enc, err := EncodeTxDAG(dag) + require.NoError(t, err) + encoded := snappy.Encode(nil, enc) + t.Log("enc", len(enc), "compressed", len(encoded), "ratio", 1-(float64(len(encoded))/float64(len(enc)))) +} + func BenchmarkMergeTxDAGExecutionPaths(b *testing.B) { dag := mockRandomDAG(100000) for i := 0; i < b.N; i++ { @@ -154,6 +164,21 @@ func BenchmarkMergeTxDAGExecutionPaths(b *testing.B) { } } +func BenchmarkTxDAG_Encode(b *testing.B) { + dag := mockRandomDAG(10000) + for i := 0; i < b.N; i++ { + EncodeTxDAG(dag) + } +} + +func BenchmarkTxDAG_Decode(b *testing.B) { + dag := mockRandomDAG(10000) + enc, _ := EncodeTxDAG(dag) + for i := 0; i < b.N; i++ { + DecodeTxDAG(enc) + } +} + func mockSimpleDAG() TxDAG { dag := NewPlainTxDAG(10) dag.TxDeps[0].TxIndexes = []uint64{} @@ -162,7 +187,7 @@ func mockSimpleDAG() TxDAG { dag.TxDeps[3].TxIndexes = []uint64{0} dag.TxDeps[4].TxIndexes = []uint64{0} dag.TxDeps[5].TxIndexes = []uint64{1, 2} - dag.TxDeps[6].TxIndexes = []uint64{2, 5} + dag.TxDeps[6].TxIndexes = []uint64{5} dag.TxDeps[7].TxIndexes = []uint64{6} dag.TxDeps[8].TxIndexes = []uint64{} dag.TxDeps[9].TxIndexes = []uint64{8} @@ -219,7 +244,7 @@ func mockSystemTxDAG() TxDAG { dag.TxDeps[3].TxIndexes = []uint64{0} dag.TxDeps[4].TxIndexes = []uint64{0} dag.TxDeps[5].TxIndexes = []uint64{1, 2} - dag.TxDeps[6].TxIndexes = []uint64{2, 5} + dag.TxDeps[6].TxIndexes = []uint64{5} dag.TxDeps[7].TxIndexes = []uint64{6} dag.TxDeps[8].TxIndexes = []uint64{} dag.TxDeps[9].TxIndexes = []uint64{8} @@ -236,7 +261,7 @@ func mockSystemTxDAG2() TxDAG { dag.TxDeps[3] = NewTxDep([]uint64{0}) dag.TxDeps[4] = NewTxDep([]uint64{0}) dag.TxDeps[5] = NewTxDep([]uint64{1, 2}) - dag.TxDeps[6] = NewTxDep([]uint64{2, 5}) + dag.TxDeps[6] = NewTxDep([]uint64{5}) dag.TxDeps[7] = NewTxDep([]uint64{6}) dag.TxDeps[8] = NewTxDep([]uint64{}) dag.TxDeps[9] = NewTxDep([]uint64{8}) @@ -253,11 +278,11 @@ func mockSystemTxDAGWithLargeDeps() TxDAG { dag.TxDeps[3].TxIndexes = []uint64{0} dag.TxDeps[4].TxIndexes = []uint64{0} dag.TxDeps[5].TxIndexes = []uint64{1, 2} - dag.TxDeps[6].TxIndexes = []uint64{2, 5} - dag.TxDeps[7].TxIndexes = []uint64{0, 1, 3, 5, 6} + dag.TxDeps[6].TxIndexes = []uint64{5} + dag.TxDeps[7].TxIndexes = []uint64{3} dag.TxDeps[8].TxIndexes = []uint64{} - //dag.TxDeps[9].TxIndexes = []uint64{0, 1, 2, 3, 4, 8} - dag.TxDeps[9] = NewTxDep([]uint64{5, 6, 7, 10, 11}, NonDependentRelFlag) + //dag.TxDeps[9].TxIndexes = []uint64{0, 1, 2, 6, 7, 8} + dag.TxDeps[9] = NewTxDep([]uint64{3, 4, 5, 10, 11}, NonDependentRelFlag) dag.TxDeps[10] = NewTxDep([]uint64{}, ExcludedTxFlag) dag.TxDeps[11] = NewTxDep([]uint64{}, ExcludedTxFlag) return dag diff --git a/core/types/mvstates.go b/core/types/mvstates.go index c789dbeabc..eca3fd17ac 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -283,14 +283,30 @@ func (w *PendingWrites) FindLastWrite(txIndex int) *RWItem { return nil } +func (w *PendingWrites) FindPrevWrites(txIndex int) []*RWItem { + var i, _ = w.SearchTxIndex(txIndex) + for j := i - 1; j >= 0; j-- { + if w.list[j].TxIndex() < txIndex { + return w.list[:j+1] + } + } + + return nil +} + type MVStates struct { rwSets map[int]*RWSet pendingWriteSet map[RWKey]*PendingWrites nextFinaliseIndex int // dependency map cache for generating TxDAG - // depsCache[i].exist(j) means j->i, and i > j - depsCache map[int]TxDepMap + // depMapCache[i].exist(j) means j->i, and i > j + depMapCache map[int]TxDepMap + depsCache map[int][]uint64 + + // async dep analysis + depsGenChan chan int + stopChan chan struct{} // execution stat infos stats map[int]*ExeStat @@ -301,11 +317,38 @@ func NewMVStates(txCount int) *MVStates { return &MVStates{ rwSets: make(map[int]*RWSet, txCount), pendingWriteSet: make(map[RWKey]*PendingWrites, txCount*8), - depsCache: make(map[int]TxDepMap, txCount), + depMapCache: make(map[int]TxDepMap, txCount), + depsCache: make(map[int][]uint64, txCount), stats: make(map[int]*ExeStat, txCount), } } +func (s *MVStates) EnableAsyncDepGen() *MVStates { + s.depsGenChan = make(chan int, 100) + s.stopChan = make(chan struct{}, 1) + go s.asyncDepGenLoop() + return s +} + +func (s *MVStates) stopAsyncDepGen() { + if s.stopChan != nil { + s.stopChan <- struct{}{} + } +} + +func (s *MVStates) asyncDepGenLoop() { + for { + select { + case tx := <-s.depsGenChan: + s.lock.Lock() + s.resolveDepsCacheByWrites(tx, s.rwSets[tx]) + s.lock.Unlock() + case <-s.stopChan: + return + } + } +} + func (s *MVStates) RWSets() map[int]*RWSet { s.lock.RLock() defer s.lock.RUnlock() @@ -363,12 +406,6 @@ func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { } } s.rwSets[index] = rwSet - // async resolve dependency - go func() { - s.lock.Lock() - defer s.lock.Unlock() - s.resolveDepsCache(index, rwSet) - }() return nil } @@ -395,36 +432,78 @@ func (s *MVStates) Finalise(index int) error { s.pendingWriteSet[k].Append(v) } s.nextFinaliseIndex++ + // async resolve dependency + if s.depsGenChan != nil { + s.depsGenChan <- index + } return nil } +func (s *MVStates) resolveDepsCacheByWrites(index int, rwSet *RWSet) { + // analysis dep, if the previous transaction is not executed/validated, re-analysis is required + s.depMapCache[index] = NewTxDeps(0) + if rwSet.excludedTx { + return + } + seen := make(map[int]struct{}) + for key := range rwSet.readSet { + // check self destruct + if key.IsAccountSelf() { + key = AccountStateKey(key.Addr(), AccountSuicide) + } + writes := s.pendingWriteSet[key] + if writes == nil { + continue + } + items := writes.FindPrevWrites(index) + for _, item := range items { + seen[item.TxIndex()] = struct{}{} + } + } + for prev := 0; prev < index; prev++ { + if _, ok := seen[prev]; !ok { + continue + } + s.depMapCache[index].add(prev) + // clear redundancy deps compared with prev + for dep := range s.depMapCache[index] { + if s.depMapCache[prev].exist(dep) { + s.depMapCache[index].remove(dep) + } + } + } + s.depsCache[index] = s.depMapCache[index].toArray() +} + func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { // analysis dep, if the previous transaction is not executed/validated, re-analysis is required - s.depsCache[index] = NewTxDeps(0) + s.depMapCache[index] = NewTxDeps(0) if rwSet.excludedTx { return } for prev := 0; prev < index; prev++ { // if there are some parallel execution or system txs, it will fulfill in advance // it's ok, and try re-generate later - if _, ok := s.rwSets[prev]; !ok { + prevSet, ok := s.rwSets[prev] + if !ok { continue } // if prev tx is tagged ExcludedTxFlag, just skip the check - if s.rwSets[prev].excludedTx { + if prevSet.excludedTx { continue } // check if there has written op before i - if checkDependency(s.rwSets[prev].writeSet, rwSet.readSet) { - s.depsCache[index].add(prev) + if checkDependency(prevSet.writeSet, rwSet.readSet) { + s.depMapCache[index].add(prev) // clear redundancy deps compared with prev - for dep := range s.depsCache[index] { - if s.depsCache[prev].exist(dep) { - s.depsCache[index].remove(dep) + for dep := range s.depMapCache[index] { + if s.depMapCache[prev].exist(dep) { + s.depMapCache[index].remove(dep) } } } } + s.depsCache[index] = s.depMapCache[index].toArray() } func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*RWItem, writeSet map[RWKey]*RWItem) bool { @@ -452,13 +531,17 @@ func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*RWItem, write // ResolveTxDAG generate TxDAG from RWSets func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (TxDAG, error) { - s.lock.RLock() - defer s.lock.RUnlock() + s.lock.Lock() + defer s.lock.Unlock() if len(s.rwSets) != txCnt { return nil, fmt.Errorf("wrong rwSet count, expect: %v, actual: %v", txCnt, len(s.rwSets)) } + if txCnt != s.nextFinaliseIndex { + return nil, fmt.Errorf("resolve in wrong order, next: %d, input: %d", s.nextFinaliseIndex, txCnt) + } + s.stopAsyncDepGen() txDAG := NewPlainTxDAG(len(s.rwSets)) - for i := txCnt - 1; i >= 0; i-- { + for i := 0; i < txCnt; i++ { // check if there are RW with gas fee receiver for gas delay calculation for _, addr := range gasFeeReceivers { if _, ok := s.rwSets[i].readSet[AccountStateKey(addr, AccountSelf)]; ok { @@ -470,10 +553,10 @@ func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (Tx txDAG.TxDeps[i].SetFlag(ExcludedTxFlag) continue } - if s.depsCache[i] == nil { - s.resolveDepsCache(i, s.rwSets[i]) + if s.depMapCache[i] == nil { + s.resolveDepsCacheByWrites(i, s.rwSets[i]) } - deps := s.depsCache[i].toArray() + deps := s.depsCache[i] if len(deps) <= (txCnt-1)/2 { txDAG.TxDeps[i].TxIndexes = deps continue diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 10ee095861..c9d46ebddb 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -1,12 +1,21 @@ package types import ( + "bytes" + "compress/gzip" + "fmt" "testing" + "time" + + "github.com/cometbft/cometbft/libs/rand" + "github.com/golang/snappy" "github.com/holiman/uint256" "github.com/stretchr/testify/require" ) +const mockRWSetSize = 10000 + func TestMVStates_BasicUsage(t *testing.T) { ms := NewMVStates(0) require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(0, []interface{}{"0x00", 0}, []interface{}{"0x00", 0}), nil)) @@ -41,17 +50,40 @@ func TestMVStates_BasicUsage(t *testing.T) { func TestMVStates_SimpleResolveTxDAG(t *testing.T) { ms := NewMVStates(10) + finaliseRWSets(t, ms, []*RWSet{ + mockRWSet(0, []string{"0x00"}, []string{"0x00"}), + mockRWSet(1, []string{"0x01"}, []string{"0x01"}), + mockRWSet(2, []string{"0x02"}, []string{"0x02"}), + mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}), + mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}), + mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}), + mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}), + mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}), + mockRWSet(8, []string{"0x08"}, []string{"0x08"}), + mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}), + }) + + dag, err := ms.ResolveTxDAG(10, nil) + require.NoError(t, err) + require.Equal(t, mockSimpleDAG(), dag) + t.Log(dag) +} - ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) - ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) - ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) - ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) - ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) - ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) - ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) - ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}) - ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) - ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) +func TestMVStates_AsyncDepGen_SimpleResolveTxDAG(t *testing.T) { + ms := NewMVStates(10).EnableAsyncDepGen() + finaliseRWSets(t, ms, []*RWSet{ + mockRWSet(0, []string{"0x00"}, []string{"0x00"}), + mockRWSet(1, []string{"0x01"}, []string{"0x01"}), + mockRWSet(2, []string{"0x02"}, []string{"0x02"}), + mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}), + mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}), + mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}), + mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}), + mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}), + mockRWSet(8, []string{"0x08"}, []string{"0x08"}), + mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}), + }) + time.Sleep(10 * time.Millisecond) dag, err := ms.ResolveTxDAG(10, nil) require.NoError(t, err) @@ -59,21 +91,123 @@ func TestMVStates_SimpleResolveTxDAG(t *testing.T) { t.Log(dag) } +func TestMVStates_ResolveTxDAG_Compare(t *testing.T) { + txCnt := 3000 + rwSets := mockRandomRWSet(txCnt) + ms1 := NewMVStates(txCnt) + ms2 := NewMVStates(txCnt) + for i, rwSet := range rwSets { + ms1.rwSets[i] = rwSet + ms2.rwSets[i] = rwSet + require.NoError(t, ms2.Finalise(i)) + } + + d1 := resolveTxDAGInMVStates(ms1) + d2 := resolveTxDAGByWritesInMVStates(ms2) + require.Equal(t, d1.(*PlainTxDAG).String(), d2.(*PlainTxDAG).String()) +} + +func TestMVStates_TxDAG_Compression(t *testing.T) { + txCnt := 10000 + rwSets := mockRandomRWSet(txCnt) + ms1 := NewMVStates(txCnt) + for i, rwSet := range rwSets { + ms1.rwSets[i] = rwSet + ms1.Finalise(i) + } + dag := resolveTxDAGByWritesInMVStates(ms1) + enc, err := EncodeTxDAG(dag) + require.NoError(t, err) + + // snappy compression + start := time.Now() + encoded := snappy.Encode(nil, enc) + t.Log("snappy", "enc", len(enc), "compressed", len(encoded), + "ratio", 1-(float64(len(encoded))/float64(len(enc))), + "time", float64(time.Since(start).Microseconds())/1000) + + // gzip compression + start = time.Now() + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + _, err = zw.Write(enc) + require.NoError(t, err) + err = zw.Close() + require.NoError(t, err) + encoded = buf.Bytes() + t.Log("gzip", "enc", len(enc), "compressed", len(encoded), + "ratio", 1-(float64(len(encoded))/float64(len(enc))), + "time", float64(time.Since(start).Microseconds())/1000) +} + +func BenchmarkResolveTxDAGInMVStates(b *testing.B) { + rwSets := mockRandomRWSet(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize) + for i, rwSet := range rwSets { + ms1.rwSets[i] = rwSet + } + for i := 0; i < b.N; i++ { + resolveTxDAGInMVStates(ms1) + } +} + +func BenchmarkResolveTxDAGByWritesInMVStates(b *testing.B) { + rwSets := mockRandomRWSet(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize) + for i, rwSet := range rwSets { + ms1.rwSets[i] = rwSet + ms1.Finalise(i) + } + for i := 0; i < b.N; i++ { + resolveTxDAGByWritesInMVStates(ms1) + } +} + +func BenchmarkMVStates_Finalise(b *testing.B) { + rwSets := mockRandomRWSet(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize) + for i := 0; i < b.N; i++ { + for k, rwSet := range rwSets { + ms1.rwSets[k] = rwSet + ms1.Finalise(k) + } + } +} + +func resolveTxDAGInMVStates(s *MVStates) TxDAG { + txDAG := NewPlainTxDAG(len(s.rwSets)) + for i := 0; i < len(s.rwSets); i++ { + s.resolveDepsCache(i, s.rwSets[i]) + txDAG.TxDeps[i].TxIndexes = s.depsCache[i] + } + return txDAG +} + +func resolveTxDAGByWritesInMVStates(s *MVStates) TxDAG { + txDAG := NewPlainTxDAG(len(s.rwSets)) + for i := 0; i < len(s.rwSets); i++ { + s.resolveDepsCacheByWrites(i, s.rwSets[i]) + txDAG.TxDeps[i].TxIndexes = s.depsCache[i] + } + return txDAG +} + func TestMVStates_SystemTxResolveTxDAG(t *testing.T) { ms := NewMVStates(12) - - ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) - ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) - ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) - ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) - ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) - ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) - ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) - ms.rwSets[7] = mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}) - ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) - ms.rwSets[9] = mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}) - ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithExcludedTxFlag() - ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithExcludedTxFlag() + finaliseRWSets(t, ms, []*RWSet{ + mockRWSet(0, []string{"0x00"}, []string{"0x00"}), + mockRWSet(1, []string{"0x01"}, []string{"0x01"}), + mockRWSet(2, []string{"0x02"}, []string{"0x02"}), + mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}), + mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}), + mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}), + mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}), + mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}), + mockRWSet(8, []string{"0x08"}, []string{"0x08"}), + mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}), + mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithExcludedTxFlag(), + mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithExcludedTxFlag(), + }) dag, err := ms.ResolveTxDAG(12, nil) require.NoError(t, err) @@ -83,20 +217,20 @@ func TestMVStates_SystemTxResolveTxDAG(t *testing.T) { func TestMVStates_SystemTxWithLargeDepsResolveTxDAG(t *testing.T) { ms := NewMVStates(12) - - ms.rwSets[0] = mockRWSet(0, []string{"0x00"}, []string{"0x00"}) - ms.rwSets[1] = mockRWSet(1, []string{"0x01"}, []string{"0x01"}) - ms.rwSets[2] = mockRWSet(2, []string{"0x02"}, []string{"0x02"}) - ms.rwSets[3] = mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}) - ms.rwSets[4] = mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}) - ms.rwSets[5] = mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}) - ms.rwSets[6] = mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}) - ms.rwSets[7] = mockRWSet(7, []string{"0x00", "0x01", "0x03", "0x05", "0x06", "0x07"}, []string{"0x07"}) - ms.rwSets[8] = mockRWSet(8, []string{"0x08"}, []string{"0x08"}) - ms.rwSets[9] = mockRWSet(9, []string{"0x00", "0x01", "0x02", "0x03", "0x04", "0x08", "0x09"}, []string{"0x09"}) - ms.rwSets[10] = mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithExcludedTxFlag() - ms.rwSets[11] = mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithExcludedTxFlag() - + finaliseRWSets(t, ms, []*RWSet{ + mockRWSet(0, []string{"0x00"}, []string{"0x00"}), + mockRWSet(1, []string{"0x01"}, []string{"0x01"}), + mockRWSet(2, []string{"0x02"}, []string{"0x02"}), + mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}), + mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}), + mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}), + mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}), + mockRWSet(7, []string{"0x00", "0x03", "0x07"}, []string{"0x07"}), + mockRWSet(8, []string{"0x08"}, []string{"0x08"}), + mockRWSet(9, []string{"0x00", "0x01", "0x02", "0x06", "0x07", "0x08", "0x09"}, []string{"0x09"}), + mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithExcludedTxFlag(), + mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithExcludedTxFlag(), + }) dag, err := ms.ResolveTxDAG(12, nil) require.NoError(t, err) require.Equal(t, mockSystemTxDAGWithLargeDeps(), dag) @@ -255,6 +389,51 @@ func mockRWSetWithVal(index int, read []interface{}, write []interface{}) *RWSet return set } +func mockRandomRWSet(count int) []*RWSet { + var ret []*RWSet + for i := 0; i < count; i++ { + read := []string{fmt.Sprintf("0x%d", i)} + write := []string{fmt.Sprintf("0x%d", i)} + if i != 0 && rand.Bool() { + depCnt := rand.Int()%i + 1 + last := 0 + for j := 0; j < depCnt; j++ { + num, ok := randInRange(last, i) + if !ok { + break + } + read = append(read, fmt.Sprintf("0x%d", num)) + last = num + } + } + // random read + for j := 0; j < 20; j++ { + read = append(read, fmt.Sprintf("rr-%d-%d", j, rand.Int())) + } + for j := 0; j < 5; j++ { + read = append(read, fmt.Sprintf("rw-%d-%d", j, rand.Int())) + } + // random write + s := mockRWSet(i, read, write) + ret = append(ret, s) + } + return ret +} + +func finaliseRWSets(t *testing.T, mv *MVStates, rwSets []*RWSet) { + for i, rwSet := range rwSets { + require.NoError(t, mv.FulfillRWSet(rwSet, nil)) + require.NoError(t, mv.Finalise(i)) + } +} + +func randInRange(i, j int) (int, bool) { + if i >= j { + return 0, false + } + return rand.Int()%(j-i) + i, true +} + func str2key(k string) RWKey { key := RWKey{} if len(k) > len(key) { From f614341fba6ab8dfc908c6828d61242f43775f19 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Tue, 13 Aug 2024 21:51:21 +0800 Subject: [PATCH 35/72] feat: code cleanup (#23) This PR refine and clean up the code of PEVM Co-authored-by: Sunny --- cmd/utils/flags.go | 6 +- consensus/ethash/consensus.go | 10 +- core/block_validator.go | 1 + core/blockchain.go | 41 +----- core/blockchain_repair_test.go | 4 - core/blockchain_test.go | 8 +- core/error.go | 2 +- core/parallel_state_processor.go | 114 ++++++---------- core/state/journal.go | 1 - core/state/parallel_statedb.go | 215 ++++-------------------------- core/state/snapshot/conversion.go | 21 ++- core/state/snapshot/difflayer.go | 1 - core/state/snapshot/snapshot.go | 5 +- core/state/state_object.go | 82 +----------- core/state/statedb.go | 56 +------- core/state/statedb_test.go | 14 +- core/state_processor.go | 1 + core/state_processor_test.go | 5 +- core/state_transition.go | 1 - core/types/block.go | 2 - core/vm/evm.go | 8 +- core/vm/gas_table.go | 1 + core/vm/instructions.go | 1 - core/vm/interface.go | 5 - core/vm/interpreter.go | 4 +- core/vm/operations_acl.go | 3 +- eth/downloader/testchain_test.go | 1 + eth/handler_eth.go | 1 - metrics/exp/exp.go | 3 - miner/worker.go | 10 -- triedb/pathdb/database.go | 3 - triedb/pathdb/disklayer.go | 1 - 32 files changed, 121 insertions(+), 510 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a668e813f3..4c40a31995 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2019,11 +2019,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(ParallelTxFlag.Name) { cfg.ParallelTxMode = ctx.Bool(ParallelTxFlag.Name) - // The best prallel num will be tuned later, we do a simple parallel num set here + // The best parallel num will be tuned later, we do a simple parallel num set here numCpu := runtime.NumCPU() var parallelNum int if ctx.IsSet(ParallelTxNumFlag.Name) { - // first of all, we use "--parallel.num", but "--parallel.num 0" is not allowed + // Use value set by "--parallel.num", and "--parallel.num 0" is not allowed and be set to 1 parallelNum = ctx.Int(ParallelTxNumFlag.Name) if parallelNum < 1 { parallelNum = 1 @@ -2033,7 +2033,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } else if numCpu < 10 { parallelNum = numCpu - 1 } else { - parallelNum = 8 // we found concurrency 8 is slightly better than 15 + parallelNum = 8 } cfg.ParallelTxNum = parallelNum } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 15d5ba84ec..c2936fd4b3 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -514,16 +514,10 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea } // Finalize block ethash.Finalize(chain, header, state, txs, uncles, nil) - /* - js, _ := header.MarshalJSON() - fmt.Printf("== Dav -- ethash FinalizeAndAssemble, before Root update, Root %s, header json: %s\n", header.Root, js) - */ + // Assign the final state root to header. header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - /* - js, _ = header.MarshalJSON() - fmt.Printf(" == Dav -- ethash FinalizeAndAssemble, after Root update, Root %s, header json: %s\n", header.Root, js) - */ + // Header seems complete, assemble into a block and return return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), nil } diff --git a/core/block_validator.go b/core/block_validator.go index 538cea51b0..79839d7176 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -156,6 +156,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if ancestorErr != nil { return ancestorErr } + return nil } diff --git a/core/blockchain.go b/core/blockchain.go index f188d917d6..ec27404b80 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -115,8 +115,6 @@ var ( errChainStopped = errors.New("blockchain is stopped") errInvalidOldChain = errors.New("invalid old chain") errInvalidNewChain = errors.New("invalid new chain") - - ParallelTxMode = false // parallel transaction execution ) const ( @@ -300,12 +298,13 @@ type BlockChain struct { stopping atomic.Bool // false if chain is running, true when stopped procInterrupt atomic.Bool // interrupt signaler for block processing - engine consensus.Engine - validator Validator // Block and state validator interface - prefetcher Prefetcher - processor Processor // Block transaction processor interface - forker *ForkChoice - vmConfig vm.Config + engine consensus.Engine + validator Validator // Block and state validator interface + prefetcher Prefetcher + processor Processor // Block transaction processor interface + forker *ForkChoice + vmConfig vm.Config + parallelExecution bool enableTxDAG bool txDAGWriteCh chan TxDAGOutputItem @@ -1582,7 +1581,6 @@ func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types // 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) { - if err := bc.writeBlockWithState(block, receipts, state); err != nil { return NonStatTy, err } @@ -1624,7 +1622,6 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types } else { bc.chainSideFeed.Send(ChainSideEvent{Block: block}) } - return status, nil } @@ -1899,9 +1896,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } - // TODO(galaio): load TxDAG from block, use txDAG in some accelerate scenarios, like state pre-fetcher. - //if bc.enableTxDAG {} - // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") activeState = statedb @@ -1949,7 +1943,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) 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) @@ -2005,28 +1998,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if !setHead { // Don't set the head, only insert the block err = bc.writeBlockWithState(block, receipts, statedb) - if false { - fmt.Printf("Dav -- After writeBlockWithState: %d check balance\n", block.NumberU64()) - actual := statedb.GetBalance(block.Coinbase()) - fmt.Printf("Dav -- AfterwriteBlockWithState: %d balance: %d\n", block.NumberU64(), actual.Uint64()) - } } else { status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false) - if false { - fmt.Printf("Dav -- After writeBlockAndSetHead: %d check balance\n", block.NumberU64()) - actual := statedb.GetBalance(block.Coinbase()) - fmt.Printf("Dav -- writeBlockAndSetHead: %d balance: %d\n", block.NumberU64(), actual.Uint64()) - - s, _ := bc.State() - bk := bc.CurrentBlock() - fmt.Printf("Dav -- writeBlockAndSetHead - currentBlock: %d root: %s\n", bk.Number.Uint64(), bk.Root) - obj, _ := s.GetStateObjectFromSnapshotOrTrie(block.Coinbase()) - - //obj, _ := statedb.GetStateObjectFromSnapshotOrTrie(block.Coinbase()) - - fmt.Printf("Dav -- writeBlockAndSetHead: %d obj from snap or trie: %p\n", block.NumberU64(), obj) - - } } followupInterrupt.Store(true) if err != nil { diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 7fe44bf14c..7f3d2b9983 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1798,13 +1798,9 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s if err != nil { t.Fatalf("Failed to create chain: %v", err) } - - // fmt.Printf("Dav -- test -- testRepairWithScheme -- chain after NewBlockChain processor: %v, parallel: %v, vmConfig: %v\n", chain.processor, chain.parallelExecution, chain.vmConfig) - // If sidechain blocks are needed, make a light chain and import it var sideblocks types.Blocks if tt.sidechainBlocks > 0 { - //fmt.Printf("Dav -- test -- testRepairWithScheme -- tt.sidechainBlocks: %d\n", tt.sidechainBlocks) sideblocks, _ = GenerateChain(gspec.Config, gspec.ToBlock(), engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0x01}) }) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 04b931f629..dc6822ee42 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1742,7 +1742,7 @@ func testEIP161AccountRemoval(t *testing.T, scheme string) { t.Fatal(err) } if st, _ := blockchain.State(); st.Exist(theAddr) { - t.Error("account should not exist", "triExist?", st.TriHasAccount(theAddr), "SnapExist?", st.SnapHasAccount(theAddr)) + t.Error("account should not exist") } // account mustn't be created post eip 161 @@ -2173,7 +2173,6 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon } nonce++ }) - if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } @@ -2181,6 +2180,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon lastPrunedIndex := len(blocks) - TriesInMemory - 1 lastPrunedBlock := blocks[lastPrunedIndex] firstNonPrunedBlock := blocks[len(blocks)-TriesInMemory] + // Verify pruning of lastPrunedBlock if chain.HasBlockAndState(lastPrunedBlock.Hash(), lastPrunedBlock.NumberU64()) { t.Errorf("Block %d not pruned", lastPrunedBlock.NumberU64()) @@ -2189,6 +2189,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon if !chain.HasBlockAndState(firstNonPrunedBlock.Hash(), firstNonPrunedBlock.NumberU64()) { t.Errorf("Block %d pruned", firstNonPrunedBlock.NumberU64()) } + // Activate the transition in the middle of the chain if mergePoint == 1 { merger.ReachTTD() @@ -3941,8 +3942,7 @@ func testSetCanonical(t *testing.T, scheme string) { diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) defer diskdb.Close() - chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, - vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } diff --git a/core/error.go b/core/error.go index 0e8f8286c2..7fa4556fdf 100644 --- a/core/error.go +++ b/core/error.go @@ -114,6 +114,6 @@ var ( // ErrSystemTxNotSupported is returned for any deposit tx with IsSystemTx=true after the Regolith fork ErrSystemTxNotSupported = errors.New("system tx not supported") - // ErrParallelUnexpectedConflict is returned when execution finally get conflict error for more than block tx number + // ErrParallelUnexpectedConflict is returned when execution finally get conflict error that should not occur ErrParallelUnexpectedConflict = errors.New("parallel execution unexpected conflict") ) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 1f74e1f583..b274cec4e0 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -32,23 +32,23 @@ type ParallelStateProcessor struct { slotState []*SlotState // idle, or pending messages allTxReqs []*ParallelTxRequest txResultChan chan *ParallelTxResult // to notify dispatcher that a tx is done - mergedTxIndex atomic.Int32 // the latest finalized tx index, fixme: use Atomic + mergedTxIndex atomic.Int32 // the latest finalized tx index pendingConfirmResults map[int][]*ParallelTxResult // tx could be executed several times, with several result to check - unconfirmedResults *sync.Map // this is for stage2 confirm, since pendingConfirmResults can not be accessed in stage2 loop - unconfirmedDBs *sync.Map + unconfirmedResults *sync.Map // for stage2 confirm, since pendingConfirmResults can not be accessed in stage2 loop + unconfirmedDBs *sync.Map // intermediate store of slotDB that is not verified slotDBsToRelease []*state.ParallelStateDB stopSlotChan chan struct{} stopConfirmChan chan struct{} debugConflictRedoNum int - // start for confirm stage2 + confirmStage2Chan chan int stopConfirmStage2Chan chan struct{} txReqExecuteRecord map[int]int txReqExecuteCount int inConfirmStage2 bool - targetStage2Count int // when executed txNUM reach it, enter stage2 RT confirm + targetStage2Count int nextStage2TxIndex int - delayGasFee bool // it is provided by TxDAG + delayGasFee bool } func newParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine, parallelNum int) *ParallelStateProcessor { @@ -61,7 +61,7 @@ func newParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engin } type MergedTxInfo struct { - slotDB *state.StateDB // used for SlotDb reuse only, otherwise, it can be discarded + slotDB *state.StateDB StateObjectSuicided map[common.Address]struct{} StateChangeSet map[common.Address]state.StateKeys BalanceChangeSet map[common.Address]struct{} @@ -80,11 +80,11 @@ type SlotState struct { } type ParallelTxResult struct { - executedIndex int32 // the TxReq can be executed several time, increase index for each execution - slotIndex int // slot index + executedIndex int32 // record the current execute number of the tx + slotIndex int txReq *ParallelTxRequest receipt *types.Receipt - slotDB *state.ParallelStateDB // if updated, it is not equal to txReq.slotDB + slotDB *state.ParallelStateDB gpSlot *GasPool evm *vm.EVM result *ExecutionResult @@ -95,7 +95,7 @@ type ParallelTxResult struct { type ParallelTxRequest struct { txIndex int baseStateDB *state.StateDB - staticSlotIndex int // static dispatched id + staticSlotIndex int tx *types.Transaction gasLimit uint64 msg *Message @@ -103,15 +103,13 @@ type ParallelTxRequest struct { vmConfig vm.Config usedGas *uint64 curTxChan chan int - systemAddrRedo bool - runnable int32 // 0: not runnable, executing, 1: runnable, on hold, can be scheduled + runnable int32 // 0: not runnable 1: runnable - can be scheduled executedNum atomic.Int32 - retryNum int32 - conflictIndex atomic.Int32 + conflictIndex atomic.Int32 // the conflicted mainDB index, the txs will not be executed before this number useDAG bool } -// to create and start the execution slot goroutines +// init to initialize and start the execution goroutines func (p *ParallelStateProcessor) init() { log.Info("Parallel execution mode is enabled", "Parallel Num", p.parallelNum, "CPUNum", runtime.NumCPU()) @@ -137,18 +135,18 @@ func (p *ParallelStateProcessor) init() { // It is back up of the primary slot to make sure transaction can be redone ASAP, // since the primary slot could be busy at executing another transaction go func(slotIndex int) { - p.runSlotLoop(slotIndex, parallelShadowSlot) // this loop will be permanent live + p.runSlotLoop(slotIndex, parallelShadowSlot) }(i) } p.confirmStage2Chan = make(chan int, 10) go func() { - p.runConfirmStage2Loop() // this loop will be permanent live + p.runConfirmStage2Loop() }() } -// clear slot state for each block. +// resetState clear slot state for each block. func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { if txNum == 0 { return @@ -191,7 +189,7 @@ func (p *ParallelStateProcessor) doStaticDispatch(txReqs []*ParallelTxRequest) { for _, txReq := range txReqs { var slotIndex = -1 if i, ok := fromSlotMap[txReq.msg.From]; ok { - // first: same From are all in same slot + // first: same From goes to same slot slotIndex = i } else if txReq.msg.To != nil { // To Address, with txIndex sorted, could be in different slot. @@ -234,16 +232,16 @@ func (p *ParallelStateProcessor) mostHungrySlot() int { return slotIndex } -// do conflict detect +// hasConflict conducts conflict check func (p *ParallelStateProcessor) hasConflict(txResult *ParallelTxResult, isStage2 bool) bool { slotDB := txResult.slotDB if txResult.err != nil { return true } else if slotDB.NeedsRedo() { - // if this is any reason that indicates this transaction needs to redo, skip the conflict check + // if there is any reason that indicates this transaction needs to redo, skip the conflict check return true } else { - // to check if what the slot db read is correct. + // check whether the slot db reads during execution are correct. if !slotDB.IsParallelReadsValid(isStage2) { return true } @@ -256,21 +254,22 @@ func (p *ParallelStateProcessor) switchSlot(slotIndex int) { if atomic.CompareAndSwapInt32(&slot.activatedType, parallelPrimarySlot, parallelShadowSlot) { // switch from normal to shadow slot if len(slot.shadowWakeUpChan) == 0 { - slot.shadowWakeUpChan <- struct{}{} // only notify when target once + slot.shadowWakeUpChan <- struct{}{} } } else if atomic.CompareAndSwapInt32(&slot.activatedType, parallelShadowSlot, parallelPrimarySlot) { // switch from shadow to normal slot if len(slot.primaryWakeUpChan) == 0 { - slot.primaryWakeUpChan <- struct{}{} // only notify when target once + slot.primaryWakeUpChan <- struct{}{} } } } +// executeInSlot do tx execution with thread local slot. func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxRequest) *ParallelTxResult { mIndex := p.mergedTxIndex.Load() conflictIndex := txReq.conflictIndex.Load() if mIndex < conflictIndex { - // The conflicted TX has not been finished executing, skip execution. + // The conflicted TX has not been finished executing, skip. // the transaction failed at check(nonce or balance), actually it has not been executed yet. atomic.CompareAndSwapInt32(&txReq.runnable, 0, 1) return nil @@ -300,7 +299,7 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR executedIndex: execNum, slotIndex: slotIndex, txReq: txReq, - receipt: nil, // receipt is generated in finalize stage + receipt: nil, slotDB: slotDB, err: err, gpSlot: gpSlot, @@ -313,11 +312,11 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR p.unconfirmedDBs.Store(txReq.txIndex, slotDB) } else { // the transaction failed at check(nonce or balance), actually it has not been executed yet. - // the error here can be both expected and unexpected + // the error here can be either expected or unexpected. // expected - the execution is correct and the error is normal result // unexpected - the execution is incorrectly accessed the state because of parallelization. // In both case, rerun with next version of stateDB, it is a waste and buggy to rerun with same - // version of stateDB. + // version of stateDB that has been marked conflict. // Therefore, treat it as conflict and rerun, leave the result to conflict check. // Load conflict as it maybe updated by conflict checker or other execution slots. // use old mIndex so that we can try the new one that is updated by other thread of merging @@ -339,7 +338,7 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR return &txResult } -// to confirm a serial TxResults with same txIndex +// toConfirmTxIndex confirm a serial TxResults with same txIndex func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bool) *ParallelTxResult { if isStage2 { if targetTxIndex <= int(p.mergedTxIndex.Load())+1 { @@ -383,12 +382,11 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo valid := p.toConfirmTxIndexResult(targetResult, isStage2) if !valid { - staticSlotIndex := targetResult.txReq.staticSlotIndex // it is better to run the TxReq in its static dispatch slot + staticSlotIndex := targetResult.txReq.staticSlotIndex conflictBase := targetResult.slotDB.BaseTxIndex() conflictIndex := targetResult.txReq.conflictIndex.Load() if conflictIndex < int32(conflictBase) { if targetResult.txReq.conflictIndex.CompareAndSwap(conflictIndex, int32(conflictBase)) { - // updated successfully log.Debug("Update conflict index", "conflictIndex", conflictIndex, "conflictBase", conflictBase) } } @@ -405,14 +403,6 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo // TODO-dav: p.mergedTxIndex+2 may be more reasonable? - this is buggy for expected exit if targetResult.txReq.txIndex == int(p.mergedTxIndex.Load())+1 && targetResult.slotDB.BaseTxIndex() == int(p.mergedTxIndex.Load()) { - /* - // txReq is the next to merge - if atomic.LoadInt32(&targetResult.txReq.retryNum) <= int32(blockTxCount)+3000 { - atomic.AddInt32(&targetResult.txReq.retryNum, 1) - // conflict retry - } else { - */ - // retry many times and still conflict, either the tx is expected to be wrong, or something wrong. if targetResult.err != nil { if false { // TODO: delete the printf fmt.Printf("!!!!!!!!!!! Parallel execution exited with error!!!!!, txIndex:%d, err: %v\n", targetResult.txReq.txIndex, targetResult.err) @@ -499,12 +489,9 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { if atomic.LoadInt32(&curSlot.activatedType) != slotType { interrupted = true - // fmt.Printf("Dav -- runInLoop, - activatedType - TxREQ: %d\n", txReq.txIndex) - break } if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { - // not swapped: txReq.runnable == 0 continue } res := p.executeInSlot(slotIndex, txReq) @@ -512,7 +499,6 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { continue } p.txResultChan <- res - // fmt.Printf("Dav -- runInLoop, - loopbody tail - TxREQ: %d\n", txReq.txIndex) } // switched to the other slot. if interrupted { @@ -522,37 +508,28 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { // txReq in this Slot have all been executed, try steal one from other slot. // as long as the TxReq is runnable, we steal it, mark it as stolen for _, stealTxReq := range p.allTxReqs { - // fmt.Printf("Dav -- stealLoop, handle TxREQ: %d\n", stealTxReq.txIndex) if stealTxReq.txIndex <= int(p.mergedTxIndex.Load()) { - // fmt.Printf("Dav -- stealLoop, - txReq.txIndex <= p.mergedTxIndex - TxREQ: %d\n", stealTxReq.txIndex) continue } if atomic.LoadInt32(&curSlot.activatedType) != slotType { interrupted = true - // fmt.Printf("Dav -- stealLoop, - activatedType - TxREQ: %d\n", stealTxReq.txIndex) break } if !atomic.CompareAndSwapInt32(&stealTxReq.runnable, 1, 0) { - // not swapped: txReq.runnable == 0 - // fmt.Printf("Dav -- stealLoop, - not runnable - TxREQ: %d\n", stealTxReq.txIndex) - continue } - // fmt.Printf("Dav -- stealLoop, - executeInSlot - TxREQ: %d\n", stealTxReq.txIndex) res := p.executeInSlot(slotIndex, stealTxReq) if res == nil { continue } p.txResultChan <- res - // fmt.Printf("Dav -- stealLoop, - loopbody tail - TxREQ: %d\n", stealTxReq.txIndex) } } } func (p *ParallelStateProcessor) runConfirmStage2Loop() { for { - // var mergedTxIndex int select { case <-p.stopConfirmStage2Chan: for len(p.confirmStage2Chan) > 0 { @@ -568,11 +545,6 @@ func (p *ParallelStateProcessor) runConfirmStage2Loop() { // stage 2,if all tx have been executed at least once, and its result has been received. // in Stage 2, we will run check when merge is advanced. // more aggressive tx result confirm, even for these Txs not in turn - // now we will be more aggressive: - // do conflict check , as long as tx result is generated, - // if lucky, it is the Tx's turn, we will do conflict check with WBNB makeup - // otherwise, do conflict check without WBNB makeup, but we will ignore WBNB's balance conflict. - // throw these likely conflicted tx back to re-execute startTxIndex := int(p.mergedTxIndex.Load()) + 2 // stage 2's will start from the next target merge index endTxIndex := startTxIndex + stage2CheckNumber txSize := len(p.allTxReqs) @@ -580,7 +552,6 @@ func (p *ParallelStateProcessor) runConfirmStage2Loop() { endTxIndex = txSize - 1 } log.Debug("runConfirmStage2Loop", "startTxIndex", startTxIndex, "endTxIndex", endTxIndex) - // conflictNumMark := p.debugConflictRedoNum for txIndex := startTxIndex; txIndex < endTxIndex; txIndex++ { p.toConfirmTxIndex(txIndex, true) } @@ -589,7 +560,6 @@ func (p *ParallelStateProcessor) runConfirmStage2Loop() { p.switchSlot(i) } } - } func (p *ParallelStateProcessor) handleTxResults() *ParallelTxResult { @@ -687,7 +657,7 @@ func (p *ParallelStateProcessor) doCleanUp() { } // 2.discard delayed txResults if any for { - if len(p.txResultChan) > 0 { // drop prefetch addr? + if len(p.txResultChan) > 0 { <-p.txResultChan continue } @@ -698,7 +668,7 @@ func (p *ParallelStateProcessor) doCleanUp() { <-p.stopSlotChan } -// Implement BEP-130: Parallel Transaction Execution. +// Process implements BEP-130 Parallel Transaction Execution func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { var ( receipts types.Receipts @@ -791,9 +761,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat vmConfig: cfg, usedGas: usedGas, curTxChan: make(chan int, 1), - systemAddrRedo: false, // set to true, when systemAddr access is detected. - runnable: 1, // 0: not runnable, 1: runnable - retryNum: 0, + runnable: 1, // 0: not runnable, 1: runnable useDAG: txDAG != nil, } txReq.executedNum.Store(0) @@ -834,7 +802,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat unconfirmedResult := <-p.txResultChan unconfirmedTxIndex := unconfirmedResult.txReq.txIndex if unconfirmedTxIndex <= int(p.mergedTxIndex.Load()) { - // log.Warn("drop merged txReq", "unconfirmedTxIndex", unconfirmedTxIndex, "p.mergedTxIndex", p.mergedTxIndex) + log.Warn("drop merged txReq", "unconfirmedTxIndex", unconfirmedTxIndex, "p.mergedTxIndex", p.mergedTxIndex) continue } p.pendingConfirmResults[unconfirmedTxIndex] = append(p.pendingConfirmResults[unconfirmedTxIndex], unconfirmedResult) @@ -844,8 +812,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat if _, ok := p.txReqExecuteRecord[unconfirmedTxIndex]; !ok { p.txReqExecuteRecord[unconfirmedTxIndex] = 0 p.txReqExecuteCount++ - statedb.AddrPrefetch(unconfirmedResult.slotDB) // todo: prefetch when it is not merged - // enter stage2, RT confirm + statedb.AddrPrefetch(unconfirmedResult.slotDB) if !p.inConfirmStage2 && p.txReqExecuteCount == p.targetStage2Count { p.inConfirmStage2 = true } @@ -870,7 +837,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat } } - // to do clean up when the block is processed + // clean up when the block is processed p.doCleanUp() // len(commonTxs) could be 0, such as: https://bscscan.com/block/14580486 @@ -925,13 +892,12 @@ func applyTransactionStageExecution(msg *Message, gp *GasPool, statedb *state.Pa return evm, result, err } -func applyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg Message, config *params.ChainConfig, - statedb *state.ParallelStateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, nonce *uint64) (*types.Receipt, error) { +func applyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg Message, + config *params.ChainConfig, statedb *state.ParallelStateDB, header *types.Header, + tx *types.Transaction, usedGas *uint64, nonce *uint64) (*types.Receipt, error) { *usedGas += result.UsedGas - - // Create a new receipt for the transaction, storing the intermediate root and gas used - // by the tx. + // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx. receipt := &types.Receipt{Type: tx.Type(), PostState: nil, CumulativeGasUsed: *usedGas} if result.Failed() { receipt.Status = types.ReceiptStatusFailed diff --git a/core/state/journal.go b/core/state/journal.go index 488e313f60..38ea922292 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -49,7 +49,6 @@ func newJournal() *journal { // append inserts a new modification entry to the end of the change journal. func (j *journal) append(entry journalEntry) { j.entries = append(j.entries, entry) - if addr := entry.dirtied(); addr != nil { j.dirties[*addr]++ } diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 8388a616e8..4d9c68de71 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -7,12 +7,10 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" "github.com/holiman/uint256" "runtime" "sort" "sync" - "time" ) const defaultNumOfSlots = 100 @@ -158,7 +156,8 @@ func (s *ParallelStateDB) RevertSlotDB(from common.Address) { s.parallel.nonceChangesInSlot[from] = struct{}{} } -func (s *ParallelStateDB) getBaseStateDB() *StateDB { +// getStateDBBasePtr get the pointer of parallelStateDB. +func (s *ParallelStateDB) getStateDBBasePtr() *StateDB { return &s.StateDB } @@ -166,8 +165,8 @@ func (s *ParallelStateDB) SetSlotIndex(index int) { s.parallel.SlotIndex = index } -// for parallel execution mode, try to get dirty StateObject in slot first. -// it is mainly used by journal revert right now. +// getStateObject get the state object from parallel stateDB for journal revert. +// for parallel execution, try to get dirty StateObject in slot first. func (s *ParallelStateDB) getStateObject(addr common.Address) *stateObject { var object *stateObject if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { @@ -176,21 +175,14 @@ func (s *ParallelStateDB) getStateObject(addr common.Address) *stateObject { } object = obj } else { - // can not call s.StateDB.getStateObject(), since `newObject` need ParallelStateDB as the interface object = s.getStateObjectNoSlot(addr) } return object } func (s *ParallelStateDB) storeStateObj(addr common.Address, stateObject *stateObject) { - // When a state object is stored into s.parallel.stateObjects, - // it belongs to base StateDB, it is confirmed and valid. - // todo Dav: why need change this? -- delete me ! - // stateObject.db = s.parallel.baseStateDB - // stateObject.dbItf = s.parallel.baseStateDB - - // the object could be created in SlotDB, if it got the object from DB and - // update it to the shared `s.parallel.stateObjects`` + // The object could be created in SlotDB, if it got the object from DB and + // update it to the `s.parallel.stateObjects` stateObject.db.parallelStateAccessLock.Lock() if _, ok := s.parallel.stateObjects.Load(addr); !ok { s.parallel.stateObjects.Store(addr, stateObject) @@ -331,7 +323,6 @@ func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *stateObject } // not found, or found in NoSlot or found in unconfirmed. exist := true - // TODO-dav: the check of nil and delete already done by NoSlot and unconfirmedDB, may optimize it for dirty only. if object == nil || object.deleted { object = s.createObject(addr) exist = false @@ -399,7 +390,7 @@ func (s *ParallelStateDB) Empty(addr common.Address) bool { } // 2.2 Try to get from unconfirmed DB if exist if exist, ok := s.getAddrStateFromUnconfirmedDB(addr, true); ok { - s.parallel.addrStateReadsInSlot[addr] = exist // update and cache + s.parallel.addrStateReadsInSlot[addr] = exist // update read cache return !exist } // 2.3 Try to get from NoSlot. @@ -572,7 +563,7 @@ func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { var code []byte // 2.2 Try to get from unconfirmed DB if exist if cd, ok := s.getCodeFromUnconfirmedDB(addr); ok { - cs = len(cd) // len(nil) is 0 too + cs = len(cd) code = cd } else { // 3. Try to get from main StateObject @@ -638,7 +629,7 @@ func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { s.parallel.codeHashReadsInSlot[addr] = codeHash } - // fill slots in dirty if exist. + // fill slots in dirty if existed. // A case for this: // TX0: createAccount at addr 0x123, set code and codehash // TX1: AddBalance - now an obj in dirty with empty codehash, and codeChangesInSlot is false (not changed) @@ -676,10 +667,8 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common // 1.Try to get from dirty if exist, ok := s.parallel.addrStateChangesInSlot[addr]; ok { if !exist { - // it could be suicided within this SlotDB? - // it should be able to get state from suicided address within a Tx: - // e.g. within a transaction: call addr:suicide -> get state: should be ok - // return common.Hash{} + // it should be able to get state from selfDestruct address within a Tx: + // e.g. within a transaction: call addr:selfDestruct -> get state: should be ok log.Info("ParallelStateDB GetState suicided", "addr", addr, "hash", hash) } else { // It is possible that an object get created but not dirtied since there is no state set, such as recreate. @@ -718,8 +707,8 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common return val } } - // 2.2 Object in dirty because of other changes, such as getBalance etc. - // load from dirty directly and the stateObject.GetState() will care of the KvReadInSlot update. + // 2.2 Object in dirty due to other changes, such as getBalance etc. + // load from dirty directly and the stateObject.GetState() will take care of the KvReadInSlot update. // So there is no chance for create different objects with same address. (one in dirty and one from non-slot, and inconsistency) if dirtyObj != nil { return dirtyObj.GetState(hash) @@ -752,21 +741,21 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common // So it should not access/update dirty, and not check delete of dirty objects. func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { - // 2.Try to get from unconfirmed DB or main DB + // 1.Try to get from unconfirmed DB or main DB // KVs in unconfirmed DB can be seen as pending storage // KVs in main DB are merged from SlotDB and has done finalise() on merge, can be seen as pending storage too. - // 2.1 Already read before + // 1.1 Already read before if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { if val, ok := storage.GetValue(hash); ok { return val } } value := common.Hash{} - // 2.2 Try to get from unconfirmed DB if exist + // 1.2 Try to get from unconfirmed DB if exist if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { value = val } else { - // 3. Try to get from main DB + // 2. Try to get from main DB val = common.Hash{} object := s.getStateObjectNoSlot(addr) if object != nil { @@ -810,7 +799,7 @@ func (s *ParallelStateDB) AddBalance(addr common.Address, amount *uint256.Int) { object := s.GetOrNewStateObject(addr) if object != nil { if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := object.lightCopy(s) // light copy from main DB + newStateObject := object.lightCopy(s) // do balance fixup from the confirmed DB, it could be more reliable than main DB balance := s.GetBalance(addr) // it will record the balance read operation newStateObject.setBalance(balance) @@ -838,7 +827,7 @@ func (s *ParallelStateDB) SubBalance(addr common.Address, amount *uint256.Int) { object := s.GetOrNewStateObject(addr) if object != nil { if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := object.lightCopy(s) // light copy from main DB + newStateObject := object.lightCopy(s) // do balance fixup from the confirmed DB, it could be more reliable than main DB balance := s.GetBalance(addr) newStateObject.setBalance(balance) @@ -926,11 +915,6 @@ func (s *ParallelStateDB) SetState(addr common.Address, key, value common.Hash) object := s.GetOrNewStateObject(addr) // attention: if StateObject's lightCopy, its storage is only a part of the full storage, if object != nil { if s.parallel.baseTxIndex+1 == s.txIndex { - // we check if state is unchanged - // only when current transaction is the next transaction to be committed - // fixme: there is a bug, block: 14,962,284, - // stateObject is in dirty (light copy), but the key is in mainStateDB - // stateObject dirty -> committed, will skip mainStateDB dirty if s.GetState(addr, key) == value { log.Debug("Skip set same state", "baseTxIndex", s.parallel.baseTxIndex, "txIndex", s.txIndex, "addr", addr, @@ -940,7 +924,7 @@ func (s *ParallelStateDB) SetState(addr common.Address, key, value common.Hash) } if s.parallel.kvChangesInSlot[addr] == nil { - s.parallel.kvChangesInSlot[addr] = make(StateKeys) // make(Storage, defaultNumOfSlots) + s.parallel.kvChangesInSlot[addr] = make(StateKeys) } if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { @@ -1000,16 +984,13 @@ func (s *ParallelStateDB) SelfDestruct(addr common.Address) { }) if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - // do copy-on-write for suicide "write" newStateObject := object.lightCopy(s) newStateObject.markSelfdestructed() newStateObject.setBalance(new(uint256.Int)) s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, - // s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.addrStateChangesInSlot[addr] = false s.parallel.balanceChangesInSlot[addr] = struct{}{} s.parallel.codeChangesInSlot[addr] = struct{}{} - // s.parallel.kvChangesInSlot[addr] = make(StateKeys) // all key changes are discarded return } @@ -1044,9 +1025,10 @@ func (s *ParallelStateDB) CreateAccount(addr common.Address) { // no matter it is got from dirty, unconfirmed or main DB // if addr not exist, preBalance will be common.U2560, it is same as new(uint256.Int) which // is the value newObject(), - preBalance := s.GetBalance(addr) // parallel balance read will be recorded inside GetBalance + preBalance := s.GetBalance(addr) newObj := s.createObject(addr) newObj.setBalance(new(uint256.Int).Set(preBalance)) // new uint256.Int for newObj + } // RevertToSnapshot reverts all state changes made since the given revision. @@ -1067,7 +1049,7 @@ func (s *ParallelStateDB) RevertToSnapshot(revid int) { // AddRefund adds gas to the refund counter // journal.append will use ParallelState for revert -func (s *ParallelStateDB) AddRefund(gas uint64) { // todo: not needed, can be deleted +func (s *ParallelStateDB) AddRefund(gas uint64) { s.journal.append(refundChange{prev: s.refund}) s.refund += gas } @@ -1109,7 +1091,7 @@ func (s *ParallelStateDB) getBalanceFromUnconfirmedDB(addr common.Address) *uint if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { balanceHit = true } - if _, exist := db.parallel.balanceChangesInSlot[addr]; exist { // only changed balance is reliable + if _, exist := db.parallel.balanceChangesInSlot[addr]; exist { balanceHit = true } if !balanceHit { @@ -1121,7 +1103,6 @@ func (s *ParallelStateDB) getBalanceFromUnconfirmedDB(addr common.Address) *uint balance = common.U2560 } return balance - } return nil } @@ -1153,8 +1134,6 @@ func (s *ParallelStateDB) getNonceFromUnconfirmedDB(addr common.Address) (uint64 // nonce hit, return the nonce obj := db.parallel.dirtiedStateObjectsInSlot[addr] if obj == nil { - // could not exist, if it is changed but reverted - // fixme: revert should remove the change record log.Debug("Get nonce from UnconfirmedDB, changed but object not exist, ", "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) continue @@ -1196,8 +1175,6 @@ func (s *ParallelStateDB) getCodeFromUnconfirmedDB(addr common.Address) ([]byte, } obj := db.parallel.dirtiedStateObjectsInSlot[addr] if obj == nil { - // could not exist, if it is changed but reverted - // fixme: revert should remove the change record log.Debug("Get code from UnconfirmedDB, changed but object not exist, ", "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) continue @@ -1238,8 +1215,6 @@ func (s *ParallelStateDB) getCodeHashFromUnconfirmedDB(addr common.Address) (com } obj := db.parallel.dirtiedStateObjectsInSlot[addr] if obj == nil { - // could not exist, if it is changed but reverted - // fixme: revert should remove the change record log.Debug("Get codeHash from UnconfirmedDB, changed but object not exist, ", "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) continue @@ -1271,8 +1246,6 @@ func (s *ParallelStateDB) getAddrStateFromUnconfirmedDB(addr common.Address, tes db := db_.(*ParallelStateDB) if exist, ok := db.parallel.addrStateChangesInSlot[addr]; ok { if obj, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - // could not exist, if it is changed but reverted - // fixme: revert should remove the change record log.Debug("Get addr State from UnconfirmedDB, changed but object not exist, ", "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) continue @@ -1364,7 +1337,6 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } } } - /* can not use mainDB.GetNonce() because we do not want to record the stateObject */ var nonceMain uint64 = 0 mainObj := mainDB.getStateObjectNoUpdate(addr) if mainObj != nil { @@ -1417,7 +1389,6 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { }) } readLen := len(units) - // TODO-dav: change back to 8 or 1? if readLen < 80000 || isStage2 { for _, unit := range units { if hasKvConflict(slotDB, unit.addr, unit.key, unit.val, isStage2) { @@ -1561,7 +1532,7 @@ func (s *ParallelStateDB) NeedsRedo() bool { // FinaliseForParallel finalises the state by removing the destructed objects and clears // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. -// It also handle the mainDB dirties for the first TX. +// It also handles the mainDB dirties for the first TX. func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *StateDB) { addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) @@ -1577,12 +1548,6 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S var exist bool obj, exist = mainDB.getStateObjectFromStateObjects(addr) if !exist { - // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 - // That tx goes out of gas, and although the notion of 'touched' does not exist there, the - // touch-event will still be recorded in the journal. Since ripeMD is a special snowflake, - // it will persist in the journal even though the journal is reverted. In this special circumstance, - // it may exist in `s.journal.dirties` but not in `s.stateObjects`. - // Thus, we can safely ignore it here continue } @@ -1618,7 +1583,7 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S mainDB.stateObjectsPending[addr] = struct{}{} mainDB.stateObjectsDirty[addr] = struct{}{} - // At this point, also ship the address off to the precacher. The precacher + // At this point, also ship the address off to the prefetch. The prefetcher // will start loading tries, and when the change is eventually committed, // the commit-phase will be a lot faster addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure @@ -1641,12 +1606,6 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S obj, exist = s.getStateObjectFromStateObjects(addr) } if !exist { - // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 - // That tx goes out of gas, and although the notion of 'touched' does not exist there, the - // touch-event will still be recorded in the journal. Since ripeMD is a special snowflake, - // it will persist in the journal even though the journal is reverted. In this special circumstance, - // it may exist in `s.journal.dirties` but not in `s.stateObjects`. - // Thus, we can safely ignore it here continue } @@ -1700,7 +1659,7 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} - // At this point, also ship the address off to the precacher. The precacher + // At this point, also ship the address off to the prefetcher. The prefetcher // will start loading tries, and when the change is eventually committed, // the commit-phase will be a lot faster addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure @@ -1712,121 +1671,3 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() } - -// IntermediateRootForSlotDB computes the current root hash of the state trie. -// It is called in between transactions to get the root hash that -// goes into transaction receipts. -// For parallel SlotDB, the intermediateRoot can be used to calculate the temporary root after executing single tx. -func (s *ParallelStateDB) IntermediateRootForSlotDB(deleteEmptyObjects bool, mainDB *StateDB) common.Hash { - // Finalise all the dirty storage states and write them into the tries - s.FinaliseForParallel(deleteEmptyObjects, mainDB) - - // If there was a trie prefetcher operating, it gets aborted and irrevocably - // modified after we start retrieving tries. Remove it from the statedb after - // this round of use. - // - // This is weird pre-byzantium since the first tx runs with a prefetcher and - // the remainder without, but pre-byzantium even the initial prefetcher is - // useless, so no sleep lost. - prefetcher := mainDB.prefetcher - if mainDB.prefetcher != nil { - defer func() { - mainDB.prefetcher.close() - mainDB.prefetcher = nil - }() - } - - if s.TxIndex() == 0 && len(mainDB.stateObjectsPending) > 0 { - for addr := range mainDB.stateObjectsPending { - var obj *stateObject - if obj, _ = mainDB.getStateObjectFromStateObjects(addr); !obj.deleted { - obj.updateRoot() - } - } - } - - // Although naively it makes sense to retrieve the account trie and then do - // the contract storage and account updates sequentially, that short circuits - // the account prefetcher. Instead, let's process all the storage updates - // first, giving the account prefetches just a few more milliseconds of time - // to pull useful data from disk. - for addr := range s.stateObjectsPending { - var obj *stateObject - if s.parallel.isSlotDB { - if obj = s.parallel.dirtiedStateObjectsInSlot[addr]; !obj.deleted { - obj.updateRoot() - } - } else { - if obj, _ = s.getStateObjectFromStateObjects(addr); !obj.deleted { - obj.updateRoot() - } - } - } - - // Now we're about to start to write changes to the trie. The trie is so far - // _untouched_. We can check with the prefetcher, if it can give us a trie - // which has the same root, but also has some content loaded into it. - // The parallel execution do the change incrementally, so can not check the prefetcher here - if prefetcher != nil { - if trie := prefetcher.trie(common.Hash{}, mainDB.originalRoot); trie != nil { - mainDB.trie = trie - } - } - - usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) - - if s.TxIndex() == 0 && len(mainDB.stateObjectsPending) > 0 { - usedAddrs = make([][]byte, 0, len(s.stateObjectsPending)+len(mainDB.stateObjectsPending)) - for addr := range mainDB.stateObjectsPending { - if obj, _ := mainDB.getStateObjectFromStateObjects(addr); obj.deleted { - mainDB.deleteStateObject(obj) - mainDB.AccountDeleted += 1 - } else { - mainDB.updateStateObject(obj) - mainDB.AccountUpdated += 1 - } - usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure - } - } - - for addr := range s.stateObjectsPending { - if s.parallel.isSlotDB { - if obj := s.parallel.dirtiedStateObjectsInSlot[addr]; obj.deleted { - mainDB.deleteStateObject(obj) - mainDB.AccountDeleted += 1 - } else { - mainDB.updateStateObject(obj) - mainDB.AccountUpdated += 1 - } - } else if obj, _ := s.getStateObjectFromStateObjects(addr); obj.deleted { - mainDB.deleteStateObject(obj) - mainDB.AccountDeleted += 1 - } else { - mainDB.updateStateObject(obj) - mainDB.AccountUpdated += 1 - } - usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure - } - - if prefetcher != nil { - prefetcher.used(common.Hash{}, mainDB.originalRoot, usedAddrs) - } - // parallel slotDB trie will be updated to mainDB since intermediateRoot happens after conflict check. - // so it should be save to clear pending here. - // otherwise there can be a case that the deleted object get ignored and processes as live object in verify phase. - - if s.TxIndex() == 0 && len(mainDB.stateObjectsPending) > 0 { - mainDB.stateObjectsPending = make(map[common.Address]struct{}) - } - - if /*s.isParallel == false &&*/ len(s.stateObjectsPending) > 0 { - s.stateObjectsPending = make(map[common.Address]struct{}) - } - // Track the amount of time wasted on hashing the account trie - if metrics.EnabledExpensive { - defer func(start time.Time) { mainDB.AccountHashes += time.Since(start) }(time.Now()) - } - ret := mainDB.trie.Hash() - - return ret -} diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 65d3af7525..365660caa2 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -243,7 +243,7 @@ func runReport(stats *generateStats, stop chan bool) { // generateTrieRoot generates the trie hash based on the snapshot iterator. // It can be used for generating account trie, storage trie or even the // whole state which connects the accounts and the corresponding storages. -func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accountExt common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { +func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { var ( in = make(chan trieKV) // chan to pass leaves out = make(chan common.Hash, 1) // chan to collect result @@ -254,7 +254,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou wg.Add(1) go func() { defer wg.Done() - generatorFn(db, scheme, accountExt, in, out) + generatorFn(db, scheme, account, in, out) }() // Spin up a go-routine for progress logging if report && stats != nil { @@ -294,7 +294,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou ) // Start to feed leaves for it.Next() { - if accountExt == (common.Hash{}) { + if account == (common.Hash{}) { var ( err error fullData []byte @@ -324,12 +324,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou return } if account.Root != subroot { - - // results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot) - - results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x\n accountEXT: %s, account.ROOT: %v, codehash: %s\n", - hash, account.Root, subroot, accountExt.Hex(), account.Root, common.Bytes2Hex(account.CodeHash)) - + results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot) return } results <- nil @@ -348,20 +343,20 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou // Accumulate the generation statistic if it's required. processed++ if time.Since(logged) > 3*time.Second && stats != nil { - if accountExt == (common.Hash{}) { + if account == (common.Hash{}) { stats.progressAccounts(it.Hash(), processed) } else { - stats.progressContract(accountExt, it.Hash(), processed) + stats.progressContract(account, it.Hash(), processed) } logged, processed = time.Now(), 0 } } // Commit the last part statistic. if processed > 0 && stats != nil { - if accountExt == (common.Hash{}) { + if account == (common.Hash{}) { stats.finishAccounts(processed) } else { - stats.finishContract(accountExt, processed) + stats.finishContract(account, processed) } } return stop(nil) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 8bd863b350..70c9f44189 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -458,7 +458,6 @@ func (dl *diffLayer) flatten() snapshot { comboData[storageHash] = data } } - // Return the combo parent return &diffLayer{ parent: parent.parent, diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 807a10c35f..3077468b48 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -369,6 +369,7 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m // Save the new snapshot for later t.lock.Lock() defer t.lock.Unlock() + t.layers[snap.root] = snap return nil } @@ -411,6 +412,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error { diff.lock.RLock() base := diffToDisk(diff.flatten().(*diffLayer)) diff.lock.RUnlock() + // Replace the entire snapshot tree with the flat base t.layers = map[common.Hash]snapshot{base.root: base} return nil @@ -517,6 +519,7 @@ func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { bottom.lock.RLock() base := diffToDisk(bottom) bottom.lock.RUnlock() + t.layers[base.root] = base diff.parent = base return base @@ -749,7 +752,6 @@ func (t *Tree) Rebuild(root common.Hash) { // Start generating a new snapshot from scratch on a background thread. The // generator will run a wiper first if there's not one running right now. log.Info("Rebuilding state snapshot") - t.layers = map[common.Hash]snapshot{ root: generateSnapshot(t.diskdb, t.triedb, t.config.CacheSize, root), } @@ -796,6 +798,7 @@ func (t *Tree) Verify(root common.Hash) error { return common.Hash{}, err } defer storageIt.Release() + hash, err := generateTrieRoot(nil, "", storageIt, accountHash, stackTrieGenerate, nil, stat, false) if err != nil { return common.Hash{}, err diff --git a/core/state/state_object.go b/core/state/state_object.go index 6560c8752b..010a9abe71 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -66,7 +66,6 @@ func (s StorageMap) Copy() Storage { for key, value := range s { cpy[key] = value } - return cpy } @@ -132,7 +131,6 @@ func (s *StorageSyncMap) Copy() Storage { cpy.Store(key, value) return true }) - return &cpy } @@ -225,7 +223,7 @@ func (s *stateObject) empty() bool { // since it could be invalid. // e.g., AddBalance() to an address, we will do lightCopy to get a new StateObject, we did balance fixup to // make sure object's Balance is reliable. But we did not fixup nonce or code, we only do nonce or codehash - // fixup on need, that's when we wanna to update the nonce or codehash. + // fixup on need, that's when we want to update the nonce or codehash. // So nonce, balance // Before the block is processed, addr_1 account: nonce = 0, emptyCodeHash, balance = 100 // Slot 0 tx 0: no access to addr_1 @@ -240,7 +238,6 @@ func (s *stateObject) empty() bool { } codeHash := s.dbItf.GetCodeHash(s.address) return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty - } // newObject creates a state object. @@ -353,7 +350,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { } if s.db.isParallel && s.db.parallel.isSlotDB { - // Add-Dav: // Need to confirm the object is not destructed in unconfirmed db and resurrected in this tx. // otherwise there is an issue for cases like: // B0: TX0 --> createAccount @addr1 -- merged into DB @@ -385,7 +381,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // 1) resurrect happened, and new slot values were set -- those should // have been handles via pendingStorage above. // 2) we don't have new values, and can deliver empty response back - //if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed { s.db.stateObjectDestructLock.RLock() if _, destructed := s.db.getStateObjectsDestruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? s.db.stateObjectDestructLock.RUnlock() @@ -459,7 +454,7 @@ func (s *stateObject) SetState(key, value common.Hash) { }) if s.db.isParallel && s.db.parallel.isSlotDB { - s.db.parallel.kvChangesInSlot[s.address][key] = struct{}{} // should be moved to here, after `s.db.GetState()` + s.db.parallel.kvChangesInSlot[s.address][key] = struct{}{} } s.setState(key, value) } @@ -483,7 +478,6 @@ func (s *stateObject) finalise(prefetch bool) { } return true }) - if s.dirtyNonce != nil { s.data.Nonce = *s.dirtyNonce s.dirtyNonce = nil @@ -655,73 +649,6 @@ func (s *stateObject) updateTrie() (Trie, error) { s.pendingStorage = newStorage(s.isParallel) // reset pending map return tr, nil - /* - s.pendingStorage.Range(func(keyItf, valueItf interface{}) bool { - key := keyItf.(common.Hash) - value := valueItf.(common.Hash) - // Skip noop changes, persist actual changes - originalValue, _ := s.originStorage.GetValue(key) - if value == originalValue { - return true - } - - prev, _ := s.originStorage.GetValue(key) - s.originStorage.StoreValue(key, value) - - var encoded []byte // rlp-encoded value to be used by the snapshot - if (value == common.Hash{}) { - if err := tr.DeleteStorage(s.address, key[:]); err != nil { - maindb.setError(err) - } - maindb.StorageDeleted += 1 - } else { - // Encoding []byte cannot fail, ok to ignore the error. - trimmed := common.TrimLeftZeroes(value[:]) - encoded, _ = rlp.EncodeToBytes(trimmed) - if err := tr.UpdateStorage(s.address, key[:], trimmed); err != nil { - maindb.setError(err) - } - maindb.StorageUpdated += 1 - } - // Cache the mutated storage slots until commit - if storage == nil { - if storage = maindb.storages[s.addrHash]; storage == nil { - storage = make(map[common.Hash][]byte) - maindb.storages[s.addrHash] = storage - } - } - - khash := crypto.HashData(maindb.hasher, key[:]) - storage[khash] = encoded // encoded will be nil if it's deleted - - // Cache the original value of mutated storage slots - if origin == nil { - if origin = maindb.storagesOrigin[s.address]; origin == nil { - origin = make(map[common.Hash][]byte) - maindb.storagesOrigin[s.address] = origin - } - } - // Track the original value of slot only if it's mutated first time - if _, ok := origin[khash]; !ok { - if prev == (common.Hash{}) { - origin[khash] = nil // nil if it was not present previously - } else { - // Encoding []byte cannot fail, ok to ignore the error. - b, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(prev[:])) - origin[khash] = b - } - } - // Cache the items for preloading - usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure - return true - }) - if maindb.prefetcher != nil { - maindb.prefetcher.used(s.addrHash, s.data.Root, usedStorage) - } - s.pendingStorage = newStorage(s.isParallel) // reset pending map - - return tr, nil - */ } // updateRoot flushes all cached storage mutations to trie, recalculating the @@ -729,7 +656,6 @@ func (s *stateObject) updateTrie() (Trie, error) { func (s *stateObject) updateRoot() { // Flush cached storage mutations into trie, short circuit if any error // is occurred or there is not change in the trie. - // TODO: The trieParallelLock seems heavy, can we remove it? s.db.trieParallelLock.Lock() defer s.db.trieParallelLock.Unlock() @@ -765,10 +691,8 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) { return nil, err } s.data.Root = root - // Update original account data after commit s.origin = s.data.Copy() - return nodes, nil } @@ -863,7 +787,6 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { } object.code = s.code - // The lock is unnecessary since deepCopy only invoked at global phase and with dirty object that never changed. object.dirtyStorage = s.dirtyStorage.Copy() object.originStorage = s.originStorage.Copy() object.pendingStorage = s.pendingStorage.Copy() @@ -873,7 +796,6 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { object.dirtyBalance = s.dirtyBalance object.dirtyNonce = s.dirtyNonce object.dirtyCodeHash = s.dirtyCodeHash - return object } diff --git a/core/state/statedb.go b/core/state/statedb.go index 16500e2c4d..52eb7911fb 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -98,7 +98,6 @@ func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { if s.isParallel { // When a state object is stored into s.parallel.stateObjects, // it belongs to base StateDB, it is confirmed and valid. - // TODO-dav: remove the lock/unlock? s.parallelStateAccessLock.Lock() s.parallel.stateObjects.StoreStateObject(addr, stateObject) s.parallelStateAccessLock.Unlock() @@ -121,20 +120,16 @@ func (s *StateDB) deleteStateObj(addr common.Address) { // ParallelState is for parallel mode only type ParallelState struct { isSlotDB bool // denotes StateDB is used in slot, we will try to remove it - SlotIndex int // for debug, to be removed + SlotIndex int // for debug // stateObjects holds the state objects in the base slot db - // the reason for using stateObjects instead of stateObjects on the outside is - // we need a thread safe map to hold state objects since there are many slots will read - // state objects from it; - // And we will merge all the changes made by the concurrent slot into it. stateObjects *StateObjectSyncMap baseStateDB *StateDB // for parallel mode, there will be a base StateDB in dispatcher routine. baseTxIndex int // slotDB is created base on this tx index. dirtiedStateObjectsInSlot map[common.Address]*stateObject - unconfirmedDBs *sync.Map /*map[int]*ParallelStateDB*/ // do unconfirmed reference in same slot. + unconfirmedDBs *sync.Map // do unconfirmed reference in same slot. - // we will record the read detail for conflict check and + // record the read detail for conflict check and // the changed addr or key for object merge, the changed detail can be achieved from the dirty object nonceChangesInSlot map[common.Address]struct{} nonceReadsInSlot map[common.Address]uint64 @@ -160,17 +155,7 @@ type ParallelState struct { storagesDeleteRecord []common.Hash accountsOriginDeleteRecord []common.Address storagesOriginDeleteRecord []common.Address - - createdObjectRecord map[common.Address]struct{} - - // Transaction will pay gas fee to system address. - // Parallel execution will clear system address's balance at first, in order to maintain transaction's - // gas fee value. Normal transaction will access system address twice, otherwise it means the transaction - // needs real system address's balance, the transaction will be marked redo with keepSystemAddressBalance = true - // systemAddress common.Address - // systemAddressOpsCount int - // keepSystemAddressBalance bool - + createdObjectRecord map[common.Address]struct{} // we may need to redo for some specific reasons, like we read the wrong state and need to panic in sequential mode in SubRefund needsRedo bool useDAG bool @@ -1409,7 +1394,6 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { // handle tx1, so what thread1's slotDB see in the s.parallel.stateObjects might be the middle result of Thread2. // // We are not do simple copy (lightweight pointer copy) as the stateObject can be accessed by different thread. - // Todo-dav: remove lock guard of parallel.stateObject access. stateObjects: &StateObjectSyncMap{}, // s.parallel.stateObjects, codeReadsInSlot: addressToBytesPool.Get().(map[common.Address][]byte), @@ -1454,10 +1438,6 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { parallel: parallel, }, } - // no need to copy preimages, comment out and remove later - // for hash, preimage := range s.preimages { - // state.preimages[hash] = preimage - // } // copy parallel stateObjects s.parallelStateAccessLock.Lock() @@ -1467,7 +1447,6 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { }) s.parallelStateAccessLock.Unlock() - // deep copy needed state.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) s.snapParallelLock.RLock() for k, v := range s.snapDestructs { @@ -1476,31 +1455,8 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { s.snapParallelLock.RUnlock() if s.snaps != nil { - // In order for the miner to be able to use and make additions - // to the snapshot tree, we need to copy that as well. - // Otherwise, any block mined by ourselves will cause gaps in the tree, - // and force the miner to operate trie-backed only state.snaps = s.snaps state.snap = s.snap - // snapAccounts is useless in SlotDB, comment out and remove later - // state.snapAccounts = make(map[common.Address][]byte) // snapAccountPool.Get().(map[common.Address][]byte) - // for k, v := range s.snapAccounts { - // state.snapAccounts[k] = v - // } - - // snapStorage is useless in SlotDB either, it is updated on updateTrie, which is validation phase to update the snapshot of a finalized block. - // state.snapStorage = snapStoragePool.Get().(map[common.Address]map[string][]byte) - // for k, v := range s.snapStorage { - // temp := snapStorageValuePool.Get().(map[string][]byte) - // for kk, vv := range v { - // temp[kk] = vv - // } - // state.snapStorage[k] = temp - // } - - // trie prefetch should be done by dispatcher on StateObject Merge, - // disable it in parallel slot - // state.prefetcher = s.prefetcher } // Deep copy the state changes made in the scope of block @@ -2844,7 +2800,3 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip s.SetTxContext(slotDb.thash, slotDb.txIndex) return s } - -func (s *StateDB) ParallelMakeUp(common.Address, []byte) { - // do nothing, this API is for parallel mode -} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 274854587f..d98a81143b 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -45,7 +45,7 @@ import ( ) var ( - systemAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") + testAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") ) // Tests that updating a state trie does not leak any database writes prior to @@ -1380,7 +1380,7 @@ func TestSetAndGetBalance(t *testing.T) { db := NewDatabase(memDb) state, _ := New(common.Hash{}, db, nil) - addr := systemAddress + addr := testAddress state.SetBalance(addr, big.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) @@ -1415,7 +1415,7 @@ func TestSubBalance(t *testing.T) { memDb := rawdb.NewMemoryDatabase() db := NewDatabase(memDb) state, _ := New(common.Hash{}, db, nil) - addr := systemAddress + addr := testAddress state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() @@ -1450,7 +1450,7 @@ func TestAddBalance(t *testing.T) { memDb := rawdb.NewMemoryDatabase() db := NewDatabase(memDb) state, _ := New(common.Hash{}, db, nil) - addr := systemAddress + addr := testAddress state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) @@ -1484,7 +1484,7 @@ func TestEmpty(t *testing.T) { memDb := rawdb.NewMemoryDatabase() db := NewDatabase(memDb) state, _ := New(common.Hash{}, db, nil) - addr := systemAddress + addr := testAddress state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() @@ -1505,7 +1505,7 @@ func TestExist(t *testing.T) { memDb := rawdb.NewMemoryDatabase() db := NewDatabase(memDb) state, _ := New(common.Hash{}, db, nil) - addr := systemAddress + addr := testAddress state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) @@ -1532,7 +1532,7 @@ func TestMergeSlotDB(t *testing.T) { newSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) - addr := systemAddress + addr := testAddress newSlotDb.SetBalance(addr, big.NewInt(2)) newSlotDb.SetState(addr, common.BytesToHash([]byte("test key")), common.BytesToHash([]byte("test store"))) newSlotDb.SetCode(addr, []byte("test code")) diff --git a/core/state_processor.go b/core/state_processor.go index 7a0252d36f..85a26ffa9a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -145,6 +145,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta if msg.IsDepositTx && config.IsOptimismRegolith(evm.Context.Time) { nonce = statedb.GetNonce(msg.From) } + // Apply the transaction to the current state (included in the env). result, err := ApplyMessage(evm, msg, gp) if err != nil { diff --git a/core/state_processor_test.go b/core/state_processor_test.go index fbc6632a75..0a2b388805 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -18,7 +18,6 @@ package core import ( "crypto/ecdsa" - "github.com/holiman/uint256" "math/big" "testing" @@ -35,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" "golang.org/x/crypto/sha3" ) @@ -73,7 +73,6 @@ func TestStateProcessorErrors(t *testing.T) { tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, key) return tx } - var mkDynamicTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction { tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ Nonce: nonce, @@ -147,7 +146,6 @@ func TestStateProcessorErrors(t *testing.T) { }, want: "could not apply tx 1 [0x0026256b3939ed97e2c4a6f3fce8ecf83bdcfa6d507c47838c308a1fb0436f62]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1", }, - { // ErrNonceTooHigh txs: []*types.Transaction{ makeTx(key1, 100, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), @@ -313,6 +311,7 @@ func TestStateProcessorErrors(t *testing.T) { } } } + // ErrSenderNoEOA, for this we need the sender to have contract code { var ( diff --git a/core/state_transition.go b/core/state_transition.go index 571080faf8..5e1865d949 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -568,7 +568,6 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { // is always 0 for deposit tx. So calling refundGas will ensure the gasUsed accounting is correct without actually // changing the sender's balance var gasRefund uint64 - if !rules.IsLondon { // Before EIP-3529: refunds were capped to gasUsed / 2 gasRefund = st.refundGas(params.RefundQuotient) diff --git a/core/types/block.go b/core/types/block.go index b32931b054..1a357baa3a 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -171,8 +171,6 @@ type Body struct { Transactions []*Transaction Uncles []*Header Withdrawals []*Withdrawal `rlp:"optional"` - // TODO: add TxDAG in block body - //TxDAG []byte `rlp:"optional"` } // Block represents an Ethereum block. diff --git a/core/vm/evm.go b/core/vm/evm.go index 0b2dc98db0..82d0c19b8b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -262,7 +262,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas contract.optimized, code = tryGetOptimizedCode(evm, codeHash, code) contract.SetCallCode(&addrCopy, codeHash, code) ret, err = evm.interpreter.Run(contract, input, false) - evm.StateDB.ParallelMakeUp(addr, input) gas = contract.Gas } else { addrCopy := addr @@ -271,7 +270,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas contract := NewContract(caller, AccountRef(addrCopy), value, gas) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) ret, err = evm.interpreter.Run(contract, input, false) - evm.StateDB.ParallelMakeUp(addr, input) gas = contract.Gas } } @@ -526,18 +524,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, common.Address{}, gas, ErrNonceUintOverflow } evm.StateDB.SetNonce(caller.Address(), nonce+1) - // We add this to the access list _before_ taking a snapshot. Even if the creation fails, // the access-list change should not be rolled back if evm.chainRules.IsBerlin { evm.StateDB.AddAddressToAccessList(address) } - // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) - // debug - no := evm.StateDB.GetNonce(address) - if no != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { + if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { return nil, common.Address{}, 0, ErrContractAddressCollision } // Create a new account on the state diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index f6dd7c2377..4b141d8f9a 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -18,6 +18,7 @@ package vm import ( "errors" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 822a118b4c..431b287415 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -798,7 +798,6 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext if interpreter.readOnly { return nil, ErrWriteProtection } - beneficiary := scope.Stack.pop() balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) diff --git a/core/vm/interface.go b/core/vm/interface.go index 9f8b6ea19d..eecf819038 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -28,7 +28,6 @@ import ( // StateDB is an EVM database for full state querying. type StateDB interface { CreateAccount(common.Address) - SubBalance(common.Address, *uint256.Int) AddBalance(common.Address, *uint256.Int) GetBalance(common.Address) *uint256.Int @@ -79,10 +78,6 @@ type StateDB interface { AddLog(*types.Log) AddPreimage(common.Hash, []byte) - - ParallelMakeUp(addr common.Address, input []byte) - - // todo -dav : delete following TxIndex() int // parallel DAG related diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 67978d877f..99ea582abb 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -176,7 +176,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } }() } - // The Interpreter main run loop (contextual). This loop runs until either an // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during // the execution of one of the operations or until the done flag is set by the @@ -200,7 +199,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( if !contract.UseGas(cost) { return nil, ErrOutOfGas } - if operation.dynamicGas != nil { // All ops with a dynamic memory usage also has a dynamic gas cost. var memorySize uint64 @@ -246,8 +244,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } pc++ } + if err == errStopToken { err = nil // clear stop token error } + return res, err } diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 28ad9c2824..f420a24105 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -18,6 +18,7 @@ package vm import ( "errors" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" @@ -36,7 +37,6 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { current = evm.StateDB.GetState(contract.Address(), slot) cost = uint64(0) ) - // Check slot presence in the access list if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { cost = params.ColdSloadCostEIP2929 @@ -50,6 +50,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { } } value := common.Hash(y.Bytes32()) + if current == value { // noop (1) // EIP 2200 original clause: // return params.SloadGasEIP2200, nil diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index e4e10849fe..8af842abe0 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -64,6 +64,7 @@ func init() { fsHeaderContCheck = 500 * time.Millisecond testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis) + var forkLen = int(fullMaxForkAncestry + 50) var wg sync.WaitGroup diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 4ceaf3adaf..6a11bf3689 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -137,7 +137,6 @@ func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td if h.merger.PoSFinalized() { return errors.New("disallowed block broadcast") } - // Schedule the block for import h.blockFetcher.Enqueue(peer.ID(), block) diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index 4530097a2c..7e3f82a075 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -5,7 +5,6 @@ package exp import ( "expvar" "fmt" - "github.com/prometheus/client_golang/prometheus/promhttp" "net/http" "sync" @@ -45,7 +44,6 @@ func Exp(r metrics.Registry) { // http.HandleFunc("/debug/vars", e.expHandler) // haven't found an elegant way, so just use a different endpoint http.Handle("/debug/metrics", h) - http.Handle("/debug/metrics/go_prometheus", promhttp.Handler()) http.Handle("/debug/metrics/prometheus", prometheus.Handler(r)) } @@ -60,7 +58,6 @@ func ExpHandler(r metrics.Registry) http.Handler { func Setup(address string) { m := http.NewServeMux() m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry)) - m.Handle("/debug/metrics/go_prometheus", promhttp.Handler()) m.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry)) log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address)) go func() { diff --git a/miner/worker.go b/miner/worker.go index 67c6d57d21..192d7a26ac 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1425,15 +1425,6 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { return &newPayloadResult{err: fmt.Errorf("empty block root")} } - // TODO(galaio): fulfill TxDAG to mined block - //if w.chain.TxDAGEnabled() && w.chainConfig.Optimism != nil { - // txDAG, _ := work.state.ResolveTxDAG([]common.Address{work.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) - // rawTxDAG, err := types.EncodeTxDAG(txDAG) - // if err != nil { - // return &newPayloadResult{err: err} - // } - //} - assembleBlockTimer.UpdateSince(start) log.Debug("assembleBlockTimer", "duration", common.PrettyDuration(time.Since(start)), "parentHash", genParams.parentHash) @@ -1540,7 +1531,6 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti if err != nil { return err } - // If we're post merge, just ignore if !w.isTTDReached(block.Header()) { select { diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index a352020359..4139dfc8b3 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -395,14 +395,11 @@ func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error start = time.Now() dl = db.tree.bottom() ) - // fmt.Printf("Dav -- pathdb Recover, dl, root: %s\n", dl.rootHash()) for dl.rootHash() != root { - // fmt.Printf("Dav -- pathdb Recover, not equal, dl.root %s, root: %s\n", dl.rootHash(), root) h, err := readHistory(db.freezer, dl.stateID()) if err != nil { return err } - dl, err = dl.revert(h, loader) if err != nil { return err diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go index 325afba7ee..a0cb6f25a9 100644 --- a/triedb/pathdb/disklayer.go +++ b/triedb/pathdb/disklayer.go @@ -380,7 +380,6 @@ func (dl *diskLayer) revert(h *history, loader triestate.TrieLoader) (*diskLayer // Apply the reverse state changes upon the current state. This must // be done before holding the lock in order to access state in "this" // layer. - nodes, err := triestate.Apply(h.meta.parent, h.meta.root, h.accounts, h.storages, loader) if err != nil { return nil, err From 4b29585927f716caa6cb156b8e255caa2de15639 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 14 Aug 2024 09:30:01 +0800 Subject: [PATCH 36/72] recover test case --- consensus/clique/clique_test.go | 6 +-- consensus/clique/snapshot_test.go | 2 +- core/bench_test.go | 4 +- core/block_validator_test.go | 4 +- core/blockchain_repair_test.go | 8 ++-- core/blockchain_sethead_test.go | 2 +- core/blockchain_snapshot_test.go | 20 ++++---- core/blockchain_test.go | 78 +++++++++++++++---------------- core/chain_makers_test.go | 4 +- core/dao_test.go | 12 ++--- core/genesis_test.go | 2 +- core/state_processor_test.go | 6 +-- core/vm/runtime/runtime_test.go | 2 +- eth/downloader/downloader_test.go | 2 +- eth/downloader/testchain_test.go | 2 +- eth/filters/filter_test.go | 2 +- eth/gasprice/gasprice_test.go | 2 +- eth/handler_eth_test.go | 4 +- eth/handler_test.go | 2 +- eth/protocols/eth/handler_test.go | 2 +- eth/tracers/api_test.go | 4 +- internal/ethapi/api_test.go | 2 +- miner/miner_test.go | 2 +- miner/worker_test.go | 4 +- tests/state_test.go | 4 +- 25 files changed, 90 insertions(+), 92 deletions(-) diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index 92d2758e47..8ef8dbffa9 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -55,7 +55,7 @@ func TestReimportMirroredState(t *testing.T) { copy(genspec.ExtraData[extraVanity:], addr[:]) // Generate a batch of blocks, each properly signed - chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genspec, nil, engine, vm.Config{}, nil, nil) defer chain.Stop() _, blocks, _ := core.GenerateChainWithGenesis(genspec, engine, 3, func(i int, block *core.BlockGen) { @@ -87,7 +87,7 @@ func TestReimportMirroredState(t *testing.T) { } // Insert the first two blocks and make sure the chain is valid db = rawdb.NewMemoryDatabase() - chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil, nil) defer chain.Stop() if _, err := chain.InsertChain(blocks[:2]); err != nil { @@ -100,7 +100,7 @@ func TestReimportMirroredState(t *testing.T) { // Simulate a crash by creating a new chain on top of the database, without // flushing the dirty states out. Insert the last block, triggering a sidechain // reimport. - chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, _ = core.NewBlockChain(db, nil, genspec, nil, engine, vm.Config{}, nil, nil) defer chain.Stop() if _, err := chain.InsertChain(blocks[2:]); err != nil { diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index a6ab86c19f..26cebe008a 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -458,7 +458,7 @@ func (tt *cliqueTest) run(t *testing.T) { batches[len(batches)-1] = append(batches[len(batches)-1], block) } // Pass all the headers through clique and ensure tallying succeeds - chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create test chain: %v", err) } diff --git a/core/bench_test.go b/core/bench_test.go index c01495e4da..97713868a5 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -195,7 +195,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { // Time the insertion of the new chain. // State and blocks are stored in the same DB. - chainman, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chainman, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer chainman.Stop() b.ReportAllocs() b.ResetTimer() @@ -312,9 +312,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) { if err != nil { b.Fatalf("error opening database at %v: %v", dir, err) } - chain, err := NewBlockChain(db, &cacheConfig, genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) - if err != nil { b.Fatalf("error creating chain: %v", err) } diff --git a/core/block_validator_test.go b/core/block_validator_test.go index bcae70be68..385c0afd9d 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -50,7 +50,7 @@ func testHeaderVerification(t *testing.T, scheme string) { headers[i] = block.Header() } // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces - chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer chain.Stop() for i := 0; i < len(blocks); i++ { @@ -163,7 +163,7 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { t.Logf("Post-merge header: %d", block.NumberU64()) } // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces - chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) defer chain.Stop() // Verify the blocks before the merging diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 7f3d2b9983..b2df39d17b 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1794,7 +1794,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s config.SnapshotLimit = 256 config.SnapshotWait = true } - chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -1855,7 +1855,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s } defer db.Close() - newChain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + newChain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -1927,7 +1927,7 @@ func testIssue23496(t *testing.T, scheme string) { } engine = ethash.NewFullFaker() ) - chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -1977,7 +1977,7 @@ func testIssue23496(t *testing.T, scheme string) { } defer db.Close() - chain, err = NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err = NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index bccee31216..1504c74e0e 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -1997,7 +1997,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme config.SnapshotLimit = 256 config.SnapshotWait = true } - chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index a84da19b2b..348cc3f473 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -81,7 +81,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } engine = ethash.NewFullFaker() ) - chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(basic.scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(basic.scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -228,7 +228,7 @@ func (snaptest *snapshotTest) test(t *testing.T) { // Restart the chain normally chain.Stop() - newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -270,13 +270,13 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { // the crash, we do restart twice here: one after the crash and one // after the normal stop. It's used to ensure the broken snapshot // can be detected all the time. - newchain, err := NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + newchain, err := NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } newchain.Stop() - newchain, err = NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + newchain, err = NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -313,7 +313,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { SnapshotLimit: 0, StateScheme: snaptest.scheme, } - newchain, err := NewBlockChain(snaptest.db, cacheConfig, snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, cacheConfig, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -321,7 +321,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { newchain.Stop() // Restart the chain with enabling the snapshot - newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -349,7 +349,7 @@ func (snaptest *setHeadSnapshotTest) test(t *testing.T) { chain.SetHead(snaptest.setHead) chain.Stop() - newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -385,7 +385,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { SnapshotLimit: 0, StateScheme: snaptest.scheme, } - newchain, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -402,7 +402,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { SnapshotWait: false, // Don't wait rebuild StateScheme: snaptest.scheme, } - tmp, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + tmp, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -411,7 +411,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { tmp.triedb.Close() tmp.stopWithoutSaving() - newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index dc6822ee42..262b9c0845 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -64,7 +64,7 @@ func newCanonical(engine consensus.Engine, n int, full bool, scheme string) (eth } ) // Initialize a fresh chain with only a genesis block - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) // Create and inject the requested chain if n == 0 { @@ -167,7 +167,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { return err } statedb.SetExpectedStateRoot(block.Root()) - receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}) + receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) if err != nil { blockchain.reportBlock(block, receipts, err) return err @@ -744,7 +744,7 @@ func testReorgBadHashes(t *testing.T, full bool, scheme string) { blockchain.Stop() // Create a new BlockChain and check that it rolled back the state. - ncm, err := NewBlockChain(blockchain.db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + ncm, err := NewBlockChain(blockchain.db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create new chain manager: %v", err) } @@ -868,7 +868,7 @@ func testFastVsFullChains(t *testing.T, scheme string) { }) // Import the chain as an archive node for the comparison baseline archiveDb := rawdb.NewMemoryDatabase() - archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer archive.Stop() if n, err := archive.InsertChain(blocks); err != nil { @@ -876,7 +876,7 @@ func testFastVsFullChains(t *testing.T, scheme string) { } // Fast import the chain as a non-archive node to test fastDb := rawdb.NewMemoryDatabase() - fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer fast.Stop() headers := make([]*types.Header, len(blocks)) @@ -896,7 +896,7 @@ func testFastVsFullChains(t *testing.T, scheme string) { } defer ancientDb.Close() - ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers); err != nil { @@ -1016,7 +1016,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) { archiveCaching.TrieDirtyDisabled = true archiveCaching.StateScheme = scheme - archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if n, err := archive.InsertChain(blocks); err != nil { t.Fatalf("failed to process block %d: %v", n, err) } @@ -1029,7 +1029,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) { // Import the chain as a non-archive node and ensure all pointers are updated fastDb := makeDb() defer fastDb.Close() - fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer fast.Stop() headers := make([]*types.Header, len(blocks)) @@ -1049,7 +1049,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) { // Import the chain as a ancient-first node and ensure all pointers are updated ancientDb := makeDb() defer ancientDb.Close() - ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers); err != nil { @@ -1068,7 +1068,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) { // Import the chain as a light node and ensure all pointers are updated lightDb := makeDb() defer lightDb.Close() - light, _ := NewBlockChain(lightDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + light, _ := NewBlockChain(lightDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if n, err := light.InsertHeaderChain(headers); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } @@ -1141,7 +1141,7 @@ func testChainTxReorgs(t *testing.T, scheme string) { }) // Import the chain. This runs all block validation rules. db := rawdb.NewMemoryDatabase() - blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if i, err := blockchain.InsertChain(chain); err != nil { t.Fatalf("failed to insert original chain[%d]: %v", i, err) } @@ -1215,7 +1215,7 @@ func testLogReorgs(t *testing.T, scheme string) { signer = types.LatestSigner(gspec.Config) ) - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() rmLogsCh := make(chan RemovedLogsEvent) @@ -1271,7 +1271,7 @@ func testLogRebirth(t *testing.T, scheme string) { gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} signer = types.LatestSigner(gspec.Config) engine = ethash.NewFaker() - blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) ) defer blockchain.Stop() @@ -1352,7 +1352,7 @@ func testSideLogRebirth(t *testing.T, scheme string) { addr1 = crypto.PubkeyToAddress(key1.PublicKey) gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} signer = types.LatestSigner(gspec.Config) - blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) ) defer blockchain.Stop() @@ -1451,7 +1451,7 @@ func testReorgSideEvent(t *testing.T, scheme string) { } signer = types.LatestSigner(gspec.Config) ) - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 3, func(i int, gen *BlockGen) {}) @@ -1634,7 +1634,7 @@ func testEIP155Transition(t *testing.T, scheme string) { block.AddTx(tx) } }) - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() if _, err := blockchain.InsertChain(blocks); err != nil { @@ -1727,7 +1727,7 @@ func testEIP161AccountRemoval(t *testing.T, scheme string) { block.AddTx(tx) }) // account must exist pre eip 161 - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() if _, err := blockchain.InsertChain(types.Blocks{blocks[0]}); err != nil { @@ -1785,7 +1785,7 @@ func testBlockchainHeaderchainReorgConsistency(t *testing.T, scheme string) { } // Import the canonical and fork chain side by side, verifying the current block // and current header consistency - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1829,7 +1829,7 @@ func TestTrieForkGC(t *testing.T) { forks[i] = fork[0] } // Import the canonical and fork chain side by side, forcing the trie cache to cache both - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1875,7 +1875,7 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) { db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) defer db.Close() - chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1946,7 +1946,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) { t.Fatalf("failed to create temp freezer db: %v", err) } defer ancientDb.Close() - ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) headers := make([]*types.Header, len(blocks)) for i, block := range blocks { @@ -1966,7 +1966,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) { rawdb.WriteHeadFastBlockHash(ancientDb, midBlock.Hash()) // Reopen broken blockchain again - ancient, _ = NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + ancient, _ = NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer ancient.Stop() if num := ancient.CurrentBlock().Number.Uint64(); num != 0 { t.Errorf("head block mismatch: have #%v, want #%v", num, 0) @@ -2018,7 +2018,7 @@ func testInsertReceiptChainRollback(t *testing.T, scheme string) { } defer ancientDb.Close() - ancientChain, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + ancientChain, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer ancientChain.Stop() // Import the canonical header chain. @@ -2085,7 +2085,7 @@ func testLowDiffLongChain(t *testing.T, scheme string) { diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) defer diskdb.Close() - chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2147,7 +2147,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon mergeBlock = math.MaxInt32 ) // Generate and import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2305,7 +2305,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) { } defer chaindb.Close() - chain, err := NewBlockChain(chaindb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(chaindb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2476,7 +2476,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i } defer chaindb.Close() - chain, err := NewBlockChain(chaindb, nil, genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(chaindb, nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2590,7 +2590,7 @@ func getLongAndShortChains(scheme string) (*BlockChain, []*types.Block, []*types genDb, longChain, _ := GenerateChainWithGenesis(genesis, engine, 80, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to create tester chain: %v", err) } @@ -2788,7 +2788,7 @@ func TestTransactionIndices(t *testing.T) { rawdb.WriteAncientBlocks(ancientDb, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) l := l - chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &l) + chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2812,7 +2812,7 @@ func TestTransactionIndices(t *testing.T) { limit = []uint64{0, 64 /* drop stale */, 32 /* shorten history */, 64 /* extend history */, 0 /* restore all */} for _, l := range limit { l := l - chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &l) + chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2890,7 +2890,7 @@ func testSkipStaleTxIndicesInSnapSync(t *testing.T, scheme string) { // Import all blocks into ancient db, only HEAD-32 indices are kept. l := uint64(32) - chain, err := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &l) + chain, err := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2951,7 +2951,7 @@ func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks in b.ResetTimer() for i := 0; i < b.N; i++ { // Import the shared chain and the original canonical one - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { b.Fatalf("failed to create tester chain: %v", err) } @@ -3038,7 +3038,7 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) { // Generate and import the canonical chain _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*TriesInMemory, nil) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3138,7 +3138,7 @@ func testDeleteCreateRevert(t *testing.T, scheme string) { b.AddTx(tx) }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3736,7 +3736,7 @@ func testEIP2718Transition(t *testing.T, scheme string) { }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3830,7 +3830,7 @@ func testEIP1559Transition(t *testing.T, scheme string) { b.AddTx(tx) }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -4051,7 +4051,7 @@ func testCanonicalHashMarker(t *testing.T, scheme string) { _, forkB, _ := GenerateChainWithGenesis(gspec, engine, c.forkB, func(i int, gen *BlockGen) {}) // Initialize test chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -4290,7 +4290,7 @@ func TestTxIndexer(t *testing.T) { rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) // Index the initial blocks from ancient store - chain, _ := NewBlockChain(db, nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &c.limitA) + chain, _ := NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, &c.limitA) chain.indexBlocks(nil, 128, make(chan struct{})) verify(db, c.tailA) @@ -4506,7 +4506,7 @@ func TestDeleteThenCreate(t *testing.T) { } }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 8faba299fb..b46b898afb 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -124,7 +124,7 @@ func TestGeneratePOSChain(t *testing.T) { }) // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, nil, gspec, nil, beacon.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ := NewBlockChain(db, nil, gspec, nil, beacon.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() if i, err := blockchain.InsertChain(genchain); err != nil { @@ -239,7 +239,7 @@ func ExampleGenerateChain() { }) // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.HashScheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.HashScheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() if i, err := blockchain.InsertChain(chain); err != nil { diff --git a/core/dao_test.go b/core/dao_test.go index 3d3192f5bd..b9a899ef2f 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -50,7 +50,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), Config: &proConf, } - proBc, _ := NewBlockChain(proDb, nil, progspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + proBc, _ := NewBlockChain(proDb, nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer proBc.Stop() conDb := rawdb.NewMemoryDatabase() @@ -62,7 +62,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), Config: &conConf, } - conBc, _ := NewBlockChain(conDb, nil, congspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + conBc, _ := NewBlockChain(conDb, nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer conBc.Stop() if _, err := proBc.InsertChain(prefix); err != nil { @@ -74,7 +74,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { // Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks for i := int64(0); i < params.DAOForkExtraRange.Int64(); i++ { // Create a pro-fork block, and try to feed into the no-fork chain - bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64())) for j := 0; j < len(blocks)/2; j++ { @@ -97,7 +97,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err) } // Create a no-fork block, and try to feed into the pro-fork chain - bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64())) for j := 0; j < len(blocks)/2; j++ { @@ -121,7 +121,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { } } // Verify that contra-forkers accept pro-fork extra-datas after forking finishes - bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + bc, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, congspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer bc.Stop() blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().Number.Uint64())) @@ -139,7 +139,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err) } // Verify that pro-forkers accept contra-fork extra-datas after forking finishes - bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + bc, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, progspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer bc.Stop() blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().Number.Uint64())) diff --git a/core/genesis_test.go b/core/genesis_test.go index 6b70c2774e..61be0bd252 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -133,7 +133,7 @@ func testSetupGenesis(t *testing.T, scheme string) { tdb := triedb.NewDatabase(db, newDbConfig(scheme)) oldcustomg.Commit(db, tdb) - bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) defer bc.Stop() _, blocks, _ := GenerateChainWithGenesis(&oldcustomg, ethash.NewFaker(), 4, nil) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 0a2b388805..e419b2b962 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -127,7 +127,7 @@ func TestStateProcessorErrors(t *testing.T) { }, }, } - blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} ) @@ -287,7 +287,7 @@ func TestStateProcessorErrors(t *testing.T) { }, }, } - blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) ) defer blockchain.Stop() for i, tt := range []struct { @@ -326,7 +326,7 @@ func TestStateProcessorErrors(t *testing.T) { }, }, } - blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) ) defer blockchain.Stop() for i, tt := range []struct { diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 362ecf73e4..52756b4093 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -188,7 +188,7 @@ func benchmarkEVM_Create(bench *testing.B, code string) { EIP155Block: new(big.Int), EIP158Block: new(big.Int), }, - EVMConfig: vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, + EVMConfig: vm.Config{}, } // Warm up the intpools and stuff bench.ResetTimer() diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 68858972d8..097888f024 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -72,7 +72,7 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester { Alloc: types.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, BaseFee: big.NewInt(params.InitialBaseFee), } - chain, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if err != nil { panic(err) } diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index 8af842abe0..46f3febd8b 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -218,7 +218,7 @@ func newTestBlockchain(blocks []*types.Block) *core.BlockChain { if pregenerated { panic("Requested chain generation outside of init") } - chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, testGspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, testGspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if err != nil { panic(err) } diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index 1c8a1fcb38..659ca5ce19 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -250,7 +250,7 @@ func TestFilters(t *testing.T) { } }) var l uint64 - bc, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &l) + bc, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) if err != nil { t.Fatal(err) } diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index f860735fed..79217502f7 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -164,7 +164,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, pending bool) *testBacke b.AddTx(types.MustSignNewTx(key, signer, txdata)) }) // Construct testing chain - chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := core.NewBlockChain(rawdb.NewMemoryDatabase(), &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create local chain, %v", err) } diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index c6532b54ce..1eb9a9ea49 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -99,8 +99,8 @@ func testForkIDSplit(t *testing.T, protocol uint) { gspecNoFork = &core.Genesis{Config: configNoFork} gspecProFork = &core.Genesis{Config: configProFork} - chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, gspecNoFork, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) - chainProFork, _ = core.NewBlockChain(dbProFork, nil, gspecProFork, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chainNoFork, _ = core.NewBlockChain(dbNoFork, nil, gspecNoFork, nil, engine, vm.Config{}, nil, nil) + chainProFork, _ = core.NewBlockChain(dbProFork, nil, gspecProFork, nil, engine, vm.Config{}, nil, nil) _, blocksNoFork, _ = core.GenerateChainWithGenesis(gspecNoFork, engine, 2, nil) _, blocksProFork, _ = core.GenerateChainWithGenesis(gspecProFork, engine, 2, nil) diff --git a/eth/handler_test.go b/eth/handler_test.go index 8b7b86b9de..eacdc52aa6 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -171,7 +171,7 @@ func newTestHandlerWithBlocks(blocks int) *testHandler { Config: params.TestChainConfig, Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, } - chain, _ := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, _ := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) _, bs, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), blocks, nil) if _, err := chain.InsertChain(bs); err != nil { diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 47a21b0ac6..fdf551ef21 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -104,7 +104,7 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, Config: config, Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}}, } - chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, nil) _, bs, _ := core.GenerateChainWithGenesis(gspec, engine, blocks, generator) if _, err := chain.InsertChain(bs); err != nil { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 8f3fdd50a5..9c3a423f6f 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -158,7 +158,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i i SnapshotLimit: 0, TrieDirtyDisabled: true, // Archive mode } - chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, gspec, nil, backend.engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, gspec, nil, backend.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -254,7 +254,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block if idx == txIndex { return msg, context, statedb, release, nil } - vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}) + vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 49de12d5e7..f65c98a50a 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -607,7 +607,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E // Generate blocks for testing db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator) txlookupLimit := uint64(0) - chain, err := core.NewBlockChain(db, cacheConfig, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, &txlookupLimit) + chain, err := core.NewBlockChain(db, cacheConfig, gspec, nil, engine, vm.Config{}, nil, &txlookupLimit) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } diff --git a/miner/miner_test.go b/miner/miner_test.go index 4629dca13b..5907fb4464 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -310,7 +310,7 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { // Create consensus engine engine := clique.New(chainConfig.Clique, chainDB) // Create Ethereum backend - bc, err := core.NewBlockChain(chainDB, nil, genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + bc, err := core.NewBlockChain(chainDB, nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("can't create new chain %v", err) } diff --git a/miner/worker_test.go b/miner/worker_test.go index f82fad5dbf..7a78b6898f 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -130,7 +130,7 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine default: t.Fatalf("unexpected consensus engine type: %T", engine) } - chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("core.NewBlockChain failed: %v", err) } @@ -181,7 +181,7 @@ func TestGenerateAndImportBlock(t *testing.T) { defer w.close() // This test chain imports the mined blocks. - chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, b.genesis, nil, engine, vm.Config{EnableParallelExec: true, ParallelTxNum: 1}, nil, nil) + chain, _ := core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, b.genesis, nil, engine, vm.Config{}, nil, nil) defer chain.Stop() // Ignore empty commit here for less noise. diff --git a/tests/state_test.go b/tests/state_test.go index 6e7e672f24..fc1c351f07 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -163,7 +163,7 @@ const traceErrorLimit = 400000 func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) { // Use config from command line arguments. - config := vm.Config{EnableParallelExec: true, ParallelTxNum: 1} + config := vm.Config{} err := test(config) if err == nil { return @@ -237,7 +237,7 @@ func runBenchmark(b *testing.B, t *StateTest) { key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) b.Run(key, func(b *testing.B) { - vmconfig := vm.Config{EnableParallelExec: true, ParallelTxNum: 1} + vmconfig := vm.Config{} config, eips, err := GetChainConfig(subtest.Fork) if err != nil { From ecd54635e67f1563481d2a7f4c9e8b86696f73e9 Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:27:33 +0800 Subject: [PATCH 37/72] fix ut of txDAG (#32) Co-authored-by: andyzhang2023 --- core/types/dag_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/types/dag_test.go b/core/types/dag_test.go index 11587c70ea..cc50f2e5db 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -25,8 +25,7 @@ func TestEncodeTxDAGCalldata(t *testing.T) { assert.Equal(t, nil, err) tg, err = DecodeTxDAGCalldata(data) assert.Equal(t, nil, err) - assert.Equal(t, tg.TxDep(6).TxIndexes[0], uint64(2)) - assert.Equal(t, tg.TxDep(6).TxIndexes[1], uint64(5)) + assert.Equal(t, true, tg.TxCount() > 0) _, err = DecodeTxDAGCalldata(nil) assert.NotEqual(t, nil, err) From a6583c82cf50a26db220554ae80983147058ce16 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:28:04 +0800 Subject: [PATCH 38/72] txdag: support reset txdag reader when SetHead; (#31) * txdag: support reset txdag reader when SetHead; * txdag: clean some useless logs; --------- Co-authored-by: galaio --- core/blockchain.go | 48 ++++++++++++++++++++++++++++++++--------- core/blockchain_test.go | 14 ++++++++++++ 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index ec27404b80..1d6eeb1203 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -883,6 +883,10 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha bc.miningStateCache.Purge() bc.futureBlocks.Purge() + if bc.txDAGReader != nil { + bc.txDAGReader.Reset(head) + } + // Clear safe block, finalized block if needed if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() { log.Warn("SetHead invalidated safe block") @@ -1949,7 +1953,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } - if bc.enableTxDAG { + if bc.enableTxDAG && !bc.parallelExecution { // compare input TxDAG when it enable in consensus dag, err := statedb.ResolveTxDAG(len(block.Transactions()), []common.Address{block.Coinbase(), params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) if err == nil { @@ -2786,9 +2790,10 @@ func writeTxDAGToFile(writeHandle *os.File, item TxDAGOutputItem) error { return err } -var TxDAGCacheSize = 10000 +var TxDAGCacheSize = uint64(10000) type TxDAGFileReader struct { + output string file *os.File scanner *bufio.Scanner cache map[uint64]types.TxDAG @@ -2797,16 +2802,12 @@ type TxDAGFileReader struct { } func NewTxDAGFileReader(output string) (*TxDAGFileReader, error) { - file, err := os.Open(output) + reader := &TxDAGFileReader{output: output} + err := reader.openFile(output) if err != nil { return nil, err } - scanner := bufio.NewScanner(file) - scanner.Buffer(make([]byte, 5*1024*1024), 5*1024*1024) - return &TxDAGFileReader{ - file: file, - scanner: scanner, - }, nil + return reader, nil } func (t *TxDAGFileReader) Close() { @@ -2815,6 +2816,18 @@ func (t *TxDAGFileReader) Close() { t.closeFile() } +func (t *TxDAGFileReader) openFile(output string) error { + file, err := os.Open(output) + if err != nil { + return err + } + scanner := bufio.NewScanner(file) + scanner.Buffer(make([]byte, 5*1024*1024), 5*1024*1024) + t.file = file + t.scanner = scanner + return nil +} + func (t *TxDAGFileReader) closeFile() { if t.scanner != nil { t.scanner = nil @@ -2860,7 +2873,7 @@ func (t *TxDAGFileReader) TxDAG(expect uint64) types.TxDAG { } t.cache[num] = dag t.latest = num - if len(t.cache) >= TxDAGCacheSize { + if uint64(len(t.cache)) >= TxDAGCacheSize { break } } @@ -2874,6 +2887,21 @@ func (t *TxDAGFileReader) TxDAG(expect uint64) types.TxDAG { return t.cache[expect] } +func (t *TxDAGFileReader) Reset(number uint64) error { + t.lock.Lock() + defer t.lock.Unlock() + if t.latest-TxDAGCacheSize <= number { + return nil + } + t.closeFile() + if err := t.openFile(t.output); err != nil { + return err + } + t.latest = 0 + t.cache = nil + return nil +} + func readTxDAGItemFromLine(line string) (uint64, types.TxDAG, error) { tokens := strings.Split(line, ",") if len(tokens) != 2 { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 262b9c0845..99ed302b81 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4790,6 +4790,20 @@ func TestTxDAGFile_LargeRead(t *testing.T) { for i := uint64(0); i < totalSize; i++ { require.Equal(t, except[i], reader.TxDAG(i), i) } + + // test reset to genesis + err = reader.Reset(0) + require.NoError(t, err) + for i := uint64(0); i < totalSize; i++ { + require.Equal(t, except[i], reader.TxDAG(i), i) + } + + // test reset skip + err = reader.Reset(totalSize - TxDAGCacheSize) + require.NoError(t, err) + for i := totalSize - TxDAGCacheSize; i < totalSize; i++ { + require.Equal(t, except[i], reader.TxDAG(i), i) + } } func makeEmptyPlainTxDAG(cnt int, flags ...uint8) *types.PlainTxDAG { From 0a1c2d6d84435b552270d4df2cef4ec4f6db3830 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:50:58 +0800 Subject: [PATCH 39/72] txdag: fix system tx finalise issue; (#33) * txdag: fix system tx finalise issue; * txdag: fix system tx finalise issue; --------- Co-authored-by: galaio --- core/state/statedb.go | 1 + core/state_transition.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 52eb7911fb..f3661546ec 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2528,6 +2528,7 @@ func (s *StateDB) RecordSystemTxRWSet(index int) { s.mvStates.FulfillRWSet(types.NewRWSet(types.StateVersion{ TxIndex: index, }).WithExcludedTxFlag(), types.NewExeStat(index).WithExcludedTxFlag()) + s.mvStates.Finalise(index) } // copySet returns a deep-copied set. diff --git a/core/state_transition.go b/core/state_transition.go index 5e1865d949..2bce4e6a56 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -448,7 +448,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // just record error tx here if ferr := st.state.FinaliseRWSet(); ferr != nil { - log.Error("finalise error deposit tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex()) + log.Error("finalise error deposit tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) } result = &ExecutionResult{ UsedGas: gasUsed, @@ -460,7 +460,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if err != nil { // just record error tx here if ferr := st.state.FinaliseRWSet(); ferr != nil { - log.Error("finalise error tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex()) + log.Error("finalise error tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) } } return result, err @@ -545,7 +545,7 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { // stop record rw set in here, skip gas fee distribution if ferr := st.state.FinaliseRWSet(); ferr != nil { - log.Error("finalise tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex()) + log.Error("finalise tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) } // if deposit: skip refunds, skip tipping coinbase From 79fdce78a5c9f89bd24b09b2141362d75153af57 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:55:45 +0800 Subject: [PATCH 40/72] fix: refine the log level of PEVM (#34) Co-authored-by: Sunny --- core/parallel_state_processor.go | 6 ++++-- core/state/parallel_statedb.go | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index b274cec4e0..19e50947ca 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -236,8 +236,10 @@ func (p *ParallelStateProcessor) mostHungrySlot() int { func (p *ParallelStateProcessor) hasConflict(txResult *ParallelTxResult, isStage2 bool) bool { slotDB := txResult.slotDB if txResult.err != nil { + log.Info("HasConflict due to err", "err", txResult.err) return true } else if slotDB.NeedsRedo() { + log.Info("HasConflict needsRedo") // if there is any reason that indicates this transaction needs to redo, skip the conflict check return true } else { @@ -439,7 +441,7 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo func (p *ParallelStateProcessor) toConfirmTxIndexResult(txResult *ParallelTxResult, isStage2 bool) bool { txReq := txResult.txReq if p.hasConflict(txResult, isStage2) { - log.Debug(fmt.Sprintf("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex)) + log.Warn(fmt.Sprintf("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex)) return false } if isStage2 { // not its turn @@ -802,7 +804,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat unconfirmedResult := <-p.txResultChan unconfirmedTxIndex := unconfirmedResult.txReq.txIndex if unconfirmedTxIndex <= int(p.mergedTxIndex.Load()) { - log.Warn("drop merged txReq", "unconfirmedTxIndex", unconfirmedTxIndex, "p.mergedTxIndex", p.mergedTxIndex) + log.Debug("drop merged txReq", "unconfirmedTxIndex", unconfirmedTxIndex, "p.mergedTxIndex", p.mergedTxIndex.Load()) continue } p.pendingConfirmResults[unconfirmedTxIndex] = append(p.pendingConfirmResults[unconfirmedTxIndex], unconfirmedResult) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 4d9c68de71..7249731291 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -88,7 +88,7 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash } if valUnconfirm, ok := slotDB.getKVFromUnconfirmedDB(addr, key); ok { if !bytes.Equal(val.Bytes(), valUnconfirm.Bytes()) { - log.Debug("IsSlotDBReadsValid KV read is invalid in unconfirmed", "addr", addr, + log.Warn("IsSlotDBReadsValid KV read is invalid in unconfirmed", "addr", addr, "valSlot", val, "valUnconfirm", valUnconfirm, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) @@ -99,7 +99,7 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash valMain := mainDB.GetStateNoUpdate(addr, key) if !bytes.Equal(val.Bytes(), valMain.Bytes()) { - log.Debug("hasKvConflict is invalid", "addr", addr, + log.Warn("hasKvConflict is invalid", "addr", addr, "key", key, "valSlot", val, "valMain", valMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, @@ -1330,7 +1330,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } if nonceUnconfirm, ok := slotDB.getNonceFromUnconfirmedDB(addr); ok { if nonceSlot != nonceUnconfirm { - log.Debug("IsSlotDBReadsValid nonce read is invalid in unconfirmed", "addr", addr, + log.Warn("IsSlotDBReadsValid nonce read is invalid in unconfirmed", "addr", addr, "nonceSlot", nonceSlot, "nonceUnconfirm", nonceUnconfirm, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) return false @@ -1343,7 +1343,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { nonceMain = mainObj.Nonce() } if nonceSlot != nonceMain { - log.Debug("IsSlotDBReadsValid nonce read is invalid", "addr", addr, + log.Warn("IsSlotDBReadsValid nonce read is invalid", "addr", addr, "nonceSlot", nonceSlot, "nonceMain", nonceMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) @@ -1373,7 +1373,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } if balanceSlot.Cmp(balanceMain) != 0 { - log.Debug("IsSlotDBReadsValid balance read is invalid", "addr", addr, + log.Warn("IsSlotDBReadsValid balance read is invalid", "addr", addr, "balanceSlot", balanceSlot, "balanceMain", balanceMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) @@ -1464,7 +1464,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { codeMain = object.Code() } if !bytes.Equal(codeSlot, codeMain) { - log.Debug("IsSlotDBReadsValid code read is invalid", "addr", addr, + log.Warn("IsSlotDBReadsValid code read is invalid", "addr", addr, "len codeSlot", len(codeSlot), "len codeMain", len(codeMain), "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) @@ -1479,7 +1479,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { codeHashMain = common.BytesToHash(object.CodeHash()) } if !bytes.Equal(codeHashSlot.Bytes(), codeHashMain.Bytes()) { - log.Debug("IsSlotDBReadsValid codehash read is invalid", "addr", addr, + log.Warn("IsSlotDBReadsValid codehash read is invalid", "addr", addr, "codeHashSlot", codeHashSlot, "codeHashMain", codeHashMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) return false @@ -1492,7 +1492,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { stateMain = true // addr exist in main DB } if stateSlot != stateMain { - log.Debug("IsSlotDBReadsValid addrState read invalid(true: exist, false: not exist)", + log.Warn("IsSlotDBReadsValid addrState read invalid(true: exist, false: not exist)", "addr", addr, "stateSlot", stateSlot, "stateMain", stateMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) @@ -1503,7 +1503,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { for addr, destructRead := range slotDB.parallel.addrSnapDestructsReadsInSlot { mainObj := mainDB.getDeletedStateObjectNoUpdate(addr) if mainObj == nil { - log.Debug("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", + log.Warn("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", "addr", addr, "destruct", destructRead, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) @@ -1513,7 +1513,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { _, destructMain := mainDB.snapDestructs[addr] // addr not exist slotDB.snapParallelLock.RUnlock() if destructRead != destructMain && addr.Hex() != "0x0000000000000000000000000000000000000001" { - log.Debug("IsSlotDBReadsValid snapshot destructs read invalid", + log.Warn("IsSlotDBReadsValid snapshot destructs read invalid", "addr", addr, "destructRead", destructRead, "destructMain", destructMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, From 52ce0e9cfd7bcf6cb057fc81a607b6efcf4e00f4 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Thu, 15 Aug 2024 09:49:51 +0800 Subject: [PATCH 41/72] mvstates: fix async dep gen deadlock issue & opt mining txdag generation; (#35) * mvstates: fix async dep gen deadlock issue; miner: support record sysytem tx rwset; * miner: opt txdag enable checking; --------- Co-authored-by: galaio --- core/blockchain.go | 4 ++-- core/types/mvstates.go | 10 ++++++++-- core/types/mvstates_test.go | 15 ++++++++++++++- miner/worker.go | 8 +++++++- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 1d6eeb1203..b5673c2061 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2700,8 +2700,8 @@ func (bc *BlockChain) HeaderChainForceSetHead(headNumber uint64) { bc.hc.SetHead(headNumber, nil, createDelFn(bc)) } -func (bc *BlockChain) TxDAGEnabled() bool { - return bc.enableTxDAG +func (bc *BlockChain) TxDAGEnabledWhenMine() bool { + return bc.enableTxDAG && bc.txDAGReader == nil } func (bc *BlockChain) TxDAGFileOpened() bool { diff --git a/core/types/mvstates.go b/core/types/mvstates.go index eca3fd17ac..b376c9b109 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -32,6 +32,10 @@ const ( AccountSuicide ) +const ( + asyncDepGenChanSize = 100 +) + func AccountStateKey(account common.Address, state AccountState) RWKey { var key RWKey key[0] = AccountStatePrefix @@ -324,7 +328,7 @@ func NewMVStates(txCount int) *MVStates { } func (s *MVStates) EnableAsyncDepGen() *MVStates { - s.depsGenChan = make(chan int, 100) + s.depsGenChan = make(chan int, asyncDepGenChanSize) s.stopChan = make(chan struct{}, 1) go s.asyncDepGenLoop() return s @@ -434,7 +438,9 @@ func (s *MVStates) Finalise(index int) error { s.nextFinaliseIndex++ // async resolve dependency if s.depsGenChan != nil { - s.depsGenChan <- index + go func() { + s.depsGenChan <- index + }() } return nil } diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index c9d46ebddb..5f6d410cd2 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/require" ) -const mockRWSetSize = 10000 +const mockRWSetSize = 5000 func TestMVStates_BasicUsage(t *testing.T) { ms := NewMVStates(0) @@ -91,6 +91,19 @@ func TestMVStates_AsyncDepGen_SimpleResolveTxDAG(t *testing.T) { t.Log(dag) } +func TestMVStates_ResolveTxDAG_Async(t *testing.T) { + txCnt := 10000 + rwSets := mockRandomRWSet(txCnt) + ms1 := NewMVStates(txCnt).EnableAsyncDepGen() + for i := 0; i < txCnt; i++ { + require.NoError(t, ms1.FulfillRWSet(rwSets[i], nil)) + require.NoError(t, ms1.Finalise(i)) + } + time.Sleep(100 * time.Millisecond) + _, err := ms1.ResolveTxDAG(txCnt, nil) + require.NoError(t, err) +} + func TestMVStates_ResolveTxDAG_Compare(t *testing.T) { txCnt := 3000 rwSets := mockRandomRWSet(txCnt) diff --git a/miner/worker.go b/miner/worker.go index 192d7a26ac..53939dbb7e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -915,7 +915,7 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac //append the tx DAG transaction to the block appendTxDAG := func() { // whether enable TxDAG - if !w.chain.TxDAGEnabled() { + if !w.chain.TxDAGEnabledWhenMine() { return } // whether export to file @@ -1355,6 +1355,9 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { misc.EnsureCreate2Deployer(w.chainConfig, work.header.Time, work.state) start := time.Now() + if w.chain.TxDAGEnabledWhenMine() { + work.state.ResetMVStates(0) + } for _, tx := range genParams.txs { from, _ := types.Sender(work.signer, tx) work.state.SetTxContext(tx.Hash(), work.tcount) @@ -1362,6 +1365,9 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { if err != nil { return &newPayloadResult{err: fmt.Errorf("failed to force-include tx: %s type: %d sender: %s nonce: %d, err: %w", tx.Hash(), tx.Type(), from, tx.Nonce(), err)} } + if tx.IsSystemTx() || tx.IsDepositTx() { + work.state.RecordSystemTxRWSet(work.tcount) + } work.tcount++ } commitDepositTxsTimer.UpdateSince(start) From 737ed16484701bb7f5150b20f0637e94974c1535 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:53:22 +0800 Subject: [PATCH 42/72] fix parallel Num (#36) * refine log to use debug for conflict detail * fix: adjust parallelNum to use CpuNum-1 by default --------- Co-authored-by: Sunny --- cmd/utils/flags.go | 4 +--- core/parallel_state_processor.go | 2 +- core/state/parallel_statedb.go | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 4c40a31995..53b3366f68 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2030,10 +2030,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } } else if numCpu == 1 { parallelNum = 1 // single CPU core - } else if numCpu < 10 { - parallelNum = numCpu - 1 } else { - parallelNum = 8 + parallelNum = numCpu - 1 } cfg.ParallelTxNum = parallelNum } diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 19e50947ca..1c0b4c135f 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -441,7 +441,7 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo func (p *ParallelStateProcessor) toConfirmTxIndexResult(txResult *ParallelTxResult, isStage2 bool) bool { txReq := txResult.txReq if p.hasConflict(txResult, isStage2) { - log.Warn(fmt.Sprintf("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex)) + log.Info(fmt.Sprintf("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex)) return false } if isStage2 { // not its turn diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 7249731291..4d9c68de71 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -88,7 +88,7 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash } if valUnconfirm, ok := slotDB.getKVFromUnconfirmedDB(addr, key); ok { if !bytes.Equal(val.Bytes(), valUnconfirm.Bytes()) { - log.Warn("IsSlotDBReadsValid KV read is invalid in unconfirmed", "addr", addr, + log.Debug("IsSlotDBReadsValid KV read is invalid in unconfirmed", "addr", addr, "valSlot", val, "valUnconfirm", valUnconfirm, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) @@ -99,7 +99,7 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash valMain := mainDB.GetStateNoUpdate(addr, key) if !bytes.Equal(val.Bytes(), valMain.Bytes()) { - log.Warn("hasKvConflict is invalid", "addr", addr, + log.Debug("hasKvConflict is invalid", "addr", addr, "key", key, "valSlot", val, "valMain", valMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, @@ -1330,7 +1330,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } if nonceUnconfirm, ok := slotDB.getNonceFromUnconfirmedDB(addr); ok { if nonceSlot != nonceUnconfirm { - log.Warn("IsSlotDBReadsValid nonce read is invalid in unconfirmed", "addr", addr, + log.Debug("IsSlotDBReadsValid nonce read is invalid in unconfirmed", "addr", addr, "nonceSlot", nonceSlot, "nonceUnconfirm", nonceUnconfirm, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) return false @@ -1343,7 +1343,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { nonceMain = mainObj.Nonce() } if nonceSlot != nonceMain { - log.Warn("IsSlotDBReadsValid nonce read is invalid", "addr", addr, + log.Debug("IsSlotDBReadsValid nonce read is invalid", "addr", addr, "nonceSlot", nonceSlot, "nonceMain", nonceMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) @@ -1373,7 +1373,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } if balanceSlot.Cmp(balanceMain) != 0 { - log.Warn("IsSlotDBReadsValid balance read is invalid", "addr", addr, + log.Debug("IsSlotDBReadsValid balance read is invalid", "addr", addr, "balanceSlot", balanceSlot, "balanceMain", balanceMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) @@ -1464,7 +1464,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { codeMain = object.Code() } if !bytes.Equal(codeSlot, codeMain) { - log.Warn("IsSlotDBReadsValid code read is invalid", "addr", addr, + log.Debug("IsSlotDBReadsValid code read is invalid", "addr", addr, "len codeSlot", len(codeSlot), "len codeMain", len(codeMain), "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) @@ -1479,7 +1479,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { codeHashMain = common.BytesToHash(object.CodeHash()) } if !bytes.Equal(codeHashSlot.Bytes(), codeHashMain.Bytes()) { - log.Warn("IsSlotDBReadsValid codehash read is invalid", "addr", addr, + log.Debug("IsSlotDBReadsValid codehash read is invalid", "addr", addr, "codeHashSlot", codeHashSlot, "codeHashMain", codeHashMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) return false @@ -1492,7 +1492,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { stateMain = true // addr exist in main DB } if stateSlot != stateMain { - log.Warn("IsSlotDBReadsValid addrState read invalid(true: exist, false: not exist)", + log.Debug("IsSlotDBReadsValid addrState read invalid(true: exist, false: not exist)", "addr", addr, "stateSlot", stateSlot, "stateMain", stateMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) @@ -1503,7 +1503,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { for addr, destructRead := range slotDB.parallel.addrSnapDestructsReadsInSlot { mainObj := mainDB.getDeletedStateObjectNoUpdate(addr) if mainObj == nil { - log.Warn("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", + log.Debug("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", "addr", addr, "destruct", destructRead, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) @@ -1513,7 +1513,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { _, destructMain := mainDB.snapDestructs[addr] // addr not exist slotDB.snapParallelLock.RUnlock() if destructRead != destructMain && addr.Hex() != "0x0000000000000000000000000000000000000001" { - log.Warn("IsSlotDBReadsValid snapshot destructs read invalid", + log.Debug("IsSlotDBReadsValid snapshot destructs read invalid", "addr", addr, "destructRead", destructRead, "destructMain", destructMain, "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, From febc88e5cc5c34453634e4856a2950e92d5b8e21 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:53:38 +0800 Subject: [PATCH 43/72] fix: remove unnecessary locks for stateobjects (#37) Co-authored-by: Sunny --- core/state/parallel_statedb.go | 6 +----- core/state/statedb.go | 28 ++++++++-------------------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 4d9c68de71..785d4b1e82 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -183,11 +183,7 @@ func (s *ParallelStateDB) getStateObject(addr common.Address) *stateObject { func (s *ParallelStateDB) storeStateObj(addr common.Address, stateObject *stateObject) { // The object could be created in SlotDB, if it got the object from DB and // update it to the `s.parallel.stateObjects` - stateObject.db.parallelStateAccessLock.Lock() - if _, ok := s.parallel.stateObjects.Load(addr); !ok { - s.parallel.stateObjects.Store(addr, stateObject) - } - stateObject.db.parallelStateAccessLock.Unlock() + s.parallel.stateObjects.Store(addr, stateObject) } func (s *ParallelStateDB) getStateObjectNoSlot(addr common.Address) *stateObject { diff --git a/core/state/statedb.go b/core/state/statedb.go index f3661546ec..ef87f1ce95 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -81,10 +81,15 @@ func (s *StateObjectSyncMap) StoreStateObject(addr common.Address, stateObject * // loadStateObj is the entry for loading state object from stateObjects in StateDB or stateObjects in parallel func (s *StateDB) loadStateObj(addr common.Address) (*stateObject, bool) { - if s.isParallel { - s.parallelStateAccessLock.Lock() - defer s.parallelStateAccessLock.Unlock() + if s.parallel.isSlotDB { + if ret, ok := s.parallel.stateObjects.LoadStateObject(addr); ok { + return ret, ok + } else { + ret, ok := s.parallel.baseStateDB.loadStateObj(addr) + return ret, ok + } + } ret, ok := s.parallel.stateObjects.LoadStateObject(addr) return ret, ok } @@ -96,11 +101,7 @@ func (s *StateDB) loadStateObj(addr common.Address) (*stateObject, bool) { // storeStateObj is the entry for storing state object to stateObjects in StateDB or stateObjects in parallel func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { if s.isParallel { - // When a state object is stored into s.parallel.stateObjects, - // it belongs to base StateDB, it is confirmed and valid. - s.parallelStateAccessLock.Lock() s.parallel.stateObjects.StoreStateObject(addr, stateObject) - s.parallelStateAccessLock.Unlock() } else { s.stateObjects[addr] = stateObject } @@ -109,9 +110,7 @@ func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { // deleteStateObj is the entry for deleting state object to stateObjects in StateDB or stateObjects in parallel func (s *StateDB) deleteStateObj(addr common.Address) { if s.isParallel { - s.parallelStateAccessLock.Lock() s.parallel.stateObjects.Delete(addr) - s.parallelStateAccessLock.Unlock() } else { delete(s.stateObjects, addr) } @@ -181,7 +180,6 @@ type StateDB struct { snaps *snapshot.Tree // Nil if snapshot is not available snap snapshot.Snapshot // Nil if snapshot is not available - parallelStateAccessLock sync.RWMutex snapParallelLock sync.RWMutex // for parallel mode, for main StateDB, slot will read snapshot, while processor will write. trieParallelLock sync.Mutex // for parallel mode of trie, mostly for get states/objects from trie, lock required to handle trie tracer. stateObjectDestructLock sync.RWMutex // for parallel mode, used in mainDB for mergeSlot and conflict check. @@ -976,9 +974,7 @@ func (s *StateDB) setStateObject(object *stateObject) { if s.isParallel { // When a state object is stored into s.parallel.stateObjects, // it belongs to base StateDB, it is confirmed and valid. - s.parallelStateAccessLock.Lock() s.parallel.stateObjects.Store(object.address, object) - s.parallelStateAccessLock.Unlock() } else { s.stateObjects[object.Address()] = object } @@ -1439,14 +1435,6 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { }, } - // copy parallel stateObjects - s.parallelStateAccessLock.Lock() - s.parallel.stateObjects.Range(func(addr any, stateObj any) bool { - state.parallel.stateObjects.StoreStateObject(addr.(common.Address), stateObj.(*stateObject).lightCopy(state)) - return true - }) - s.parallelStateAccessLock.Unlock() - state.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) s.snapParallelLock.RLock() for k, v := range s.snapDestructs { From 34012e139ba637c9f2f30dfd51bc60347ab481f2 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:20:20 +0800 Subject: [PATCH 44/72] mvstates: opt async dep generation; (#38) mvstates: opt resolve dep logic; Co-authored-by: galaio --- core/blockchain.go | 8 ++-- core/types/mvstates.go | 90 ++++++++++++++++++++++++++---------------- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index b5673c2061..71bb8d636f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1953,7 +1953,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } - if bc.enableTxDAG && !bc.parallelExecution { + vtime := time.Since(vstart) + proctime := time.Since(start) // processing + validation + + if bc.enableTxDAG && !bc.vmConfig.EnableParallelExec { // compare input TxDAG when it enable in consensus dag, err := statedb.ResolveTxDAG(len(block.Transactions()), []common.Address{block.Coinbase(), params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) if err == nil { @@ -1974,9 +1977,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } } - 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) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index b376c9b109..047b3d913d 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -7,8 +7,6 @@ import ( "strings" "sync" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/holiman/uint256" @@ -33,7 +31,7 @@ const ( ) const ( - asyncDepGenChanSize = 100 + asyncDepGenChanSize = 10000 ) func AccountStateKey(account common.Address, state AccountState) RWKey { @@ -111,8 +109,8 @@ type RWSet struct { func NewRWSet(ver StateVersion) *RWSet { return &RWSet{ ver: ver, - readSet: make(map[RWKey]*RWItem), - writeSet: make(map[RWKey]*RWItem), + readSet: make(map[RWKey]*RWItem, 64), + writeSet: make(map[RWKey]*RWItem, 32), } } @@ -242,7 +240,7 @@ type PendingWrites struct { func NewPendingWrites() *PendingWrites { return &PendingWrites{ - list: make([]*RWItem, 0), + list: make([]*RWItem, 0, 8), } } @@ -309,8 +307,9 @@ type MVStates struct { depsCache map[int][]uint64 // async dep analysis - depsGenChan chan int - stopChan chan struct{} + depsGenChan chan int + stopChan chan struct{} + asyncRunning bool // execution stat infos stats map[int]*ExeStat @@ -328,16 +327,20 @@ func NewMVStates(txCount int) *MVStates { } func (s *MVStates) EnableAsyncDepGen() *MVStates { + s.lock.Lock() + defer s.lock.Unlock() s.depsGenChan = make(chan int, asyncDepGenChanSize) - s.stopChan = make(chan struct{}, 1) + s.stopChan = make(chan struct{}) + s.asyncRunning = true go s.asyncDepGenLoop() return s } func (s *MVStates) stopAsyncDepGen() { if s.stopChan != nil { - s.stopChan <- struct{}{} + close(s.stopChan) } + s.asyncRunning = false } func (s *MVStates) asyncDepGenLoop() { @@ -403,12 +406,12 @@ func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { s.stats[index] = stat } - if metrics.EnabledExpensive { - for k := range rwSet.writeSet { - // this action is only for testing, it runs when enable expensive metrics. - checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet) - } - } + //if metrics.EnabledExpensive { + // for k := range rwSet.writeSet { + // // this action is only for testing, it runs when enable expensive metrics. + // checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet) + // } + //} s.rwSets[index] = rwSet return nil } @@ -417,14 +420,15 @@ func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { func (s *MVStates) Finalise(index int) error { log.Debug("Finalise", "total", len(s.rwSets), "index", index) s.lock.Lock() - defer s.lock.Unlock() rwSet := s.rwSets[index] if rwSet == nil { + s.lock.Unlock() return fmt.Errorf("finalise a non-exist RWSet, index: %d", index) } if index != s.nextFinaliseIndex { + s.lock.Unlock() return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) } @@ -436,34 +440,50 @@ func (s *MVStates) Finalise(index int) error { s.pendingWriteSet[k].Append(v) } s.nextFinaliseIndex++ - // async resolve dependency - if s.depsGenChan != nil { - go func() { - s.depsGenChan <- index - }() + s.lock.Unlock() + // async resolve dependency, but non-block action + if s.asyncRunning && s.depsGenChan != nil { + s.depsGenChan <- index } return nil } func (s *MVStates) resolveDepsCacheByWrites(index int, rwSet *RWSet) { // analysis dep, if the previous transaction is not executed/validated, re-analysis is required - s.depMapCache[index] = NewTxDeps(0) + s.depMapCache[index] = NewTxDeps(8) if rwSet.excludedTx { return } - seen := make(map[int]struct{}) - for key := range rwSet.readSet { - // check self destruct - if key.IsAccountSelf() { - key = AccountStateKey(key.Addr(), AccountSuicide) - } - writes := s.pendingWriteSet[key] - if writes == nil { - continue + seen := make(map[int]struct{}, 8) + // check tx dependency, only check key, skip version + if len(s.pendingWriteSet) > len(rwSet.readSet) { + for key := range rwSet.readSet { + // check self destruct + if key.IsAccountSelf() { + key = AccountStateKey(key.Addr(), AccountSuicide) + } + writes := s.pendingWriteSet[key] + if writes == nil { + continue + } + items := writes.FindPrevWrites(index) + for _, item := range items { + seen[item.TxIndex()] = struct{}{} + } } - items := writes.FindPrevWrites(index) - for _, item := range items { - seen[item.TxIndex()] = struct{}{} + } else { + for k, w := range s.pendingWriteSet { + // check suicide, add read address flag, it only for check suicide quickly, and cannot for other scenarios. + if k.IsAccountSuicide() { + k = k.ToAccountSelf() + } + if _, ok := rwSet.readSet[k]; !ok { + continue + } + items := w.FindPrevWrites(index) + for _, item := range items { + seen[item.TxIndex()] = struct{}{} + } } } for prev := 0; prev < index; prev++ { From 1a40629ff7f0e6aed355d8cfc202a2768ada0546 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:46:21 +0800 Subject: [PATCH 45/72] mvstates: fix oom issue when mining is enabled; (#40) Co-authored-by: galaio --- core/blockchain.go | 2 +- core/state/statedb.go | 3 +++ core/types/mvstates.go | 12 +++++++++++- core/types/mvstates_test.go | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 71bb8d636f..cc43a25ae5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2701,7 +2701,7 @@ func (bc *BlockChain) HeaderChainForceSetHead(headNumber uint64) { } func (bc *BlockChain) TxDAGEnabledWhenMine() bool { - return bc.enableTxDAG && bc.txDAGReader == nil + return bc.enableTxDAG && bc.txDAGWriteCh == nil } func (bc *BlockChain) TxDAGFileOpened() bool { diff --git a/core/state/statedb.go b/core/state/statedb.go index ef87f1ce95..3690eae9fa 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2388,6 +2388,9 @@ func (s *StateDB) ResetMVStates(txCount int) { if s.isParallel && s.parallel.isSlotDB { return } + if s.mvStates != nil { + s.mvStates.Stop() + } s.mvStates = types.NewMVStates(txCount).EnableAsyncDepGen() s.rwSet = nil } diff --git a/core/types/mvstates.go b/core/types/mvstates.go index 047b3d913d..a18623530c 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -336,11 +336,21 @@ func (s *MVStates) EnableAsyncDepGen() *MVStates { return s } +func (s *MVStates) Stop() error { + s.lock.Lock() + defer s.lock.Unlock() + s.stopAsyncDepGen() + return nil +} + func (s *MVStates) stopAsyncDepGen() { + if s.asyncRunning { + return + } + s.asyncRunning = false if s.stopChan != nil { close(s.stopChan) } - s.asyncRunning = false } func (s *MVStates) asyncDepGenLoop() { diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go index 5f6d410cd2..7a0e16db8c 100644 --- a/core/types/mvstates_test.go +++ b/core/types/mvstates_test.go @@ -87,6 +87,8 @@ func TestMVStates_AsyncDepGen_SimpleResolveTxDAG(t *testing.T) { dag, err := ms.ResolveTxDAG(10, nil) require.NoError(t, err) + time.Sleep(100 * time.Millisecond) + require.NoError(t, ms.Stop()) require.Equal(t, mockSimpleDAG(), dag) t.Log(dag) } From f5cc9cab364a6b19d63f57994a9a5640ebdabb0b Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Mon, 19 Aug 2024 08:39:13 +0800 Subject: [PATCH 46/72] reduce overhead of slotDB initialize (#39) * fix: use slotdb pool * fix UT of parallel slotDB --------- Co-authored-by: Sunny --- core/blockchain.go | 1 + core/state/statedb.go | 68 ++++++++++++++++++++++++++++++++++---- core/state/statedb_test.go | 13 +++++++- 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index cc43a25ae5..43d863f96f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1909,6 +1909,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) threshold := min(bc.vmConfig.ParallelTxNum/2+2, 4) if txsCount >= threshold { bc.UseParallelProcessor() + statedb.CreateParallelDBManager(2 * txsCount) log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) } else { bc.UseSerialProcessor() diff --git a/core/state/statedb.go b/core/state/statedb.go index 3690eae9fa..c9469a7960 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,6 +18,7 @@ package state import ( + "container/list" "errors" "fmt" "runtime" @@ -272,8 +273,9 @@ type StateDB struct { AccountDeleted int StorageDeleted int - isParallel bool - parallel ParallelState // to keep all the parallel execution elements + isParallel bool + parallel ParallelState // to keep all the parallel execution elements + parallelDBManager *ParallelDBManager // Testing hooks onCommit func(states *triestate.Set) // Hook invoked when commit is performed } @@ -1374,8 +1376,7 @@ func (s *StateDB) PutSyncPool() { snapStoragePool.Put(s.snapStorage) } -// CopyForSlot copy all the basic fields, initialize the memory ones -func (s *StateDB) CopyForSlot() *ParallelStateDB { +func NewEmptySlotDB() *ParallelStateDB { parallel := ParallelState{ // The stateObjects in Parallel is thread-local. // The base stateDB's stateObjects is thread-unsafe as it is not guarded by lock. @@ -1414,7 +1415,7 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { } state := &ParallelStateDB{ StateDB: StateDB{ - db: s.db, + db: nil, trie: nil, // Parallel StateDB may access the trie, but it takes no effect to the baseDB. accounts: make(map[common.Hash][]byte), storages: make(map[common.Hash]map[common.Hash][]byte), @@ -1427,15 +1428,23 @@ func (s *StateDB) CopyForSlot() *ParallelStateDB { refund: 0, // should be 0 logs: logsPool.Get().(map[common.Hash][]*types.Log), logSize: 0, - preimages: make(map[common.Hash][]byte, len(s.preimages)), + preimages: nil, journal: journalPool.Get().(*journal), hasher: crypto.NewKeccakState(), isParallel: true, parallel: parallel, }, } - state.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) + return state +} + +// CopyForSlot copy all the basic fields, initialize the memory ones +func (s *StateDB) CopyForSlot() *ParallelStateDB { + state := s.parallelDBManager.allocate() + state.db = s.db + s.preimages = make(map[common.Hash][]byte, len(s.preimages)) + s.snapParallelLock.RLock() for k, v := range s.snapDestructs { state.snapDestructs[k] = v @@ -2792,3 +2801,48 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip s.SetTxContext(slotDb.thash, slotDb.txIndex) return s } + +func (s *StateDB) CreateParallelDBManager(txCount int) { + // if enableDAG, it is high likely no conflict and hence no re-execution + // allocate the txCount of slotDBs to use. + if s.parallelDBManager == nil { + s.parallelDBManager = NewParallelDBManager(txCount, NewEmptySlotDB) + } +} + +// ParallelDBManager manages a pool of ParallelDB instances +type ParallelDBManager struct { + pool *list.List + mutex sync.Mutex + newFunc func() *ParallelStateDB // Function to create a new ParallelDB instance +} + +// NewParallelDBManager creates a new ParallelDBManager with the specified number of instance +func NewParallelDBManager(initialCount int, newFunc func() *ParallelStateDB) *ParallelDBManager { + manager := &ParallelDBManager{ + pool: list.New(), + mutex: sync.Mutex{}, + newFunc: newFunc, + } + + for i := 0; i < initialCount; i++ { + manager.pool.PushBack(newFunc()) + } + + return manager +} + +// allocate acquires a ParallelStateDB instance from the pool +// if the pool is empty, directly create a new one. +func (m *ParallelDBManager) allocate() *ParallelStateDB { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.pool.Len() == 0 { + return m.newFunc() + } + + elem := m.pool.Front() + m.pool.Remove(elem) + return elem.Value.(*ParallelStateDB) +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index d98a81143b..7de877b6b1 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1205,6 +1205,7 @@ func TestSuicide(t *testing.T) { unconfirmedDBs := new(sync.Map) state.PrepareForParallel() + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) addr := common.BytesToAddress([]byte("so")) @@ -1239,6 +1240,7 @@ func TestSetAndGetState(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) unconfirmedDBs := new(sync.Map) state.PrepareForParallel() + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetState(addr, common.BytesToHash([]byte("test key")), common.BytesToHash([]byte("test store"))) @@ -1276,6 +1278,7 @@ func TestSetAndGetCode(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; ok { t.Fatalf("address should not exist in dirtiedStateObjectsInSlot") @@ -1311,6 +1314,7 @@ func TestGetCodeSize(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetCode(addr, []byte("test code")) @@ -1333,6 +1337,7 @@ func TestGetCodeHash(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetCode(addr, []byte("test code")) @@ -1358,6 +1363,7 @@ func TestSetNonce(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetNonce(addr, 2) @@ -1384,6 +1390,7 @@ func TestSetAndGetBalance(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SetBalance(addr, big.NewInt(2)) @@ -1420,6 +1427,7 @@ func TestSubBalance(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.SubBalance(addr, big.NewInt(1)) @@ -1454,6 +1462,7 @@ func TestAddBalance(t *testing.T) { state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) slotDb.AddBalance(addr, big.NewInt(1)) @@ -1489,6 +1498,7 @@ func TestEmpty(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) empty := slotDb.Empty(addr) @@ -1509,6 +1519,7 @@ func TestExist(t *testing.T) { state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) + state.CreateParallelDBManager(1) slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) exist := slotDb.Exist(addr) @@ -1527,7 +1538,7 @@ func TestMergeSlotDB(t *testing.T) { state, _ := New(common.Hash{}, db, nil) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - + state.CreateParallelDBManager(1) oldSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) newSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) From fb8203edbcefd73b05ace25d8b750ea8f1556664 Mon Sep 17 00:00:00 2001 From: galaio Date: Fri, 16 Aug 2024 21:48:03 +0800 Subject: [PATCH 47/72] mvstates: fix oom issue when mining is enabled; --- core/types/mvstates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/mvstates.go b/core/types/mvstates.go index a18623530c..d0cafa2124 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -344,7 +344,7 @@ func (s *MVStates) Stop() error { } func (s *MVStates) stopAsyncDepGen() { - if s.asyncRunning { + if !s.asyncRunning { return } s.asyncRunning = false From 9c9bdcacd8b077e149709cf8fa9ee7eb2ad88123 Mon Sep 17 00:00:00 2001 From: DavidZang <110075234+DavidZangNR@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:08:58 +0800 Subject: [PATCH 48/72] Fix: contention issue of Trie for PEVM (#41) Co-authored-by: Sunny --- core/state/state_object.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 010a9abe71..d2f988c6c5 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -411,14 +411,14 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // If the snapshot is unavailable or reading from it fails, load from the database. if s.db.snap == nil || err != nil { start := time.Now() + s.db.trieParallelLock.Lock() + defer s.db.trieParallelLock.Unlock() tr, err := s.getTrie() if err != nil { s.db.setError(err) return common.Hash{} } - s.db.trieParallelLock.Lock() val, err := tr.GetStorage(s.address, key.Bytes()) - s.db.trieParallelLock.Unlock() if metrics.EnabledExpensive { s.db.StorageReads += time.Since(start) } @@ -977,14 +977,14 @@ func (s *stateObject) GetCommittedStateNoUpdate(key common.Hash) common.Hash { // If the snapshot is unavailable or reading from it fails, load from the database. if s.db.snap == nil || err != nil { start := time.Now() + s.db.trieParallelLock.Lock() + defer s.db.trieParallelLock.Unlock() tr, err := s.getTrie() if err != nil { s.db.setError(err) return common.Hash{} } - s.db.trieParallelLock.Lock() val, err := tr.GetStorage(s.address, key.Bytes()) - s.db.trieParallelLock.Unlock() if metrics.EnabledExpensive { s.db.StorageReads += time.Since(start) } From c96af03b7bea093b38bb643a7d25a4fc8e94aba7 Mon Sep 17 00:00:00 2001 From: galaio <12880651+galaio@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:33:53 +0800 Subject: [PATCH 49/72] worker: fix TxDAG generation issues when mining block; (#43) * blockchain: avoid enable txdag generation when pevm is enabled; mvstates: add timeout timer for async loop; worker: change append TxDAG position; worker: fix append TxDAG missing issue; * blockchain: opt mining txdag generation logic; --------- Co-authored-by: galaio --- core/blockchain.go | 6 +--- core/state_processor.go | 2 +- core/types/mvstates.go | 5 +++ miner/worker.go | 76 +++++++++++++++-------------------------- 4 files changed, 35 insertions(+), 54 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 43d863f96f..49f634edc4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2702,11 +2702,7 @@ func (bc *BlockChain) HeaderChainForceSetHead(headNumber uint64) { } func (bc *BlockChain) TxDAGEnabledWhenMine() bool { - return bc.enableTxDAG && bc.txDAGWriteCh == nil -} - -func (bc *BlockChain) TxDAGFileOpened() bool { - return bc.txDAGWriteCh != nil + return bc.enableTxDAG && bc.txDAGWriteCh == nil && bc.txDAGReader == nil } func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { diff --git a/core/state_processor.go b/core/state_processor.go index 85a26ffa9a..df9d788707 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -98,7 +98,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } statedb.MarkFullProcessed() - if p.bc.enableTxDAG { + if p.bc.enableTxDAG && !p.bc.vmConfig.EnableParallelExec { statedb.ResetMVStates(len(block.Transactions())) } // Iterate over and process the individual transactions diff --git a/core/types/mvstates.go b/core/types/mvstates.go index d0cafa2124..4637b71d2f 100644 --- a/core/types/mvstates.go +++ b/core/types/mvstates.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" "sync" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -354,6 +355,7 @@ func (s *MVStates) stopAsyncDepGen() { } func (s *MVStates) asyncDepGenLoop() { + timeout := time.After(3 * time.Second) for { select { case tx := <-s.depsGenChan: @@ -362,6 +364,9 @@ func (s *MVStates) asyncDepGenLoop() { s.lock.Unlock() case <-s.stopChan: return + case <-timeout: + log.Warn("asyncDepGenLoop exit by timeout") + return } } } diff --git a/miner/worker.go b/miner/worker.go index 53939dbb7e..c9a8179aa3 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -912,36 +912,10 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac } var coalescedLogs []*types.Log - //append the tx DAG transaction to the block - appendTxDAG := func() { - // whether enable TxDAG - if !w.chain.TxDAGEnabledWhenMine() { - return - } - // whether export to file - if w.chain.TxDAGFileOpened() { - return - } - // TODO this is a placeholder for the tx DAG data that will be generated by the stateDB - txForDAG, err := w.generateDAGTx(env.signer, env.tcount, env.coinbase) - if err != nil { - log.Warn("failed to generate DAG tx", "err", err) - return - } - logs, err := w.commitTransaction(env, txForDAG) - if err != nil { - log.Warn("failed to commit DAG tx", "err", err) - return - } - coalescedLogs = append(coalescedLogs, logs...) - env.tcount++ - } - for { // Check interruption signal and abort building if it's fired. if interrupt != nil { if signal := interrupt.Load(); signal != commitInterruptNone { - appendTxDAG() return signalToErr(signal) } } @@ -1040,7 +1014,6 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac txErrUnknownMeter.Mark(1) } } - appendTxDAG() if !w.isRunning() && len(coalescedLogs) > 0 { // We don't push the pendingLogsEvent while we are sealing. The reason is that // when we are sealing, the worker will regenerate a sealing block every 3 seconds. @@ -1060,56 +1033,57 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac } // generateDAGTx generates a DAG transaction for the block -func (w *worker) generateDAGTx(signer types.Signer, txIndex int, coinbase common.Address) (*types.Transaction, error) { - statedb, err := w.chain.State() - if err != nil { - return nil, fmt.Errorf("failed to get state db, err: %v", err) +func (w *worker) generateDAGTx(env *environment) error { + // get txDAG data from the stateDB + txDAG, err := env.state.ResolveTxDAG(env.tcount, []common.Address{env.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + if txDAG == nil || err != nil { + return err } + // txIndex is the index of this txDAG transaction + txDAG.SetTxDep(env.tcount, types.TxDep{Flags: &types.NonDependentRelFlag}) - if signer == nil { - return nil, fmt.Errorf("current signer is nil") + if env.signer == nil { + return fmt.Errorf("current signer is nil") } //privateKey, err := crypto.HexToECDSA(privateKeyHex) sender := w.config.ParallelTxDAGSenderPriv receiver := DefaultTxDAGAddress if sender == nil { - return nil, fmt.Errorf("missing sender private key") - } - - // get txDAG data from the stateDB - txDAG, err := statedb.ResolveTxDAG(txIndex, []common.Address{coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) - if txDAG == nil { - return nil, err + return fmt.Errorf("missing sender private key") } - // txIndex is the index of this txDAG transaction - txDAG.SetTxDep(txIndex, types.TxDep{Flags: &types.NonDependentRelFlag}) publicKey := sender.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { - return nil, fmt.Errorf("error casting public key to ECDSA") + return fmt.Errorf("error casting public key to ECDSA") } fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) // get nonce from the - nonce := statedb.GetNonce(fromAddress) + nonce := env.state.GetNonce(fromAddress) data, err := types.EncodeTxDAGCalldata(txDAG) if err != nil { - return nil, fmt.Errorf("failed to encode txDAG, err: %v", err) + return fmt.Errorf("failed to encode txDAG, err: %v", err) } // Create the transaction tx := types.NewTransaction(nonce, receiver, big.NewInt(0), 21100, big.NewInt(0), data) // Sign the transaction with the private key - signedTx, err := types.SignTx(tx, signer, sender) + signedTx, err := types.SignTx(tx, env.signer, sender) if err != nil { - return nil, fmt.Errorf("failed to sign transaction, err: %v", err) + return fmt.Errorf("failed to sign transaction, err: %v", err) } - return signedTx, nil + _, err = w.commitTransaction(env, signedTx) + if err != nil { + log.Warn("failed to commit DAG tx", "err", err) + return err + } + env.tcount++ + return nil } // generateParams wraps various of settings for generating sealing task. @@ -1421,6 +1395,12 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { if intr := genParams.interrupt; intr != nil && genParams.isUpdate && intr.Load() != commitInterruptNone { return &newPayloadResult{err: errInterruptedUpdate} } + //append the tx DAG transaction to the block + if w.chain.TxDAGEnabledWhenMine() { + if err := w.generateDAGTx(work); err != nil { + log.Warn("failed to generate DAG tx", "err", err) + } + } start = time.Now() block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, nil, work.receipts, genParams.withdrawals) From 87987ccfa7823c211357263d5ea0b1fd895fe1d6 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 21 Aug 2024 18:05:41 +0800 Subject: [PATCH 50/72] pevm-opt: Enable parallel kv conflict check --- core/state/parallel_statedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 785d4b1e82..848f15c932 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1385,7 +1385,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { }) } readLen := len(units) - if readLen < 80000 || isStage2 { + if readLen < 8 || isStage2 { for _, unit := range units { if hasKvConflict(slotDB, unit.addr, unit.key, unit.val, isStage2) { return false From 061cd9b9de629ae0ad5366ae78e4517a49a9fea9 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 21 Aug 2024 23:31:10 +0800 Subject: [PATCH 51/72] pevm-opt: Add conflict check cache --- core/state/parallel_statedb.go | 56 ++++++++++++++++++++++++++++++---- core/state/statedb.go | 6 ++-- miner/worker.go | 4 --- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 848f15c932..bc72640347 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -96,7 +96,7 @@ func hasKvConflict(slotDB *ParallelStateDB, addr common.Address, key common.Hash } } } - valMain := mainDB.GetStateNoUpdate(addr, key) + valMain := slotDB.getStateFromMainNoUpdate(addr, key) // mainDB.GetStateNoUpdate(addr, key) if !bytes.Equal(val.Bytes(), valMain.Bytes()) { log.Debug("hasKvConflict is invalid", "addr", addr, @@ -1309,6 +1309,45 @@ func (s *ParallelStateDB) getStateObjectFromUnconfirmedDB(addr common.Address) ( return nil, false } +func (s *ParallelStateDB) getStateObjectFromMainDBNoUpdate(addr common.Address) *stateObject { + var mainObj *stateObject + + if m, ok := s.parallel.conflictCheckStateObjectCache.Load(addr); ok { + mainObj = m.(*stateObject) + return mainObj + } else { + mainDB := s.parallel.baseStateDB + mainObj = mainDB.getStateObjectNoUpdate(addr) + s.parallel.conflictCheckStateObjectCache.Store(addr, mainObj) + } + return mainObj +} + +// GetStateNoUpdate retrieves a value from the given account's storage trie, but do not update the db.stateObjects cache. +func (s *ParallelStateDB) getStateFromMainNoUpdate(addr common.Address, key common.Hash) (ret common.Hash) { + + if kvPair, ok := s.parallel.conflictCheckKVReadCache.Load(addr); !ok { + s.parallel.conflictCheckKVReadCache.Store(addr, newStorage(true)) + } else { + st := kvPair.(*StorageSyncMap) + if val, ok := st.GetValue(key); ok { + return val + } + } + val := common.Hash{} + object := s.getStateObjectFromMainDBNoUpdate(addr) + if object != nil { + val = object.GetStateNoUpdate(key) + } + if kvPair, ok := s.parallel.conflictCheckKVReadCache.Load(addr); ok { + st := kvPair.(*StorageSyncMap) + st.StoreValue(key, val) + s.parallel.conflictCheckKVReadCache.Store(addr, st) + } + + return val +} + // IsParallelReadsValid If stage2 is true, it is a likely conflict check, // to detect these potential conflict results in advance and schedule redo ASAP. func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { @@ -1317,6 +1356,10 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { }) mainDB := slotDB.parallel.baseStateDB + // conservatively use kvRead size as the initial size. + slotDB.parallel.conflictCheckStateObjectCache = new(sync.Map) + slotDB.parallel.conflictCheckKVReadCache = new(sync.Map) + // for nonce for addr, nonceSlot := range slotDB.parallel.nonceReadsInSlot { if isStage2 { // update slotDB's unconfirmed DB list and try @@ -1334,7 +1377,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } } var nonceMain uint64 = 0 - mainObj := mainDB.getStateObjectNoUpdate(addr) + mainObj := slotDB.getStateObjectFromMainDBNoUpdate(addr) if mainObj != nil { nonceMain = mainObj.Nonce() } @@ -1363,7 +1406,8 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { } balanceMain := common.U2560 - mainObj := mainDB.getStateObjectNoUpdate(addr) + mainObj := slotDB.getStateObjectFromMainDBNoUpdate(addr) + if mainObj != nil { balanceMain = mainObj.Balance() } @@ -1455,7 +1499,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { // check code for addr, codeSlot := range slotDB.parallel.codeReadsInSlot { var codeMain []byte = nil - object := mainDB.getStateObjectNoUpdate(addr) + object := slotDB.getStateObjectFromMainDBNoUpdate(addr) if object != nil { codeMain = object.Code() } @@ -1470,7 +1514,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { // check codeHash for addr, codeHashSlot := range slotDB.parallel.codeHashReadsInSlot { codeHashMain := common.Hash{} - object := mainDB.getStateObjectNoUpdate(addr) + object := slotDB.getStateObjectFromMainDBNoUpdate(addr) if object != nil { codeHashMain = common.BytesToHash(object.CodeHash()) } @@ -1484,7 +1528,7 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { // addr state check for addr, stateSlot := range slotDB.parallel.addrStateReadsInSlot { stateMain := false // addr not exist - if mainDB.getStateObjectNoUpdate(addr) != nil { + if slotDB.getStateObjectFromMainDBNoUpdate(addr) != nil { stateMain = true // addr exist in main DB } if stateSlot != stateMain { diff --git a/core/state/statedb.go b/core/state/statedb.go index c9469a7960..7f528f9df1 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -157,8 +157,10 @@ type ParallelState struct { storagesOriginDeleteRecord []common.Address createdObjectRecord map[common.Address]struct{} // we may need to redo for some specific reasons, like we read the wrong state and need to panic in sequential mode in SubRefund - needsRedo bool - useDAG bool + needsRedo bool + useDAG bool + conflictCheckStateObjectCache *sync.Map + conflictCheckKVReadCache *sync.Map } // StateDB structs within the ethereum protocol are used to store anything diff --git a/miner/worker.go b/miner/worker.go index c9a8179aa3..0b5e807b16 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1257,10 +1257,6 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err filter.OnlyPlainTxs, filter.OnlyBlobTxs = false, true pendingBlobTxs := w.eth.TxPool().Pending(filter) - if w.chain.TxDAGEnabled() { - env.state.ResetMVStates(0) - } - packFromTxpoolTimer.UpdateSince(start) log.Debug("packFromTxpoolTimer", "duration", common.PrettyDuration(time.Since(start)), "hash", env.header.Hash()) From 2768aea7d028a51568e7bb06bd4f421716842b30 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 22 Aug 2024 11:21:40 +0800 Subject: [PATCH 52/72] PEVM-fix: avoid checkout old tx in stage2 conflict check --- core/state/parallel_statedb.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index bc72640347..bc5df06d4c 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1360,6 +1360,11 @@ func (slotDB *ParallelStateDB) IsParallelReadsValid(isStage2 bool) bool { slotDB.parallel.conflictCheckStateObjectCache = new(sync.Map) slotDB.parallel.conflictCheckKVReadCache = new(sync.Map) + if isStage2 && slotDB.txIndex < mainDB.TxIndex() { + // already merged, no need to check + return true + } + // for nonce for addr, nonceSlot := range slotDB.parallel.nonceReadsInSlot { if isStage2 { // update slotDB's unconfirmed DB list and try From 857157162d6016e4ee696ee0b7de3049e7a0b84e Mon Sep 17 00:00:00 2001 From: galaio Date: Fri, 23 Aug 2024 11:18:12 +0800 Subject: [PATCH 53/72] pevm: add a side slot to trigger next tx advance; --- core/parallel_state_processor.go | 101 ++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 1c0b4c135f..8bedca8b6a 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -113,13 +113,14 @@ type ParallelTxRequest struct { func (p *ParallelStateProcessor) init() { log.Info("Parallel execution mode is enabled", "Parallel Num", p.parallelNum, "CPUNum", runtime.NumCPU()) - p.txResultChan = make(chan *ParallelTxResult, 200) + p.txResultChan = make(chan *ParallelTxResult, 20000) p.stopSlotChan = make(chan struct{}, 1) p.stopConfirmChan = make(chan struct{}, 1) p.stopConfirmStage2Chan = make(chan struct{}, 1) p.slotState = make([]*SlotState, p.parallelNum) - for i := 0; i < p.parallelNum; i++ { + quickMergeNum := p.parallelNum / 2 + for i := 0; i < p.parallelNum-quickMergeNum; i++ { p.slotState[i] = &SlotState{ primaryWakeUpChan: make(chan struct{}, 1), shadowWakeUpChan: make(chan struct{}, 1), @@ -137,7 +138,22 @@ func (p *ParallelStateProcessor) init() { go func(slotIndex int) { p.runSlotLoop(slotIndex, parallelShadowSlot) }(i) + } + for i := p.parallelNum - quickMergeNum; i < p.parallelNum; i++ { + // init a quick merge slot + p.slotState[i] = &SlotState{ + primaryWakeUpChan: make(chan struct{}, 1), + shadowWakeUpChan: make(chan struct{}, 1), + primaryStopChan: make(chan struct{}, 1), + shadowStopChan: make(chan struct{}, 1), + } + go func(slotIndex int) { + p.runQuickMergeSlotLoop(slotIndex, parallelPrimarySlot) + }(i) + go func(slotIndex int) { + p.runQuickMergeSlotLoop(slotIndex, parallelShadowSlot) + }(i) } p.confirmStage2Chan = make(chan int, 10) @@ -456,9 +472,8 @@ func (p *ParallelStateProcessor) toConfirmTxIndexResult(txResult *ParallelTxResu } // ok, time to do finalize, stage2 should not be parallel - header := txReq.block.Header() txResult.receipt, txResult.err = applyTransactionStageFinalization(txResult.evm, txResult.result, - *txReq.msg, p.config, txResult.slotDB, header, + *txReq.msg, p.config, txResult.slotDB, txReq.block, txReq.tx, txReq.usedGas, txResult.originalNonce) return true } @@ -530,6 +545,49 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { } } +func (p *ParallelStateProcessor) runQuickMergeSlotLoop(slotIndex int, slotType int32) { + curSlot := p.slotState[slotIndex] + var wakeupChan chan struct{} + var stopChan chan struct{} + + if slotType == parallelPrimarySlot { + wakeupChan = curSlot.primaryWakeUpChan + stopChan = curSlot.primaryStopChan + } else { + wakeupChan = curSlot.shadowWakeUpChan + stopChan = curSlot.shadowStopChan + } + for { + select { + case <-stopChan: + p.stopSlotChan <- struct{}{} + continue + case <-wakeupChan: + } + + next := int(p.mergedTxIndex.Load()) + 1 + for i := next; i < len(p.allTxReqs); i++ { + txReq := p.allTxReqs[next] + if txReq.txIndex <= int(p.mergedTxIndex.Load()) { + continue + } + + if txReq.txIndex != next { + log.Warn("query next txReq wrong", "slot", slotIndex, "next", next, "actual", txReq.txIndex) + break + } + if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { + continue + } + res := p.executeInSlot(slotIndex, txReq) + if res != nil { + p.txResultChan <- res + } + break + } + } +} + func (p *ParallelStateProcessor) runConfirmStage2Loop() { for { select { @@ -646,6 +704,18 @@ func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *Ga default: } } + // schedule prefetch once only when unconfirmedResult is valid + if result.err == nil { + if _, ok := p.txReqExecuteRecord[resultTxIndex]; !ok { + p.txReqExecuteRecord[resultTxIndex] = 0 + p.txReqExecuteCount++ + statedb.AddrPrefetch(result.slotDB) + if !p.inConfirmStage2 && p.txReqExecuteCount == p.targetStage2Count { + p.inConfirmStage2 = true + } + } + p.txReqExecuteRecord[resultTxIndex]++ + } return result } @@ -809,19 +879,6 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat } p.pendingConfirmResults[unconfirmedTxIndex] = append(p.pendingConfirmResults[unconfirmedTxIndex], unconfirmedResult) - // schedule prefetch once only when unconfirmedResult is valid - if unconfirmedResult.err == nil { - if _, ok := p.txReqExecuteRecord[unconfirmedTxIndex]; !ok { - p.txReqExecuteRecord[unconfirmedTxIndex] = 0 - p.txReqExecuteCount++ - statedb.AddrPrefetch(unconfirmedResult.slotDB) - if !p.inConfirmStage2 && p.txReqExecuteCount == p.targetStage2Count { - p.inConfirmStage2 = true - } - } - p.txReqExecuteRecord[unconfirmedTxIndex]++ - } - for { result := p.confirmTxResults(statedb, gp) if result == nil { @@ -894,9 +951,7 @@ func applyTransactionStageExecution(msg *Message, gp *GasPool, statedb *state.Pa return evm, result, err } -func applyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg Message, - config *params.ChainConfig, statedb *state.ParallelStateDB, header *types.Header, - tx *types.Transaction, usedGas *uint64, nonce *uint64) (*types.Receipt, error) { +func applyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg Message, config *params.ChainConfig, statedb *state.ParallelStateDB, block *types.Block, tx *types.Transaction, usedGas *uint64, nonce *uint64) (*types.Receipt, error) { *usedGas += result.UsedGas // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx. @@ -929,10 +984,10 @@ func applyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, *nonce) } // Set the receipt logs and create the bloom filter. - receipt.Logs = statedb.GetLogs(tx.Hash(), header.Number.Uint64(), header.Hash()) + receipt.Logs = statedb.GetLogs(tx.Hash(), block.NumberU64(), block.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) - receipt.BlockHash = header.Hash() - receipt.BlockNumber = header.Number + receipt.BlockHash = block.Hash() + receipt.BlockNumber = block.Number() receipt.TransactionIndex = uint(statedb.TxIndex()) return receipt, nil } From fb2630ce58f436a6e034ad9fd348e7ac9abfd4b7 Mon Sep 17 00:00:00 2001 From: galaio Date: Tue, 27 Aug 2024 10:48:26 +0800 Subject: [PATCH 54/72] pevm: fix some bad check & support to fallback to serial processor; --- core/blockchain.go | 4 ++++ core/parallel_state_processor.go | 14 ++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 49f634edc4..6c42e04f86 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1939,6 +1939,10 @@ 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 == FallbackToSerialProcessorErr { + bc.UseSerialProcessor() + receipts, logs, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig) + } if err != nil { bc.reportBlock(block, receipts, err) followupInterrupt.Store(true) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 8bedca8b6a..62c00b5aa8 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -26,6 +26,10 @@ const ( stage2AheadNum = 3 // enter ConfirmStage2 in advance to avoid waiting for Fat Tx ) +var ( + FallbackToSerialProcessorErr = errors.New("fallback to serial processor") +) + type ParallelStateProcessor struct { StateProcessor parallelNum int // leave a CPU to dispatcher @@ -571,11 +575,6 @@ func (p *ParallelStateProcessor) runQuickMergeSlotLoop(slotIndex int, slotType i if txReq.txIndex <= int(p.mergedTxIndex.Load()) { continue } - - if txReq.txIndex != next { - log.Warn("query next txReq wrong", "slot", slotIndex, "next", next, "actual", txReq.txIndex) - break - } if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { continue } @@ -782,7 +781,6 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat if p.bc.txDAGReader != nil { // load cache txDAG from file first txDAG = p.bc.txDAGReader.TxDAG(block.NumberU64()) - } else { // load TxDAG from block txDAG, err = types.GetTxDAG(block) @@ -797,6 +795,10 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat } } + if txDAG != nil && txDAG.Type() == types.EmptyTxDAGType { + return nil, nil, 0, FallbackToSerialProcessorErr + } + txNum := len(allTxs) latestExcludedTx := -1 // Iterate over and process the individual transactions From c97b6c11843aaa6d8179bfca1505a981ee3c9a18 Mon Sep 17 00:00:00 2001 From: galaio Date: Tue, 27 Aug 2024 12:23:56 +0800 Subject: [PATCH 55/72] pevm: fix some bad check & support to fallback to serial processor; --- core/blockchain.go | 42 +++++++++++++++++++++++++++++--- core/parallel_state_processor.go | 27 +------------------- core/vm/interpreter.go | 2 ++ 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 6c42e04f86..505d50f69c 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1905,15 +1905,16 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) activeState = statedb if bc.vmConfig.EnableParallelExec { + bc.parseTxDAG(block) txsCount := block.Transactions().Len() threshold := min(bc.vmConfig.ParallelTxNum/2+2, 4) - if txsCount >= threshold { + if txsCount < threshold || bc.isEmptyTxDAG() { + bc.UseSerialProcessor() + log.Debug("Disable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) + } else { bc.UseParallelProcessor() statedb.CreateParallelDBManager(2 * txsCount) log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) - } else { - bc.UseSerialProcessor() - log.Debug("Disable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) } } // If we have a followup block, run that against the current state to pre-cache @@ -2093,6 +2094,39 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } +func (bc *BlockChain) parseTxDAG(block *types.Block) { + if !bc.enableTxDAG { + return + } + var ( + txDAG types.TxDAG + err error + ) + if bc.txDAGReader != nil { + // load cache txDAG from file first + txDAG = bc.txDAGReader.TxDAG(block.NumberU64()) + } else { + // load TxDAG from block + txDAG, err = types.GetTxDAG(block) + if err != nil { + log.Warn("pevm decode txdag failed", "block", block.NumberU64(), "err", err) + } + } + if err := types.ValidateTxDAG(txDAG, len(block.Transactions())); err != nil { + log.Warn("pevm cannot apply wrong txdag", + "block", block.NumberU64(), "txs", len(block.Transactions()), "err", err) + txDAG = nil + } + bc.vmConfig.TxDAG = txDAG +} + +func (bc *BlockChain) isEmptyTxDAG() bool { + if bc.vmConfig.TxDAG != nil && bc.vmConfig.TxDAG.Type() == types.EmptyTxDAGType { + return true + } + return false +} + // insertSideChain is called when an import batch hits upon a pruned ancestor // error, which happens when a sidechain with a sufficiently old fork-block is // found. diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 62c00b5aa8..c73948d32c 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -772,32 +772,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } statedb.MarkFullProcessed() - - var ( - txDAG types.TxDAG - ) - if p.bc.enableTxDAG { - var err error - if p.bc.txDAGReader != nil { - // load cache txDAG from file first - txDAG = p.bc.txDAGReader.TxDAG(block.NumberU64()) - } else { - // load TxDAG from block - txDAG, err = types.GetTxDAG(block) - if err != nil { - log.Debug("pevm decode txdag failed", "block", block.NumberU64(), "err", err) - } - } - if err := types.ValidateTxDAG(txDAG, len(block.Transactions())); err != nil { - log.Warn("pevm cannot apply wrong txdag", - "block", block.NumberU64(), "txs", len(block.Transactions()), "err", err) - txDAG = nil - } - } - - if txDAG != nil && txDAG.Type() == types.EmptyTxDAGType { - return nil, nil, 0, FallbackToSerialProcessorErr - } + txDAG := cfg.TxDAG txNum := len(allTxs) latestExcludedTx := -1 diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 99ea582abb..2278b81710 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -19,6 +19,7 @@ package vm import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -37,6 +38,7 @@ type Config struct { ParallelTxNum int // Number of slot for transaction execution OptimismPrecompileOverrides PrecompileOverrides // Precompile overrides for Optimism EnableOpcodeOptimizations bool // Enable opcode optimization + TxDAG types.TxDAG } // ScopeContext contains the things that are per-call, such as stack and memory, From 94d3658bf39842c170c083d0d40d275eb8c0898b Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 28 Aug 2024 21:43:19 +0800 Subject: [PATCH 56/72] async the merge phase --- cmd/utils/flags.go | 5 +- core/parallel_state_processor.go | 237 ++++++++++++++++++++++++------- 2 files changed, 189 insertions(+), 53 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 53b3366f68..1e050e1e13 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2031,7 +2031,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } else if numCpu == 1 { parallelNum = 1 // single CPU core } else { - parallelNum = numCpu - 1 + // 1-2 core for merge (with parallel KV check) + // 1-2 core for others (bc optimizer, main) + // 1-2 core for possible other concurrent routine + parallelNum = max(1, numCpu-6) } cfg.ParallelTxNum = parallelNum } diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index c73948d32c..23a3f9b7e2 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -30,16 +30,22 @@ var ( FallbackToSerialProcessorErr = errors.New("fallback to serial processor") ) +type ResultHandleEnv struct { + statedb *state.StateDB + gp *GasPool + txCount int +} + type ParallelStateProcessor struct { StateProcessor parallelNum int // leave a CPU to dispatcher slotState []*SlotState // idle, or pending messages allTxReqs []*ParallelTxRequest - txResultChan chan *ParallelTxResult // to notify dispatcher that a tx is done - mergedTxIndex atomic.Int32 // the latest finalized tx index - pendingConfirmResults map[int][]*ParallelTxResult // tx could be executed several times, with several result to check - unconfirmedResults *sync.Map // for stage2 confirm, since pendingConfirmResults can not be accessed in stage2 loop - unconfirmedDBs *sync.Map // intermediate store of slotDB that is not verified + txResultChan chan *ParallelTxResult // to notify dispatcher that a tx is done + mergedTxIndex atomic.Int32 // the latest finalized tx index + pendingConfirmResults *sync.Map // tx could be executed several times, with several result to check + unconfirmedResults *sync.Map // for stage2 confirm, since pendingConfirmResults can not be accessed in stage2 loop + unconfirmedDBs *sync.Map // intermediate store of slotDB that is not verified slotDBsToRelease []*state.ParallelStateDB stopSlotChan chan struct{} stopConfirmChan chan struct{} @@ -53,6 +59,13 @@ type ParallelStateProcessor struct { targetStage2Count int nextStage2TxIndex int delayGasFee bool + + commonTxs []*types.Transaction + receipts types.Receipts + error error + resultMutex sync.RWMutex + resultProcessChan chan *ResultHandleEnv + resultAppendChan chan struct{} } func newParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine, parallelNum int) *ParallelStateProcessor { @@ -122,8 +135,11 @@ func (p *ParallelStateProcessor) init() { p.stopConfirmChan = make(chan struct{}, 1) p.stopConfirmStage2Chan = make(chan struct{}, 1) + p.resultProcessChan = make(chan *ResultHandleEnv, 1) + p.resultAppendChan = make(chan struct{}, 20000) + p.slotState = make([]*SlotState, p.parallelNum) - quickMergeNum := p.parallelNum / 2 + quickMergeNum := 2 // p.parallelNum / 2 for i := 0; i < p.parallelNum-quickMergeNum; i++ { p.slotState[i] = &SlotState{ primaryWakeUpChan: make(chan struct{}, 1), @@ -164,6 +180,10 @@ func (p *ParallelStateProcessor) init() { go func() { p.runConfirmStage2Loop() }() + + go func() { + p.handlePendingResultLoop() + }() } // resetState clear slot state for each block. @@ -176,7 +196,7 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { p.inConfirmStage2 = false statedb.PrepareForParallel() - p.allTxReqs = make([]*ParallelTxRequest, 0) + p.allTxReqs = make([]*ParallelTxRequest, 0, txNum) p.slotDBsToRelease = make([]*state.ParallelStateDB, 0, txNum) stateDBsToRelease := p.slotDBsToRelease @@ -191,8 +211,8 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { } p.unconfirmedResults = new(sync.Map) p.unconfirmedDBs = new(sync.Map) - p.pendingConfirmResults = make(map[int][]*ParallelTxResult, 200) - p.txReqExecuteRecord = make(map[int]int, 200) + p.pendingConfirmResults = new(sync.Map) + p.txReqExecuteRecord = make(map[int]int, txNum) p.txReqExecuteCount = 0 p.nextStage2TxIndex = 0 } @@ -392,14 +412,11 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo } } else { // pop one result as target result. - results := p.pendingConfirmResults[targetTxIndex] - resultsLen := len(results) - if resultsLen == 0 { // there is no pending result can be verified, break and wait for incoming results + result, ok := p.pendingConfirmResults.LoadAndDelete(targetTxIndex) + if !ok { return nil } - targetResult = results[len(results)-1] - // last is the freshest, stack based priority - p.pendingConfirmResults[targetTxIndex] = p.pendingConfirmResults[targetTxIndex][:resultsLen-1] // remove from the queue + targetResult = result.(*ParallelTxResult) } valid := p.toConfirmTxIndexResult(targetResult, isStage2) @@ -420,9 +437,8 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo return nil } - if len(p.pendingConfirmResults[targetTxIndex]) == 0 { // this is the last result to check, and it is not valid + if _, ok := p.pendingConfirmResults.Load(targetTxIndex); !ok { // this is the last result to check, and it is not valid // This means that the tx has been executed more than blockTxCount times, so it exits with the error. - // TODO-dav: p.mergedTxIndex+2 may be more reasonable? - this is buggy for expected exit if targetResult.txReq.txIndex == int(p.mergedTxIndex.Load())+1 && targetResult.slotDB.BaseTxIndex() == int(p.mergedTxIndex.Load()) { if targetResult.err != nil { @@ -494,6 +510,8 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { wakeupChan = curSlot.shadowWakeUpChan stopChan = curSlot.shadowStopChan } + + lastStartPos := 0 for { select { case <-stopChan: @@ -503,15 +521,37 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { } interrupted := false - for _, txReq := range curSlot.pendingTxReqList { + + for i := lastStartPos; i < len(curSlot.pendingTxReqList); i++ { + // for i, txReq := range curSlot.pendingTxReqList { + txReq := curSlot.pendingTxReqList[i] if txReq.txIndex <= int(p.mergedTxIndex.Load()) { continue } + lastStartPos = i + + if txReq.conflictIndex.Load() > p.mergedTxIndex.Load() { + break + } if atomic.LoadInt32(&curSlot.activatedType) != slotType { interrupted = true break } + + // first try next to be merged req. + nextIdx := p.mergedTxIndex.Load() + 1 + if nextIdx < int32(len(p.allTxReqs)) { + nextMergeReq := p.allTxReqs[nextIdx] + if atomic.CompareAndSwapInt32(&nextMergeReq.runnable, 1, 0) { + // execute. + res := p.executeInSlot(slotIndex, nextMergeReq) + if res != nil { + p.txResultChan <- res + } + } + } + // try the next req in loop sequence. if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { continue } @@ -528,15 +568,35 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { // txReq in this Slot have all been executed, try steal one from other slot. // as long as the TxReq is runnable, we steal it, mark it as stolen - for _, stealTxReq := range p.allTxReqs { + + for j := int(p.mergedTxIndex.Load()) + 1; j < len(p.allTxReqs); j++ { + stealTxReq := p.allTxReqs[j] if stealTxReq.txIndex <= int(p.mergedTxIndex.Load()) { continue } + + if stealTxReq.conflictIndex.Load() > p.mergedTxIndex.Load() { + break + } + if atomic.LoadInt32(&curSlot.activatedType) != slotType { interrupted = true break } + // first try next to be merged req. + nextIdx := p.mergedTxIndex.Load() + 1 + if nextIdx < int32(len(p.allTxReqs)) { + nextMergeReq := p.allTxReqs[nextIdx] + if atomic.CompareAndSwapInt32(&nextMergeReq.runnable, 1, 0) { + // execute. + res := p.executeInSlot(slotIndex, nextMergeReq) + if res != nil { + p.txResultChan <- res + } + } + } + if !atomic.CompareAndSwapInt32(&stealTxReq.runnable, 1, 0) { continue } @@ -570,19 +630,27 @@ func (p *ParallelStateProcessor) runQuickMergeSlotLoop(slotIndex int, slotType i } next := int(p.mergedTxIndex.Load()) + 1 + + executed := 5 for i := next; i < len(p.allTxReqs); i++ { txReq := p.allTxReqs[next] + if executed == 0 { + break + } if txReq.txIndex <= int(p.mergedTxIndex.Load()) { continue } + if txReq.conflictIndex.Load() > p.mergedTxIndex.Load() { + break + } if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { continue } res := p.executeInSlot(slotIndex, txReq) if res != nil { + executed-- p.txResultChan <- res } - break } } } @@ -742,10 +810,9 @@ func (p *ParallelStateProcessor) doCleanUp() { // Process implements BEP-130 Parallel Transaction Execution func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { var ( - receipts types.Receipts - usedGas = new(uint64) - header = block.Header() - gp = new(GasPool).AddGas(block.GasLimit()) + usedGas = new(uint64) + header = block.Header() + gp = new(GasPool).AddGas(block.GasLimit()) ) // Mutate the block and state according to any hard-fork specs @@ -777,8 +844,9 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat txNum := len(allTxs) latestExcludedTx := -1 // Iterate over and process the individual transactions - commonTxs := make([]*types.Transaction, 0, txNum) - // var txReqs []*ParallelTxRequest + p.commonTxs = make([]*types.Transaction, 0, txNum) + p.receipts = make([]*types.Receipt, 0, txNum) + for i, tx := range allTxs { // can be moved it into slot for efficiency, but signer is not concurrent safe // Parallel Execution 1.0&2.0 is for full sync mode, Nonce PreCheck is not necessary @@ -823,8 +891,9 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat latestExcludedTx = i } } + allTxCount := len(p.allTxReqs) // set up stage2 enter criteria - p.targetStage2Count = len(p.allTxReqs) + p.targetStage2Count = allTxCount if p.targetStage2Count > 50 { // usually, the last Tx could be the bottleneck it could be very slow, // so it is better for us to enter stage 2 a bit earlier @@ -842,51 +911,49 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat slot.primaryWakeUpChan <- struct{}{} } - // wait until all Txs have processed. + // kick off the result handler. + p.resultProcessChan <- &ResultHandleEnv{statedb: statedb, gp: gp, txCount: allTxCount} for { - if len(commonTxs) == txNum { + if int(p.mergedTxIndex.Load())+1 == allTxCount { // put it ahead of chan receive to avoid waiting for empty block break } unconfirmedResult := <-p.txResultChan + if unconfirmedResult.txReq == nil && int(p.mergedTxIndex.Load())+1 == allTxCount { + // all tx results are merged. + break + } + unconfirmedTxIndex := unconfirmedResult.txReq.txIndex if unconfirmedTxIndex <= int(p.mergedTxIndex.Load()) { log.Debug("drop merged txReq", "unconfirmedTxIndex", unconfirmedTxIndex, "p.mergedTxIndex", p.mergedTxIndex.Load()) continue } - p.pendingConfirmResults[unconfirmedTxIndex] = append(p.pendingConfirmResults[unconfirmedTxIndex], unconfirmedResult) - - for { - result := p.confirmTxResults(statedb, gp) - if result == nil { - break - } - // update tx result - if result.err != nil { - log.Error("ProcessParallel a failed tx", "resultSlotIndex", result.slotIndex, - "resultTxIndex", result.txReq.txIndex, "result.err", result.err) - p.doCleanUp() - return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", result.txReq.txIndex, result.txReq.tx.Hash().Hex(), result.err) - } - commonTxs = append(commonTxs, result.txReq.tx) - receipts = append(receipts, result.receipt) + prevResult, ok := p.pendingConfirmResults.Load(unconfirmedTxIndex) + if !ok || prevResult.(*ParallelTxResult).slotDB.BaseTxIndex() < unconfirmedResult.slotDB.BaseTxIndex() { + p.pendingConfirmResults.Store(unconfirmedTxIndex, unconfirmedResult) + p.resultAppendChan <- struct{}{} } } // clean up when the block is processed p.doCleanUp() + if p.error != nil { + return nil, nil, 0, p.error + } // len(commonTxs) could be 0, such as: https://bscscan.com/block/14580486 - if len(commonTxs) > 0 && p.debugConflictRedoNum > 0 { + // all txs have been merged at this point, no need to acquire the lock of commonTxs + if p.mergedTxIndex.Load() >= 0 && p.debugConflictRedoNum > 0 { log.Info("ProcessParallel tx all done", "block", header.Number, "usedGas", *usedGas, "txNum", txNum, - "len(commonTxs)", len(commonTxs), + "len(commonTxs)", len(p.commonTxs), "conflictNum", p.debugConflictRedoNum, - "redoRate(%)", 100*(p.debugConflictRedoNum)/len(commonTxs), + "redoRate(%)", 100*(p.debugConflictRedoNum)/len(p.commonTxs), "txDAG", txDAG != nil) } if metrics.EnabledExpensive { - parallelTxNumMeter.Mark(int64(len(commonTxs))) + parallelTxNumMeter.Mark(int64(len(p.commonTxs))) parallelConflictTxNumMeter.Mark(int64(p.debugConflictRedoNum)) } @@ -896,13 +963,79 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat return nil, nil, 0, errors.New("withdrawals before shanghai") } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.engine.Finalize(p.bc, header, statedb, commonTxs, block.Uncles(), withdrawals) + p.engine.Finalize(p.bc, header, statedb, p.commonTxs, block.Uncles(), withdrawals) var allLogs []*types.Log - for _, receipt := range receipts { + for _, receipt := range p.receipts { allLogs = append(allLogs, receipt.Logs...) } - return receipts, allLogs, *usedGas, nil + return p.receipts, allLogs, *usedGas, nil +} + +func (p *ParallelStateProcessor) handlePendingResultLoop() { + var info *ResultHandleEnv + var stateDB *state.StateDB + var gp *GasPool + var txCount int + for { + select { + case info = <-p.resultProcessChan: + stateDB = info.statedb + gp = info.gp + txCount = info.txCount + log.Debug("handlePendingResult get Env", "stateDBTx", stateDB.TxIndex(), "gp", gp.String(), "txCount", txCount) + case <-p.resultAppendChan: + } + + // if all merged, notify the main routine. continue to wait for next block. + if p.error != nil || p.mergedTxIndex.Load()+1 == int32(txCount) { + // log.Info("handlePendingResult merged all") + p.txResultChan <- &ParallelTxResult{txReq: nil, result: nil} + // clear the pending chan. + for len(p.resultAppendChan) > 0 { + <-p.resultAppendChan + } + continue + } + // busy waiting. + for { + nextTxIndex := int(p.mergedTxIndex.Load()) + 1 + if p.error != nil || nextTxIndex == txCount { + p.txResultChan <- &ParallelTxResult{txReq: nil, result: nil} + // clear the pending chan. + for len(p.resultAppendChan) > 0 { + <-p.resultAppendChan + } + break + } + if _, ok := p.pendingConfirmResults.Load(nextTxIndex); !ok { + break + } + log.Debug("Start to check result", "TxIndex", int(nextTxIndex), "stateDBTx", stateDB.TxIndex(), "gp", gp.String()) + + result := p.confirmTxResults(stateDB, gp) + if result == nil { + break + } else { + log.Debug("in Confirm Loop - after confirmTxResults", + "mergedIndex", p.mergedTxIndex.Load(), + "confirmedIndex", result.txReq.txIndex, + "result.err", result.err) + } + p.resultMutex.Lock() + // update tx result + if result.err != nil { + log.Error("ProcessParallel a failed tx", "resultSlotIndex", result.slotIndex, + "resultTxIndex", result.txReq.txIndex, "result.err", result.err) + p.error = fmt.Errorf("could not apply tx %d [%v]: %w", result.txReq.txIndex, result.txReq.tx.Hash().Hex(), result.err) + p.resultMutex.Unlock() + continue + } + p.commonTxs = append(p.commonTxs, result.txReq.tx) + p.receipts = append(p.receipts, result.receipt) + p.resultMutex.Unlock() + } + } } func applyTransactionStageExecution(msg *Message, gp *GasPool, statedb *state.ParallelStateDB, evm *vm.EVM, delayGasFee bool) (*vm.EVM, *ExecutionResult, error) { From 72b27f492b2832a70b0bcbc1078ab6776df04bb2 Mon Sep 17 00:00:00 2001 From: Sunny Date: Fri, 30 Aug 2024 09:50:33 +0800 Subject: [PATCH 57/72] disable parallel if parallel.num is low --- core/blockchain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 505d50f69c..d12c6f8cdd 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1908,7 +1908,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) bc.parseTxDAG(block) txsCount := block.Transactions().Len() threshold := min(bc.vmConfig.ParallelTxNum/2+2, 4) - if txsCount < threshold || bc.isEmptyTxDAG() { + if bc.vmConfig.ParallelTxNum < 2 || txsCount < threshold || bc.isEmptyTxDAG() { bc.UseSerialProcessor() log.Debug("Disable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) } else { From d300ab86e7c2d3f64bcd17924bc933288b46657a Mon Sep 17 00:00:00 2001 From: Sunny Date: Fri, 30 Aug 2024 15:03:56 +0800 Subject: [PATCH 58/72] do CompareAndSwap only necessary --- core/parallel_state_processor.go | 75 ++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 23a3f9b7e2..499f6231a6 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -543,23 +543,28 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { nextIdx := p.mergedTxIndex.Load() + 1 if nextIdx < int32(len(p.allTxReqs)) { nextMergeReq := p.allTxReqs[nextIdx] - if atomic.CompareAndSwapInt32(&nextMergeReq.runnable, 1, 0) { - // execute. - res := p.executeInSlot(slotIndex, nextMergeReq) - if res != nil { - p.txResultChan <- res + if nextMergeReq.runnable == 1 { + if atomic.CompareAndSwapInt32(&nextMergeReq.runnable, 1, 0) { + // execute. + res := p.executeInSlot(slotIndex, nextMergeReq) + if res != nil { + p.txResultChan <- res + } } } } - // try the next req in loop sequence. - if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { - continue - } - res := p.executeInSlot(slotIndex, txReq) - if res == nil { - continue + + if txReq.runnable == 1 { + // try the next req in loop sequence. + if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { + continue + } + res := p.executeInSlot(slotIndex, txReq) + if res == nil { + continue + } + p.txResultChan <- res } - p.txResultChan <- res } // switched to the other slot. if interrupted { @@ -588,23 +593,27 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int, slotType int32) { nextIdx := p.mergedTxIndex.Load() + 1 if nextIdx < int32(len(p.allTxReqs)) { nextMergeReq := p.allTxReqs[nextIdx] - if atomic.CompareAndSwapInt32(&nextMergeReq.runnable, 1, 0) { - // execute. - res := p.executeInSlot(slotIndex, nextMergeReq) - if res != nil { - p.txResultChan <- res + if nextMergeReq.runnable == 1 { + if atomic.CompareAndSwapInt32(&nextMergeReq.runnable, 1, 0) { + // execute. + res := p.executeInSlot(slotIndex, nextMergeReq) + if res != nil { + p.txResultChan <- res + } } } } - if !atomic.CompareAndSwapInt32(&stealTxReq.runnable, 1, 0) { - continue - } - res := p.executeInSlot(slotIndex, stealTxReq) - if res == nil { - continue + if stealTxReq.runnable == 1 { + if !atomic.CompareAndSwapInt32(&stealTxReq.runnable, 1, 0) { + continue + } + res := p.executeInSlot(slotIndex, stealTxReq) + if res == nil { + continue + } + p.txResultChan <- res } - p.txResultChan <- res } } } @@ -643,13 +652,15 @@ func (p *ParallelStateProcessor) runQuickMergeSlotLoop(slotIndex int, slotType i if txReq.conflictIndex.Load() > p.mergedTxIndex.Load() { break } - if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { - continue - } - res := p.executeInSlot(slotIndex, txReq) - if res != nil { - executed-- - p.txResultChan <- res + if txReq.runnable == 1 { + if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { + continue + } + res := p.executeInSlot(slotIndex, txReq) + if res != nil { + executed-- + p.txResultChan <- res + } } } } From 4d6273fdb769bb64fd83cb97b0fbdbe33b62fc36 Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 2 Sep 2024 14:58:43 +0800 Subject: [PATCH 59/72] remove uncessary memory overhead and reuse SyncPool --- core/blockchain.go | 9 +++++--- core/parallel_state_processor.go | 13 +++++------- core/state/parallel_statedb.go | 1 + core/state/statedb.go | 35 ++------------------------------ 4 files changed, 14 insertions(+), 44 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d12c6f8cdd..15a7c54387 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1913,8 +1913,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) log.Debug("Disable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) } else { bc.UseParallelProcessor() - statedb.CreateParallelDBManager(2 * txsCount) - log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) + if bc.processor == bc.parallelProcessor { + statedb.CreateParallelDBManager(2 * txsCount) + log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) + } } } // If we have a followup block, run that against the current state to pre-cache @@ -2793,7 +2795,8 @@ func (bc *BlockChain) UseParallelProcessor() { bc.parallelExecution = true bc.processor = bc.parallelProcessor } else { - bc.CreateParallelProcessor(bc.vmConfig.ParallelTxNum) + log.Error("bc.ParallelProcessor is nil! fallback to serial processor!") + bc.UseSerialProcessor() } } diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 499f6231a6..a0532a6c1d 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -197,14 +197,7 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { statedb.PrepareForParallel() p.allTxReqs = make([]*ParallelTxRequest, 0, txNum) - p.slotDBsToRelease = make([]*state.ParallelStateDB, 0, txNum) - stateDBsToRelease := p.slotDBsToRelease - go func() { - for _, slotDB := range stateDBsToRelease { - slotDB.PutSyncPool() - } - }() for _, slot := range p.slotState { slot.pendingTxReqList = make([]*ParallelTxRequest, 0) slot.activatedType = parallelPrimarySlot @@ -335,7 +328,6 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR } slotDB.SetTxContext(txReq.tx.Hash(), txReq.txIndex) - evm, result, err := applyTransactionStageExecution(txReq.msg, gpSlot, slotDB, vmenv, p.delayGasFee) txResult := ParallelTxResult{ executedIndex: execNum, @@ -460,6 +452,8 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo p.debugConflictRedoNum++ // interrupt its current routine, and switch to the other routine p.switchSlot(staticSlotIndex) + // reclaim the result. + targetResult.slotDB.PutSyncPool() return nil } continue @@ -794,6 +788,8 @@ func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *Ga } p.txReqExecuteRecord[resultTxIndex]++ } + // after merge, the slotDB will not accessible, reclaim the resource + result.slotDB.PutSyncPool() return result } @@ -949,6 +945,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat // clean up when the block is processed p.doCleanUp() + if p.error != nil { return nil, nil, 0, p.error } diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index bc5df06d4c..d6992daf7f 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -524,6 +524,7 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { code = object.Code() } } + if _, ok := s.parallel.codeReadsInSlot[addr]; !ok { s.parallel.codeReadsInSlot[addr] = code } diff --git a/core/state/statedb.go b/core/state/statedb.go index 7f528f9df1..e7204db556 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -187,8 +187,6 @@ type StateDB struct { trieParallelLock sync.Mutex // for parallel mode of trie, mostly for get states/objects from trie, lock required to handle trie tracer. stateObjectDestructLock sync.RWMutex // for parallel mode, used in mainDB for mergeSlot and conflict check. snapDestructs map[common.Address]struct{} - snapAccounts map[common.Address][]byte - snapStorage map[common.Address]map[string][]byte // originalRoot is the pre-state root, before any changes were made. // It will be updated when the Commit is called. @@ -1254,14 +1252,6 @@ var addressToUintPool = sync.Pool{ New: func() interface{} { return make(map[common.Address]uint64, defaultNumOfSlots) }, } -var snapStoragePool = sync.Pool{ - New: func() interface{} { return make(map[common.Address]map[string][]byte, defaultNumOfSlots) }, -} - -var snapStorageValuePool = sync.Pool{ - New: func() interface{} { return make(map[string][]byte, defaultNumOfSlots) }, -} - var logsPool = sync.Pool{ New: func() interface{} { return make(map[common.Hash][]*types.Log, defaultNumOfSlots) }, } @@ -1362,20 +1352,6 @@ func (s *StateDB) PutSyncPool() { delete(s.parallel.createdObjectRecord, key) } addressToStructPool.Put(s.parallel.createdObjectRecord) - - for key := range s.snapAccounts { - delete(s.snapAccounts, key) - } - addressToBytesPool.Put(s.snapAccounts) - - for key, storage := range s.snapStorage { - for key := range storage { - delete(storage, key) - } - snapStorageValuePool.Put(storage) - delete(s.snapStorage, key) - } - snapStoragePool.Put(s.snapStorage) } func NewEmptySlotDB() *ParallelStateDB { @@ -2653,10 +2629,6 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip // remove the addr from snapAccounts&snapStorage only when object is deleted. // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts - s.snapParallelLock.Lock() - delete(s.snapAccounts, addr) - delete(s.snapStorage, addr) - s.snapParallelLock.Unlock() s.AccountMux.Lock() delete(s.accounts, dirtyObj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) delete(s.accountsOrigin, dirtyObj.address) // Clear out any previously updated account data (may be recreated via a resurrect) @@ -2720,10 +2692,6 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip // remove the addr from snapAccounts&snapStorage only when object is deleted. // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts - s.snapParallelLock.Lock() - delete(s.snapAccounts, addr) - delete(s.snapStorage, addr) - s.snapParallelLock.Unlock() s.AccountMux.Lock() delete(s.accounts, dirtyObj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) delete(s.accountsOrigin, dirtyObj.address) // Clear out any previously updated account data (may be recreated via a resurrect) @@ -2846,5 +2814,6 @@ func (m *ParallelDBManager) allocate() *ParallelStateDB { elem := m.pool.Front() m.pool.Remove(elem) - return elem.Value.(*ParallelStateDB) + ret := elem.Value.(*ParallelStateDB) + return ret } From 8ea114005f9418e5e0a80fbb7e621fde5d697646 Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 3 Sep 2024 15:41:06 +0800 Subject: [PATCH 60/72] fix putSyncPool and GC issue --- core/parallel_state_processor.go | 19 +++- core/state/parallel_statedb.go | 169 ++++++++++++++++++++++++++----- core/state/statedb.go | 138 +++---------------------- 3 files changed, 171 insertions(+), 155 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index a0532a6c1d..e9347939e3 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -46,7 +46,7 @@ type ParallelStateProcessor struct { pendingConfirmResults *sync.Map // tx could be executed several times, with several result to check unconfirmedResults *sync.Map // for stage2 confirm, since pendingConfirmResults can not be accessed in stage2 loop unconfirmedDBs *sync.Map // intermediate store of slotDB that is not verified - slotDBsToRelease []*state.ParallelStateDB + slotDBsToRelease *sync.Map stopSlotChan chan struct{} stopConfirmChan chan struct{} debugConflictRedoNum int @@ -204,6 +204,7 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { } p.unconfirmedResults = new(sync.Map) p.unconfirmedDBs = new(sync.Map) + p.slotDBsToRelease = new(sync.Map) p.pendingConfirmResults = new(sync.Map) p.txReqExecuteRecord = make(map[int]int, txNum) p.txReqExecuteCount = 0 @@ -453,7 +454,7 @@ func (p *ParallelStateProcessor) toConfirmTxIndex(targetTxIndex int, isStage2 bo // interrupt its current routine, and switch to the other routine p.switchSlot(staticSlotIndex) // reclaim the result. - targetResult.slotDB.PutSyncPool() + p.slotDBsToRelease.Store(targetResult.slotDB, targetResult.slotDB) return nil } continue @@ -789,7 +790,7 @@ func (p *ParallelStateProcessor) confirmTxResults(statedb *state.StateDB, gp *Ga p.txReqExecuteRecord[resultTxIndex]++ } // after merge, the slotDB will not accessible, reclaim the resource - result.slotDB.PutSyncPool() + p.slotDBsToRelease.Store(result.slotDB, result.slotDB) return result } @@ -812,6 +813,18 @@ func (p *ParallelStateProcessor) doCleanUp() { // 3.make sure the confirmation routine is stopped p.stopConfirmStage2Chan <- struct{}{} <-p.stopSlotChan + + go func() { + p.slotDBsToRelease.Range(func(key, value any) bool { + sdb := value.(*state.ParallelStateDB) + sdb.PutSyncPool() + return true + }) + }() + + p.unconfirmedResults = nil + p.unconfirmedDBs = nil + p.pendingConfirmResults = nil } // Process implements BEP-130 Parallel Transaction Execution diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index d6992daf7f..2e13e4d5c2 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -13,7 +13,7 @@ import ( "sync" ) -const defaultNumOfSlots = 100 +const defaultNumOfSlots = 5 var parallelKvOnce sync.Once @@ -138,22 +138,106 @@ func NewSlotDB(db *StateDB, txIndex int, baseTxIndex int, unconfirmedDBs *sync.M return slotDB } -// RevertSlotDB keep the Read list for conflict detect, -// discard all state changes except: -// - nonce and balance of from address -// - balance of system address: will be used on merge to update SystemAddress's balance -func (s *ParallelStateDB) RevertSlotDB(from common.Address) { - s.parallel.kvChangesInSlot = make(map[common.Address]StateKeys) - s.parallel.nonceChangesInSlot = make(map[common.Address]struct{}) - s.parallel.balanceChangesInSlot = make(map[common.Address]struct{}, 1) - s.parallel.addrStateChangesInSlot = make(map[common.Address]bool) // 0: created, 1: deleted - - selfStateObject := s.parallel.dirtiedStateObjectsInSlot[from] - s.parallel.dirtiedStateObjectsInSlot = make(map[common.Address]*stateObject, 2) - // keep these elements - s.parallel.dirtiedStateObjectsInSlot[from] = selfStateObject - s.parallel.balanceChangesInSlot[from] = struct{}{} - s.parallel.nonceChangesInSlot[from] = struct{}{} +func (s *ParallelStateDB) PutSyncPool() { + for key := range s.parallel.codeReadsInSlot { + delete(s.parallel.codeReadsInSlot, key) + } + addressToBytesPool.Put(s.parallel.codeReadsInSlot) + + for key := range s.parallel.codeHashReadsInSlot { + delete(s.parallel.codeHashReadsInSlot, key) + } + addressToHashPool.Put(s.parallel.codeHashReadsInSlot) + + for key := range s.parallel.codeChangesInSlot { + delete(s.parallel.codeChangesInSlot, key) + } + addressToStructPool.Put(s.parallel.codeChangesInSlot) + + for key := range s.parallel.kvChangesInSlot { + delete(s.parallel.kvChangesInSlot, key) + } + addressToStateKeysPool.Put(s.parallel.kvChangesInSlot) + + for key := range s.parallel.kvReadsInSlot { + delete(s.parallel.kvReadsInSlot, key) + } + addressToStoragePool.Put(s.parallel.kvReadsInSlot) + + for key := range s.parallel.balanceChangesInSlot { + delete(s.parallel.balanceChangesInSlot, key) + } + addressToStructPool.Put(s.parallel.balanceChangesInSlot) + + for key := range s.parallel.balanceReadsInSlot { + delete(s.parallel.balanceReadsInSlot, key) + } + balancePool.Put(s.parallel.balanceReadsInSlot) + + for key := range s.parallel.addrStateReadsInSlot { + delete(s.parallel.addrStateReadsInSlot, key) + } + addressToBoolPool.Put(s.parallel.addrStateReadsInSlot) + + for key := range s.parallel.addrStateChangesInSlot { + delete(s.parallel.addrStateChangesInSlot, key) + } + addressToBoolPool.Put(s.parallel.addrStateChangesInSlot) + + for key := range s.parallel.nonceChangesInSlot { + delete(s.parallel.nonceChangesInSlot, key) + } + addressToStructPool.Put(s.parallel.nonceChangesInSlot) + + for key := range s.parallel.nonceReadsInSlot { + delete(s.parallel.nonceReadsInSlot, key) + } + addressToUintPool.Put(s.parallel.nonceReadsInSlot) + + for key := range s.parallel.addrSnapDestructsReadsInSlot { + delete(s.parallel.addrSnapDestructsReadsInSlot, key) + } + addressToBoolPool.Put(s.parallel.addrSnapDestructsReadsInSlot) + + for key := range s.parallel.dirtiedStateObjectsInSlot { + delete(s.parallel.dirtiedStateObjectsInSlot, key) + } + addressToStateObjectsPool.Put(s.parallel.dirtiedStateObjectsInSlot) + + for key := range s.stateObjectsPending { + delete(s.stateObjectsPending, key) + } + addressToStructPool.Put(s.stateObjectsPending) + + for key := range s.stateObjectsDirty { + delete(s.stateObjectsDirty, key) + } + addressToStructPool.Put(s.stateObjectsDirty) + + for key := range s.logs { + delete(s.logs, key) + } + logsPool.Put(s.logs) + + for key := range s.journal.dirties { + delete(s.journal.dirties, key) + } + s.journal.entries = s.journal.entries[:0] + journalPool.Put(s.journal) + + for key := range s.snapDestructs { + delete(s.snapDestructs, key) + } + addressToStructPool.Put(s.snapDestructs) + + for key := range s.parallel.createdObjectRecord { + delete(s.parallel.createdObjectRecord, key) + } + addressToStructPool.Put(s.parallel.createdObjectRecord) + + manager := s.parallel.baseStateDB.parallelDBManager + s.reset() + manager.reclaim(s) } // getStateDBBasePtr get the pointer of parallelStateDB. @@ -1676,15 +1760,6 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S delete(mainDB.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(mainDB.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) mainDB.StorageMux.Unlock() - - // todo: The following record seems unnecessary. - if s.parallel.isSlotDB { - s.parallel.accountsDeletedRecord = append(s.parallel.accountsDeletedRecord, obj.addrHash) - s.parallel.storagesDeleteRecord = append(s.parallel.storagesDeleteRecord, obj.addrHash) - s.parallel.accountsOriginDeleteRecord = append(s.parallel.accountsOriginDeleteRecord, obj.address) - s.parallel.storagesOriginDeleteRecord = append(s.parallel.storagesOriginDeleteRecord, obj.address) - } - } else { // 1.none parallel mode, we do obj.finalise(true) as normal // 2.with parallel mode, we do obj.finalise(true) on dispatcher, not on slot routine @@ -1717,3 +1792,45 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() } + +func (s *ParallelStateDB) reset() { + parallel := ParallelState{ + stateObjects: &StateObjectSyncMap{}, // s.parallel.stateObjects, + codeReadsInSlot: addressToBytesPool.Get().(map[common.Address][]byte), + codeHashReadsInSlot: addressToHashPool.Get().(map[common.Address]common.Hash), + codeChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), + kvChangesInSlot: addressToStateKeysPool.Get().(map[common.Address]StateKeys), + kvReadsInSlot: addressToStoragePool.Get().(map[common.Address]Storage), + balanceChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), + balanceReadsInSlot: balancePool.Get().(map[common.Address]*big.Int), + addrStateReadsInSlot: addressToBoolPool.Get().(map[common.Address]bool), + addrStateChangesInSlot: addressToBoolPool.Get().(map[common.Address]bool), + nonceChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), + nonceReadsInSlot: addressToUintPool.Get().(map[common.Address]uint64), + addrSnapDestructsReadsInSlot: addressToBoolPool.Get().(map[common.Address]bool), + isSlotDB: true, + dirtiedStateObjectsInSlot: addressToStateObjectsPool.Get().(map[common.Address]*stateObject), + createdObjectRecord: addressToStructPool.Get().(map[common.Address]struct{}), + } + s.StateDB = StateDB{ + db: nil, + trie: nil, // Parallel StateDB may access the trie, but it takes no effect to the baseDB. + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), + stateObjects: make(map[common.Address]*stateObject), // replaced by parallel.stateObjects in parallel mode + stateObjectsPending: addressToStructPool.Get().(map[common.Address]struct{}), + stateObjectsDirty: addressToStructPool.Get().(map[common.Address]struct{}), + stateObjectsDestruct: make(map[common.Address]*types.StateAccount), + refund: 0, // should be 0 + logs: logsPool.Get().(map[common.Hash][]*types.Log), + logSize: 0, + preimages: nil, + journal: journalPool.Get().(*journal), + hasher: crypto.NewKeccakState(), + isParallel: true, + parallel: parallel, + } + s.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) +} diff --git a/core/state/statedb.go b/core/state/statedb.go index e7204db556..ea6947ab00 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -150,12 +150,7 @@ type ParallelState struct { addrStateChangesInSlot map[common.Address]bool // true: created, false: deleted addrSnapDestructsReadsInSlot map[common.Address]bool - - accountsDeletedRecord []common.Hash - storagesDeleteRecord []common.Hash - accountsOriginDeleteRecord []common.Address - storagesOriginDeleteRecord []common.Address - createdObjectRecord map[common.Address]struct{} + createdObjectRecord map[common.Address]struct{} // we may need to redo for some specific reasons, like we read the wrong state and need to panic in sequential mode in SubRefund needsRedo bool useDAG bool @@ -1256,104 +1251,6 @@ var logsPool = sync.Pool{ New: func() interface{} { return make(map[common.Hash][]*types.Log, defaultNumOfSlots) }, } -func (s *StateDB) PutSyncPool() { - for key := range s.parallel.codeReadsInSlot { - delete(s.parallel.codeReadsInSlot, key) - } - addressToBytesPool.Put(s.parallel.codeReadsInSlot) - - for key := range s.parallel.codeHashReadsInSlot { - delete(s.parallel.codeHashReadsInSlot, key) - } - addressToHashPool.Put(s.parallel.codeHashReadsInSlot) - - for key := range s.parallel.codeChangesInSlot { - delete(s.parallel.codeChangesInSlot, key) - } - addressToStructPool.Put(s.parallel.codeChangesInSlot) - - for key := range s.parallel.kvChangesInSlot { - delete(s.parallel.kvChangesInSlot, key) - } - addressToStateKeysPool.Put(s.parallel.kvChangesInSlot) - - for key := range s.parallel.kvReadsInSlot { - delete(s.parallel.kvReadsInSlot, key) - } - addressToStoragePool.Put(s.parallel.kvReadsInSlot) - - for key := range s.parallel.balanceChangesInSlot { - delete(s.parallel.balanceChangesInSlot, key) - } - addressToStructPool.Put(s.parallel.balanceChangesInSlot) - - for key := range s.parallel.balanceReadsInSlot { - delete(s.parallel.balanceReadsInSlot, key) - } - balancePool.Put(s.parallel.balanceReadsInSlot) - - for key := range s.parallel.addrStateReadsInSlot { - delete(s.parallel.addrStateReadsInSlot, key) - } - addressToBoolPool.Put(s.parallel.addrStateReadsInSlot) - - for key := range s.parallel.addrStateChangesInSlot { - delete(s.parallel.addrStateChangesInSlot, key) - } - addressToBoolPool.Put(s.parallel.addrStateChangesInSlot) - - for key := range s.parallel.nonceChangesInSlot { - delete(s.parallel.nonceChangesInSlot, key) - } - addressToStructPool.Put(s.parallel.nonceChangesInSlot) - - for key := range s.parallel.nonceReadsInSlot { - delete(s.parallel.nonceReadsInSlot, key) - } - addressToUintPool.Put(s.parallel.nonceReadsInSlot) - - for key := range s.parallel.addrSnapDestructsReadsInSlot { - delete(s.parallel.addrSnapDestructsReadsInSlot, key) - } - addressToBoolPool.Put(s.parallel.addrSnapDestructsReadsInSlot) - - for key := range s.parallel.dirtiedStateObjectsInSlot { - delete(s.parallel.dirtiedStateObjectsInSlot, key) - } - addressToStateObjectsPool.Put(s.parallel.dirtiedStateObjectsInSlot) - - for key := range s.stateObjectsPending { - delete(s.stateObjectsPending, key) - } - addressToStructPool.Put(s.stateObjectsPending) - - for key := range s.stateObjectsDirty { - delete(s.stateObjectsDirty, key) - } - addressToStructPool.Put(s.stateObjectsDirty) - - for key := range s.logs { - delete(s.logs, key) - } - logsPool.Put(s.logs) - - for key := range s.journal.dirties { - delete(s.journal.dirties, key) - } - s.journal.entries = s.journal.entries[:0] - journalPool.Put(s.journal) - - for key := range s.snapDestructs { - delete(s.snapDestructs, key) - } - addressToStructPool.Put(s.snapDestructs) - - for key := range s.parallel.createdObjectRecord { - delete(s.parallel.createdObjectRecord, key) - } - addressToStructPool.Put(s.parallel.createdObjectRecord) -} - func NewEmptySlotDB() *ParallelStateDB { parallel := ParallelState{ // The stateObjects in Parallel is thread-local. @@ -1385,10 +1282,6 @@ func NewEmptySlotDB() *ParallelStateDB { addrSnapDestructsReadsInSlot: addressToBoolPool.Get().(map[common.Address]bool), isSlotDB: true, dirtiedStateObjectsInSlot: addressToStateObjectsPool.Get().(map[common.Address]*stateObject), - accountsDeletedRecord: make([]common.Hash, 10), - storagesDeleteRecord: make([]common.Hash, 10), - accountsOriginDeleteRecord: make([]common.Address, 10), - storagesOriginDeleteRecord: make([]common.Address, 10), createdObjectRecord: addressToStructPool.Get().(map[common.Address]struct{}), } state := &ParallelStateDB{ @@ -1530,14 +1423,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) - - if s.parallel.isSlotDB { - s.parallel.accountsDeletedRecord = append(s.parallel.accountsDeletedRecord, obj.addrHash) - s.parallel.storagesDeleteRecord = append(s.parallel.storagesDeleteRecord, obj.addrHash) - s.parallel.accountsOriginDeleteRecord = append(s.parallel.accountsOriginDeleteRecord, obj.address) - s.parallel.storagesOriginDeleteRecord = append(s.parallel.storagesOriginDeleteRecord, obj.address) - } - } else { // 1.none parallel mode, we do obj.finalise(true) as normal // 2.with parallel mode, we do obj.finalise(true) on dispatcher, not on slot routine @@ -1925,9 +1810,6 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A if aborted { incomplete[addr] = struct{}{} delete(s.storagesOrigin, addr) - if s.parallel.isSlotDB { - s.parallel.storagesOriginDeleteRecord = append(s.parallel.storagesOriginDeleteRecord, addr) - } continue } if s.storagesOrigin[addr] == nil { @@ -2780,13 +2662,6 @@ func (s *StateDB) CreateParallelDBManager(txCount int) { } } -// ParallelDBManager manages a pool of ParallelDB instances -type ParallelDBManager struct { - pool *list.List - mutex sync.Mutex - newFunc func() *ParallelStateDB // Function to create a new ParallelDB instance -} - // NewParallelDBManager creates a new ParallelDBManager with the specified number of instance func NewParallelDBManager(initialCount int, newFunc func() *ParallelStateDB) *ParallelDBManager { manager := &ParallelDBManager{ @@ -2802,6 +2677,13 @@ func NewParallelDBManager(initialCount int, newFunc func() *ParallelStateDB) *Pa return manager } +// ParallelDBManager manages a pool of ParallelDB instances +type ParallelDBManager struct { + pool *list.List + mutex sync.Mutex + newFunc func() *ParallelStateDB // Function to create a new ParallelDB instance +} + // allocate acquires a ParallelStateDB instance from the pool // if the pool is empty, directly create a new one. func (m *ParallelDBManager) allocate() *ParallelStateDB { @@ -2817,3 +2699,7 @@ func (m *ParallelDBManager) allocate() *ParallelStateDB { ret := elem.Value.(*ParallelStateDB) return ret } + +func (m *ParallelDBManager) reclaim(s *ParallelStateDB) { + m.pool.PushBack(s) +} From 3c5eda59dfbe9c84ec61f52fb6795c8a3dcd63b8 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 4 Sep 2024 17:21:34 +0800 Subject: [PATCH 61/72] parallelDBManager global --- core/blockchain.go | 4 - core/parallel_state_processor.go | 17 ++-- core/state/parallel_statedb.go | 137 +++++++++++++++++++++---------- core/state/statedb.go | 19 ++--- core/state/statedb_test.go | 50 +++++------ 5 files changed, 135 insertions(+), 92 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 15a7c54387..71ae310e66 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1913,10 +1913,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) log.Debug("Disable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) } else { bc.UseParallelProcessor() - if bc.processor == bc.parallelProcessor { - statedb.CreateParallelDBManager(2 * txsCount) - log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) - } } } // If we have a followup block, run that against the current state to pre-cache diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index e9347939e3..9b742da649 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -66,6 +66,7 @@ type ParallelStateProcessor struct { resultMutex sync.RWMutex resultProcessChan chan *ResultHandleEnv resultAppendChan chan struct{} + parallelDBManager *state.ParallelDBManager } func newParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine, parallelNum int) *ParallelStateProcessor { @@ -139,6 +140,9 @@ func (p *ParallelStateProcessor) init() { p.resultAppendChan = make(chan struct{}, 20000) p.slotState = make([]*SlotState, p.parallelNum) + + p.parallelDBManager = state.NewParallelDBManager(20000, state.NewEmptySlotDB) + quickMergeNum := 2 // p.parallelNum / 2 for i := 0; i < p.parallelNum-quickMergeNum; i++ { p.slotState[i] = &SlotState{ @@ -184,6 +188,7 @@ func (p *ParallelStateProcessor) init() { go func() { p.handlePendingResultLoop() }() + } // resetState clear slot state for each block. @@ -311,7 +316,7 @@ func (p *ParallelStateProcessor) executeInSlot(slotIndex int, txReq *ParallelTxR return nil } execNum := txReq.executedNum.Add(1) - slotDB := state.NewSlotDB(txReq.baseStateDB, txReq.txIndex, int(mIndex), p.unconfirmedDBs, txReq.useDAG) + slotDB := state.NewSlotDB(txReq.baseStateDB, txReq.txIndex, int(mIndex), p.parallelDBManager, p.unconfirmedDBs, txReq.useDAG) blockContext := NewEVMBlockContext(txReq.block.Header(), p.bc, nil, p.config, slotDB) // can share blockContext within a block for efficiency txContext := NewEVMTxContext(txReq.msg) vmenv := vm.NewEVM(blockContext, txContext, slotDB, p.config, txReq.vmConfig) @@ -814,17 +819,17 @@ func (p *ParallelStateProcessor) doCleanUp() { p.stopConfirmStage2Chan <- struct{}{} <-p.stopSlotChan + p.unconfirmedResults = nil + p.unconfirmedDBs = nil + p.pendingConfirmResults = nil + go func() { p.slotDBsToRelease.Range(func(key, value any) bool { sdb := value.(*state.ParallelStateDB) - sdb.PutSyncPool() + sdb.PutSyncPool(p.parallelDBManager) return true }) }() - - p.unconfirmedResults = nil - p.unconfirmedDBs = nil - p.pendingConfirmResults = nil } // Process implements BEP-130 Parallel Transaction Execution diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 2e13e4d5c2..46253d5cec 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -127,8 +127,8 @@ func StartKvCheckLoop() { // NewSlotDB creates a new State DB based on the provided StateDB. // With parallel, each execution slot would have its own StateDB. // This method must be called after the baseDB call PrepareParallel() -func NewSlotDB(db *StateDB, txIndex int, baseTxIndex int, unconfirmedDBs *sync.Map, useDAG bool) *ParallelStateDB { - slotDB := db.CopyForSlot() +func NewSlotDB(db *StateDB, txIndex int, baseTxIndex int, manager *ParallelDBManager, unconfirmedDBs *sync.Map, useDAG bool) *ParallelStateDB { + slotDB := db.CopyForSlot(manager) slotDB.txIndex = txIndex slotDB.originalRoot = db.originalRoot slotDB.parallel.baseStateDB = db @@ -138,7 +138,7 @@ func NewSlotDB(db *StateDB, txIndex int, baseTxIndex int, unconfirmedDBs *sync.M return slotDB } -func (s *ParallelStateDB) PutSyncPool() { +func (s *ParallelStateDB) PutSyncPool(parallelDBManager *ParallelDBManager) { for key := range s.parallel.codeReadsInSlot { delete(s.parallel.codeReadsInSlot, key) } @@ -235,9 +235,8 @@ func (s *ParallelStateDB) PutSyncPool() { } addressToStructPool.Put(s.parallel.createdObjectRecord) - manager := s.parallel.baseStateDB.parallelDBManager s.reset() - manager.reclaim(s) + parallelDBManager.reclaim(s) } // getStateDBBasePtr get the pointer of parallelStateDB. @@ -1794,43 +1793,93 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S } func (s *ParallelStateDB) reset() { - parallel := ParallelState{ - stateObjects: &StateObjectSyncMap{}, // s.parallel.stateObjects, - codeReadsInSlot: addressToBytesPool.Get().(map[common.Address][]byte), - codeHashReadsInSlot: addressToHashPool.Get().(map[common.Address]common.Hash), - codeChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), - kvChangesInSlot: addressToStateKeysPool.Get().(map[common.Address]StateKeys), - kvReadsInSlot: addressToStoragePool.Get().(map[common.Address]Storage), - balanceChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), - balanceReadsInSlot: balancePool.Get().(map[common.Address]*big.Int), - addrStateReadsInSlot: addressToBoolPool.Get().(map[common.Address]bool), - addrStateChangesInSlot: addressToBoolPool.Get().(map[common.Address]bool), - nonceChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), - nonceReadsInSlot: addressToUintPool.Get().(map[common.Address]uint64), - addrSnapDestructsReadsInSlot: addressToBoolPool.Get().(map[common.Address]bool), - isSlotDB: true, - dirtiedStateObjectsInSlot: addressToStateObjectsPool.Get().(map[common.Address]*stateObject), - createdObjectRecord: addressToStructPool.Get().(map[common.Address]struct{}), - } - s.StateDB = StateDB{ - db: nil, - trie: nil, // Parallel StateDB may access the trie, but it takes no effect to the baseDB. - accounts: make(map[common.Hash][]byte), - storages: make(map[common.Hash]map[common.Hash][]byte), - accountsOrigin: make(map[common.Address][]byte), - storagesOrigin: make(map[common.Address]map[common.Hash][]byte), - stateObjects: make(map[common.Address]*stateObject), // replaced by parallel.stateObjects in parallel mode - stateObjectsPending: addressToStructPool.Get().(map[common.Address]struct{}), - stateObjectsDirty: addressToStructPool.Get().(map[common.Address]struct{}), - stateObjectsDestruct: make(map[common.Address]*types.StateAccount), - refund: 0, // should be 0 - logs: logsPool.Get().(map[common.Hash][]*types.Log), - logSize: 0, - preimages: nil, - journal: journalPool.Get().(*journal), - hasher: crypto.NewKeccakState(), - isParallel: true, - parallel: parallel, - } - s.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) + + s.StateDB.db = nil + s.StateDB.prefetcher = nil + s.StateDB.trie = nil + s.StateDB.noTrie = false + s.StateDB.hasher = crypto.NewKeccakState() + s.StateDB.snaps = nil + s.StateDB.snap = nil + s.StateDB.snapParallelLock = sync.RWMutex{} + s.StateDB.trieParallelLock = sync.Mutex{} + s.StateDB.stateObjectDestructLock = sync.RWMutex{} + s.StateDB.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) + s.StateDB.originalRoot = common.Hash{} + s.StateDB.expectedRoot = common.Hash{} + s.StateDB.stateRoot = common.Hash{} + s.StateDB.fullProcessed = false + s.StateDB.AccountMux = sync.Mutex{} + s.StateDB.StorageMux = sync.Mutex{} + s.StateDB.accounts = make(map[common.Hash][]byte) + s.StateDB.storages = make(map[common.Hash]map[common.Hash][]byte) + s.StateDB.accountsOrigin = make(map[common.Address][]byte) + s.StateDB.storagesOrigin = make(map[common.Address]map[common.Hash][]byte) + s.StateDB.stateObjects = make(map[common.Address]*stateObject) // replaced by parallel.stateObjects in parallel mode + s.StateDB.stateObjectsPending = addressToStructPool.Get().(map[common.Address]struct{}) + s.StateDB.stateObjectsDirty = addressToStructPool.Get().(map[common.Address]struct{}) + s.StateDB.stateObjectsDestruct = make(map[common.Address]*types.StateAccount) + s.StateDB.stateObjectsDestructDirty = make(map[common.Address]*types.StateAccount) + s.StateDB.dbErr = nil + s.StateDB.refund = 0 + s.StateDB.thash = common.Hash{} + s.StateDB.txIndex = 0 + s.StateDB.logs = logsPool.Get().(map[common.Hash][]*types.Log) + s.StateDB.logSize = 0 + s.StateDB.rwSet = nil + s.StateDB.mvStates = nil + s.StateDB.stat = nil + s.StateDB.preimages = nil + s.StateDB.accessList = nil + s.StateDB.transientStorage = nil + s.StateDB.journal = journalPool.Get().(*journal) + s.StateDB.validRevisions = nil + s.StateDB.nextRevisionId = 0 + s.StateDB.AccountReads = 0 + s.StateDB.AccountHashes = 0 + s.StateDB.AccountUpdates = 0 + s.StateDB.AccountCommits = 0 + s.StateDB.StorageReads = 0 + s.StateDB.StorageHashes = 0 + s.StateDB.StorageUpdates = 0 + s.StateDB.StorageCommits = 0 + s.StateDB.SnapshotAccountReads = 0 + s.StateDB.SnapshotStorageReads = 0 + s.StateDB.SnapshotCommits = 0 + s.StateDB.TrieDBCommits = 0 + s.StateDB.TrieCommits = 0 + s.StateDB.CodeCommits = 0 + s.StateDB.TxDAGGenerate = 0 + s.StateDB.AccountUpdated = 0 + s.StateDB.StorageUpdated = 0 + s.StateDB.AccountDeleted = 0 + s.StateDB.StorageDeleted = 0 + s.StateDB.isParallel = true + s.StateDB.parallel = ParallelState{} + s.StateDB.onCommit = nil + + s.parallel.isSlotDB = true + s.parallel.SlotIndex = -1 + s.parallel.stateObjects = &StateObjectSyncMap{} + s.parallel.baseStateDB = nil + s.parallel.baseTxIndex = -1 + s.parallel.dirtiedStateObjectsInSlot = addressToStateObjectsPool.Get().(map[common.Address]*stateObject) + s.parallel.unconfirmedDBs = nil + s.parallel.nonceChangesInSlot = addressToStructPool.Get().(map[common.Address]struct{}) + s.parallel.nonceReadsInSlot = addressToUintPool.Get().(map[common.Address]uint64) + s.parallel.balanceChangesInSlot = addressToStructPool.Get().(map[common.Address]struct{}) + s.parallel.balanceReadsInSlot = balancePool.Get().(map[common.Address]*big.Int) + s.parallel.codeReadsInSlot = addressToBytesPool.Get().(map[common.Address][]byte) + s.parallel.codeHashReadsInSlot = addressToHashPool.Get().(map[common.Address]common.Hash) + s.parallel.codeChangesInSlot = addressToStructPool.Get().(map[common.Address]struct{}) + s.parallel.kvChangesInSlot = addressToStateKeysPool.Get().(map[common.Address]StateKeys) + s.parallel.kvReadsInSlot = addressToStoragePool.Get().(map[common.Address]Storage) + s.parallel.addrStateReadsInSlot = addressToBoolPool.Get().(map[common.Address]bool) + s.parallel.addrStateChangesInSlot = addressToBoolPool.Get().(map[common.Address]bool) + s.parallel.addrSnapDestructsReadsInSlot = addressToBoolPool.Get().(map[common.Address]bool) + s.parallel.createdObjectRecord = addressToStructPool.Get().(map[common.Address]struct{}) + s.parallel.needsRedo = false + s.parallel.useDAG = false + s.parallel.conflictCheckStateObjectCache = nil + s.parallel.conflictCheckKVReadCache = nil } diff --git a/core/state/statedb.go b/core/state/statedb.go index ea6947ab00..92439dc361 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -268,9 +268,8 @@ type StateDB struct { AccountDeleted int StorageDeleted int - isParallel bool - parallel ParallelState // to keep all the parallel execution elements - parallelDBManager *ParallelDBManager + isParallel bool + parallel ParallelState // to keep all the parallel execution elements // Testing hooks onCommit func(states *triestate.Set) // Hook invoked when commit is performed } @@ -1311,8 +1310,8 @@ func NewEmptySlotDB() *ParallelStateDB { } // CopyForSlot copy all the basic fields, initialize the memory ones -func (s *StateDB) CopyForSlot() *ParallelStateDB { - state := s.parallelDBManager.allocate() +func (s *StateDB) CopyForSlot(parallelDBManager *ParallelDBManager) *ParallelStateDB { + state := parallelDBManager.allocate() state.db = s.db s.preimages = make(map[common.Hash][]byte, len(s.preimages)) @@ -2654,14 +2653,6 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip return s } -func (s *StateDB) CreateParallelDBManager(txCount int) { - // if enableDAG, it is high likely no conflict and hence no re-execution - // allocate the txCount of slotDBs to use. - if s.parallelDBManager == nil { - s.parallelDBManager = NewParallelDBManager(txCount, NewEmptySlotDB) - } -} - // NewParallelDBManager creates a new ParallelDBManager with the specified number of instance func NewParallelDBManager(initialCount int, newFunc func() *ParallelStateDB) *ParallelDBManager { manager := &ParallelDBManager{ @@ -2701,5 +2692,7 @@ func (m *ParallelDBManager) allocate() *ParallelStateDB { } func (m *ParallelDBManager) reclaim(s *ParallelStateDB) { + m.mutex.Lock() + defer m.mutex.Unlock() m.pool.PushBack(s) } diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 7de877b6b1..fcf8ad685b 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1205,8 +1205,8 @@ func TestSuicide(t *testing.T) { unconfirmedDBs := new(sync.Map) state.PrepareForParallel() - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) addr := common.BytesToAddress([]byte("so")) slotDb.SetBalance(addr, big.NewInt(1)) @@ -1240,8 +1240,8 @@ func TestSetAndGetState(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) unconfirmedDBs := new(sync.Map) state.PrepareForParallel() - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) slotDb.SetState(addr, common.BytesToHash([]byte("test key")), common.BytesToHash([]byte("test store"))) if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; !ok { @@ -1278,8 +1278,8 @@ func TestSetAndGetCode(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) if _, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr]; ok { t.Fatalf("address should not exist in dirtiedStateObjectsInSlot") } @@ -1314,8 +1314,8 @@ func TestGetCodeSize(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) slotDb.SetCode(addr, []byte("test code")) codeSize := slotDb.GetCodeSize(addr) @@ -1337,8 +1337,8 @@ func TestGetCodeHash(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) slotDb.SetCode(addr, []byte("test code")) @@ -1363,8 +1363,8 @@ func TestSetNonce(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) slotDb.SetNonce(addr, 2) oldNonce := state.GetNonce(addr) @@ -1390,8 +1390,8 @@ func TestSetAndGetBalance(t *testing.T) { state.SetBalance(addr, big.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) slotDb.SetBalance(addr, big.NewInt(2)) @@ -1427,8 +1427,8 @@ func TestSubBalance(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) slotDb.SubBalance(addr, big.NewInt(1)) oldBalance := state.GetBalance(addr) @@ -1462,8 +1462,8 @@ func TestAddBalance(t *testing.T) { state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) slotDb.AddBalance(addr, big.NewInt(1)) oldBalance := state.GetBalance(addr) @@ -1498,8 +1498,8 @@ func TestEmpty(t *testing.T) { state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) empty := slotDb.Empty(addr) if empty { @@ -1519,8 +1519,8 @@ func TestExist(t *testing.T) { state.SetBalance(addr, big.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - slotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(1, NewEmptySlotDB) + slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) exist := slotDb.Exist(addr) if !exist { @@ -1538,10 +1538,10 @@ func TestMergeSlotDB(t *testing.T) { state, _ := New(common.Hash{}, db, nil) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) - state.CreateParallelDBManager(1) - oldSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + manager := NewParallelDBManager(2, NewEmptySlotDB) + oldSlotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - newSlotDb := NewSlotDB(state, 0, 0, unconfirmedDBs, false) + newSlotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) addr := testAddress newSlotDb.SetBalance(addr, big.NewInt(2)) From fc50e31a237d59b699b2ea8e484ff85834f59ffe Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:14:38 +0800 Subject: [PATCH 62/72] pevm-opt: lock free localstateObjects (#167) --- core/blockchain.go | 2 ++ core/state/parallel_statedb.go | 11 +++++++++-- core/state/statedb.go | 27 ++++++++++++++++++++------- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 71ae310e66..c43b7a9f69 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1913,6 +1913,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) log.Debug("Disable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) } else { bc.UseParallelProcessor() + log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) + } } // If we have a followup block, run that against the current state to pre-cache diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 46253d5cec..1d9a7bf0ae 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -139,6 +139,11 @@ func NewSlotDB(db *StateDB, txIndex int, baseTxIndex int, manager *ParallelDBMan } func (s *ParallelStateDB) PutSyncPool(parallelDBManager *ParallelDBManager) { + for key := range s.parallel.locatStateObjects { + delete(s.parallel.locatStateObjects, key) + } + addressToStateObjectsPool.Put(s.parallel.locatStateObjects) + for key := range s.parallel.codeReadsInSlot { delete(s.parallel.codeReadsInSlot, key) } @@ -266,7 +271,7 @@ func (s *ParallelStateDB) getStateObject(addr common.Address) *stateObject { func (s *ParallelStateDB) storeStateObj(addr common.Address, stateObject *stateObject) { // The object could be created in SlotDB, if it got the object from DB and // update it to the `s.parallel.stateObjects` - s.parallel.stateObjects.Store(addr, stateObject) + s.parallel.locatStateObjects[addr] = stateObject } func (s *ParallelStateDB) getStateObjectNoSlot(addr common.Address) *stateObject { @@ -1860,7 +1865,8 @@ func (s *ParallelStateDB) reset() { s.parallel.isSlotDB = true s.parallel.SlotIndex = -1 - s.parallel.stateObjects = &StateObjectSyncMap{} + s.parallel.stateObjects = nil + s.parallel.locatStateObjects = nil s.parallel.baseStateDB = nil s.parallel.baseTxIndex = -1 s.parallel.dirtiedStateObjectsInSlot = addressToStateObjectsPool.Get().(map[common.Address]*stateObject) @@ -1869,6 +1875,7 @@ func (s *ParallelStateDB) reset() { s.parallel.nonceReadsInSlot = addressToUintPool.Get().(map[common.Address]uint64) s.parallel.balanceChangesInSlot = addressToStructPool.Get().(map[common.Address]struct{}) s.parallel.balanceReadsInSlot = balancePool.Get().(map[common.Address]*big.Int) + s.parallel.locatStateObjects = addressToStateObjectsPool.Get().(map[common.Address]*stateObject) s.parallel.codeReadsInSlot = addressToBytesPool.Get().(map[common.Address][]byte) s.parallel.codeHashReadsInSlot = addressToHashPool.Get().(map[common.Address]common.Hash) s.parallel.codeChangesInSlot = addressToStructPool.Get().(map[common.Address]struct{}) diff --git a/core/state/statedb.go b/core/state/statedb.go index 92439dc361..7b959f4440 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -84,7 +84,7 @@ func (s *StateObjectSyncMap) StoreStateObject(addr common.Address, stateObject * func (s *StateDB) loadStateObj(addr common.Address) (*stateObject, bool) { if s.isParallel { if s.parallel.isSlotDB { - if ret, ok := s.parallel.stateObjects.LoadStateObject(addr); ok { + if ret, ok := s.parallel.locatStateObjects[addr]; ok { return ret, ok } else { ret, ok := s.parallel.baseStateDB.loadStateObj(addr) @@ -102,7 +102,11 @@ func (s *StateDB) loadStateObj(addr common.Address) (*stateObject, bool) { // storeStateObj is the entry for storing state object to stateObjects in StateDB or stateObjects in parallel func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { if s.isParallel { - s.parallel.stateObjects.StoreStateObject(addr, stateObject) + if s.parallel.isSlotDB { + s.parallel.locatStateObjects[addr] = stateObject + } else { + s.parallel.stateObjects.StoreStateObject(addr, stateObject) + } } else { s.stateObjects[addr] = stateObject } @@ -111,6 +115,9 @@ func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { // deleteStateObj is the entry for deleting state object to stateObjects in StateDB or stateObjects in parallel func (s *StateDB) deleteStateObj(addr common.Address) { if s.isParallel { + if s.parallel.isSlotDB { + delete(s.parallel.locatStateObjects, addr) + } s.parallel.stateObjects.Delete(addr) } else { delete(s.stateObjects, addr) @@ -122,7 +129,8 @@ type ParallelState struct { isSlotDB bool // denotes StateDB is used in slot, we will try to remove it SlotIndex int // for debug // stateObjects holds the state objects in the base slot db - stateObjects *StateObjectSyncMap + stateObjects *StateObjectSyncMap + locatStateObjects map[common.Address]*stateObject baseStateDB *StateDB // for parallel mode, there will be a base StateDB in dispatcher routine. baseTxIndex int // slotDB is created base on this tx index. @@ -968,9 +976,13 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { func (s *StateDB) setStateObject(object *stateObject) { if s.isParallel { - // When a state object is stored into s.parallel.stateObjects, - // it belongs to base StateDB, it is confirmed and valid. - s.parallel.stateObjects.Store(object.address, object) + if s.parallel.isSlotDB { + s.parallel.locatStateObjects[object.address] = object + } else { + // When a state object is stored into s.parallel.stateObjects, + // it belongs to base StateDB, it is confirmed and valid. + s.parallel.stateObjects.Store(object.address, object) + } } else { s.stateObjects[object.Address()] = object } @@ -1266,7 +1278,8 @@ func NewEmptySlotDB() *ParallelStateDB { // // We are not do simple copy (lightweight pointer copy) as the stateObject can be accessed by different thread. - stateObjects: &StateObjectSyncMap{}, // s.parallel.stateObjects, + stateObjects: nil, /* The parallel execution will not use this field, except the base DB */ + locatStateObjects: addressToStateObjectsPool.Get().(map[common.Address]*stateObject), codeReadsInSlot: addressToBytesPool.Get().(map[common.Address][]byte), codeHashReadsInSlot: addressToHashPool.Get().(map[common.Address]common.Hash), codeChangesInSlot: addressToStructPool.Get().(map[common.Address]struct{}), From db146bca7aeddce5d0a3a9b3c744187c409818b7 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:45:26 +0800 Subject: [PATCH 63/72] PEVM-fix: assesslist append and optimize mergeSlotDB (#168) --- core/state/access_list.go | 33 +++++++++++++++++++++++++++++++++ core/state/state_object.go | 2 +- core/state/statedb.go | 3 ++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/core/state/access_list.go b/core/state/access_list.go index 4194691345..942829787a 100644 --- a/core/state/access_list.go +++ b/core/state/access_list.go @@ -134,3 +134,36 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { func (al *accessList) DeleteAddress(address common.Address) { delete(al.addresses, address) } + +// Copy creates an independent copy of an accessList. +func (dest *accessList) Append(src *accessList) *accessList { + for addr, sIdx := range src.addresses { + if i, present := dest.addresses[addr]; present { + // dest already has addr. + if sIdx >= 0 { + // has slot in list + if i == -1 { + dest.addresses[addr] = len(dest.slots) + slotmap := src.slots[sIdx] + dest.slots = append(dest.slots, slotmap) + } else { + slotmap := src.slots[sIdx] + for hash := range slotmap { + if _, ok := dest.slots[i][hash]; !ok { + dest.slots[i][hash] = struct{}{} + } + } + } + } + } else { + // dest doesn't have the address + dest.addresses[addr] = -1 + if sIdx >= 0 { + dest.addresses[addr] = len(dest.slots) + slotmap := src.slots[sIdx] + dest.slots = append(dest.slots, slotmap) + } + } + } + return dest +} diff --git a/core/state/state_object.go b/core/state/state_object.go index d2f988c6c5..ac0b95dac9 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -1004,7 +1004,7 @@ func (s *stateObject) GetCommittedStateNoUpdate(key common.Hash) common.Hash { func (s *stateObject) fixUpOriginAndResetPendingStorage() { if s.db.isParallel && s.db.parallel.isSlotDB { mainDB := s.db.parallel.baseStateDB - origObj := mainDB.getStateObjectNoUpdate(s.address) + origObj := mainDB.getStateObject(s.address) s.storageRecordsLock.Lock() if origObj != nil && origObj.originStorage.Length() != 0 { // There can be racing issue with CopyForSlot/LightCopy diff --git a/core/state/statedb.go b/core/state/statedb.go index 7b959f4440..e814343bf9 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2652,8 +2652,9 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip for hash, preimage := range slotDb.preimages { s.preimages[hash] = preimage } + if s.accessList != nil && slotDb.accessList != nil { - s.accessList = slotDb.accessList.Copy() + s.accessList.Append(slotDb.accessList) } for k := range slotDb.snapDestructs { From d03c21811a6f5793083457194a049ae66b1812a9 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Thu, 12 Sep 2024 10:29:15 +0800 Subject: [PATCH 64/72] PEVM-opt: parallel Txs Prepare (#176) --- core/parallel_state_processor.go | 181 ++++++++++++++++++++++++------- 1 file changed, 144 insertions(+), 37 deletions(-) diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 9b742da649..1e9f75717c 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -1,6 +1,7 @@ package core import ( + "context" "errors" "fmt" "github.com/ethereum/go-ethereum/metrics" @@ -201,7 +202,7 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { p.inConfirmStage2 = false statedb.PrepareForParallel() - p.allTxReqs = make([]*ParallelTxRequest, 0, txNum) + p.allTxReqs = make([]*ParallelTxRequest, txNum) for _, slot := range p.slotState { slot.pendingTxReqList = make([]*ParallelTxRequest, 0) @@ -872,48 +873,110 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat p.commonTxs = make([]*types.Transaction, 0, txNum) p.receipts = make([]*types.Receipt, 0, txNum) - for i, tx := range allTxs { - // can be moved it into slot for efficiency, but signer is not concurrent safe - // Parallel Execution 1.0&2.0 is for full sync mode, Nonce PreCheck is not necessary - // And since we will do out-of-order execution, the Nonce PreCheck could fail. - // We will disable it and leave it to Parallel 3.0 which is for validator mode - msg, err := TransactionToMessage(tx, signer, header.BaseFee) - if err != nil { - return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) - } + parallelNum := p.parallelNum + + if txNum > parallelNum*2 && txNum >= 4 { + var wg sync.WaitGroup + errChan := make(chan error) - // find the latestDepTx from TxDAG or latestExcludedTx - latestDepTx := -1 - if dep := types.TxDependency(txDAG, i); len(dep) > 0 { - latestDepTx = int(dep[len(dep)-1]) + begin := 0 + // first try to find latestExcludeTx, as for opBNB, they are the first consecutive txs. + for idx := 0; idx < len(allTxs); idx++ { + if txDAG != nil && txDAG.TxDep(idx).CheckFlag(types.ExcludedTxFlag) { + if err := p.transferTxs(allTxs, idx, signer, block, statedb, cfg, usedGas, latestExcludedTx); err != nil { + return nil, nil, 0, err + } + latestExcludedTx = idx + } else { + begin = idx + break + } } - if latestDepTx < latestExcludedTx { - latestDepTx = latestExcludedTx + + // Create a cancelable context + ctx, cancel := context.WithCancel(context.Background()) + + // Create a pool of workers + transactionsPerWorker := (len(allTxs) - begin) / parallelNum + + // Create a pool of workers + for i := 0; i < parallelNum; i++ { + wg.Add(1) + go func(start, end int, signer types.Signer, blk *types.Block, sdb *state.StateDB, cfg vm.Config, usedGas *uint64) { + defer wg.Done() + for j := start; j < end; j++ { + select { + case <-ctx.Done(): + return // Exit the goroutine if the context is canceled + default: + if err := p.transferTxs(allTxs, j, signer, block, statedb, cfg, usedGas, latestExcludedTx); err != nil { + errChan <- err + cancel() // Cancel the context to stop other goroutines + return + } + } + } + }(begin+i*transactionsPerWorker, begin+(i+1)*transactionsPerWorker, signer, block, statedb, cfg, usedGas) } - // parallel start, wrap an exec message, which will be dispatched to a slot - txReq := &ParallelTxRequest{ - txIndex: i, - baseStateDB: statedb, - staticSlotIndex: -1, - tx: tx, - gasLimit: block.GasLimit(), // gp.Gas(). - msg: msg, - block: block, - vmConfig: cfg, - usedGas: usedGas, - curTxChan: make(chan int, 1), - runnable: 1, // 0: not runnable, 1: runnable - useDAG: txDAG != nil, + // Distribute any remaining transactions + for i := begin + parallelNum*transactionsPerWorker; i < len(allTxs); i++ { + if err := p.transferTxs(allTxs, i, signer, block, statedb, cfg, usedGas, latestExcludedTx); err != nil { + errChan <- err + cancel() // Cancel the context to stop other goroutines + } } - txReq.executedNum.Store(0) - txReq.conflictIndex.Store(-2) - if latestDepTx >= 0 { - txReq.conflictIndex.Store(int32(latestDepTx)) + + // Wait for all workers to finish and handle errors + go func() { + wg.Wait() + close(errChan) + }() + + for err := range errChan { + return nil, nil, 0, err } - p.allTxReqs = append(p.allTxReqs, txReq) - if txDAG != nil && txDAG.TxDep(i).CheckFlag(types.ExcludedTxFlag) { - latestExcludedTx = i + // + } else { + for i, tx := range allTxs { + msg, err := TransactionToMessage(tx, signer, header.BaseFee) + if err != nil { + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + } + + // find the latestDepTx from TxDAG or latestExcludedTx + latestDepTx := -1 + if dep := types.TxDependency(txDAG, i); len(dep) > 0 { + latestDepTx = int(dep[len(dep)-1]) + } + if latestDepTx < latestExcludedTx { + latestDepTx = latestExcludedTx + } + + // parallel start, wrap an exec message, which will be dispatched to a slot + txReq := &ParallelTxRequest{ + txIndex: i, + baseStateDB: statedb, + staticSlotIndex: -1, + tx: tx, + gasLimit: block.GasLimit(), // gp.Gas(). + msg: msg, + block: block, + vmConfig: cfg, + usedGas: usedGas, + curTxChan: make(chan int, 1), + runnable: 1, // 0: not runnable, 1: runnable + useDAG: txDAG != nil, + } + txReq.executedNum.Store(0) + txReq.conflictIndex.Store(-2) + if latestDepTx >= 0 { + txReq.conflictIndex.Store(int32(latestDepTx)) + } + p.allTxReqs[i] = txReq + if txDAG != nil && txDAG.TxDep(i).CheckFlag(types.ExcludedTxFlag) { + latestExcludedTx = i + } } } allTxCount := len(p.allTxReqs) @@ -1064,6 +1127,50 @@ func (p *ParallelStateProcessor) handlePendingResultLoop() { } } +func (p *ParallelStateProcessor) transferTxs(txs types.Transactions, i int, signer types.Signer, block *types.Block, statedb *state.StateDB, cfg vm.Config, usedGas *uint64, latestExcludedTx int) error { + if p.allTxReqs[i] != nil { + return nil + } + tx := txs[i] + txDAG := cfg.TxDAG + msg, err := TransactionToMessage(tx, signer, block.Header().BaseFee) + if err != nil { + return fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + } + + // find the latestDepTx from TxDAG or latestExcludedTx + latestDepTx := -1 + if dep := types.TxDependency(txDAG, i); len(dep) > 0 { + latestDepTx = int(dep[len(dep)-1]) + } + if latestDepTx < latestExcludedTx { + latestDepTx = latestExcludedTx + } + + // parallel start, wrap an exec message, which will be dispatched to a slot + txReq := &ParallelTxRequest{ + txIndex: i, + baseStateDB: statedb, + staticSlotIndex: -1, + tx: tx, + gasLimit: block.GasLimit(), // gp.Gas(). + msg: msg, + block: block, + vmConfig: cfg, + usedGas: usedGas, + curTxChan: make(chan int, 1), + runnable: 1, // 0: not runnable, 1: runnable + useDAG: txDAG != nil, + } + txReq.executedNum.Store(0) + txReq.conflictIndex.Store(-2) + if latestDepTx >= 0 { + txReq.conflictIndex.Store(int32(latestDepTx)) + } + p.allTxReqs[i] = txReq + return nil +} + func applyTransactionStageExecution(msg *Message, gp *GasPool, statedb *state.ParallelStateDB, evm *vm.EVM, delayGasFee bool) (*vm.EVM, *ExecutionResult, error) { // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) From f8f5dc4b6fc49ae18e0b5ae7acc2b39f5326a39e Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 25 Sep 2024 20:10:11 +0800 Subject: [PATCH 65/72] fix issue after rebase --- core/state/journal.go | 2 +- core/state/parallel_statedb.go | 2 +- core/state/state_object.go | 11 ++- core/state/statedb.go | 119 ++++++++++++++++----------------- core/state_processor_test.go | 1 + core/state_transition.go | 9 ++- core/vm/interface.go | 1 + 7 files changed, 77 insertions(+), 68 deletions(-) diff --git a/core/state/journal.go b/core/state/journal.go index 38ea922292..d436dbd5ac 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -177,7 +177,7 @@ func (ch resetObjectChange) revert(dber StateDBer) { s.parallel.dirtiedStateObjectsInSlot[ch.prev.address] = ch.prev } else { // ch.prev was got from main DB, put it back to main DB. - s.storeStateObj(ch.prev.address, ch.prev) + s.setStateObject(ch.prev) } if !ch.prevdestruct { diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 1d9a7bf0ae..4aff3ce5ea 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1874,7 +1874,7 @@ func (s *ParallelStateDB) reset() { s.parallel.nonceChangesInSlot = addressToStructPool.Get().(map[common.Address]struct{}) s.parallel.nonceReadsInSlot = addressToUintPool.Get().(map[common.Address]uint64) s.parallel.balanceChangesInSlot = addressToStructPool.Get().(map[common.Address]struct{}) - s.parallel.balanceReadsInSlot = balancePool.Get().(map[common.Address]*big.Int) + s.parallel.balanceReadsInSlot = balancePool.Get().(map[common.Address]*uint256.Int) s.parallel.locatStateObjects = addressToStateObjectsPool.Get().(map[common.Address]*stateObject) s.parallel.codeReadsInSlot = addressToBytesPool.Get().(map[common.Address][]byte) s.parallel.codeHashReadsInSlot = addressToHashPool.Get().(map[common.Address]common.Hash) diff --git a/core/state/state_object.go b/core/state/state_object.go index ac0b95dac9..0c2d326799 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -604,7 +604,7 @@ func (s *stateObject) updateTrie() (Trie, error) { go func() { defer wg.Done() maindb.StorageMux.Lock() - defer maindb.StorageMux.Unlock() + // The snapshot storage map for the object storage = maindb.storages[s.addrHash] if storage == nil { @@ -617,6 +617,7 @@ func (s *stateObject) updateTrie() (Trie, error) { origin = make(map[common.Hash][]byte) maindb.storagesOrigin[s.address] = origin } + maindb.StorageMux.Unlock() for key, value := range dirtyStorage { khash := crypto.HashData(hasher, key[:]) @@ -654,6 +655,14 @@ func (s *stateObject) updateTrie() (Trie, error) { // updateRoot flushes all cached storage mutations to trie, recalculating the // new storage trie root. func (s *stateObject) updateRoot() { + + // If node runs in no trie mode, set root to empty + defer func() { + if s.db.db.NoTries() { + s.data.Root = types.EmptyRootHash + } + }() + // Flush cached storage mutations into trie, short circuit if any error // is occurred or there is not change in the trie. s.db.trieParallelLock.Lock() diff --git a/core/state/statedb.go b/core/state/statedb.go index e814343bf9..3a45fde021 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -493,11 +493,9 @@ func (s *StateDB) GetBalance(addr common.Address) (ret *uint256.Int) { defer func() { s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), ret) }() - - object := s.getStateObject(addr) - - if object != nil { - return object.Balance() + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.Balance() } return common.U2560 } @@ -507,9 +505,9 @@ func (s *StateDB) GetNonce(addr common.Address) (ret uint64) { defer func() { s.RecordRead(types.AccountStateKey(addr, types.AccountNonce), ret) }() - object := s.getStateObject(addr) - if object != nil { - return object.Nonce() + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.Nonce() } return 0 } @@ -517,9 +515,9 @@ func (s *StateDB) GetNonce(addr common.Address) (ret uint64) { // GetStorageRoot retrieves the storage root from the given address or empty // if object not found. func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash { - object := s.getStateObject(addr) - if object != nil { - return object.Root() + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.Root() } return common.Hash{} } @@ -538,9 +536,9 @@ func (s *StateDB) GetCode(addr common.Address) []byte { defer func() { s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), s.GetCodeHash(addr)) }() - object := s.getStateObject(addr) - if object != nil { - return object.Code() + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.Code() } return nil } @@ -549,9 +547,9 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { defer func() { s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), s.GetCodeHash(addr)) }() - object := s.getStateObject(addr) - if object != nil { - return object.CodeSize() + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.CodeSize() } return 0 } @@ -564,8 +562,8 @@ func (s *StateDB) GetCodeHash(addr common.Address) (ret common.Hash) { defer func() { s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), ret.Bytes()) }() - object := s.getStateObject(addr) - if object == nil { + stateObject := s.getStateObject(addr) + if stateObject == nil { return common.Hash{} } return common.Hash{} @@ -576,9 +574,9 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) (ret common.Ha defer func() { s.RecordRead(types.StorageStateKey(addr, hash), ret) }() - object := s.getStateObject(addr) - if object != nil { - return object.GetState(hash) + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.GetState(hash) } return common.Hash{} } @@ -588,9 +586,9 @@ func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) (ret defer func() { s.RecordRead(types.StorageStateKey(addr, hash), ret) }() - object := s.getStateObject(addr) - if object != nil { - return object.GetCommittedState(hash) + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.GetCommittedState(hash) } return common.Hash{} } @@ -601,9 +599,9 @@ func (s *StateDB) Database() Database { } func (s *StateDB) HasSelfDestructed(addr common.Address) bool { - object := s.getStateObject(addr) - if object != nil { - return object.selfDestructed + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.selfDestructed } return false } @@ -614,10 +612,10 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool { // AddBalance adds amount to the account associated with addr. func (s *StateDB) AddBalance(addr common.Address, amount *uint256.Int) { - object := s.getOrNewStateObject(addr) - if object != nil { - s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), object.Balance()) - object.AddBalance(amount) + stateObject := s.getOrNewStateObject(addr) + if stateObject != nil { + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), stateObject.Balance()) + stateObject.AddBalance(amount) return } s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), common.U2560) @@ -625,10 +623,10 @@ func (s *StateDB) AddBalance(addr common.Address, amount *uint256.Int) { // SubBalance subtracts amount from the account associated with addr. func (s *StateDB) SubBalance(addr common.Address, amount *uint256.Int) { - object := s.getOrNewStateObject(addr) - if object != nil { - s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), object.Balance()) - object.SubBalance(amount) + stateObject := s.getOrNewStateObject(addr) + if stateObject != nil { + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), stateObject.Balance()) + stateObject.SubBalance(amount) return } s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), common.U2560) @@ -700,15 +698,15 @@ func (s *StateDB) SelfDestruct(addr common.Address) { prevbalance: new(uint256.Int).Set(stateObject.Balance()), }) stateObject.markSelfdestructed() - stateObject.data.Balance = new(uint256.Int) + stateObject.setBalance(new(uint256.Int)) } func (s *StateDB) Selfdestruct6780(addr common.Address) { - object := s.getStateObject(addr) - if object == nil { + stateObject := s.getStateObject(addr) + if stateObject == nil { return } - if object.created { + if stateObject.created { s.SelfDestruct(addr) } } @@ -746,10 +744,12 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common // updateStateObject writes the given object to the trie. func (s *StateDB) updateStateObject(obj *stateObject) { - if !(s.isParallel && s.parallel.isSlotDB) { - obj.storageRecordsLock.Lock() - defer obj.storageRecordsLock.Unlock() - } + /* + if !(s.isParallel && s.parallel.isSlotDB) { + obj.storageRecordsLock.Lock() + defer obj.storageRecordsLock.Unlock() + } + */ if !s.noTrie { // Track the amount of time wasted on updating the account from the trie if metrics.EnabledExpensive { @@ -799,7 +799,6 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { } // Delete the account from the trie addr := obj.Address() - if err := s.trie.DeleteAccount(addr); err != nil { s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) } @@ -809,8 +808,7 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { // the object is not found or was deleted in this execution context. If you need // to differentiate between non-existent/just-deleted, use getDeletedStateObject. func (s *StateDB) getStateObject(addr common.Address) *stateObject { - obj := s.getDeletedStateObject(addr) - if obj != nil && !obj.deleted { + if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { return obj } return nil @@ -901,7 +899,7 @@ func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *t Root: common.BytesToHash(acc.Root), } if len(data.CodeHash) == 0 { - data.CodeHash = emptyCodeHash + data.CodeHash = types.EmptyCodeHash.Bytes() } if data.Root == (common.Hash{}) { data.Root = types.EmptyRootHash @@ -970,7 +968,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { } // Insert into the live set obj := newObject(s, s.isParallel, addr, data) - s.storeStateObj(addr, obj) + s.setStateObject(obj) return obj } @@ -992,7 +990,7 @@ func (s *StateDB) setStateObject(object *stateObject) { func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { stateObject := s.getStateObject(addr) if stateObject == nil { - stateObject = s.createObject(addr) + stateObject, _ = s.createObject(addr) } return stateObject } @@ -1012,8 +1010,8 @@ func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { // a.if it is not in SlotDB, `revert` will remove it from the SlotDB // b.if it is existed in SlotDB, `revert` will recover to the `prev` in SlotDB // c.as `snapDestructs` it is the same -func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { - prev := s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! +func (s *StateDB) createObject(addr common.Address) (newobj *stateObject, prev *stateObject) { + prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! newobj = newObject(s, s.isParallel, addr, nil) if prev == nil { s.journal.append(createObjectChange{account: &addr}) @@ -1054,7 +1052,10 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject) { newobj.created = true s.setStateObject(newobj) - return newobj + if prev != nil && prev.deleted { + return newobj, prev + } + return newobj, nil } // CreateAccount explicitly creates a state object. If a state object with the address @@ -1071,9 +1072,10 @@ func (s *StateDB) CreateAccount(addr common.Address) { // no matter it is got from dirty, unconfirmed or main DB // if addr not exist, preBalance will be common.U2560, it is same as new(big.Int) which // is the value newObject(), - preBalance := s.GetBalance(addr) - newObj := s.createObject(addr) - newObj.setBalance(new(uint256.Int).Set(preBalance)) // new big.Int for newObj + newObj, prev := s.createObject(addr) + if prev != nil { + newObj.setBalance(prev.Balance()) + } } // CopyWithMvStates will copy state with MVStates @@ -1417,14 +1419,12 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // Thus, we can safely ignore it here continue } - if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { obj.deleted = true // We need to maintain account deletions explicitly (will remain // set indefinitely). Note only the first occurred self-destruct // event is tracked. - // The finalise of stateDB is called at verify & commit phase, which is global, no need to acquire the lock. if _, ok := s.stateObjectsDestruct[obj.address]; !ok { s.stateObjectsDestruct[obj.address] = obj.origin } @@ -1595,7 +1595,7 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { // so it should be save to clear pending here. // otherwise there can be a case that the deleted object get ignored and processes as live object in verify phase. - if /*s.isParallel == false &&*/ len(s.stateObjectsPending) > 0 { + if len(s.stateObjectsPending) > 0 { s.stateObjectsPending = make(map[common.Address]struct{}) } // Track the amount of time wasted on hashing the account trie @@ -1788,7 +1788,6 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A return incomplete, nil } - // Commit phase, no need to acquire lock. for addr, prev := range s.stateObjectsDestruct { // The original account was non-existing, and it's marked as destructed // in the scope of block. It can be case (a) or (b). diff --git a/core/state_processor_test.go b/core/state_processor_test.go index e419b2b962..77efaede58 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -111,6 +111,7 @@ func TestStateProcessorErrors(t *testing.T) { } return tx } + { // Tests against a 'recent' chain definition var ( db = rawdb.NewMemoryDatabase() diff --git a/core/state_transition.go b/core/state_transition.go index 2bce4e6a56..5b87b84fdd 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -563,7 +563,6 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { ReturnData: ret, }, nil } - // Note for deposit tx there is no ETH refunded for unused gas, but that's taken care of by the fact that gasPrice // is always 0 for deposit tx. So calling refundGas will ensure the gasUsed accounting is correct without actually // changing the sender's balance @@ -626,9 +625,9 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { } if st.msg.GasPrice.Cmp(big.NewInt(0)) == 0 && st.evm.ChainConfig().IsWright(st.evm.Context.Time) { if st.delayGasFee { - baseFee = uint256.NewInt(0) + l1Fee = uint256.NewInt(0) } else { - st.state.AddBalance(params.OptimismBaseFeeRecipient, uint256.NewInt(0)) + st.state.AddBalance(params.OptimismL1FeeRecipient, uint256.NewInt(0)) } } else if l1Cost := st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time); l1Cost != nil { amtU256, overflow = uint256.FromBig(l1Cost) @@ -636,9 +635,9 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { return nil, fmt.Errorf("optimism l1 cost overflows U256: %d", l1Cost) } if st.delayGasFee { - baseFee = amtU256 + l1Fee = amtU256 } else { - st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256) + st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256) } } } diff --git a/core/vm/interface.go b/core/vm/interface.go index eecf819038..537e300b97 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -28,6 +28,7 @@ import ( // StateDB is an EVM database for full state querying. type StateDB interface { CreateAccount(common.Address) + SubBalance(common.Address, *uint256.Int) AddBalance(common.Address, *uint256.Int) GetBalance(common.Address) *uint256.Int From 8c429adf2d3fa7699db4b6d6ea48a1cbecb44a22 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 26 Sep 2024 10:26:47 +0800 Subject: [PATCH 66/72] fix code hash issue after rebase --- core/state/parallel_statedb.go | 4 ++-- core/state/state_object.go | 7 +++---- core/state/statedb.go | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 4aff3ce5ea..a0da320361 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -464,7 +464,7 @@ func (s *ParallelStateDB) Empty(addr common.Address) bool { return false } codeHash := s.GetCodeHash(addr) - return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty + return bytes.Equal(codeHash.Bytes(), types.EmptyCodeHash.Bytes()) // code is empty, the object is empty } // 2.Try to get from unconfirmed & main DB // 2.1 Already read before @@ -725,7 +725,7 @@ func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { // wrong 'empty' hash. if dirtyObj != nil { // found one - if dirtyObj.CodeHash() == nil || bytes.Equal(dirtyObj.CodeHash(), emptyCodeHash) { + if dirtyObj.CodeHash() == nil || bytes.Equal(dirtyObj.CodeHash(), types.EmptyCodeHash.Bytes()) { dirtyObj.data.CodeHash = codeHash.Bytes() } } diff --git a/core/state/state_object.go b/core/state/state_object.go index 0c2d326799..1b5059140d 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -35,8 +35,6 @@ import ( "github.com/holiman/uint256" ) -var emptyCodeHash = crypto.Keccak256(nil) - type Code []byte func (c Code) String() string { @@ -230,14 +228,15 @@ func (s *stateObject) empty() bool { // Slot 1 tx 1: sub balance 100, it is empty and deleted // Slot 0 tx 2: GetNonce, lightCopy based on main DB(balance = 100) , not empty - if s.dbItf.GetBalance(s.address).Sign() != 0 { // check balance first, since it is most likely not zero + if !s.dbItf.GetBalance(s.address).IsZero() { // check balance first, since it is most likely not zero + return false } if s.dbItf.GetNonce(s.address) != 0 { return false } codeHash := s.dbItf.GetCodeHash(s.address) - return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty + return bytes.Equal(codeHash.Bytes(), types.EmptyCodeHash.Bytes()) // code is empty, the object is empty } // newObject creates a state object. diff --git a/core/state/statedb.go b/core/state/statedb.go index 3a45fde021..71491e587f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -563,8 +563,8 @@ func (s *StateDB) GetCodeHash(addr common.Address) (ret common.Hash) { s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), ret.Bytes()) }() stateObject := s.getStateObject(addr) - if stateObject == nil { - return common.Hash{} + if stateObject != nil { + return common.BytesToHash(stateObject.CodeHash()) } return common.Hash{} } From c8b6a41c1e8810def4dd3c147e4293d8ec79d72d Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 26 Sep 2024 10:26:47 +0800 Subject: [PATCH 67/72] log --- core/state/state_object.go | 8 ++++++++ core/state/statedb.go | 26 ++++++++++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 1b5059140d..93cbc44858 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -19,6 +19,7 @@ package state import ( "bytes" "fmt" + "github.com/ethereum/go-ethereum/log" "io" "sync" "time" @@ -482,6 +483,9 @@ func (s *stateObject) finalise(prefetch bool) { s.dirtyNonce = nil } if s.dirtyBalance != nil { + if s.address.Hex() == "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4" { + log.Debug("finalise - set dirtyBalance", "addr", s.address.Hex(), "balance", s.data.Balance, "dirtyBalance", s.dirtyBalance) + } s.data.Balance = s.dirtyBalance s.dirtyBalance = nil } @@ -676,6 +680,7 @@ func (s *stateObject) updateRoot() { defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now()) } s.data.Root = tr.Hash() + log.Debug("updateRoot", "addr", s.address, "data", s.data) } // commit obtains a set of dirty storage trie nodes and updates the account data. @@ -736,6 +741,9 @@ func (s *stateObject) SetBalance(amount *uint256.Int) { } func (s *stateObject) setBalance(amount *uint256.Int) { + if s.address.Hex() == "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4" { + log.Debug("setBalance", "addr", "amount", amount, "s.dirtyBlance", s.dirtyBalance, "s.data.balance", s.data.Balance) + } s.dirtyBalance = amount } diff --git a/core/state/statedb.go b/core/state/statedb.go index 71491e587f..810cdf674d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -744,12 +744,9 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common // updateStateObject writes the given object to the trie. func (s *StateDB) updateStateObject(obj *stateObject) { - /* - if !(s.isParallel && s.parallel.isSlotDB) { - obj.storageRecordsLock.Lock() - defer obj.storageRecordsLock.Unlock() - } - */ + log.Debug("updateStateObject", "addr", obj.address, "data", obj.data, + "object.dirtyNonce", obj.dirtyNonce, + "object.dirtyBalance", obj.dirtyBalance) if !s.noTrie { // Track the amount of time wasted on updating the account from the trie if metrics.EnabledExpensive { @@ -789,6 +786,7 @@ func (s *StateDB) updateStateObject(obj *stateObject) { // deleteStateObject removes the given object from the state trie. func (s *StateDB) deleteStateObject(obj *stateObject) { + log.Debug("deleteStateObject", "addr", obj.address, "data", obj.data) if s.noTrie { return } @@ -1073,6 +1071,9 @@ func (s *StateDB) CreateAccount(addr common.Address) { // if addr not exist, preBalance will be common.U2560, it is same as new(big.Int) which // is the value newObject(), newObj, prev := s.createObject(addr) + if addr.Hex() == "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4" { + log.Debug("CreateAccount - setBalance", "addr", addr.Hex(), "prev", prev) + } if prev != nil { newObj.setBalance(prev.Balance()) } @@ -1466,6 +1467,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // TODO: For parallel SlotDB, IntermediateRootForSlot is used, need to clean up this method. func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Finalise all the dirty storage states and write them into the tries + log.Debug("IntermediateRoot", "deleteEmptyObjects", deleteEmptyObjects) s.Finalise(deleteEmptyObjects) s.AccountsIntermediateRoot() return s.StateIntermediateRoot() @@ -1604,8 +1606,10 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { } if s.noTrie { + log.Debug("StateIntermediateRoot", "root noTrie", s.expectedRoot) return s.expectedRoot } else { + log.Debug("StateIntermediateRoot", "root", s.trie.Hash()) return s.trie.Hash() } } @@ -2556,6 +2560,11 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip } else { // Merge the dirtyObject with mainObject if _, balanced := slotDb.parallel.balanceChangesInSlot[addr]; balanced { + if addr.Hex() == "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4" { + log.Debug("MergeSlotDB", "update MainOBJ balance, origin dirtyBalance", newMainObj.dirtyBalance, + "origin balance", newMainObj.data.Balance, "dirty dirtyBalance", dirtyObj.dirtyBalance, + "dirty.dataBalance", dirtyObj.data.Balance) + } newMainObj.dirtyBalance = dirtyObj.dirtyBalance newMainObj.data.Balance = dirtyObj.data.Balance } @@ -2602,6 +2611,11 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip // to "mainObj.finalise()", just in case that newMainObj.delete == true and somewhere potentially // access the Nonce, balance or codehash later. if _, balanced := slotDb.parallel.balanceChangesInSlot[addr]; balanced { + if addr.Hex() == "0x13f4EA83D0bd40E75C8222255bc855a974568Dd4" { + log.Debug("MergeSlotDB", "update MainOBJ balance, origin dirtyBalance", newMainObj.dirtyBalance, + "origin balance", newMainObj.data.Balance, "dirty dirtyBalance", dirtyObj.dirtyBalance, + "dirty.dataBalance", dirtyObj.data.Balance) + } newMainObj.dirtyBalance = dirtyObj.dirtyBalance newMainObj.data.Balance = dirtyObj.data.Balance } From e66ad25416177882807d7f4d4d8b1bf40a4bbaea Mon Sep 17 00:00:00 2001 From: Sunny Date: Fri, 27 Sep 2024 16:13:02 +0800 Subject: [PATCH 68/72] fix createObject prev --- core/state/statedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 810cdf674d..3f81e7ef5b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1050,7 +1050,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj *stateObject, prev * newobj.created = true s.setStateObject(newobj) - if prev != nil && prev.deleted { + if prev != nil && !prev.deleted { return newobj, prev } return newobj, nil From ba207bc92a36be0256f25d278dc72c0e74755a2a Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 29 Sep 2024 11:06:36 +0800 Subject: [PATCH 69/72] fix: test case issue after rebase --- core/blockchain_test.go | 391 ------------------------------- core/parallel_state_processor.go | 2 +- core/state/statedb_test.go | 44 ++-- 3 files changed, 23 insertions(+), 414 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 99ed302b81..afc664171d 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2726,191 +2726,6 @@ func testReorgToShorterRemovesCanonMappingHeaderChain(t *testing.T, scheme strin } } -func TestTransactionIndices(t *testing.T) { - // Configure and generate a sample block chain - var ( - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - address = crypto.PubkeyToAddress(key.PublicKey) - funds = big.NewInt(100000000000000000) - gspec = &Genesis{ - Config: params.TestChainConfig, - Alloc: GenesisAlloc{address: {Balance: funds}}, - BaseFee: big.NewInt(params.InitialBaseFee), - } - signer = types.LatestSigner(gspec.Config) - ) - _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 128, func(i int, block *BlockGen) { - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key) - if err != nil { - panic(err) - } - block.AddTx(tx) - }) - - check := func(tail *uint64, chain *BlockChain) { - stored := rawdb.ReadTxIndexTail(chain.db) - if tail == nil && stored != nil { - t.Fatalf("Oldest indexded block mismatch, want nil, have %d", *stored) - } - if tail != nil && *stored != *tail { - t.Fatalf("Oldest indexded block mismatch, want %d, have %d", *tail, *stored) - } - if tail != nil { - for i := *tail; i <= chain.CurrentBlock().Number.Uint64(); i++ { - block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) - if block.Transactions().Len() == 0 { - continue - } - for _, tx := range block.Transactions() { - if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index == nil { - t.Fatalf("Miss transaction indice, number %d hash %s", i, tx.Hash().Hex()) - } - } - } - for i := uint64(0); i < *tail; i++ { - block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) - if block.Transactions().Len() == 0 { - continue - } - for _, tx := range block.Transactions() { - if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index != nil { - t.Fatalf("Transaction indice should be deleted, number %d hash %s", i, tx.Hash().Hex()) - } - } - } - } - } - // Init block chain with external ancients, check all needed indices has been indexed. - limit := []uint64{0, 32, 64, 128} - for _, l := range limit { - frdir := t.TempDir() - ancientDb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) - rawdb.WriteAncientBlocks(ancientDb, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) - - l := l - chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) - if err != nil { - t.Fatalf("failed to create tester chain: %v", err) - } - chain.indexBlocks(rawdb.ReadTxIndexTail(ancientDb), 128, make(chan struct{})) - - var tail uint64 - if l != 0 { - tail = uint64(128) - l + 1 - } - check(&tail, chain) - chain.Stop() - ancientDb.Close() - os.RemoveAll(frdir) - } - - // Reconstruct a block chain which only reserves HEAD-64 tx indices - ancientDb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) - defer ancientDb.Close() - - rawdb.WriteAncientBlocks(ancientDb, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) - limit = []uint64{0, 64 /* drop stale */, 32 /* shorten history */, 64 /* extend history */, 0 /* restore all */} - for _, l := range limit { - l := l - chain, err := NewBlockChain(ancientDb, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) - if err != nil { - t.Fatalf("failed to create tester chain: %v", err) - } - var tail uint64 - if l != 0 { - tail = uint64(128) - l + 1 - } - chain.indexBlocks(rawdb.ReadTxIndexTail(ancientDb), 128, make(chan struct{})) - check(&tail, chain) - chain.Stop() - } -} - -func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { - testSkipStaleTxIndicesInSnapSync(t, rawdb.HashScheme) - testSkipStaleTxIndicesInSnapSync(t, rawdb.PathScheme) -} - -func testSkipStaleTxIndicesInSnapSync(t *testing.T, scheme string) { - // Configure and generate a sample block chain - var ( - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - address = crypto.PubkeyToAddress(key.PublicKey) - funds = big.NewInt(100000000000000000) - gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} - signer = types.LatestSigner(gspec.Config) - ) - _, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 128, func(i int, block *BlockGen) { - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, key) - if err != nil { - panic(err) - } - block.AddTx(tx) - }) - - check := func(tail *uint64, chain *BlockChain) { - stored := rawdb.ReadTxIndexTail(chain.db) - if tail == nil && stored != nil { - t.Fatalf("Oldest indexded block mismatch, want nil, have %d", *stored) - } - if tail != nil && *stored != *tail { - t.Fatalf("Oldest indexded block mismatch, want %d, have %d", *tail, *stored) - } - if tail != nil { - for i := *tail; i <= chain.CurrentBlock().Number.Uint64(); i++ { - block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) - if block.Transactions().Len() == 0 { - continue - } - for _, tx := range block.Transactions() { - if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index == nil { - t.Fatalf("Miss transaction indice, number %d hash %s", i, tx.Hash().Hex()) - } - } - } - for i := uint64(0); i < *tail; i++ { - block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) - if block.Transactions().Len() == 0 { - continue - } - for _, tx := range block.Transactions() { - if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index != nil { - t.Fatalf("Transaction indice should be deleted, number %d hash %s", i, tx.Hash().Hex()) - } - } - } - } - } - - ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) - if err != nil { - t.Fatalf("failed to create temp freezer db: %v", err) - } - defer ancientDb.Close() - - // Import all blocks into ancient db, only HEAD-32 indices are kept. - l := uint64(32) - chain, err := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) - if err != nil { - t.Fatalf("failed to create tester chain: %v", err) - } - defer chain.Stop() - - headers := make([]*types.Header, len(blocks)) - for i, block := range blocks { - headers[i] = block.Header() - } - if n, err := chain.InsertHeaderChain(headers); err != nil { - t.Fatalf("failed to insert header %d: %v", n, err) - } - // The indices before ancient-N(32) should be ignored. After that all blocks should be indexed. - if n, err := chain.InsertReceiptChain(blocks, receipts, 64); err != nil { - t.Fatalf("block %d: failed to insert into chain: %v", n, err) - } - tail := uint64(32) - check(&tail, chain) -} - // Benchmarks large blocks with value transfers to non-existing accounts func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) { var ( @@ -4107,212 +3922,6 @@ func testCanonicalHashMarker(t *testing.T, scheme string) { } } -// TestTxIndexer tests the tx indexes are updated correctly. -func TestTxIndexer(t *testing.T) { - var ( - testBankKey, _ = crypto.GenerateKey() - testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) - testBankFunds = big.NewInt(1000000000000000000) - - gspec = &Genesis{ - Config: params.TestChainConfig, - Alloc: GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, - BaseFee: big.NewInt(params.InitialBaseFee), - } - engine = ethash.NewFaker() - nonce = uint64(0) - ) - _, blocks, receipts := GenerateChainWithGenesis(gspec, engine, 128, func(i int, gen *BlockGen) { - tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey) - gen.AddTx(tx) - nonce += 1 - }) - - // verifyIndexes checks if the transaction indexes are present or not - // of the specified block. - verifyIndexes := func(db ethdb.Database, number uint64, exist bool) { - if number == 0 { - return - } - block := blocks[number-1] - for _, tx := range block.Transactions() { - lookup := rawdb.ReadTxLookupEntry(db, tx.Hash()) - if exist && lookup == nil { - t.Fatalf("missing %d %x", number, tx.Hash().Hex()) - } - if !exist && lookup != nil { - t.Fatalf("unexpected %d %x", number, tx.Hash().Hex()) - } - } - } - // verifyRange runs verifyIndexes for a range of blocks, from and to are included. - verifyRange := func(db ethdb.Database, from, to uint64, exist bool) { - for number := from; number <= to; number += 1 { - verifyIndexes(db, number, exist) - } - } - verify := func(db ethdb.Database, expTail uint64) { - tail := rawdb.ReadTxIndexTail(db) - if tail == nil { - t.Fatal("Failed to write tx index tail") - } - if *tail != expTail { - t.Fatalf("Unexpected tx index tail, want %v, got %d", expTail, *tail) - } - if *tail != 0 { - verifyRange(db, 0, *tail-1, false) - } - verifyRange(db, *tail, 128, true) - } - - var cases = []struct { - limitA uint64 - tailA uint64 - limitB uint64 - tailB uint64 - limitC uint64 - tailC uint64 - }{ - { - // LimitA: 0 - // TailA: 0 - // - // all blocks are indexed - limitA: 0, - tailA: 0, - - // LimitB: 1 - // TailB: 128 - // - // block-128 is indexed - limitB: 1, - tailB: 128, - - // LimitB: 64 - // TailB: 65 - // - // block [65, 128] are indexed - limitC: 64, - tailC: 65, - }, - { - // LimitA: 64 - // TailA: 65 - // - // block [65, 128] are indexed - limitA: 64, - tailA: 65, - - // LimitB: 1 - // TailB: 128 - // - // block-128 is indexed - limitB: 1, - tailB: 128, - - // LimitB: 64 - // TailB: 65 - // - // block [65, 128] are indexed - limitC: 64, - tailC: 65, - }, - { - // LimitA: 127 - // TailA: 2 - // - // block [2, 128] are indexed - limitA: 127, - tailA: 2, - - // LimitB: 1 - // TailB: 128 - // - // block-128 is indexed - limitB: 1, - tailB: 128, - - // LimitB: 64 - // TailB: 65 - // - // block [65, 128] are indexed - limitC: 64, - tailC: 65, - }, - { - // LimitA: 128 - // TailA: 1 - // - // block [2, 128] are indexed - limitA: 128, - tailA: 1, - - // LimitB: 1 - // TailB: 128 - // - // block-128 is indexed - limitB: 1, - tailB: 128, - - // LimitB: 64 - // TailB: 65 - // - // block [65, 128] are indexed - limitC: 64, - tailC: 65, - }, - { - // LimitA: 129 - // TailA: 0 - // - // block [0, 128] are indexed - limitA: 129, - tailA: 0, - - // LimitB: 1 - // TailB: 128 - // - // block-128 is indexed - limitB: 1, - tailB: 128, - - // LimitB: 64 - // TailB: 65 - // - // block [65, 128] are indexed - limitC: 64, - tailC: 65, - }, - } - for _, c := range cases { - frdir := t.TempDir() - db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false) - rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0)) - - // Index the initial blocks from ancient store - chain, _ := NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil, &c.limitA) - chain.indexBlocks(nil, 128, make(chan struct{})) - verify(db, c.tailA) - - chain.SetTxLookupLimit(c.limitB) - chain.indexBlocks(rawdb.ReadTxIndexTail(db), 128, make(chan struct{})) - verify(db, c.tailB) - - chain.SetTxLookupLimit(c.limitC) - chain.indexBlocks(rawdb.ReadTxIndexTail(db), 128, make(chan struct{})) - verify(db, c.tailC) - - // Recover all indexes - chain.SetTxLookupLimit(0) - chain.indexBlocks(rawdb.ReadTxIndexTail(db), 128, make(chan struct{})) - verify(db, 0) - - chain.Stop() - db.Close() - os.RemoveAll(frdir) - } -} - func TestCreateThenDeletePreByzantium(t *testing.T) { // We use Ropsten chain config instead of Testchain config, this is // deliberate: we want to use pre-byz rules where we have intermediate state roots diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 1e9f75717c..e61ffe0cb9 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -1007,7 +1007,7 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat break } unconfirmedResult := <-p.txResultChan - if unconfirmedResult.txReq == nil && int(p.mergedTxIndex.Load())+1 == allTxCount { + if unconfirmedResult.txReq == nil { // all tx results are merged. break } diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index fcf8ad685b..0a759021d9 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1209,7 +1209,7 @@ func TestSuicide(t *testing.T) { slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) addr := common.BytesToAddress([]byte("so")) - slotDb.SetBalance(addr, big.NewInt(1)) + slotDb.SetBalance(addr, uint256.NewInt(1)) slotDb.SelfDestruct(addr) @@ -1237,7 +1237,7 @@ func TestSetAndGetState(t *testing.T) { state, _ := New(types.EmptyRootHash, db, nil) addr := common.BytesToAddress([]byte("so")) - state.SetBalance(addr, big.NewInt(1)) + state.SetBalance(addr, uint256.NewInt(1)) unconfirmedDBs := new(sync.Map) state.PrepareForParallel() manager := NewParallelDBManager(1, NewEmptySlotDB) @@ -1274,7 +1274,7 @@ func TestSetAndGetCode(t *testing.T) { state, _ := New(common.Hash{}, db, nil) addr := common.BytesToAddress([]byte("so")) - state.SetBalance(addr, big.NewInt(1)) + state.SetBalance(addr, uint256.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) @@ -1310,7 +1310,7 @@ func TestGetCodeSize(t *testing.T) { state, _ := New(common.Hash{}, db, nil) addr := common.BytesToAddress([]byte("so")) - state.SetBalance(addr, big.NewInt(1)) + state.SetBalance(addr, uint256.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) @@ -1334,7 +1334,7 @@ func TestGetCodeHash(t *testing.T) { state, _ := New(common.Hash{}, db, nil) addr := common.BytesToAddress([]byte("so")) - state.SetBalance(addr, big.NewInt(1)) + state.SetBalance(addr, uint256.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) manager := NewParallelDBManager(1, NewEmptySlotDB) @@ -1358,7 +1358,7 @@ func TestSetNonce(t *testing.T) { state, _ := New(common.Hash{}, db, nil) addr := common.BytesToAddress([]byte("so")) - state.SetBalance(addr, big.NewInt(1)) + state.SetBalance(addr, uint256.NewInt(1)) state.SetNonce(addr, 1) state.PrepareForParallel() @@ -1387,16 +1387,16 @@ func TestSetAndGetBalance(t *testing.T) { state, _ := New(common.Hash{}, db, nil) addr := testAddress - state.SetBalance(addr, big.NewInt(1)) + state.SetBalance(addr, uint256.NewInt(1)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) manager := NewParallelDBManager(1, NewEmptySlotDB) slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - slotDb.SetBalance(addr, big.NewInt(2)) + slotDb.SetBalance(addr, uint256.NewInt(2)) oldBalance := state.GetBalance(addr) - if oldBalance.Int64() != 1 { + if oldBalance.Uint64() != 1 { t.Fatalf("old balance should be 1") } @@ -1409,7 +1409,7 @@ func TestSetAndGetBalance(t *testing.T) { } newBalance := slotDb.GetBalance(addr) - if newBalance.Int64() != 2 { + if newBalance.Uint64() != 2 { t.Fatalf("new nonce should be 2") } @@ -1423,16 +1423,16 @@ func TestSubBalance(t *testing.T) { db := NewDatabase(memDb) state, _ := New(common.Hash{}, db, nil) addr := testAddress - state.SetBalance(addr, big.NewInt(2)) + state.SetBalance(addr, uint256.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) manager := NewParallelDBManager(1, NewEmptySlotDB) slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - slotDb.SubBalance(addr, big.NewInt(1)) + slotDb.SubBalance(addr, uint256.NewInt(1)) oldBalance := state.GetBalance(addr) - if oldBalance.Int64() != 2 { + if oldBalance.Uint64() != 2 { t.Fatalf("old balance should be 1") } @@ -1449,7 +1449,7 @@ func TestSubBalance(t *testing.T) { } newBalance := slotDb.GetBalance(addr) - if newBalance.Int64() != 1 { + if newBalance.Uint64() != 1 { t.Fatalf("new nonce should be 2") } } @@ -1459,15 +1459,15 @@ func TestAddBalance(t *testing.T) { db := NewDatabase(memDb) state, _ := New(common.Hash{}, db, nil) addr := testAddress - state.SetBalance(addr, big.NewInt(2)) + state.SetBalance(addr, uint256.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) manager := NewParallelDBManager(1, NewEmptySlotDB) slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - slotDb.AddBalance(addr, big.NewInt(1)) + slotDb.AddBalance(addr, uint256.NewInt(1)) oldBalance := state.GetBalance(addr) - if oldBalance.Int64() != 2 { + if oldBalance.Uint64() != 2 { t.Fatalf("old balance should be 1") } @@ -1484,7 +1484,7 @@ func TestAddBalance(t *testing.T) { } newBalance := slotDb.GetBalance(addr) - if newBalance.Int64() != 3 { + if newBalance.Uint64() != 3 { t.Fatalf("new nonce should be 2") } } @@ -1494,7 +1494,7 @@ func TestEmpty(t *testing.T) { db := NewDatabase(memDb) state, _ := New(common.Hash{}, db, nil) addr := testAddress - state.SetBalance(addr, big.NewInt(2)) + state.SetBalance(addr, uint256.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) @@ -1516,7 +1516,7 @@ func TestExist(t *testing.T) { db := NewDatabase(memDb) state, _ := New(common.Hash{}, db, nil) addr := testAddress - state.SetBalance(addr, big.NewInt(2)) + state.SetBalance(addr, uint256.NewInt(2)) state.PrepareForParallel() unconfirmedDBs := new(sync.Map) manager := NewParallelDBManager(1, NewEmptySlotDB) @@ -1544,7 +1544,7 @@ func TestMergeSlotDB(t *testing.T) { newSlotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) addr := testAddress - newSlotDb.SetBalance(addr, big.NewInt(2)) + newSlotDb.SetBalance(addr, uint256.NewInt(2)) newSlotDb.SetState(addr, common.BytesToHash([]byte("test key")), common.BytesToHash([]byte("test store"))) newSlotDb.SetCode(addr, []byte("test code")) newSlotDb.SelfDestruct(addr) @@ -1560,7 +1560,7 @@ func TestMergeSlotDB(t *testing.T) { t.Fatalf("address should exist in StateChangeSet") } - if ok := changeList.GetBalance(addr); ok != common.Big0 { + if ok := changeList.GetBalance(addr); ok != common.U2560 { t.Fatalf("address should exist in StateChangeSet") } From 54dd5ade9f2b17e1ed93257f9378414ea91f9b35 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 29 Sep 2024 13:46:38 +0800 Subject: [PATCH 70/72] log setcode --- core/state/parallel_statedb.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index a0da320361..43dfcfb6e6 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -588,6 +588,8 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { return nil } dirtyObj = o + } else { + dirtyObj = nil } // 1.Try to get from dirty @@ -617,7 +619,9 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { s.parallel.codeReadsInSlot[addr] = code } // fixup dirties + log.Debug(fmt.Sprintf("fixup Code, addr: %s, dirtyObj (ptr %v)\n", addr.Hex(), dirtyObj)) if dirtyObj != nil && !bytes.Equal(dirtyObj.code, code) { + log.Debug("fix up code", "addr", addr, "dirty", dirtyObj, "code", code) dirtyObj.code = code } return code From 536d5d935f35e28f0d0eedd14a7460ec8b4612ab Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 30 Sep 2024 09:38:38 +0800 Subject: [PATCH 71/72] fix getcode with log --- core/state/parallel_statedb.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 43dfcfb6e6..6c00785876 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -619,10 +619,15 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { s.parallel.codeReadsInSlot[addr] = code } // fixup dirties - log.Debug(fmt.Sprintf("fixup Code, addr: %s, dirtyObj (ptr %v)\n", addr.Hex(), dirtyObj)) - if dirtyObj != nil && !bytes.Equal(dirtyObj.code, code) { - log.Debug("fix up code", "addr", addr, "dirty", dirtyObj, "code", code) - dirtyObj.code = code + log.Debug(fmt.Sprintf("fixup Code, addr: %s, dirtyObj (ptr %p)\n", addr.Hex(), dirtyObj)) + if dirtyObj != nil { + if dirtyObj.code == nil { + dirtyObj.code = code + } + if !bytes.Equal(dirtyObj.code, code) { + log.Debug("fix up code", "addr", addr, "dirty", dirtyObj, "code", code) + dirtyObj.code = code + } } return code } From 1ffacc60d37e7cb8f33d7fe86e5657beb9a8ee97 Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 8 Oct 2024 10:14:27 +0800 Subject: [PATCH 72/72] fix trie prefetch issue after rebase --- core/state/parallel_statedb.go | 2 ++ core/state/state_object.go | 2 ++ core/state/statedb.go | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index 6c00785876..4ddc2bdac4 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1800,7 +1800,9 @@ func (s *ParallelStateDB) FinaliseForParallel(deleteEmptyObjects bool, mainDB *S } if mainDB.prefetcher != nil && len(addressesToPrefetch) > 0 { + mainDB.trieParallelLock.Lock() mainDB.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch) + mainDB.trieParallelLock.Unlock() } // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() diff --git a/core/state/state_object.go b/core/state/state_object.go index 93cbc44858..02a0b5bd26 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -494,7 +494,9 @@ func (s *stateObject) finalise(prefetch bool) { s.dirtyCodeHash = nil } if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { + s.db.trieParallelLock.Lock() s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch) + s.db.trieParallelLock.Unlock() } if s.dirtyStorage.Length() > 0 { s.dirtyStorage = newStorage(s.isParallel) diff --git a/core/state/statedb.go b/core/state/statedb.go index 3f81e7ef5b..52ba04b8ae 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2461,14 +2461,18 @@ func (s *StateDB) AddrPrefetch(slotDb *ParallelStateDB) { }) obj.storageRecordsLock.RUnlock() if s.prefetcher != nil && len(slotsToPrefetch) > 0 { + s.trieParallelLock.Lock() s.prefetcher.prefetch(obj.addrHash, obj.data.Root, obj.address, slotsToPrefetch) + s.trieParallelLock.Unlock() } } if s.prefetcher != nil && len(addressesToPrefetch) > 0 { // log.Info("AddrPrefetch", "slotDb.TxIndex", slotDb.TxIndex(), // "len(addressesToPrefetch)", len(slotDb.parallel.addressesToPrefetch)) + s.trieParallelLock.Lock() s.prefetcher.prefetch(common.Hash{}, s.originalRoot, emptyAddr, addressesToPrefetch) + s.trieParallelLock.Unlock() } } @@ -2645,7 +2649,9 @@ func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receip } if s.prefetcher != nil && len(addressesToPrefetch) > 0 { + s.trieParallelLock.Lock() s.prefetcher.prefetch(common.Hash{}, s.originalRoot, emptyAddr, addressesToPrefetch) // prefetch for trie node of account + s.trieParallelLock.Unlock() } for addr := range slotDb.stateObjectsPending {