From efc152edc9c7189fd548d1e3548ee64033552c90 Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 29 Jul 2024 16:41:56 +0800 Subject: [PATCH 001/104] 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 | 2 + triedb/pathdb/disklayer.go | 1 + 62 files changed, 5031 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 2e55edcaf3..0a300390fc 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -170,6 +170,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 e3a4a42f21..43753b183d 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -29,6 +29,7 @@ import ( "net/http" "os" "path/filepath" + "runtime" godebug "runtime/debug" "strconv" "strings" @@ -1099,6 +1100,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", @@ -1989,6 +2002,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 511d4db8a9..01958166d4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -106,6 +106,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 ( @@ -292,12 +294,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 @@ -362,7 +365,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 { @@ -541,6 +543,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() @@ -1619,6 +1627,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 } @@ -1660,6 +1669,7 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types } else { bc.chainSideFeed.Send(ChainSideEvent{Block: block}) } + return status, nil } @@ -1801,7 +1811,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 @@ -1941,7 +1950,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) @@ -1970,6 +1980,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) @@ -2728,6 +2739,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 6a257a6035..287e109e43 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1795,13 +1795,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}) }) @@ -1856,7 +1860,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) } @@ -1928,7 +1932,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) } @@ -1978,7 +1982,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 c64e9ddbc0..ce64d36b56 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, 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, 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, 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 72697551ed..1f90d5f413 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 e26eb2aa93..aa5d894c35 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 } @@ -1192,3 +1198,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 700440dba3..9e63a9df31 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 d96c20ce0f..1da768e856 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -236,6 +236,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 7af1679867..2e6df8124c 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 1c19e60de9..68381f5c15 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -131,7 +131,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) } @@ -182,7 +182,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 e8ca68dee0..f0e7e36aa2 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -153,15 +153,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 d2cd0b9975..643f96ad16 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -410,11 +410,13 @@ 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 { h, err := readHistory(db.freezer, dl.stateID(), db.fastRecovery) 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 ec6cfd15d2..6bbc53b173 100644 --- a/triedb/pathdb/disklayer.go +++ b/triedb/pathdb/disklayer.go @@ -385,6 +385,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 5e7f2e72dd5c6dca51c5fed0a4d6e73f02254cb3 Mon Sep 17 00:00:00 2001 From: DavidZangNR Date: Wed, 3 Jul 2024 22:36:41 +0800 Subject: [PATCH 002/104] 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 3e2f523e6efbd963ecfc59539b30355251dc04cf Mon Sep 17 00:00:00 2001 From: galaio Date: Sat, 6 Jul 2024 00:06:29 +0800 Subject: [PATCH 003/104] 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 01958166d4..d393f69a9c 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1944,6 +1944,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 7f36ae722d..ad1899c785 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -102,6 +102,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: @@ -170,6 +173,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 fc7c2a18ea..f84184a639 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -284,6 +284,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 b8df413238..dbb706d369 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1190,6 +1190,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, @@ -1213,6 +1214,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) @@ -1449,6 +1452,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 d3c670514b054ef6090056ad42cfc52288e5ea39 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 004/104] 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 d393f69a9c..e689d6923b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2022,8 +2022,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 f0e7e36aa2..c7b15b1af0 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -158,14 +158,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 5c81e496e31ee8f683e94c3ac1fa1e6a70bbc9ff 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 005/104] 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 0d667e6c6588a50d5118e95e83019b4c2ea47372 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 006/104] 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 9fc4328b0b46ac9a88011c1d6f9de12c20f52321 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 18 Jul 2024 17:10:51 +0800 Subject: [PATCH 007/104] 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 8ea4d1b00aaa49bf3c3b7b4c299fd8f99cf76b44 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 18 Jul 2024 15:24:12 +0800 Subject: [PATCH 008/104] 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 74f811964d6642638b5f145a7712443fc807c877 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 009/104] 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 e689d6923b..65c126e793 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1950,7 +1950,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 dbb706d369..9769f260b4 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1347,6 +1347,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) @@ -1454,7 +1473,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) } @@ -1466,6 +1485,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 756e93e9060f2c6b53115036b41ec2fdc07e5cff 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 010/104] 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 0a300390fc..721aff6d03 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -172,6 +172,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 43753b183d..9464b30507 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1112,6 +1112,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", @@ -2023,6 +2036,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 65c126e793..6dbee193c6 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" @@ -92,6 +97,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) @@ -301,6 +309,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 @@ -1944,16 +1955,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 @@ -2015,8 +2026,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) @@ -2819,3 +2831,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 ce64d36b56..c6cdd6f017 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 1da768e856..7d2bccde91 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -285,6 +285,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 53755ad632..1f407851f4 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -63,7 +63,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 9769f260b4..b78298819a 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1190,6 +1190,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{ @@ -1207,6 +1208,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(), "txs", len(pendingPlainTxs)) @@ -1214,8 +1219,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) @@ -1348,7 +1351,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 { @@ -1358,7 +1361,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 { @@ -1366,6 +1369,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) @@ -1377,6 +1381,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) @@ -1473,7 +1478,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) } @@ -1486,7 +1491,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 74cc070d8a2b46a6ae01727c865a95dc5a19366c 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 011/104] 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 7c5bf719f64d2b40465599503a433de8025efeb4 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 012/104] 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 6dbee193c6..d143f16025 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2836,7 +2836,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 @@ -2845,7 +2845,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 aa5d894c35..a06199e87f 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1540,7 +1540,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 7d2bccde91..de1ed349e2 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -286,7 +286,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 b78298819a..3ef6755e43 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1190,7 +1190,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{ @@ -1352,7 +1351,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} @@ -1362,7 +1361,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} @@ -1482,7 +1481,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 @@ -1492,7 +1491,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 c7b15b1af0..34d775ad26 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} @@ -153,7 +153,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) @@ -161,7 +161,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 a4cf50ea15a4618a18ca134cc6727016cd05bd13 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 013/104] 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 c34a671e2ebc918461dd55f2651ddac2eb7a1838 Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 30 Jul 2024 10:41:59 +0800 Subject: [PATCH 014/104] 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 60fbe9b4cb45c4197e603cac0d985c0fce27ed5d 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 015/104] 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 d143f16025..cbcd6b20cd 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1955,22 +1955,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") @@ -2849,13 +2835,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 ad1899c785..b274cc6eab 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -102,9 +102,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 f84184a639..fc7c2a18ea 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -284,7 +284,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 3ef6755e43..d49ab521bc 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1349,25 +1349,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) @@ -1476,29 +1465,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 ad37a9518f1680e357a1c12db0f0f07af69ae27f 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 016/104] 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 1ec90fb371dd636111525c89b21d3ab1656e1f46 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 017/104] 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 4217365fafdb67e06b6b848f7f359dd512db157e 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 018/104] 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 cbcd6b20cd..4d6398c072 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2823,6 +2823,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 @@ -2833,10 +2834,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 @@ -2876,6 +2878,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 c6cdd6f017..765b9b780c 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 ba16a5683d0e1401161e2583ef08f6ba98febe38 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 019/104] 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 60b93b32b79aa04c91ef3c057ba6df888f8c3656 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 020/104] 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 4d6398c072..6d43294daa 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -98,7 +98,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) @@ -2838,9 +2837,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 765b9b780c..962de9a64e 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 9c1c7dde4677b3c7308c37bb83f2dfa62bddb2b4 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 021/104] 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 f98be3ab4ad82ff437a39903bb7753aeaaf463c8 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 022/104] 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 a81f34d0dde54d7bd204d35f39605d86231d8ff8 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 023/104] 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 6d43294daa..bf68d12966 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -310,7 +310,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 @@ -1107,6 +1107,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. @@ -2821,19 +2824,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() { @@ -2877,34 +2888,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 962de9a64e..5159606e1c 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 de1ed349e2..2a82f1e074 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -286,7 +286,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 34d775ad26..6cca172f96 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -162,7 +162,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 f0744f3485243da98dd74b2f9f4c1f747c3c7d5b 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 024/104] 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 bf68d12966..09934ea285 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -99,6 +99,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) @@ -2841,8 +2844,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 } @@ -2888,7 +2891,7 @@ func writeTxDAGToFile(writeHandle *os.File, item TxDAGOutputItem) error { return err } -const TxDAGCacheSize = 200000 +var TxDAGCacheSize = 10000 type TxDAGFileReader struct { file *os.File @@ -2904,6 +2907,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, @@ -2939,18 +2943,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 { @@ -2958,9 +2965,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 5159606e1c..e5e8567fcb 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 0de95774e81df7f9065ad13f6fcebdfab62a2d84 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 7 Aug 2024 11:29:40 +0800 Subject: [PATCH 025/104] 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 09934ea285..10aeea1d26 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -556,11 +556,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) @@ -1967,6 +1963,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 0fac12d2910bce2b743c569d1442f334c0dd2308 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 7 Aug 2024 11:29:40 +0800 Subject: [PATCH 026/104] 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 10aeea1d26..9cc7e6feff 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -314,6 +314,8 @@ type BlockChain struct { enableTxDAG bool txDAGWriteCh chan TxDAGOutputItem txDAGReader *TxDAGFileReader + serialProcessor Processor + parallelProcessor Processor } // NewBlockChain returns a fully initialised block chain using information @@ -556,8 +558,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() @@ -1963,10 +1969,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. @@ -2780,17 +2792,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 { @@ -2873,6 +2880,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 dea715a036287ccd4aa2065165fee7646db8c7dd Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 8 Aug 2024 23:23:43 +0800 Subject: [PATCH 027/104] 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 37a617a0124b242c082a13763189251950edff4e Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 8 Aug 2024 21:24:31 +0800 Subject: [PATCH 028/104] 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 0d683a7429bf8845da7d603fe8077e1160479c3f Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 11 Aug 2024 21:05:41 +0800 Subject: [PATCH 029/104] 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 a06199e87f..22838b4a7d 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1206,7 +1206,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)) @@ -1240,7 +1240,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 { @@ -1277,7 +1277,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") } @@ -1312,7 +1312,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) @@ -1334,7 +1334,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")) @@ -1359,7 +1359,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) @@ -1385,7 +1385,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)) @@ -1421,7 +1421,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) @@ -1455,7 +1455,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) @@ -1490,7 +1490,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 { @@ -1510,7 +1510,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 { @@ -1529,9 +1529,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 9babd88bdc5a9e0ce54bc9c5215d8d2369b20609 Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 12 Aug 2024 07:14:59 +0800 Subject: [PATCH 030/104] 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 73172b3850a32e84f37ed811396439aacdc75a87 Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 12 Aug 2024 10:36:19 +0800 Subject: [PATCH 031/104] 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 3b6b40679aff59dd12e33e43fc38f342793c93e3 Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 13 Aug 2024 10:19:00 +0800 Subject: [PATCH 032/104] 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 1654d3fdce7400253e231900e52473deab1aaa77 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 033/104] 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 | 14 ++++++ core/blockchain.go | 4 ++ core/parallel_state_processor.go | 13 +++-- core/types/dag.go | 72 ++++++++++++++++++++++++++ core/types/dag_test.go | 14 ++++++ miner/miner.go | 3 ++ miner/worker.go | 86 ++++++++++++++++++++++++++++++++ 8 files changed, 204 insertions(+), 3 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 721aff6d03..314306020b 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -174,6 +174,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 9464b30507..58352e5ea1 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1130,6 +1130,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 ( @@ -2044,6 +2051,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 9cc7e6feff..b1838b67aa 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2835,6 +2835,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 1f407851f4..08c60c186e 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -19,6 +19,7 @@ package miner import ( "context" + "crypto/ecdsa" "errors" "fmt" "math/big" @@ -109,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 d49ab521bc..f59ccbfc9a 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -18,6 +18,7 @@ package miner import ( "context" + "crypto/ecdsa" "errors" "fmt" "math/big" @@ -40,6 +41,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" @@ -102,6 +104,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 { @@ -916,10 +922,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) } } @@ -1018,6 +1050,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. @@ -1036,6 +1069,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 0e8f82253df662b9f1cb6d040c2d0066b18f0000 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 034/104] 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 b1838b67aa..dc0c0ea860 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2018,6 +2018,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 @@ -2854,7 +2876,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 29ed6b41ca061a64569a8d480edde5606c02b95d 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 035/104] feat: code cleanup (#23) This PR refine and clean up the code of PEVM Co-authored-by: Sunny --- 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 | 2 - triedb/pathdb/disklayer.go | 1 - 31 files changed, 118 insertions(+), 506 deletions(-) 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 dc0c0ea860..02ba9bd79c 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -116,8 +116,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 ( @@ -304,12 +302,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 @@ -1645,7 +1644,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 } @@ -1687,7 +1685,6 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types } else { bc.chainSideFeed.Send(ChainSideEvent{Block: block}) } - return status, nil } @@ -1962,9 +1959,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 @@ -2012,7 +2006,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) @@ -2068,28 +2061,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 287e109e43..f2bf9781db 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1799,13 +1799,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 e5e8567fcb..f87bbe5357 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, 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 22838b4a7d..38f1bb14e2 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 @@ -1381,7 +1381,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) @@ -1416,7 +1416,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() @@ -1451,7 +1451,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) @@ -1485,7 +1485,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() @@ -1506,7 +1506,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) @@ -1533,7 +1533,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 b274cc6eab..7f36ae722d 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -170,7 +170,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 f59ccbfc9a..05a104d3b5 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1435,15 +1435,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) @@ -1550,7 +1541,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 643f96ad16..d2cd0b9975 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -410,13 +410,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 { h, err := readHistory(db.freezer, dl.stateID(), db.fastRecovery) 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 6bbc53b173..ec6cfd15d2 100644 --- a/triedb/pathdb/disklayer.go +++ b/triedb/pathdb/disklayer.go @@ -385,7 +385,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 2a1169b1d55cc1d400c05c7a642a7ca264586719 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 14 Aug 2024 09:30:01 +0800 Subject: [PATCH 036/104] 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 f2bf9781db..6a257a6035 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1795,7 +1795,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) } @@ -1856,7 +1856,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) } @@ -1928,7 +1928,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) } @@ -1978,7 +1978,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 f87bbe5357..d9a3bf42d2 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, 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, 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 1f90d5f413..72697551ed 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 2e6df8124c..7af1679867 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 68381f5c15..1c19e60de9 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -131,7 +131,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) } @@ -182,7 +182,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 bc852c736270bd5d109faefc013ff5dcbfdc4ec4 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 037/104] 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 7af9f3fc4d160d8649ccfd9a665dbe00a91c45ab 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 038/104] 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 02ba9bd79c..e0259c482f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -912,6 +912,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") @@ -2012,7 +2016,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 { @@ -2916,9 +2920,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 @@ -2927,16 +2932,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() { @@ -2945,6 +2946,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 @@ -2990,7 +3003,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 } } @@ -3004,6 +3017,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 d9a3bf42d2..1970c00be3 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 48ac372a505666b2eae177ca060a76777746ec8c 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 039/104] 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 36fc3cb15b2031bc853f836ef0f8410383b9e614 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 040/104] 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 2d45d90aacbb731c8b47bac61151e1f2aa961722 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 041/104] 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 e0259c482f..9fcb13abd4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2830,8 +2830,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 05a104d3b5..d02cee442f 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -925,7 +925,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 @@ -1365,6 +1365,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) @@ -1372,6 +1375,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 3db0ca994cc60c51e7ccbe299dfcb47e9bf9ee2c 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 042/104] 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 ++++++++++---------- miner/worker.go | 4 ---- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 58352e5ea1..297dfe5e64 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2035,10 +2035,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 // we found concurrency 8 is slightly better than 15 + 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, diff --git a/miner/worker.go b/miner/worker.go index d02cee442f..8208fb5979 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1293,10 +1293,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(), "txs", len(pendingPlainTxs)) From 8b839d2a9f7ff1bc9d6103c91a5d2d9d8392b983 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 043/104] 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 4a6ec28b0661bdc844df5fe516ba15543366f93e 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 044/104] 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 9fcb13abd4..68bcea571b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2016,7 +2016,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 { @@ -2037,9 +2040,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 058ff1b4904a2f38742c5f9ec09fde11479f7ec0 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 045/104] 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 68bcea571b..820082f6c0 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2831,7 +2831,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 446db58813694b1d7dd08b979a0c40f2e3d83c24 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 046/104] 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 820082f6c0..f723b8e9c7 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1972,6 +1972,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 38f1bb14e2..39e55a0e34 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1206,6 +1206,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")) @@ -1240,6 +1241,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"))) @@ -1277,6 +1279,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") @@ -1312,6 +1315,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")) @@ -1334,6 +1338,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")) @@ -1359,6 +1364,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) @@ -1385,6 +1391,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)) @@ -1421,6 +1428,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)) @@ -1455,6 +1463,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)) @@ -1490,6 +1499,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) @@ -1510,6 +1520,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) @@ -1528,7 +1539,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 3c0058c9bb4ec7d8bc2691c944374e3410c3833c Mon Sep 17 00:00:00 2001 From: galaio Date: Fri, 16 Aug 2024 21:48:03 +0800 Subject: [PATCH 047/104] 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 ad5f2ef400551a4878dd6da8c97bd63fa71beced 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 048/104] 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 82d12d1ccc0b35c84f9d86e283142d1ca8abf599 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 049/104] 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 f723b8e9c7..e47d5a19f8 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2832,11 +2832,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 8208fb5979..ed25a80ab0 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -922,36 +922,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) } } @@ -1050,7 +1024,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. @@ -1070,56 +1043,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. @@ -1427,6 +1401,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 5ec1321078735dfc5fed52c7ed0559d079bfad1e Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 21 Aug 2024 18:05:41 +0800 Subject: [PATCH 050/104] 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 d9bbda1e1b79b2fa4080c210fb031dbccc6100f6 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 21 Aug 2024 23:31:10 +0800 Subject: [PATCH 051/104] pevm-opt: Add conflict check cache --- core/state/parallel_statedb.go | 56 ++++++++++++++++++++++++++++++---- core/state/statedb.go | 6 ++-- 2 files changed, 54 insertions(+), 8 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 From 54dd3977398ea840bd0dd8822db1e765d0c83dd4 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 22 Aug 2024 11:21:40 +0800 Subject: [PATCH 052/104] 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 2e40b22eb375f7e169e987da272d3e94a6d237fd Mon Sep 17 00:00:00 2001 From: galaio Date: Fri, 23 Aug 2024 11:18:12 +0800 Subject: [PATCH 053/104] 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 c85e980482779271c9b7f5fa50cde044449a90a9 Mon Sep 17 00:00:00 2001 From: galaio Date: Tue, 27 Aug 2024 10:48:26 +0800 Subject: [PATCH 054/104] 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 e47d5a19f8..2bebd0dc09 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2002,6 +2002,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 d1acaf7f1e966cf6d93d0ff970776cb6e6aac370 Mon Sep 17 00:00:00 2001 From: galaio Date: Tue, 27 Aug 2024 12:23:56 +0800 Subject: [PATCH 055/104] 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 2bebd0dc09..804710a837 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1968,15 +1968,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 @@ -2156,6 +2157,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 3a92a2ae944a18907d2c1c8639d131501ef75039 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 28 Aug 2024 21:43:19 +0800 Subject: [PATCH 056/104] 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 297dfe5e64..f8d8bccca9 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2036,7 +2036,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 4c410a070dafc1d7d348bc14ad2acecc991c0bdd Mon Sep 17 00:00:00 2001 From: Sunny Date: Fri, 30 Aug 2024 09:50:33 +0800 Subject: [PATCH 057/104] 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 804710a837..261e7a3239 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1971,7 +1971,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 f4ab63847a57c15f963f85c1480dca9885bb1e1e Mon Sep 17 00:00:00 2001 From: Sunny Date: Fri, 30 Aug 2024 15:03:56 +0800 Subject: [PATCH 058/104] 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 73874190ef8e93b9b1d4176775b9439988a0e855 Mon Sep 17 00:00:00 2001 From: Sunny Date: Mon, 2 Sep 2024 14:58:43 +0800 Subject: [PATCH 059/104] 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 261e7a3239..320b0c75bd 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1976,8 +1976,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 @@ -2923,7 +2925,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 8e01b25befddd8ee676c4097d44301a56f012eeb Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 3 Sep 2024 15:41:06 +0800 Subject: [PATCH 060/104] 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 37a3222ab1680a40a9bf39f5c3be4f9089cf32fc Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 4 Sep 2024 17:21:34 +0800 Subject: [PATCH 061/104] 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 320b0c75bd..02c4a19d13 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1976,10 +1976,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 39e55a0e34..846b5fb70e 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1206,8 +1206,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)) @@ -1241,8 +1241,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 { @@ -1279,8 +1279,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") } @@ -1315,8 +1315,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) @@ -1338,8 +1338,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")) @@ -1364,8 +1364,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) @@ -1391,8 +1391,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)) @@ -1428,8 +1428,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) @@ -1463,8 +1463,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) @@ -1499,8 +1499,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 { @@ -1520,8 +1520,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 { @@ -1539,10 +1539,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 497d385c144a97e4dc6441cdbfa332834d3fe1a7 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 062/104] 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 02c4a19d13..c0e2efd9b6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1976,6 +1976,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 8602936e50fe44ae1a1bb63845a1f0ea3b533d0a 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 063/104] 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 0a7a7b86faba41d054013edf1885d87a40b4a3d5 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 064/104] 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 bfa857b4cb890c2c178dcf9d60d8b099394da977 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 25 Sep 2024 20:10:11 +0800 Subject: [PATCH 065/104] 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 a22f951731fbd5168a12c43ae8acecb1bb791a3b Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 26 Sep 2024 10:26:47 +0800 Subject: [PATCH 066/104] 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 2ffe544a195ba2dd4598787ff00fe58a74b9d125 Mon Sep 17 00:00:00 2001 From: Sunny Date: Thu, 26 Sep 2024 10:26:47 +0800 Subject: [PATCH 067/104] fix issue in createObject after rebase --- core/state/statedb.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 71491e587f..1838e2882a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -744,12 +744,6 @@ 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.noTrie { // Track the amount of time wasted on updating the account from the trie if metrics.EnabledExpensive { @@ -1052,7 +1046,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 @@ -2462,8 +2456,6 @@ func (s *StateDB) AddrPrefetch(slotDb *ParallelStateDB) { } 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) } } From 67fd6799cc144e22f44bad47c5074574bc1118f9 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Sun, 29 Sep 2024 14:00:18 +0800 Subject: [PATCH 068/104] fix: test case issue after rebase (#190) --- 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 1970c00be3..2309f0b21c 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 846b5fb70e..1a85f8f2b3 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1210,7 +1210,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) @@ -1238,7 +1238,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) @@ -1275,7 +1275,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) @@ -1311,7 +1311,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) @@ -1335,7 +1335,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) @@ -1359,7 +1359,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() @@ -1388,16 +1388,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") } @@ -1410,7 +1410,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") } @@ -1424,16 +1424,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") } @@ -1450,7 +1450,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") } } @@ -1460,15 +1460,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") } @@ -1485,7 +1485,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") } } @@ -1495,7 +1495,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) @@ -1517,7 +1517,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) @@ -1545,7 +1545,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) @@ -1561,7 +1561,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 cbb03171a77ca2722013443fbb19c4135abc0729 Mon Sep 17 00:00:00 2001 From: Sunny Date: Sun, 29 Sep 2024 13:46:38 +0800 Subject: [PATCH 069/104] fix getcode issue after rebase --- core/state/parallel_statedb.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go index a0da320361..a415604df7 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,8 +619,13 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { s.parallel.codeReadsInSlot[addr] = code } // fixup dirties - if dirtyObj != nil && !bytes.Equal(dirtyObj.code, code) { - dirtyObj.code = code + if dirtyObj != nil { + if dirtyObj.code == nil { + dirtyObj.code = code + } + if !bytes.Equal(dirtyObj.code, code) { + dirtyObj.code = code + } } return code } From dd5d24d12cc61c2b43a87bcd1634c7941dcdd287 Mon Sep 17 00:00:00 2001 From: Sunny Date: Tue, 8 Oct 2024 10:14:27 +0800 Subject: [PATCH 070/104] 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 a415604df7..6321dfb9d1 100644 --- a/core/state/parallel_statedb.go +++ b/core/state/parallel_statedb.go @@ -1798,7 +1798,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 1b5059140d..561ee0fbef 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -490,7 +490,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 1838e2882a..d71c675055 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2451,12 +2451,16 @@ 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 { + s.trieParallelLock.Lock() s.prefetcher.prefetch(common.Hash{}, s.originalRoot, emptyAddr, addressesToPrefetch) + s.trieParallelLock.Unlock() } } @@ -2623,7 +2627,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 { From eea85533514662d7e4d340cf12028769d3c077fc Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:17:20 +0800 Subject: [PATCH 071/104] PEVM-fix: check in ValidatePlainTxDAG (#195) --- core/types/dag.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/types/dag.go b/core/types/dag.go index e46f7bb897..b3d5f0f081 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -160,21 +160,23 @@ func ValidatePlainTxDAG(d TxDAG, txCnt int) error { 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 { + dp := d.TxDep(i) + if dp == nil { return fmt.Errorf("PlainTxDAG contains nil txdep, tx: %v", i) } - for j, tx := range dep.TxIndexes { + if dp.Flags != nil && *dp.Flags & ^TxDepFlagMask > 0 { + return fmt.Errorf("PlainTxDAG contains unknown flags, flags: %v", *dp.Flags) + } + dep := TxDependency(d, i) + for j, tx := range dep { 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] { + if j > 0 && dep[j] <= dep[j-1] { 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 } From 4cee5f26bb507ddcfa50dc071dd036a0fd75f222 Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:19:06 +0800 Subject: [PATCH 072/104] Pevm rebased to v0.5.5 (#192) Co-authored-by: andyzhang2023 --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 10 + core/blockchain.go | 11 +- core/parallel_state_scheduler.go | 331 +++++ core/parallel_state_scheduler_test.go | 884 +++++++++++ core/pevm_processor.go | 403 +++++ core/pevm_processor_test.go | 265 ++++ core/state/pevm_journal.go | 327 +++++ core/state/pevm_journal_test.go | 154 ++ core/state/pevm_statedb.go | 920 ++++++++++++ core/state/pevm_statedb_test.go | 1944 +++++++++++++++++++++++++ core/state/statedb.go | 7 +- core/state_processor.go | 2 +- core/vm/interpreter.go | 1 + eth/backend.go | 10 +- eth/ethconfig/config.go | 4 +- 16 files changed, 5268 insertions(+), 6 deletions(-) create mode 100644 core/parallel_state_scheduler.go create mode 100644 core/parallel_state_scheduler_test.go create mode 100644 core/pevm_processor.go create mode 100644 core/pevm_processor_test.go create mode 100644 core/state/pevm_journal.go create mode 100644 core/state/pevm_journal_test.go create mode 100644 core/state/pevm_statedb.go create mode 100644 core/state/pevm_statedb_test.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 314306020b..f851b18856 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -171,6 +171,7 @@ var ( utils.RollupHaltOnIncompatibleProtocolVersionFlag, utils.RollupSuperchainUpgradesFlag, utils.ParallelTxFlag, + utils.ParallelTx2Flag, utils.ParallelTxNumFlag, utils.ParallelTxDAGFlag, utils.ParallelTxDAGFileFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f8d8bccca9..278b7772b4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1106,6 +1106,12 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Category: flags.VMCategory, } + ParallelTx2Flag = &cli.BoolFlag{ + Name: "parallel2", + 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)", @@ -2044,6 +2050,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.ParallelTxNum = parallelNum } + if ctx.IsSet(ParallelTx2Flag.Name) { + cfg.ParallelTxMode2 = ctx.Bool(ParallelTx2Flag.Name) + } + if ctx.IsSet(ParallelTxDAGFlag.Name) { cfg.EnableParallelTxDAG = ctx.Bool(ParallelTxDAGFlag.Name) } diff --git a/core/blockchain.go b/core/blockchain.go index c0e2efd9b6..289034180d 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -105,6 +105,9 @@ var ( blockGasUsedGauge = metrics.NewRegisteredGauge("chain/block/gas/used", nil) mgaspsGauge = metrics.NewRegisteredGauge("chain/mgas/ps", nil) + pevmBuildLevelsTimer = metrics.NewRegisteredTimer("chain/pevm/buildlevels", nil) + pevmRunTimer = metrics.NewRegisteredTimer("chain/pevm/run", nil) + blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil) @@ -560,6 +563,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if bc.vmConfig.EnableParallelExec { bc.CreateParallelProcessor(bc.vmConfig.ParallelTxNum) bc.CreateSerialProcessor(chainConfig, bc, engine) + } else if bc.vmConfig.EnableParallelExecV2 { + bc.processor = newPEVMProcessor(chainConfig, bc, engine) } else { bc.processor = NewStateProcessor(chainConfig, bc, engine) } @@ -1980,6 +1985,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } } + + if bc.vmConfig.EnableParallelExecV2 { + bc.parseTxDAG(block) + } // 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 @@ -2025,7 +2034,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) vtime := time.Since(vstart) proctime := time.Since(start) // processing + validation - if bc.enableTxDAG && !bc.vmConfig.EnableParallelExec { + if bc.enableTxDAG && !bc.vmConfig.EnableParallelExec && !bc.vmConfig.EnableParallelExecV2 { // 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 { diff --git a/core/parallel_state_scheduler.go b/core/parallel_state_scheduler.go new file mode 100644 index 0000000000..ca53357567 --- /dev/null +++ b/core/parallel_state_scheduler.go @@ -0,0 +1,331 @@ +package core + +import ( + "fmt" + "runtime" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +var runner chan func() + +func init() { + runner = make(chan func(), runtime.NumCPU()) + for i := 0; i < runtime.NumCPU(); i++ { + go func() { + for f := range runner { + f() + } + }() + } +} + +func ParallelNum() int { + return cap(runner) +} + +// TxLevel contains all transactions who are independent to each other +type TxLevel []*PEVMTxRequest + +func (tl TxLevel) SplitBy(chunkSize int) []TxLevel { + if len(tl) == 0 { + return nil + } + if chunkSize <= 0 { + chunkSize = 1 + } + result := make([]TxLevel, 0, len(tl)/chunkSize+1) + for i := 0; i < len(tl); i += chunkSize { + end := i + chunkSize + if end > len(tl) { + end = len(tl) + } + result = append(result, tl[i:end]) + } + return result +} + +func (tl TxLevel) Split(chunks int) []TxLevel { + if len(tl) == 0 { + return nil + } + if chunks <= 0 { + chunks = 1 + } + result := make([]TxLevel, 0, chunks) + var chunkSize int + if len(tl)%chunks == 0 { + chunkSize = len(tl) / chunks + } else { + chunkSize = len(tl)/chunks + 1 + } + for i := 0; i < chunks; i++ { + start := i * chunkSize + end := min(start+chunkSize, len(tl)) + if start > len(tl)-1 || start >= end { + break + } + result = append(result, tl[start:end]) + } + return result +} + +// TxLevels indicates the levels of transactions +// the levels are ordered by the dependencies, and generated by the TxDAG +type TxLevels []TxLevel + +type confirmQueue struct { + queue []confirmation + confirmed int // need to be set to -1 originally +} + +type confirmation struct { + result *PEVMTxResult + executed error // error from execution in parallel + confirmed error // error from confirmation in sequence (like conflict) +} + +// put into the right position (txIndex) +func (cq *confirmQueue) collect(result *PEVMTxResult) error { + if result.txReq.txIndex >= len(cq.queue) { + // TODO add metrics + return fmt.Errorf("txIndex outof range, req.index:%d, len(queue):%d", result.txReq.txIndex, len(cq.queue)) + } + i := result.txReq.txIndex + cq.queue[i].result, cq.queue[i].executed, cq.queue[i].confirmed = result, result.err, nil + return nil +} + +func (cq *confirmQueue) confirmWithTrust(level TxLevel, execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) (error, int) { + // find all able-to-confirm transactions, and try to confirm them + for _, tx := range level { + i := tx.txIndex + toConfirm := cq.queue[i] + // the tx has not been executed yet, which means the higher-index transactions can not be confirmed before it + // so stop the loop. + if toConfirm.result == nil { + break + } + switch true { + case toConfirm.executed != nil: + if err := cq.rerun(i, execute, confirm); err != nil { + // TODO add logs for err + // rerun failed, something very wrong. + return err, toConfirm.result.txReq.txIndex + } + + default: + //try the first confirm + if err := confirm(toConfirm.result); err != nil { + // TODO add logs for err + if err = cq.rerun(i, execute, confirm); err != nil { + // TODO add logs for err + // rerun failed, something very wrong. + return err, toConfirm.result.txReq.txIndex + } + } + } + cq.confirmed = i + } + return nil, 0 +} + +// try to confirm txs as much as possible, they will be confirmed in a sequencial order. +func (cq *confirmQueue) confirm(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) (error, int) { + // find all able-to-confirm transactions, and try to confirm them + for i := cq.confirmed + 1; i < len(cq.queue); i++ { + toConfirm := cq.queue[i] + // the tx has not been executed yet, which means the higher-index transactions can not be confirmed before it + // so stop the loop. + if toConfirm.result == nil { + break + } + switch true { + case toConfirm.executed != nil: + if err := cq.rerun(i, execute, confirm); err != nil { + // TODO add logs for err + // rerun failed, something very wrong. + return err, toConfirm.result.txReq.txIndex + } + + default: + //try the first confirm + if err := confirm(toConfirm.result); err != nil { + // TODO add logs for err + if err = cq.rerun(i, execute, confirm); err != nil { + // TODO add logs for err + // rerun failed, something very wrong. + return err, toConfirm.result.txReq.txIndex + } + } + } + cq.confirmed = i + } + return nil, 0 +} + +// rerun executes the transaction of index 'i', and confirms it. +func (cq *confirmQueue) rerun(i int, execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) error { + //reset the result + cq.queue[i].result.err, cq.queue[i].executed, cq.queue[i].confirmed = nil, nil, nil + // failed, rerun and reconfirm, the rerun should alway success. + rerun := execute(cq.queue[i].result.txReq) + if rerun.err != nil { + // TODO add metrics, add error logs. + return rerun.err + } + cq.queue[i].result, cq.queue[i].executed, cq.queue[i].confirmed = rerun, nil, confirm(rerun) + if cq.queue[i].confirmed != nil { + // TODO add metrics, add error logs. + return cq.queue[i].confirmed + } + return nil +} + +// run runs the transactions in parallel +// execute must return a non-nil result, otherwise it panics. +func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) (error, int) { + toConfirm := &confirmQueue{ + queue: make([]confirmation, tls.txCount()), + confirmed: -1, + } + + trustDAG := false + + // execute all transactions in parallel + for _, txLevel := range tls { + wait := sync.WaitGroup{} + trunks := txLevel.Split(runtime.NumCPU()) + wait.Add(len(trunks)) + // split tx into chunks, to save the cost of channel communication + for _, txs := range trunks { + // execute the transactions in parallel + temp := txs + run := func() { + for _, tx := range temp { + res := execute(tx) + toConfirm.collect(res) + } + wait.Done() + } + //go run() + runner <- run + } + wait.Wait() + // all transactions of current level are executed, now try to confirm. + if trustDAG { + if err, txIndex := toConfirm.confirmWithTrust(txLevel, execute, confirm); err != nil { + // something very wrong, stop the process + return err, txIndex + } + } else { + if err, txIndex := toConfirm.confirm(execute, confirm); err != nil { + // something very wrong, stop the process + return err, txIndex + } + } + } + return nil, 0 +} + +func (tls TxLevels) txCount() int { + count := 0 + for _, txlevel := range tls { + count += len(txlevel) + } + return count +} + +// predictTxDAG predicts the TxDAG by their from address and to address, and generates the levels of transactions +func (tl TxLevel) predictTxDAG(dag types.TxDAG) { + marked := make(map[common.Address]int, len(tl)) + for _, tx := range tl { + var deps []uint64 + var tfrom, tto = -1, -1 + if ti, ok := marked[tx.msg.From]; ok { + tfrom = ti + } + if ti, ok := marked[*tx.msg.To]; ok { + tto = ti + } + if tfrom >= 0 && tto >= 0 && tfrom > tto { + // keep deps ordered by the txIndex + tfrom, tto = tto, tfrom + } + if tfrom >= 0 { + deps = append(deps, uint64(tfrom)) + } + if tto >= 0 { + deps = append(deps, uint64(tto)) + } + dag.SetTxDep(tx.txIndex, types.TxDep{TxIndexes: deps}) + marked[tx.msg.From] = tx.txIndex + marked[*tx.msg.To] = tx.txIndex + } +} + +func NewTxLevels(all []*PEVMTxRequest, dag types.TxDAG) TxLevels { + var levels TxLevels = make(TxLevels, 0, 8) + var currLevel int = 0 + + var enlargeLevelsIfNeeded = func(currLevel int, levels *TxLevels) { + if len(*levels) <= currLevel { + for i := len(*levels); i <= currLevel; i++ { + *levels = append(*levels, TxLevel{}) + } + } + } + + if len(all) == 0 { + return nil + } + if dag == nil { + return TxLevels{all} + } + + marked := make(map[int]int, len(all)) + for _, tx := range all { + dep := dag.TxDep(tx.txIndex) + switch true { + case dep != nil && dep.CheckFlag(types.ExcludedTxFlag), + dep != nil && dep.CheckFlag(types.NonDependentRelFlag): + // excluted tx, occupies the whole level + // or dependent-to-all tx, occupies the whole level, too + levels = append(levels, TxLevel{tx}) + marked[tx.txIndex], currLevel = len(levels)-1, len(levels) + + case dep == nil || len(dep.TxIndexes) == 0: + // dependent on none + enlargeLevelsIfNeeded(currLevel, &levels) + levels[currLevel] = append(levels[currLevel], tx) + marked[tx.txIndex] = currLevel + + case dep != nil && len(dep.TxIndexes) > 0: + // dependent on others + // findout the correct level that the tx should be put + prevLevel := -1 + for _, txIndex := range dep.TxIndexes { + if pl, ok := marked[int(txIndex)]; ok && pl > prevLevel { + prevLevel = pl + } + } + if prevLevel < 0 { + // broken DAG, just ignored it + enlargeLevelsIfNeeded(currLevel, &levels) + levels[currLevel] = append(levels[currLevel], tx) + marked[tx.txIndex] = currLevel + continue + } + enlargeLevelsIfNeeded(prevLevel+1, &levels) + levels[prevLevel+1] = append(levels[prevLevel+1], tx) + // record the level of this tx + marked[tx.txIndex] = prevLevel + 1 + + default: + panic("unexpected case") + } + } + return levels +} diff --git a/core/parallel_state_scheduler_test.go b/core/parallel_state_scheduler_test.go new file mode 100644 index 0000000000..9b2496cc0f --- /dev/null +++ b/core/parallel_state_scheduler_test.go @@ -0,0 +1,884 @@ +package core + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + "os" + "sync" + "sync/atomic" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" +) + +// mock transaction via ParallelTxRequest and ParallelTxResult. and the methods we need to mock is "transfer balance from on address to other" +// ParallelTxRequest: +// 1. usedGas -> value +// 2. staticSlotIndex -> fromAddress +// 3. runnable -> toAddress +// +// a mock transaction do the following steps when "execute" in parallel: +// 1. read balance of "fromAddress" from maindb +// 2. check balance and sub the amount of "value", records the "reads"(fromAddr->balance), write balance back into "slotDB" +// 3. read balance of "toAddress" from maindb +// 4. add the "value" to toAddress, records the "reads"(toAddr->balance), write it into "slotDB" +// +// a mock transaction do the following steps when "confirm": +// 1. check the "reads"(addresses->balances) in maindb +// 2. merge the "slotDB"(addresses->balances) into maindb + +type mockTx struct { + req *PEVMTxRequest + reads map[int]int + slotDB map[int]int +} + +var mockMainDB sync.Map + +func putMainDB(data map[int]int) { + for k, v := range data { + mockMainDB.Store(k, v) + } +} + +func checkMainDB(data map[int]int) bool { + for k, v := range data { + val, ok := mockMainDB.Load(k) + if !ok { + return false + } + if val.(int) != v { + return false + } + } + return true +} + +func newTxReq(from, to, value int) *PEVMTxRequest { + usedGas := uint64(value) + return &PEVMTxRequest{ + usedGas: &usedGas, + gasLimit: uint64(from), + value: to, + } +} + +func (mt *mockTx) Value() int { + return int(*mt.req.usedGas) +} + +func (mt *mockTx) From() int { + return int(mt.req.gasLimit) +} + +func (mt *mockTx) To() int { + return int(mt.req.value) +} + +func (mt *mockTx) execute(req *PEVMTxRequest) *PEVMTxResult { + result := &PEVMTxResult{ + txReq: req, + err: nil, + } + mt.req = req + // handle fromAddress + balance, ok := mockMainDB.Load(mt.From()) + if !ok { + result.err = fmt.Errorf("no balance from '%d'", mt.From()) + return result + } + if balance.(int) < mt.Value() { + result.err = fmt.Errorf("insufficient found, need '%d', have '%d', fromaddr:%d", mt.Value(), balance.(int), mt.From()) + return result + } + mt.reads[mt.From()] = balance.(int) + mt.slotDB[mt.From()] = balance.(int) - mt.Value() + + // handle toAddress + balance, ok = mockMainDB.Load(mt.To()) + if !ok { + result.err = fmt.Errorf("no balance from '%d'", mt.To()) + return result + } + mt.reads[mt.To()] = balance.(int) + mt.slotDB[mt.To()] = balance.(int) + mt.Value() + return result +} + +func (mt *mockTx) confirm(result *PEVMTxResult) error { + // check conflict + fromBalance, _ := mockMainDB.Load(mt.From()) + toBalance, _ := mockMainDB.Load(mt.To()) + if fromBalance.(int) != mt.reads[mt.From()] { + return fmt.Errorf("fromAddress state invalid") + } + if toBalance.(int) != mt.reads[mt.To()] { + return fmt.Errorf("toAddress state invalid") + } + // merge slotDB into mainDB + putMainDB(mt.slotDB) + return nil +} + +type caller struct { + lock sync.Mutex + txs map[*PEVMTxRequest]*mockTx + conflictNum int32 +} + +func (c *caller) execute(req *PEVMTxRequest) *PEVMTxResult { + mocktx := &mockTx{ + reads: make(map[int]int), + slotDB: make(map[int]int), + } + result := mocktx.execute(req) + c.lock.Lock() + c.txs[req] = mocktx + if result.err != nil { + atomic.AddInt32(&c.conflictNum, 1) + } + c.lock.Unlock() + return result +} + +func (c *caller) confirm(result *PEVMTxResult) error { + c.lock.Lock() + mtx := c.txs[result.txReq] + c.lock.Unlock() + err := mtx.confirm(result) + if err != nil { + atomic.AddInt32(&c.conflictNum, 1) + } + return err +} + +func TestParallelProcess(t *testing.T) { + // mock a state db,which provide: + // KV read/write (in memory) + // State read/write (in memory) ? + // Trie read/write (in memory) ? +} + +func TestTxLevelSplit(t *testing.T) { + // case 1: split empty levels + // case 2: split 1 level into 1 chunk + // case 3: split 1 level into 2 chunks, each have 1 tx + // case 4: split 1 level into 3 + txLevels := TxLevel{} + if len(txLevels.Split(1)) != 0 { + t.Fatalf("invalid split, expected:0, got:%d", len(txLevels.Split(1))) + } + txLevels = TxLevel{newTxReq(1, 2, 10)} + chunks := txLevels.Split(1) + if len(chunks) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks)) + } + if len(chunks[0]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[0])) + } + mock := &mockTx{req: chunks[0][0]} + if mock.Value() != 10 { + t.Fatalf("invalid split, expected:10, got:%d", mock.Value()) + } + + txLevels = TxLevel{newTxReq(1, 2, 10), newTxReq(2, 3, 20)} + chunks = txLevels.Split(2) + if len(chunks) != 2 { + t.Fatalf("invalid split, expected:2, got:%d", len(chunks)) + } + if len(chunks[0]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[0])) + } + if len(chunks[1]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[1])) + } + mock1, mock2 := &mockTx{req: chunks[0][0]}, &mockTx{req: chunks[1][0]} + if mock1.Value() != 10 { + t.Fatalf("invalid split, expected:10, got:%d", mock1.Value()) + } + if mock2.Value() != 20 { + t.Fatalf("invalid split, expected:20, got:%d", mock2.Value()) + } + + txLevels = TxLevel{newTxReq(1, 2, 10), newTxReq(2, 3, 20), newTxReq(3, 4, 30)} + chunks = txLevels.Split(2) + if len(chunks) != 2 { + t.Fatalf("invalid split, expected:2, got:%d", len(chunks)) + } + if len(chunks[0]) != 2 { + t.Fatalf("invalid split, expected:2, got:%d", len(chunks[0])) + } + if len(chunks[1]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[1])) + } + mock1, mock2, mock3 := &mockTx{req: chunks[0][0]}, &mockTx{req: chunks[0][1]}, &mockTx{req: chunks[1][0]} + if mock1.Value() != 10 { + t.Fatalf("invalid split, expected:10, got:%d", mock1.Value()) + } + if mock2.Value() != 20 { + t.Fatalf("invalid split, expected:20, got:%d", mock2.Value()) + } + if mock3.Value() != 30 { + t.Fatalf("invalid split, expected:30, got:%d", mock3.Value()) + } + + chunks = txLevels.Split(5) + if len(chunks) != 3 { + t.Fatalf("invalid split, expected:3, got:%d", len(chunks)) + } + if len(chunks[0]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[0])) + } + if len(chunks[1]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[1])) + } + if len(chunks[2]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[2])) + } + mock1, mock2, mock3 = &mockTx{req: chunks[0][0]}, &mockTx{req: chunks[1][0]}, &mockTx{req: chunks[2][0]} + if mock1.Value() != 10 { + t.Fatalf("invalid split, expected:10, got:%d", mock1.Value()) + } + if mock2.Value() != 20 { + t.Fatalf("invalid split, expected:20, got:%d", mock2.Value()) + } + if mock3.Value() != 30 { + t.Fatalf("invalid split, expected:30, got:%d", mock3.Value()) + } + +} + +func setTxIndex(allReq []*PEVMTxRequest) { + for i, req := range allReq { + req.txIndex = i + } +} + +func TestTxLevelRun(t *testing.T) { + // case 1: empty txs + case1 := func() { + levels([]uint64{}, [][]int{}).Run( + func(*PEVMTxRequest) *PEVMTxResult { return nil }, + func(*PEVMTxResult) error { return nil }) + } + // case 2: 4 txs with no dependencies, no conflicts + case2 := func() { + // mainDB: [1: 10, 2: 20, 3: 30, 4:40, 5:1, 6:1, 7:1, 8:1] + // txs: [1,2,3,4] ->(all balances)-> [5,6,7,8] + // true txdag: [][]int{nil, nil, nil, nil} + // result: + // mainDB: [1: 0, 2: 0, 3: 0, 4:0, 5:11, 6:21, 7:31, 8:41] + // conflictNum: 0 + putMainDB(map[int]int{1: 10, 2: 20, 3: 30, 4: 40, 5: 1, 6: 1, 7: 1, 8: 1}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 5, 10), + newTxReq(2, 6, 20), + newTxReq(3, 7, 30), + newTxReq(4, 8, 40), + } + setTxIndex(allReqs) + txdag := int2txdag([][]int{ + nil, nil, nil, nil, + }) + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm) + ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21, 7: 31, 8: 41}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum != 0 { + t.Fatalf("conflict found, conflict:%d", caller.conflictNum) + } + } + + // case 3: 4 txs with 2 dependencies, no conflict + // actual: t1->t0, t3->t2 + // true txdag: [t0, t2], [t3, t4] + // result: all of t1~t4 need rerun + case3 := func() { + // mainDB: [1: 10, 2: 20, 3: 0, 4:0, 5:1, 6:1] + // txs: [1->3:10, 2->4:20, 3->5:10, 4->6:20] + // true txdag: [][]int{nil, nil, {0}, {1}}) + // result: + // mainDB: [1: 0, 2: 0, 3: 0, 4:0, 5:11, 6:21] + // conflictNum: 0 + putMainDB(map[int]int{1: 10, 2: 20, 3: 0, 4: 0, 5: 1, 6: 1}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 3, 10), + newTxReq(2, 4, 20), + newTxReq(3, 5, 10), + newTxReq(4, 6, 20), + } + setTxIndex(allReqs) + txdag := int2txdag([][]int{ + nil, nil, {0}, {1}, + }) + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm) + ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum != 0 { + t.Fatalf("conflict found, conflict:%d", caller.conflictNum) + } + } + + // case 4: 4 txs with 2 dependencies, with 4 conflict + // actual: t1->t0, t3->t2 + // false txdag: [t4],[t3],[t0, t1] + // result: all of t1~t4 need rerun + case4 := func() { + // mainDB: [1: 10, 2: 20, 3: 0, 4:0, 5:1, 6:1] + // txs: [1->3:10, 2->4:20, 3->5:10, 4->6:20] + // false txdag: {0}, nil, {-1}, {-1}, + // result: + // mainDB: [1: 0, 2: 0, 3: 0, 4:0, 5:11, 6:21] + // conflictNum: 2 + putMainDB(map[int]int{1: 10, 2: 20, 3: 0, 4: 0, 5: 1, 6: 1}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 3, 10), + newTxReq(2, 4, 20), + newTxReq(3, 5, 10), + newTxReq(4, 6, 20), + } + setTxIndex(allReqs) + txdag := int2txdag([][]int{ + {0}, nil, {-1}, {-1}, + }) + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm) + ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum != 0 { + t.Fatalf("conflict found, conflict:%d", caller.conflictNum) + } + } + + // smoking test + case5 := func() { + // addressSetA: 1~1000, addressSetB: 1001~2000, addressSetC: 2001~3000 + // store balance 1~1000 for addressSetA into mainDB + // A -> B -> C + // check balance of C + // txdag: all in level 0, some conflict will come up + storeBalance := func(from, to int, value int) { + for i := from; i <= to; i++ { + if value == 0 { + mockMainDB.Store(i, 0) + } else { + mockMainDB.Store(i, i) + } + } + } + storeBalance(1, 1000, 1) + storeBalance(1001, 2000, 0) + storeBalance(2001, 3000, 0) + allReqs := make([]*PEVMTxRequest, 0, 2000) + // addressSetA -> addressSetB + for i := 1; i <= 1000; i++ { + allReqs = append(allReqs, newTxReq(i, i+1000, i)) + } + // addressSetB -> addressSetC + for i := 1; i <= 1000; i++ { + allReqs = append(allReqs, newTxReq(i+1000, i+2000, i)) + } + setTxIndex(allReqs) + // result of addressSetC + res := make(map[int]int, 1000) + for i := 1; i <= 1000; i++ { + res[i+2000] = i + } + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm) + ok := checkMainDB(res) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum <= 0 { + t.Fatalf("conflict not found, conflict:%d", caller.conflictNum) + } + } + // case 6: txs with dependencies, but no txdag + case6 := func() { + // mainDB: [1: 15, 2: 20, 3: 0] + // txs: [1->2:10, 2->3:10] + // txdag: nil + // result: + // mainDB: [1: 5, 2: 20, 3: 10] + putMainDB(map[int]int{1: 15, 2: 20, 3: 0}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 2, 10), + newTxReq(2, 3, 10), + } + setTxIndex(allReqs) + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm) + ok := checkMainDB(map[int]int{1: 5, 2: 20, 3: 10}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + } + + // case 7: txs with dependencies, with default txdag (deposit tx + all comment txs) + case7 := func() { + //dag := &types.PlainTxDAG{} + //dag.SetTxDep(0, types.TxDep{TxIndexes: nil, Flags: &types.ExcludedTxFlag}) + dag := int2txdag([][]int{ + {-1}, nil, + }) + // mainDB: [1: 15, 2: 20, 3: 0] + // txs: [1->2:10, 2->3:10] + // txdag: [-1, nil] + // result: + // mainDB: [1: 5, 2: 20, 3: 10] + putMainDB(map[int]int{1: 15, 2: 20, 3: 0}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 2, 10), + newTxReq(2, 3, 10), + } + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + setTxIndex(allReqs) + err, _ := NewTxLevels(allReqs, dag).Run(caller.execute, caller.confirm) + ok := checkMainDB(map[int]int{1: 5, 2: 20, 3: 10}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum != 0 { + t.Fatalf("conflict found, conflict:%d", caller.conflictNum) + } + + } + + case1() + case2() + case3() + case4() + case5() + case6() + case7() +} + +func TestPredictTxDAG(t *testing.T) { + var buildTxReq = func(from, to common.Address, txIndex int) *PEVMTxRequest { + return &PEVMTxRequest{ + txIndex: txIndex, + msg: &Message{ + From: from, + To: &to, + }, + } + } + + var buildLevels = func(addresses [][2]common.Address) TxLevel { + tl := make(TxLevel, 0, len(addresses)) + for i, addr := range addresses { + tl = append(tl, buildTxReq(addr[0], addr[1], i)) + } + return tl + } + + // case 1. empty txs + var addr1, addr2, addr3, addr4 = common.Address{1}, common.Address{2}, common.Address{3}, common.Address{4} + var dag = &types.PlainTxDAG{} + txs := buildLevels([][2]common.Address{}) + txs.predictTxDAG(dag) + newLevel := NewTxLevels(txs, dag) + if len(newLevel) != 0 { + t.Fatalf("failed, expected empty") + } + + // case 2. 1 tx with no dependencies + txs = buildLevels([][2]common.Address{{addr1, addr2}}) + dag = &types.PlainTxDAG{} + txs.predictTxDAG(dag) + newLevel = NewTxLevels(txs, dag) + if len(newLevel) != 1 { + t.Fatalf("failed, expected 1, got:%d", len(newLevel)) + } + if newLevel[0][0].msg.From != addr1 { + t.Fatalf("failed, expected addr1, got:%v", newLevel[0][0].msg.From) + } + // case 3. 3 txs with 2 dependencies, a->b, b->c + txs = buildLevels([][2]common.Address{{addr1, addr2}, {addr2, addr3}, {addr3, addr4}}) + dag = &types.PlainTxDAG{} + txs.predictTxDAG(dag) + newLevel = NewTxLevels(txs, dag) + if len(newLevel) != 3 { + t.Fatalf("failed, expected 3, got:%d", len(newLevel)) + } + if newLevel[0][0].msg.From != addr1 || newLevel[1][0].msg.From != addr2 || newLevel[2][0].msg.From != addr3 { + t.Fatalf("failed, expected addr1, addr2, addr3, got:%v, %v, %v", newLevel[0][0].msg.From, newLevel[1][0].msg.From, newLevel[2][0].msg.From) + } + // case 4. 4 txs with 3 dependencies, a->b, b->c, d->b + txs = buildLevels([][2]common.Address{{addr1, addr2}, {addr2, addr3}, {addr4, addr2}, {addr3, addr1}}) + dag = &types.PlainTxDAG{} + txs.predictTxDAG(dag) + newLevel = NewTxLevels(txs, dag) + if len(newLevel) != 3 { + t.Fatalf("failed, expected 3, got:%d", len(newLevel)) + } + if len(newLevel[0]) != 1 || len(newLevel[1]) != 1 || len(newLevel[2]) != 2 { + t.Fatalf("failed, expected 1, 1, 2, got:%d, %d, %d", len(newLevel[0]), len(newLevel[1]), len(newLevel[2])) + } + if newLevel[0][0].msg.From != addr1 || newLevel[1][0].msg.From != addr2 { + t.Fatalf("failed, expected addr1, addr2, got:%v, %v", newLevel[0][0].msg.From, newLevel[1][0].msg.From) + } +} + +func TestNewTxLevels(t *testing.T) { + // definition of dependencies: + // {-1} means dependent all txs + // {-2} means excluded tx + // nil means no dependencies + // {0,1} means dependent on tx[0] and tx[1] + + // case 1: empty txs + assertEqual(levels([]uint64{}, [][]int{}), [][]uint64{}, t) + + // case 2: txs with no dependencies + // tx[0] has no dependencies, tx[0].Nonce() == 1 + // tx[1] has no dependencies, tx[1].Nonce() == 2 + // tx[2] has no dependencies, tx[2].Nonce() == 3 + // tx[3] has no dependencies, tx[3].Nonce() == 4 + assertEqual(levels([]uint64{1, 2, 3, 4}, [][]int{nil, nil, nil, nil}), [][]uint64{{1, 2, 3, 4}}, t) + + // case 3: txs with dependencies + // tx[0] has no dependencies, tx[0].Nonce() == 1 + // tx[1] depends on tx[0], tx[1].Nonce() == 2 + // tx[2] depends on tx[1], tx[2].Nonce() == 3 + assertEqual(levels([]uint64{1, 2, 3}, [][]int{nil, {0}, {1}}), [][]uint64{{1}, {2}, {3}}, t) + + // case 4: txs with dependencies and no dependencies + // tx[0] has no dependencies, tx[0].Nonce() == 1 + // tx[1] has no dependencies, tx[1].Nonce() == 2 + // tx[2] dependents on t[0], tx[2].Nonce() == 3 + // tx[3] dependents on t[0], tx[3].Nonce() == 4 + // tx[4] dependents on t[2], tx[4].Nonce() == 5 + // tx[5] dependents on t[3], tx[5].Nonce() == 6 + assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6}, [][]int{nil, nil, {0}, {1}, {2}, {3}}), [][]uint64{{1, 2}, {3, 4}, {5, 6}}, t) + + // case 5: 1 excluded tx + n no dependencies tx + assertEqual(levels([]uint64{1, 2, 3}, [][]int{{-1}, nil, nil}), [][]uint64{{1}, {2, 3}}, t) + + // case 6: 1 excluded tx + n no dependencies txs + n all-dependencies txs + assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6}, [][]int{{-1}, nil, nil, nil, {-2}, {-2}}), [][]uint64{{1}, {2, 3, 4}, {5}, {6}}, t) + + // case 7: 1 excluded tx + n no dependencies txs + n dependencies txs + 1 all-dependencies tx + assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6, 7}, [][]int{{-1}, nil, nil, nil, {0, 1}, {2}, {-2}}), [][]uint64{{1}, {2, 3, 4}, {5, 6}, {7}}, t) + + // case 8: n no dependencies txs + n all-dependencies txs + assertEqual(levels([]uint64{1, 2, 3, 4, 5}, [][]int{nil, nil, nil, {-2}, {-2}}), [][]uint64{{1, 2, 3}, {4}, {5}}, t) + + // case 9: loop-back txdag + assertEqual(levels([]uint64{1, 2, 3, 4}, [][]int{{1}, nil, {0}, nil}), [][]uint64{{1, 2, 4}, {3}}, t) +} + +func TestMultiLevel(t *testing.T) { + // case 7: 1 excluded tx + n no dependencies txs + n dependencies txs + 1 all-dependencies tx + assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6, 7, 8}, [][]int{nil, nil, nil, {0}, nil, {1}, nil, {2}}), [][]uint64{{1, 2, 3, 5, 7}, {4, 6, 8}}, t) +} + +func levels(nonces []uint64, txdag [][]int) TxLevels { + return NewTxLevels(nonces2txs(nonces), int2txdag(txdag)) +} + +func nonces2txs(nonces []uint64) []*PEVMTxRequest { + rq := make([]*PEVMTxRequest, len(nonces)) + for i, nonce := range nonces { + if nonce == 0 { + rq[i] = nil + } else { + rq[i] = &PEVMTxRequest{tx: types.NewTransaction(nonce, common.Address{}, nil, 0, nil, nil), txIndex: i} + } + } + return rq +} + +func int2txdag(txdag [][]int) types.TxDAG { + dag := types.PlainTxDAG{} + for i, deps := range txdag { + var dep types.TxDep + switch true { + case len(deps) == 0: + dep = types.TxDep{TxIndexes: nil, Flags: nil} + case deps[0] == -1: + dep = types.TxDep{TxIndexes: nil, Flags: &types.ExcludedTxFlag} + case deps[0] == -2: + dep = types.TxDep{TxIndexes: nil, Flags: &types.NonDependentRelFlag} + default: + converted := make([]uint64, len(deps)) + for j, dep := range deps { + converted[j] = uint64(dep) + } + dep = types.TxDep{TxIndexes: converted, Flags: nil} + } + if err := dag.SetTxDep(i, dep); err != nil { + panic("wrong txdag") + } + } + return &dag +} + +func assertEqual(actual TxLevels, expected [][]uint64, t *testing.T) { + if len(actual) != len(expected) { + t.Fatalf("expected %d levels, got %d levels", len(expected), len(actual)) + return + } + for i, txLevel := range actual { + if len(txLevel) != len(expected[i]) { + t.Fatalf("expected %d txs in level %d, got %d txs", len(expected[i]), i, len(txLevel)) + return + } + for j, tx := range txLevel { + if tx.tx.Nonce() != expected[i][j] { + t.Fatalf("expected nonce: %d, got nonce: %d", tx.tx.Nonce(), expected[i][j]) + } + } + } +} + +func assertEqual2(actual TxLevels, expected [][]uint64, t *testing.T) { + if len(actual) != len(expected) { + t.Fatalf("expected %d levels, got %d levels", len(expected), len(actual)) + return + } + // reverse all levels + for i := 0; i < len(actual); i++ { + for j := 0; j < len(actual[i])/2; j++ { + actual[i][j], actual[i][len(actual[i])-1-j] = actual[i][len(actual[i])-1-j], actual[i][j] + } + } + for i, txLevel := range actual { + if len(txLevel) != len(expected[i]) { + t.Fatalf("expected %d txs in level %d, got %d txs", len(expected[i]), i, len(txLevel)) + return + } + for j, tx := range txLevel { + if tx.tx.Nonce() != expected[i][j] { + t.Fatalf("expected nonce: %d, got nonce: %d", tx.tx.Nonce(), expected[i][j]) + } + } + } +} + +func getBlockTransactions(url string, blockNumber int64) (types.Transactions, error) { + client, err := ethclient.Dial(url) + if err != nil { + return nil, fmt.Errorf("failed to connect to the Ethereum client: %v", err) + } + + block, err := client.BlockByNumber(context.Background(), big.NewInt(blockNumber)) + if err != nil { + return nil, fmt.Errorf("failed to get block: %v", err) + } + + return block.Transactions(), nil +} + +func loadChainConfigFromGenesis(filePath string) (*params.ChainConfig, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("failed to open genesis file: %v", err) + } + defer file.Close() + + byteValue, err := ioutil.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("failed to read genesis file: %v", err) + } + + var genesis Genesis + if err := json.Unmarshal(byteValue, &genesis); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis file: %v", err) + } + + return genesis.Config, nil +} + +func TestShowDAG(t *testing.T) { + //// Example usage + //genesisFilePath := "/Users/awen/Desktop/Nodereal/aweneagle_projects/chain-infra/qa/gitops/qa-us/opbnb-qanet-ec-5/contracts-info/genesis.json" + //chainConfig, err := loadChainConfigFromGenesis(genesisFilePath) + //if err != nil { + // log.Fatalf("Failed to load chain config: %v", err) + //} + //signer := types.LatestSigner(chainConfig) + //_, dag, err := readTxDAGItemFromLine(block8966()) + //if err != nil { + // panic(err) + //} + //txs, err := getBlockTransactions("https://opbnb-qanet-ec-5-seq-pevm-2.bk.nodereal.cc", 8966) + //if err != nil { + // panic(err) + //} + ////check dag[0]; it should be a deposit tx, and should have a exclude flag + //if !dag.TxDep(0).CheckFlag(types.ExcludedTxFlag) { + // panic("broken dag, deposit tx should have exclude flag") + //} + //var dependTo = func(a *types.Transaction, b *types.Transaction) bool { + // if a.Nonce() <= b.Nonce() { + // return false + // } + // aFromAddr, err := types.Sender(signer, a) + // if err != nil { + // panic(err) + // } + // bFromAddr, err := types.Sender(signer, b) + // if err != nil { + // panic(err) + // } + // aToAddr, bToAddr := *(a.To()), *(b.To()) + + // if aFromAddr.Cmp(bFromAddr) == 0 || aFromAddr.Cmp(bToAddr) == 0 { + // return true + // } + // return aToAddr.Cmp(bFromAddr) == 0 || aToAddr.Cmp(bToAddr) == 0 + //} + //fmt.Printf("total tx:%d, dag tx:%d\n", len(txs), dag.TxCount()) + //depCorrect, depWrong := 1, 0 + //for i := 1; i < len(txs); i++ { + // var deps = map[int]struct{}{} + // // no need to add tx[0], must be dependent on tx[0] + // for j := 1; j < i; j++ { + // if dependTo(txs[i], txs[j]) { + // deps[j] = struct{}{} + // } + // } + // ofrom, _ := types.Sender(signer, txs[i]) + // oto := txs[i].To() + // temp := dag.TxDep(i).TxIndexes + // // filter out 0 + // originDeps := make([]uint64, 0, len(temp)) + // for _, j := range temp { + // if j != 0 { + // originDeps = append(originDeps, j) + // } + // } + // passed := true + // if len(deps) != len(originDeps) { + // fmt.Printf("tx[%d:%s] has %d deps, but dag has %d deps\n", i, txs[i].Hash().String(), len(deps), len(originDeps)) + // passed = false + // //expected: + // for ti := range originDeps { + // from, _ := types.Sender(signer, txs[ti]) + // to := txs[ti].To() + // fmt.Printf("expected tindex=%d, from=%s, to=%s => tindex=%d, from=%s, to=%s\n", i, ofrom, oto, ti, from, to) + // } + // //actual: + // if len(deps) > 0 { + // for ti := range deps { + // from, _ := types.Sender(signer, txs[ti]) + // to := txs[ti].To() + // fmt.Printf("tx[%d] actual from=%s, to=%s, tindex=%d, from=%s, to=%s txhash=%s dag:%v, dagFlag:%d\n", i, ofrom, oto, ti, from, to, txs[ti].Hash().String(), dag.TxDep(ti).TxIndexes, dag.TxDep(ti).Flags) + // } + // } else { + // fmt.Printf("actual tindex=%d, from=%s, to=%s => no txs\n", i, ofrom, oto) + // } + // } else { + // for _, j := range originDeps { + // if _, ok := deps[int(j)]; !ok { + // fmt.Printf("tx[%d:%s] has %d deps, but dag has %d deps\n", i, txs[i].Hash().String(), len(deps), len(originDeps)) + // passed = false + // } + // } + // if !passed { + // //expected: + // for ti := range originDeps { + // from, _ := types.Sender(signer, txs[ti]) + // to := txs[ti].To() + // fmt.Printf("expected tindex=%d, from=%s, to=%s => tindex=%d, from=%s, to=%s\n", i, ofrom, oto, ti, from, to) + // } + // //actual: + // if len(deps) > 0 { + // for ti := range deps { + // from, _ := types.Sender(signer, txs[ti]) + // to := txs[ti].To() + // fmt.Printf("tx[%d] actual from=%s, to=%s, tindex=%d, from=%s, to=%s txhash=%s dag:%v, dagFlag:%d\n", i, ofrom, oto, ti, from, to, txs[ti].Hash().String(), dag.TxDep(ti).TxIndexes, dag.TxDep(ti).Flags) + // } + // } else { + // fmt.Printf("actual tindex=%d, from=%s, to=%s => no txs\n", i, ofrom, oto) + // } + // } + // } + // if passed { + // depCorrect++ + // fmt.Printf("tx[%d:%s] passed =====\n", i, txs[i].Hash().String()) + // } else { + // fmt.Printf("tx[%d:%s] failed =====\n", i, txs[i].Hash().String()) + // depWrong++ + // } + //} + + //fmt.Printf("total tx:%d, total dep:%d, depCorrect:%d, depWrong:%d\n", len(txs), dag.TxCount(), depCorrect, depWrong) +} + +func TestNewLevel(t *testing.T) { + //fetchTxIndexes := func(level TxLevel) []int { + // indexes := make([]int, len(level)) + // for i, tx := range level { + // indexes[i] = tx.txIndex + // } + // return indexes + //} + //dag := txDAG(txDAGData()) + //fmt.Printf("txCount:%d\n", dag.TxCount()) + //nonces := make([]uint64, dag.TxCount()) + //for i := 0; i < dag.TxCount(); i++ { + // nonces[i] = uint64(i) + 1 + //} + //txs := nonces2txs(nonces) + //fmt.Printf("txs:%d\n", len(txs)) + //levels := NewTxLevels(txs, dag) + //fmt.Printf("levels:%d\n", len(levels)) + //for i := 0; i < len(levels); i++ { + // fmt.Printf("level [%d]: len=%d, elements:%v\n", i, len(levels[i]), fetchTxIndexes(levels[i])) + //} +} + +func txDAG(encode []byte) types.TxDAG { + if dag, err := types.DecodeTxDAGCalldata(encode); err != nil { + panic(err) + } else { + return dag + } +} + +func txDAGData() []byte { + data := string("0x5517ed8c000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000012bd01f912b9f912b6c2c002c1c0c1c0c2c102c2c103c2c104c2c105c2c106c2c107c2c108c2c109c2c10ac2c10bc2c10cc2c10dc2c10ec2c10fc2c110c2c111c2c112c2c113c2c114c2c115c2c116c2c117c2c118c2c119c2c11ac2c11bc2c11cc2c11dc2c11ec2c11fc2c120c2c121c2c122c2c123c2c124c2c125c2c126c2c127c2c128c2c129c2c12ac2c12bc2c12cc2c12dc2c12ec2c12fc2c130c2c131c2c132c2c133c2c134c2c135c2c136c2c137c2c138c2c139c2c13ac2c13bc2c13cc2c13dc2c13ec2c13fc2c140c2c141c2c142c2c143c2c144c2c145c2c146c2c147c2c148c2c149c2c14ac2c14bc2c14cc2c14dc2c14ec2c14fc2c150c2c151c2c152c2c153c2c154c2c155c2c156c2c157c2c158c2c159c2c15ac2c15bc2c15cc2c15dc2c15ec2c15fc2c160c2c161c2c162c2c163c2c164c2c165c2c166c2c167c2c168c2c169c2c16ac2c16bc2c16cc2c16dc2c16ec2c16fc2c170c2c171c2c172c2c173c2c174c2c175c2c176c2c177c2c178c2c179c2c17ac2c17bc2c17cc2c17dc2c17ec2c17fc3c28180c3c28181c3c28182c3c28183c3c28184c3c28185c3c28186c3c28187c3c28188c3c28189c3c2818ac3c2818bc3c2818cc3c2818dc3c2818ec3c2818fc3c28190c3c28191c3c28192c3c28193c3c28194c3c28195c3c28196c3c28197c3c28198c3c28199c3c2819ac3c2819bc3c2819cc3c2819dc3c2819ec3c2819fc3c281a0c3c281a1c3c281a2c3c281a3c3c281a4c3c281a5c3c281a6c3c281a7c3c281a8c3c281a9c3c281aac3c281abc3c281acc3c281adc3c281aec3c281afc3c281b0c3c281b1c3c281b2c3c281b3c3c281b4c3c281b5c3c281b6c3c281b7c3c281b8c3c281b9c3c281bac3c281bbc3c281bcc3c281bdc3c281bec3c281bfc3c281c0c3c281c1c3c281c2c3c281c3c3c281c4c3c281c5c3c281c6c3c281c7c3c281c8c3c281c9c3c281cac3c281cbc3c281ccc3c281cdc3c281cec3c281cfc3c281d0c3c281d1c3c281d2c3c281d3c3c281d4c3c281d5c3c281d6c3c281d7c3c281d8c3c281d9c3c281dac3c281dbc3c281dcc3c281ddc3c281dec3c281dfc3c281e0c3c281e1c3c281e2c3c281e3c3c281e4c3c281e5c3c281e6c3c281e7c3c281e8c3c281e9c3c281eac3c281ebc3c281ecc3c281edc3c281eec3c281efc3c281f0c3c281f1c3c281f2c3c281f3c3c281f4c3c281f5c3c281f6c3c281f7c3c281f8c3c281f9c3c281fac3c281fbc3c281fcc3c281fdc3c281fec3c281ffc4c3820100c4c3820101c4c3820102c4c3820103c4c3820104c4c3820105c4c3820106c4c3820107c4c3820108c4c3820109c4c382010ac4c382010bc4c382010cc4c382010dc4c382010ec4c382010fc4c3820110c4c3820111c4c3820112c4c3820113c4c3820114c4c3820115c4c3820116c4c3820117c4c3820118c4c3820119c4c382011ac4c382011bc4c382011cc4c382011dc4c382011ec4c382011fc4c3820120c4c3820121c4c3820122c4c3820123c4c3820124c4c3820125c4c3820126c4c3820127c4c3820128c4c3820129c4c382012ac4c382012bc4c382012cc4c382012dc4c382012ec4c382012fc4c3820130c4c3820131c4c3820132c4c3820133c4c3820134c4c3820135c4c3820136c4c3820137c4c3820138c4c3820139c4c382013ac4c382013bc4c382013cc4c382013dc4c382013ec4c382013fc4c3820140c4c3820141c4c3820142c4c3820143c4c3820144c4c3820145c4c3820146c4c3820147c4c3820148c4c3820149c4c382014ac4c382014bc4c382014cc4c382014dc4c382014ec4c382014fc4c3820150c4c3820151c4c3820152c4c3820153c4c3820154c4c3820155c4c3820156c4c3820157c4c3820158c4c3820159c4c382015ac4c382015bc4c382015cc4c382015dc4c382015ec4c382015fc4c3820160c4c3820161c4c3820162c4c3820163c4c3820164c4c3820165c4c3820166c4c3820167c4c3820168c4c3820169c4c382016ac4c382016bc4c382016cc4c382016dc4c382016ec4c382016fc4c3820170c4c3820171c4c3820172c4c3820173c4c3820174c4c3820175c4c3820176c4c3820177c4c3820178c4c3820179c4c382017ac4c382017bc4c382017cc4c382017dc4c382017ec4c382017fc4c3820180c4c3820181c4c3820182c4c3820183c4c3820184c4c3820185c4c3820186c4c3820187c4c3820188c4c3820189c4c382018ac4c382018bc4c382018cc4c382018dc4c382018ec4c382018fc4c3820190c4c3820191c4c3820192c4c3820193c4c3820194c4c3820195c4c3820196c4c3820197c4c3820198c4c3820199c4c382019ac4c382019bc4c382019cc4c382019dc4c382019ec4c382019fc4c38201a0c4c38201a1c4c38201a2c4c38201a3c4c38201a4c4c38201a5c4c38201a6c4c38201a7c4c38201a8c4c38201a9c4c38201aac4c38201abc4c38201acc4c38201adc4c38201aec4c38201afc4c38201b0c4c38201b1c4c38201b2c4c38201b3c4c38201b4c4c38201b5c4c38201b6c4c38201b7c4c38201b8c4c38201b9c4c38201bac4c38201bbc4c38201bcc4c38201bdc4c38201bec4c38201bfc4c38201c0c4c38201c1c4c38201c2c4c38201c3c4c38201c4c4c38201c5c4c38201c6c4c38201c7c4c38201c8c4c38201c9c4c38201cac4c38201cbc4c38201ccc4c38201cdc4c38201cec4c38201cfc4c38201d0c4c38201d1c4c38201d2c4c38201d3c4c38201d4c4c38201d5c4c38201d6c4c38201d7c4c38201d8c4c38201d9c4c38201dac4c38201dbc4c38201dcc4c38201ddc4c38201dec4c38201dfc4c38201e0c4c38201e1c4c38201e2c4c38201e3c4c38201e4c4c38201e5c4c38201e6c4c38201e7c4c38201e8c4c38201e9c4c38201eac4c38201ebc4c38201ecc4c38201edc4c38201eec4c38201efc4c38201f0c4c38201f1c4c38201f2c4c38201f3c4c38201f4c4c38201f5c4c38201f6c4c38201f7c4c38201f8c4c38201f9c4c38201fac4c38201fbc4c38201fcc4c38201fdc4c38201fec4c38201ffc4c3820200c4c3820201c4c3820202c4c3820203c4c3820204c4c3820205c4c3820206c4c3820207c4c3820208c4c3820209c4c382020ac4c382020bc4c382020cc4c382020dc4c382020ec4c382020fc4c3820210c4c3820211c4c3820212c4c3820213c4c3820214c4c3820215c4c3820216c4c3820217c4c3820218c4c3820219c4c382021ac4c382021bc4c382021cc4c382021dc4c382021ec4c382021fc4c3820220c4c3820221c4c3820222c4c3820223c4c3820224c4c3820225c4c3820226c4c3820227c4c3820228c4c3820229c4c382022ac4c382022bc4c382022cc4c382022dc4c382022ec4c382022fc4c3820230c4c3820231c4c3820232c4c3820233c4c3820234c4c3820235c4c3820236c4c3820237c4c3820238c4c3820239c4c382023ac4c382023bc4c382023cc4c382023dc4c382023ec4c382023fc4c3820240c4c3820241c4c3820242c4c3820243c4c3820244c4c3820245c4c3820246c4c3820247c4c3820248c4c3820249c4c382024ac4c382024bc4c382024cc4c382024dc4c382024ec4c382024fc4c3820250c4c3820251c4c3820252c4c3820253c4c3820254c4c3820255c4c3820256c4c3820257c4c3820258c4c3820259c4c382025ac4c382025bc4c382025cc4c382025dc4c382025ec4c382025fc4c3820260c4c3820261c4c3820262c4c3820263c4c3820264c4c3820265c4c3820266c4c3820267c4c3820268c4c3820269c4c382026ac4c382026bc4c382026cc4c382026dc4c382026ec4c382026fc4c3820270c4c3820271c4c3820272c4c3820273c4c3820274c4c3820275c4c3820276c4c3820277c4c3820278c4c3820279c4c382027ac4c382027bc4c382027cc4c382027dc4c382027ec4c382027fc4c3820280c4c3820281c4c3820282c4c3820283c4c3820284c4c3820285c4c3820286c4c3820287c4c3820288c4c3820289c4c382028ac4c382028bc4c382028cc4c382028dc4c382028ec4c382028fc4c3820290c4c3820291c4c3820292c4c3820293c4c3820294c4c3820295c4c3820296c4c3820297c4c3820298c4c3820299c4c382029ac4c382029bc4c382029cc4c382029dc4c382029ec4c382029fc4c38202a0c4c38202a1c4c38202a2c4c38202a3c4c38202a4c4c38202a5c4c38202a6c4c38202a7c4c38202a8c4c38202a9c4c38202aac4c38202abc4c38202acc4c38202adc4c38202aec4c38202afc4c38202b0c4c38202b1c4c38202b2c4c38202b3c4c38202b4c4c38202b5c4c38202b6c4c38202b7c4c38202b8c4c38202b9c4c38202bac4c38202bbc4c38202bcc4c38202bdc4c38202bec4c38202bfc4c38202c0c4c38202c1c4c38202c2c4c38202c3c4c38202c4c4c38202c5c4c38202c6c4c38202c7c4c38202c8c4c38202c9c4c38202cac4c38202cbc4c38202ccc4c38202cdc4c38202cec4c38202cfc4c38202d0c4c38202d1c4c38202d2c4c38202d3c4c38202d4c4c38202d5c4c38202d6c4c38202d7c4c38202d8c4c38202d9c4c38202dac4c38202dbc4c38202dcc4c38202ddc4c38202dec4c38202dfc4c38202e0c4c38202e1c4c38202e2c4c38202e3c4c38202e4c4c38202e5c4c38202e6c4c38202e7c4c38202e8c4c38202e9c4c38202eac4c38202ebc4c38202ecc4c38202edc4c38202eec4c38202efc4c38202f0c4c38202f1c4c38202f2c4c38202f3c4c38202f4c4c38202f5c4c38202f6c4c38202f7c4c38202f8c4c38202f9c4c38202fac4c38202fbc4c38202fcc4c38202fdc4c38202fec4c38202ffc4c3820300c4c3820301c4c3820302c4c3820303c4c3820304c4c3820305c4c3820306c4c3820307c4c3820308c4c3820309c4c382030ac4c382030bc4c382030cc4c382030dc4c382030ec4c382030fc4c3820310c4c3820311c4c3820312c4c3820313c4c3820314c4c3820315c4c3820316c4c3820317c4c3820318c4c3820319c4c382031ac4c382031bc4c382031cc4c382031dc4c382031ec4c382031fc4c3820320c4c3820321c4c3820322c4c3820323c4c3820324c4c3820325c4c3820326c4c3820327c4c3820328c4c3820329c4c382032ac4c382032bc4c382032cc4c382032dc4c382032ec4c382032fc4c3820330c4c3820331c4c3820332c4c3820333c4c3820334c4c3820335c4c3820336c4c3820337c4c3820338c4c3820339c4c382033ac4c382033bc4c382033cc4c382033dc4c382033ec4c382033fc4c3820340c4c3820341c4c3820342c4c3820343c4c3820344c4c3820345c4c3820346c4c3820347c4c3820348c4c3820349c4c382034ac4c382034bc4c382034cc4c382034dc4c382034ec4c382034fc4c3820350c4c3820351c4c3820352c4c3820353c4c3820354c4c3820355c4c3820356c4c3820357c4c3820358c4c3820359c4c382035ac4c382035bc4c382035cc4c382035dc4c382035ec4c382035fc4c3820360c4c3820361c4c3820362c4c3820363c4c3820364c4c3820365c4c3820366c4c3820367c4c3820368c4c3820369c4c382036ac4c382036bc4c382036cc4c382036dc4c382036ec4c382036fc4c3820370c4c3820371c4c3820372c4c3820373c4c3820374c4c3820375c4c3820376c4c3820377c4c3820378c4c3820379c4c382037ac4c382037bc4c382037cc4c382037dc4c382037ec4c382037fc4c3820380c4c3820381c4c3820382c4c3820383c4c3820384c4c3820385c4c3820386c4c3820387c4c3820388c4c3820389c4c382038ac4c382038bc4c382038cc4c382038dc4c382038ec4c382038fc4c3820390c4c3820391c4c3820392c4c3820393c4c3820394c4c3820395c4c3820396c4c3820397c4c3820398c4c3820399c4c382039ac4c382039bc4c382039cc4c382039dc4c382039ec4c382039fc4c38203a0c4c38203a1c4c38203a2c4c38203a3c4c38203a4c4c38203a5c4c38203a6c4c38203a7c4c38203a8c4c38203a9c4c38203aac4c38203abc4c38203acc4c38203adc4c38203aec4c38203afc4c38203b0c4c38203b1c4c38203b2c4c38203b3c4c38203b4c4c38203b5c4c38203b6c4c38203b7c4c38203b8c4c38203b9c4c38203bac4c38203bbc4c38203bcc4c38203bdc4c38203bec4c38203bfc4c38203c0c4c38203c1c4c38203c2c4c38203c3c4c38203c4c4c38203c5c4c38203c6c4c38203c7c4c38203c8c4c38203c9c4c38203cac4c38203cbc4c38203ccc4c38203cdc4c38203cec4c38203cfc4c38203d0c4c38203d1c4c38203d2c4c38203d3c4c38203d4c4c38203d5c4c38203d6c4c38203d7c4c38203d8c4c38203d9c4c38203dac4c38203dbc4c38203dcc4c38203ddc4c38203dec4c38203dfc4c38203e0c4c38203e1c4c38203e2c4c38203e3c4c38203e4c4c38203e5c4c38203e6c4c38203e7c4c38203e8c4c38203e9c4c38203eac4c38203ebc4c38203ecc4c38203edc4c38203eec4c38203efc4c38203f0c4c38203f1c4c38203f2c4c38203f3c4c38203f4c4c38203f5c4c38203f6c4c38203f7c4c38203f8c4c38203f9c4c38203fac4c38203fbc4c38203fcc4c38203fdc4c38203fec4c38203ffc4c3820400c4c3820401c4c3820402c4c3820403c4c3820404c4c3820405c4c3820406c4c3820407c4c3820408c4c3820409c2c001000000") + return common.Hex2Bytes(data[2:]) +} + +func block340670() string { + return string("340670,01f948a4f948a1c2c002c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c10ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c119c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c16ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c28195c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c144c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c111c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c132c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c170c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c126c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820134c1c0c3c281b0c1c0c1c0c1c0c4c382017bc2c144c1c0c3c281d8c1c0c1c0c3c281c5c3c28187c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382012ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382019ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820156c1c0c1c0c1c0c1c0c1c0c1c0c2c14bc1c0c1c0c2c115c1c0c3c281d3c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382010fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201a2c1c0c1c0c1c0c1c0c2c178c3c281e4c1c0c1c0c1c0c4c382018ec1c0c2c124c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820176c1c0c1c0c1c0c2c170c1c0c1c0c1c0c1c0c4c382021ac3c28197c1c0c1c0c4c38201bec1c0c1c0c7c68201c2820215c5c40c820165c1c0c1c0c2c15bc1c0c1c0c3c281a0c1c0c1c0c1c0c1c0c1c0c4c382011ac1c0c1c0c4c38201b1c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382019dc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382017ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201e4c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c167c1c0c1c0c1c0c2c156c1c0c1c0c1c0c1c0c4c38201eac1c0c3c281efc4c382017fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820244c4c382022dc1c0c4c38201b7c1c0c1c0c1c0c4c382013ac1c0c1c0c1c0c4c382017ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201edc1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c153c1c0c4c3820141c1c0c4c3820164c1c0c1c0c1c0c4c38201d5c1c0c2c13bc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201acc1c0c4c382012dc1c0c1c0c1c0c1c0c1c0c2c15ac1c0c1c0c1c0c1c0c1c0c3c281a6c1c0c1c0c1c0c1c0c1c0c1c0c4c382018fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281e2c2c130c1c0c1c0c4c3820111c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c2819ec1c0c1c0c1c0c4c382026ec4c3820170c6c581bd820198c2c12cc1c0c4c3820173c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281afc1c0c1c0c1c0c1c0c1c0c4c382014bc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820298c1c0c1c0c4c3820104c1c0c1c0c1c0c1c0c1c0c3c2818cc3c281fdc1c0c1c0c1c0c1c0c1c0c1c0c4c382011fc1c0c4c382016fc1c0c1c0c1c0c4c3820313c1c0c1c0c1c0c1c0c1c0c4c38201bcc2c171c4c3820228c1c0c1c0c1c0c1c0c1c0c1c0c3c281eac1c0c1c0c1c0c1c0c1c0c1c0c4c3820216c6c581c1820213c1c0c1c0c1c0c1c0c2c10fc1c0c1c0c4c382034dc1c0c2c124c1c0c1c0c4c382024dc4c3820272c4c382029ec1c0c1c0c1c0c2c16dc1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38202c1c1c0c2c163c1c0c1c0c1c0c1c0c1c0c1c0c3c281b2c1c0c1c0c1c0c1c0c4c38202eec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c115c1c0c1c0c2c16ac1c0c1c0c4c3820190c4c3820270c4c382011cc4c382022fc1c0c3c28184c1c0c4c38201d6c1c0c1c0c4c3820283c4c38201adc1c0c1c0c4c3820247c4c3820347c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c14dc1c0c1c0c1c0c1c0c1c0c2c130c1c0c1c0c3c281b8c1c0c1c0c1c0c1c0c1c0c4c382035dc1c0c1c0c1c0c4c3820344c1c0c1c0c1c0c1c0c1c0c1c0c4c38201bdc1c0c1c0c4c3820301c4c382015ec4c3820303c4c382014ac7c68201788201b5c1c0c1c0c1c0c1c0c1c0c4c3820153c1c0c4c3820219c1c0c1c0c4c3820244c1c0c4c38201fec1c0c1c0c1c0c3c2819ec1c0c1c0c1c0c2c142c1c0c1c0c1c0c4c382027cc1c0c4c3820237c1c0c3c28193c1c0c1c0c1c0c7c68202bc8203aac1c0c4c3820143c4c382028fc4c3820290c1c0c3c281c5c4c382010fc1c0c1c0c4c3820186c4c382036bc1c0c1c0c3c281c1c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12bc4c3820120c1c0c1c0c1c0c1c0c1c0c2c17ec4c38202b1c1c0c4c3820253c2c11bc1c0c1c0c4c382016fc1c0c1c0c1c0c4c3820373c1c0c1c0c4c382016cc1c0c1c0c1c0c1c0c1c0c1c0c4c3820257c1c0c1c0c1c0c1c0c3c28188c4c382021dc4c3820280c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203f1c4c382023ec2c17fc4c38202b4c1c0c4c38203d5c1c0c1c0c1c0c1c0c1c0c1c0c2c154c1c0c1c0c1c0c1c0c1c0c1c0c4c38203dac1c0c1c0c4c3820416c4c3820386c4c38201f5c4c3820277c2c112c1c0c1c0c1c0c1c0c1c0c2c108c4c38201dcc1c0c4c3820403c3c2819fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203cbc1c0c2c15ac4c3820409c1c0c1c0c1c0c4c382025fc1c0c1c0c4c3820155c1c0c1c0c1c0c1c0c1c0c1c0c4c382023dc4c38201dac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201b5c4c3820232c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c13ec1c0c1c0c1c0c3c281ccc1c0c1c0c1c0c4c3820267c1c0c1c0c1c0c1c0c1c0c1c0c4c38202d1c4c3820205c2c178c1c0c1c0c1c0c5c481bc81ebc7c682018c820325c4c38201e5c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820298c4c3820163c5c44682011ac4c3820318c4c3820221c1c0c4c3820372c1c0c1c0c3c281a7c1c0c1c0c1c0c1c0c2c129c1c0c4c38202d7c1c0c2c12bc1c0c1c0c1c0c1c0c1c0c4c382046fc1c0c1c0c7c682014c820394c1c0c4c38202f5c1c0c1c0c1c0c1c0c1c0c2c13ac1c0c4c382018cc1c0c1c0c3c281b7c4c382011dc4c38203d8c1c0c4c38203a6c1c0c1c0c4c38203d2c1c0c1c0c4c3820194c4c38204bec7c68201058202acc1c0c3c281d5c1c0c1c0c7c682028182037fc2c171c1c0c1c0c4c38203c6c1c0c1c0c1c0c1c0c4c38201fcc4c3820439c1c0c1c0c4c3820463c4c3820457c4c3820110c4c3820188c1c0c4c3820351c1c0c1c0c1c0c4c38202aec1c0c1c0c1c0c2c102c1c0c4c382037bc1c0c7c6820144820482c1c0c1c0c1c0c1c0c1c0c3c281cfc4c3820113c4c3820484c4c3820257c4c38202a6c3c2819bc1c0c4c33581a2c1c0c4c38202b6c1c0c1c0c4c3820388c4c3820488c1c0c1c0c4c3820423c1c0c1c0c1c0c1c0c4c38201b9c1c0c1c0c3c281e3c4c38203cdc4c3820232c1c0c1c0c4c3820388c4c382042fc4c382050cc1c0c1c0c1c0c3c281f2c4c3820385c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382037bc1c0c1c0c1c0c3c281fbc1c0c1c0c1c0c1c0c1c0c1c0c1c0c7c68201f48203cec1c0c1c0c1c0c1c0c1c0c7c682025682048ec1c0c7c68201678202d2c1c0c7c682033c8204a2c1c0c1c0c1c0c4c3820359c4c38201f7c1c0c4c3820406c1c0c4c38202b9c1c0c4c3820455c1c0c1c0c4c3820526c4c3820442c1c0c1c0c1c0c6c581b38202f6c4c3820114c1c0c1c0c1c0c4c38204d0c1c0c1c0c1c0c1c0c4c3820226c4c382033dc4c38203f4c1c0c1c0c3c281bdc1c0c1c0c3c281a2c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281eec1c0c1c0c1c0c2c125c1c0c4c38201c4c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203c1c1c0c4c382043dc4c38204a9c1c0c4c382055fc1c0c4c38202d1c1c0c1c0c4c382056dc4c3820559c1c0c1c0c1c0c4c382035fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c7c682011882013cc1c0c1c0c4c3820241c1c0c1c0c4c382053cc1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820107c1c0c4c3820122c1c0c4c382059dc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382020cc2c110c4c38201f0c4c3820363c1c0c2c159c1c0c1c0c1c0c1c0c4c38205a0c1c0c1c0c1c0c1c0c7c68201ae8205adc1c0c1c0c4c38202abc1c0c1c0c4c382055ec4c382013dc3c2818fc1c0c1c0c1c0c1c0c4c382021ec4c382058cc4c382026dc4c3820263c1c0c1c0c3c281a4c1c0c4c3820466c1c0c1c0c1c0c1c0c1c0c4c3820229c4c3820414c1c0c1c0c1c0c1c0c7c682046b8205a3c4c3820168c1c0c1c0c1c0c4c3820315c4c3820179c1c0c1c0c4c3820558c1c0c1c0c1c0c2c149c4c382033bc1c0c4c38204dcc1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38205ccc1c0c4c3820485c1c0c4c38204c0c4c38202e5c4c38202b8c1c0c1c0c1c0c7c68203d78204c4c4c382022bc1c0c4c3820469c1c0c4c3820594c4c382014dc6c581b2820319c1c0c1c0c4c382012ec1c0c4c382046ac1c0c1c0c1c0c1c0c1c0c1c0c4c38202fec1c0c1c0c1c0c4c3820121c4c3820404c3c281dac4c38205d1c4c3820142c1c0c4c3820218c1c0c1c0c4c38205e7c1c0c1c0c1c0c1c0c4c38204cfc4c38203b5c4c3820147c1c0c4c38201f3c1c0c1c0c1c0c1c0c3c281d2c1c0c2c139c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382048ac1c0c1c0c1c0c1c0c1c0c4c3820494c1c0c1c0c2c141c1c0c2c13cc1c0c4c382027cc4c38203e2c1c0c1c0c1c0c1c0c1c0c1c0c4c3820135c2c166c4c382054cc1c0c1c0c1c0c4c38204b7c4c382063fc1c0c1c0c1c0c1c0c1c0c4c38205a8c1c0c1c0c4c38205e5c4c3820597c1c0c1c0c1c0c3c281cfc4c38201d4c4c382024bc1c0c1c0c2c17cc3c281e6c1c0c4c3820370c1c0c4c38202cbc1c0c1c0c4c38202c1c3c281e8c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203bec1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382012dc4c3820142c1c0c1c0c1c0c4c38203eac1c0c1c0c1c0c1c0c4c36681e2c1c0c4c3820166c4c3820147c1c0c4c3820307c4c38205c9c3c281efc1c0c4c38204f5c1c0c1c0c1c0c1c0c1c0c4c38203afc1c0c1c0c1c0c4c3820529c1c0c1c0c1c0c3c281fbc4c38203cfc1c0c1c0c4c38205bbc1c0c4c3820413c2c112c1c0c1c0c4c382058bc1c0c1c0c1c0c4c38201ecc1c0c4c38203ddc4c38204e6c4c38205ffc1c0c4c382018fc1c0c1c0c1c0c1c0c1c0c1c0c5c4078203a7c4c3820220c4c38204efc1c0c1c0c1c0c4c38203bec1c0c4c3820266c1c0c1c0c1c0c4c38202b7c1c0c1c0c1c0c1c0c4c3820639c1c0c4c3820514c1c0c1c0c4c3820472c6c581f9820316c4c38206cac1c0c1c0c7c6820196820656c1c0c4c382047dc7c682016e82045dc1c0c4c38205b7c4c382033ec1c0c4c3820544c1c0c4c38203dbc2c165c1c0c4c3820293c1c0c4c38205f0c4c38204c1c4c38202d5c1c0c1c0c2c122c1c0c4c38201dfc4c38204fdc4c382041cc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820598c1c0c4c382030dc4c38201e2c4c382068cc1c0c4c38202bac4c3820693c1c0c4c38201e1c1c0c1c0c1c0c1c0c1c0c1c0c4c382063ac1c0c1c0c4c38204b2c1c0c4c3820593c4c382066dc4c382029fc1c0c7c68201cd820489c1c0c1c0c4c3820676c1c0c1c0c1c0c1c0c1c0c4c3820581c4c38204b0c1c0c7c68203398205afc1c0c4c3820400c4c3820243c7c68201fa8202f9c1c0c1c0c1c0c1c0c4c3820527c1c0c4c382042fc1c0c1c0c1c0c1c0c4c38203a4c4c382032ec1c0c4c38206fac1c0c7c68202808204b9c1c0c1c0c1c0c7c68201f382061ec4c38202f2c1c0c4c382055bc4c38205a4c1c0c1c0c4c38206b5c4c3820279c4c3820728c4c3820714c4c3820239c1c0c1c0c1c0c1c0c4c382072dc4c3820271c1c0c1c0c1c0c1c0c3c281f7c1c0c1c0c4c3820233c1c0c4c38203bbc4c38204a5c1c0c4c3820289c4c38204bcc1c0c1c0c4c38203d3c1c0c1c0c1c0c4c382065cc4c382075bc1c0c4c38205ccc3c281afc1c0c1c0c1c0c1c0c1c0c7c6820240820704c1c0c6c581fc8206b6c1c0c1c0c1c0c1c0c4c382051fc1c0c4c38205b3c6c5819482027ec7c682010d820273c4c382036dc7c68201288202dcc1c0c1c0c1c0c1c0c1c0c7c682032482074bc1c0c1c0c4c3820430c4c3820129c4c382070bc4c3820192c4c3820391c1c0c4c382068bc1c0c1c0c1c0c1c0c4c3820405c4c38203b9c7c682043182045bc1c0c4c3820674c1c0c1c0c4c3820305c4c3820571c1c0c1c0c1c0c4c382058ac1c0c1c0c4c38203edc1c0c4c3820132c1c0c1c0c4c38205c8c1c0c1c0c4c38205d0c1c0c5c448820690c4c3820783c1c0c4c38205dec4c3820522c1c0c4c3820166c4c3820537c4c382033bc4c38207a7c4c3820620c4c3820705c1c0c4c3820587c1c0c4c38205e4c1c0c1c0c4c382051bc4c38203e0c1c0c1c0c1c0c1c0c4c382014ec1c0c4c3820340c4c382067ec1c0c1c0c1c0c2c10bc1c0c4c38207bac1c0c1c0c4c3820634c1c0c2c10dc1c0c1c0c1c0c1c0c1c0c4c3820138c1c0c1c0c1c0c1c0c4c3820127c4c382010dc1c0c1c0c4c3820624c4c38202a7c1c0c1c0c6c581f18203f5c1c0c1c0c1c0c4c382042dc1c0c1c0c1c0c1c0c7c68201e18207bcc4c3820433c4c38203d2c1c0c1c0c1c0c1c0c1c0c1c0c5c4388207f0c1c0c1c0c1c0c4c3820564c1c0c1c0c3c2818ec1c0c4c3820772c7c682014682075cc4c38203f6c1c0c1c0c1c0c1c0c1c0c4c3820543c4c382079ac4c3820499c1c0c4c3820312c3c28198c4c3820589c4c38201f8c1c0c1c0c1c0c4c3820302c4c38202dcc4c382064ec4c382067fc1c0c4c382032dc4c382047cc1c0c7c682031d820749c1c0c1c0c4c382050dc1c0c1c0c1c0c1c0c7c68203bd82080cc4c38206d2c7c68202e9820425c1c0c1c0c4c3820786c1c0c4c38207d2c1c0c7c6820390820421c1c0c1c0c1c0c4c38207cbc1c0c4c38201c9c4c3820805c4c38202a7c1c0c1c0c1c0c1c0c1c0c1c0c4c3820789c4c3820460c4c3820456c1c0c4c38202ebc4c3820791c1c0c4c38202e8c4c38207b4c4c38205dbc1c0c1c0c1c0c1c0c1c0c4c382028cc1c0c4c3820296c1c0c1c0c4c3820465c4c382023ec1c0c1c0c1c0c1c0c4c38202fdc4c38205cac1c0c1c0c1c0c4c38203edc4c382045ac1c0c4c3820758c1c0c1c0c1c0c1c0c1c0c7c682031c820732c1c0c4c3820339c4c382080ec4c3820211c4c38207e4c4c3820781c1c0c4c3820288c1c0c1c0c1c0c1c0c1c0c4c38201afc4c3820721c4c3820199c4c3820458c1c0c1c0c4c38204c2c1c0c1c0c1c0c1c0c1c0c1c0c4c382071fc4c38203e3c1c0c4c38202cec1c0c4c382020cc1c0c1c0c4c38206bfc1c0c1c0c1c0c1c0c4c3820645c4c38202c0c1c0c4c382047ec7c682053282060fc1c0c4c38206c9c1c0c2c103c7c6820491820573c4c3820810c1c0c7c682048b8205cec1c0c4c38206bac4c3820254c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c154c4c382017bc4c382040fc4c38203bdc4c3820195c4c3820840c7c682036582063cc4c38204fbc1c0c7c6820575820661c4c38205f5c4c382044dc1c0c1c0c1c0c1c0c1c0c4c3820292c4c38203f9c1c0c4c3820876c2c15dc2c119c4c38206acc4c3820328c1c0c4c38204b5c4c382050dc1c0c1c0c4c38204d2c1c0c4c38205bfc1c0c4c382068ac1c0c2c14ac4c3820277c2c173c4c3820323c2c167c4c38204adc4c382026cc4c38202a9c1c0c4c3820692c4c382032ec1c0c4c382020bc1c0c1c0c1c0c1c0c4c3820292c1c0c1c0c1c0c1c0c4c3820131c2c16fc2c114c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820666c1c0c1c0c1c0c1c0c4c38207b5c1c0c1c0c4c3820360c1c0c4c3820577c4c3820578c1c0c1c0c1c0c1c0c4c3820643c4c382083fc4c38207c4c1c0c4c3820600c1c0c4c382073ac1c0c1c0c4c38205adc1c0c4c382059bc1c0c1c0c1c0c7c682017982052bc1c0c1c0c4c382038ac1c0c2c134c7c6820452820733c4c38207c8c4c38202cfc4c3820603c4c3820520c4c382089ac1c0c1c0c1c0c7c682017c820572c1c0c6c58183820791c1c0c1c0c4c38205e0c2c11dc4c382042ac1c0c4c382028bc1c0c4c3820515c4c3820853c1c0c4c38203c0c1c0c1c0c1c0c4c38203c7c1c0c4c38206e7c1c0c1c0c1c0c1c0c4c3820243c4c3820730c1c0c4c382016cc1c0c4c38202f9c4c382021bc4c3820555c4c38205d1c4c38204c2c4c38205a1c1c0c1c0c1c0c4c382029dc4c3820379c4c3820850c7c68201aa8205a6c7c68201d18205e6c1c0c7c68204c18204c6c4c3820527c1c0c4c382075ac4c382061cc1c0c3c2819bc1c0c4c3820169c7c68206ba82091bc1c0c4c3820605c2c12cc1c0c4c3820408c4c38201fdc1c0c1c0c4c3820482c4c3820856c4c3820424c5c4748205f8c1c0c4c38205b0c1c0c4c3820677c4c382030dc4c382051ec4c3820157c4c38204c5c7c68201a28204f6c1c0c1c0c1c0c4c382070cc1c0c1c0c1c0c4c3820867c1c0c4c382085fc7c68206af82079fc4c38207d0c4c38207a1c2c11ec4c382052ac7c682034f8204f0c4c3820445c4c3820908c4c3820943c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38208e9c1c0c1c0c1c0c1c0c4c3820230c1c0c1c0c1c0c1c0c4c38205e2c4c382042ec1c0c2c164c1c0c1c0c4c382074bc1c0c4c38207c6c1c0c4c382010cc4c3820381c4c3820696c4c38201b8c4c38207dec1c0c2c108c4c38203e7c4c382047ac4c38204eac4c3820104c4c382073cc7c682096c82098dc4c38208abc1c0c1c0c7c68201a082034ec4c38205d9c7c6820347820505c1c0c4c38208abc1c0c4c3820464c4c3820506c1c0c1c0c1c0c1c0c1c0c1c0c4c38202f4c2c128c1c0c4c38202f7c4c3820972c1c0c1c0c1c0c7c68204b3820768c1c0c4c3820886c1c0c4c382062dc1c0c4c38208f3c4c38206f9c4c38202a0c4c3820294c7c6820608820657c1c0c1c0c4c38205f3c4c382096cc4c3820560c4c3820621c1c0c3c281dec1c0c1c0c7c68201138205fec4c38201a1c4c38202a5c1c0c2c168c4c3820756c1c0c4c3820451c4c382043cc3c281dbc4c38206f8c1c0c7c6820177820259c7c6820439820985c1c0c4c38208aac1c0c4c38206b5c1c0c1c0c4c38209bac1c0c4c3820969c1c0c1c0c4c38202bbc1c0c4c3820968c1c0c4c382016bc4c38207efc4c3820497c4c3820464c1c0c1c0c1c0c1c0c1c0c7c68206b98208a3c1c0c1c0c1c0c1c0c1c0c1c0c4c38209c0c7c68203fb820797c4c38203c7c7c68201d8820203c4c3820269c1c0c4c38207b8c1c0c7c682037f8205e3c1c0c4c382019bc1c0c1c0c4c38209acc7c68207dc820864c1c0c4c382049ec1c0c1c0c1c0c1c0c1c0c1c0c7c6820429820622c1c0c4c38202d0c4c38201ebc7c6820572820973c1c0c4c382087ac3c281dfc4c382065bc1c0c4c38208d5c1c0c1c0c4c3820997c7c682056c82070fc1c0c1c0c7c68205f4820a00c1c0c4c38201b7c1c0c1c0c6c581ca820467c1c0c1c0c5c40d820640c1c0c4c382010cc4c382050bc4c382098fc1c0c4c382063bc1c0c1c0c1c0c7c68202bf8205e5c1c0c4c38202aec1c0c1c0c4c382028ec4c3820156c1c0c1c0c1c0c4c382051dc1c0c4c38209b9c4c38209a5c4c38209cbc3c2818dc1c0c1c0c4c38206ddc4c382061ac1c0c1c0c4c3820829c1c0c4c3820918c7c6820621820715c4c38206a0c2c11cc7c68208428208b9c1c0c1c0c1c0c1c0c4c3820473c4c3820849c1c0c1c0c4c3820420c1c0c4c3820404c4c3820970c4c382029bc3c281e8c4c38202e2c1c0c1c0c4c3820515c4c382022bc4c382023ac1c0c4c38201c7c4c3820121c4c38209f5c5c41e820212c1c0c1c0c4c38209d6c1c0c1c0c4c382090dc4c3820521c1c0c4c3820630c1c0c4c382065dc1c0c4c38204dec1c0c1c0c1c0c4c38206bfc4c3820435c7c682082a8209c4c4c38207ddc5c4278207acc4c3820a23c4c3820562c4c38201b4c4c38205ddc1c0c4c3820500c1c0c1c0c1c0c4c38209dbc1c0c4c38207edc1c0c4c38208bdc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38204d6c1c0c1c0c4c3820548c4c382066bc4c382064fc4c3820534c4c38202fbc4c3820158c1c0c4c38209f4c4c3820282c3c281b7c7c682014c82075dc4c38208a2c1c0c4c38202ccc4c38201cbc1c0c4c38208f9c4c382037ac2c105c4c382079fc1c0c1c0c4c38207ffc7c68204e68209c5c4c3820979c4c38203a9c4c38203d0c1c0c4c3820261c1c0c4c38209fdc4c38208d4c1c0c7c682036f8205d8c1c0c4c3820964c4c38204dac4c3820855c1c0c1c0c1c0c7c682026e8203b3c1c0c4c38205c2c1c0c4c38204e8c1c0c4c3820902c7c682054d8206e2c5c4488202e3c4c382024ac1c0c7c682021b8207b9c4c3820914c4c3820a03c3c281bbc4c382069cc4c3820794c7c682036c82039ec1c0c4c382049dc4c38208e6c3c281bac4c38203b8c1c0c4c3820569c4c382017dc1c0c2c152c4c3820a1cc4c38203d9c7c68203a0820501c4c3820a6dc1c0c7c682024f8204c9c4c3820a01c7c682063282098cc4c382058bc1c0c4c3820223c7c68202ef820427c4c38207ddc1c0c7c682062e820672c1c0c1c0c4c3820554c4c382066ec4c3820acac4c3820480c4c382037dc4c3820570c1c0c4c38206ebc4c38206b1c4c382081fc1c0c1c0c4c3820633c5c40e82058cc4c3820546c6c581e7820190c1c0c4c382076dc7c682026f8209cdc1c0c1c0c4c3820508c1c0c1c0c1c0c7c68207d482099bc1c0c4c3820687c1c0c1c0c7c6820923820a71c4c38208f6c4c382039cc4c38202e6c7c68207bd820866c1c0c5c436820a92c1c0c1c0c1c0c4c382085cc4c382095ac4c382013cc1c0c4c382045bc4c3820516c7c68208698208d5c4c382022ac4c3820390c4c3820419c1c0c1c0c1c0c4c3820601c1c0c1c0c2c142c1c0c4c38208efc4c38209f1c3c281fec7c68209578209aec1c0c4c382056ec1c0c1c0c4c382029bc4c38208acc1c0c1c0c1c0c1c0c1c0c1c0c4c38204bbc4c382045ec1c0c1c0c4c38206a2c4c3820518c1c0c7c68203ee820a72c2c15cc4c38207c1c4c38203b1c4c382025dc1c0c4c3820109c1c0c7c68206d082080fc4c3820447c1c0c1c0c4c382048fc7c6820159820b32c4c3820738c1c0c4c382076ac1c0c1c0c4c38207e2c4c3820b11c7c68208b28209f9c1c0c7c682039a820825c4c3820348c7c6820507820875c1c0c4c3820229c1c0c4c3820a89c1c0c4c3820ae5c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38205bec1c0c2c16ec4c3820145c4c382031fc1c0c4c38206bdc1c0c4c3820590c4c3820a9bc4c3820b0bc4c38203f8c4c3820b1bc7c682062b8207e7c7c68204bd8209a8c1c0c4c382081bc4c382047cc1c0c4c382019bc4c3820a95c4c3820553c7c6820255820397c4c38206c1c4c38206e9c1c0c5c458820835c1c0c4c3820a09c1c0c4c38201b6c3c281c0c4c3820595c4c3820547c1c0c1c0c4c382028dc1c0c4c382071ec4c3820167c1c0c4c3820a45c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820a84c4c3820b82c7c6820773820852c1c0c1c0c4c3820807c4c3820adcc1c0c1c0c2c172c1c0c4c38204e2c1c0c1c0c1c0c7c6820106820927c4c38207e7c4c382096bc4c3820a47c7c68201bb820948c4c38205c1c1c0c1c0c6c581f28202f3c4c3820ae7c4c382093ec1c0c1c0c4c3820246c4c38205cdc4c3820945c4c38202ebc7c6820423820730c7c6820295820975c4c38207cec1c0c4c3820599c7c68205c382071ac4c3820443c7c6820502820890c7c68203b88208ddc1c0c4c382090bc4c3820a2dc1c0c4c3820316c4c3820b57c4c38202a4c7c68204de8207c0c3c281eac4c38209e3c1c0c1c0c1c0c1c0c1c0c3c281b6c4c3820682c4c38203f3c3c281d1c4c3820644c1c0c7c68209d1820a06c4c38208e4c1c0c4c38205afc4c3820432c1c0c1c0c7c6820382820a87c7c682010e820ac5c1c0c1c0c4c38203dbc1c0c4c3820b68c1c0c7c682034582081dc1c0c4c3820999c1c0c2c17ac4c3820a25c4c38204f1c4c38202adc1c0c1c0c4c38204afc1c0c1c0c4c382058ec7c68205ae820b6cc7c682059682097bc7c68201e0820370c1c0c4c382094ec7c68203a78206f7c4c3820513c4c382088ec1c0c1c0c4c38209cfc4c3820aa9c7c68209fa820b64c4c3820ae0c7c682077b820874c4c3820b19c4c3820936c1c0c1c0c4c3820221c4c3820336c4c3820899c4c382074ac1c0c1c0c4c38201b1c7c6820279820a81c1c0c1c0c4c38209f4c1c0c4c382088bc4c38207a5c1c0c7c68201d382075bc4c3820ad6c1c0c1c0c1c0c2c105c1c0c4c3820877c1c0c1c0c3c281f8c7c6820236820874c4c38209b5c1c0c1c0c1c0c1c0c1c0c1c0c4c382031bc4c38205dfc4c3820752c1c0c1c0c1c0c1c0c4c3820544c7c682067e82074dc7c68201ca820380c3c281c6c4c3820b84c1c0c4c38208d1c7c68204348209b6c1c0c1c0c4c3820af0c1c0c4c382052bc7c68206bb82072bc4c3820a8cc1c0c1c0c1c0c4c3820a7bc4c3820b5ec1c0c7c68208ee820b73c5c47a8201e3c4c38207d6c4c3820a1fc7c6820870820a07c1c0c1c0c7c6820422820616c3c28196c4c3820b3ac1c0c4c38203ccc6c581dd82051bc5c4778207e8c1c0c1c0c4c3820abbc7c68204e0820615c1c0c4c38206fec1c0c4c38203c5c4c38201afc7c68202888206fbc4c38204f3c1c0c1c0c4c38202b8c1c0c1c0c4c3820749c2c120c1c0c1c0c4c38202cac4c3820a52c1c0c4c3820701c4c3820c62c1c0c4c382076fc3c281bec4c3820a75c1c0c4c3820293c1c0c4c38204e7c7c682096e820bfcc4c382073cc4c3820187c1c0c3c28191c1c0c4c3820b8cc2c175c4c3820b32c1c0c4c382021fc4c3820b21c4c382020ec4c38206e1c7c682065f8206d4c1c0c2c113c4c38205cdc4c382068fc7c6820476820991c4c382071cc1c0c1c0c4c3820beec1c0c4c38209c1c1c0c4c3820a53c1c0c1c0c1c0c4c38208e1c7c682067f820afec7c682059b820889c4c3820662c4c3820642c4c38201fbc4c3820b59c4c38204dfc4c3820585c4c3820150c5c47482045fc1c0c1c0c4c3820bd0c6c581d7820bc7c4c382055dc4c38203dcc1c0c4c38206a5c4c3820aacc1c0c1c0c4c38208e7c4c3820226c4c3820932c1c0c1c0c1c0c4c3820c6ec4c3820b77c4c382032cc1c0c4c382045cc3c281c3c7c6820636820b31c1c0c4c3820b24c3c281c2c1c0c4c3820b45c4c3820b66c4c38208eac7c682070e820a14c4c38208f0c4c3820b23c1c0c1c0c4c3820ac1c4c38209d0c1c0c7c6820101820524c7c682083a8209e1c1c0c1c0c4c3820c9cc1c0c7c68208ae820a6dc4c38203abc7c68204ca820c03c4c3820605c4c3820a4fc4c3820986c4c3820c0bc1c0c1c0c1c0c1c0c4c3820bd3c4c3820a68c4c3820b04c1c0c1c0c7c6820932820c86c4c3820c74c1c0c4c38208cac1c0c1c0c1c0c7c68206b8820a61c1c0c1c0c4c3820b2cc4c3820399c4c382040dc1c0c7c68209bf820a36c4c3820cadc1c0c7c68205db820c2fc1c0c7c6820a8a820aeec4c3820628c4c3820217c4c3820916c4c3820686c4c38209d7c4c3820607c7c6820675820965c1c0c7c68202ed8208b6c4c3820affc1c0c4c382083bc4c38202a2c4c3820181c1c0c4c3820111c4c38205b6c7c6820449820652c4c3820c4bc1c0c7c6820ab1820addc4c3820358c1c0c4c38203a1c1c0c4c38206eec4c3820ceec7c68207e0820bcfc4c3820353c1c0c6c5819c8205c5c7c682052f820baec4c3820a62c4c38203ebc1c0c7c682020282050ac4c382042bc1c0c1c0c4c3820311c4c382032bc4c382084dc1c0c1c0c1c0c1c0c7c682064c8208dbc4c3820646c4c38202f2c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820958c4c3820561c1c0c4c3820a40c4c3820cccc4c3820358c7c6820392820649c4c382068fc5c46b82077ac4c3820709c1c0c4c3820ba2c4c3820330c1c0c4c38208ebc4c3820996c7c68207e48208cdc1c0c1c0c7c68205408207fdc6c581f58208a9c1c0c7c68205b582069dc4c3820803c4c382041ec1c0c1c0c1c0c7c682038e8207b2c7c682040e820549c1c0c4c3820cf2c4c38202d8c4c3820441c4c3820c1cc4c3820abec1c0c4c3820c97c4c382092cc1c0c4c3820105c7c68209f9820aa1c4c3820d05c4c38206ffc4c3820945c4c3820c0cc1c0c4c38207bfc1c0c1c0c2c17cc1c0c4c382011dc1c0c4c3820706c7c6820897820a91c1c0c4c3820c20c4c3820645c4c38207bec1c0c4c3820993c1c0c1c0c1c0c3c281dbc4c3820bf9c7c68207d8820b53c4c3820453c7c68203ef820504c7c6820584820b7fc4c3820b0dc4c3820637c1c0c4c3820b50c4c3820a22c4c382079cc7c6820503820a4ac4c3820b74c4c38203e6c1c0c1c0c4c3820921c1c0c4c3820920c4c3820b52c4c382095dc4c3820d5cc6c58189820d4bc4c382044cc1c0c1c0c4c3820c1dc7c6820123820306c4c3820a26c7c68204f0820af9c1c0c4c3820402c1c0c4c38201c9c4c3820386c7c68203108208d2c1c0c4c38207f4c1c0c4c3820606c4c3820412c7c682035a8204e8c4c38208d2c4c3820c3fc4c3820d89c4c3820278c4c3820a78c4c3820d26c7c68202a5820b41c1c0c7c6820a14820d5fc4c38209fec4c3820c51c4c3820cddc1c0c1c0c1c0c4c3820265c4c38206d2c4c382088dc4c38203f3c1c0c7c68203cb820748c4c38206c7c4c38209d3c4c3820959c4c382039fc1c0c1c0c1c0c1c0c4c3820b78c1c0c4c3820454c4c3820636c4c3820727c4c3820affc1c0c1c0c4c38207f6c4c3820c47c1c0c7c68201f2820630c4c3820981c1c0c4c382012ac3c28192c7c68205da8209fcc1c0c7c68201b8820c0cc1c0c4c3820711c4c382053dc1c0c4c38205f4c1c0c4c3820847c1c0c4c3820a48c7c682024c8206b7c1c0c7c68207fc820ad5c4c382073fc7c68205bd8207f5c4c38204d4c1c0c4c3820350c4c38207a4c4c3820b3fc7c682013682038ac4c3820b22c4c38206d5c4c3820adac3c281bac4c3820183c1c0c7c682044a820a85c4c382094ac1c0c1c0c1c0c7c6820ac0820d68c4c38208ccc4c38204aec4c3820a10c7c6820bab820d46c4c3820da9c4c3820311c4c3820c6ec4c38207f2c1c0c3c281e5c7c68202bf820dcdc7c6820802820c5fc1c0c4c3820d56c1c0c7c6820bcd820c53c1c0c1c0c4c38204afc7c68207a3820c1ac4c3820869c1c0c5c44382041ac4c3820b0bc1c0c1c0c1c0c4c3820c52c1c0c7c68201ee8202a4c1c0c7c68206688209e2c4c382044dc4c382041dc4c3820d9dc4c3820a1bc7c682058d820d4cc1c0c1c0c4c3820df4c4c3820565c1c0c1c0c7c6820225820747c4c38202c7c4c382056bc6c581e1820d4fc1c0c4c3820591c4c3820c87c1c0c4c3820a5ec1c0c4c3820745c4c382076bc7c6820969820c4ac4c38205a3c4c38204d3c7c68206e3820c19c7c68204c7820982c7c682064b820a5cc4c38204cbc1c0c1c0c7c682065e820b04c1c0c7c682085b8209e5c4c382059ac1c0c4c38209c4c4c3820821c1c0c1c0c1c0c4c3820a59c1c0c1c0c4c3820b8ec4c3820e07c1c0c7c682036e820612c1c0c4c3820b4dc4c3820938c4c3820d7ec4c3820abac4c3820470c4c3820aabc4c3820552c4c3820803c3c281dcc4c3820898c4c38201b0c4c38209d2c4c38208c9c7c6820ca6820cb9c1c0c1c0c1c0c4c382063dc1c0c7c682028b820c3ac4c38208f7c4c3820b20c4c3820750c1c0c1c0c4c3820c4bc7c6820141820681c1c0c4c3820dbac7c6820539820a18c4c38207afc4c3820b4bc7c68207d4820ddcc7c6820100820846c4c3820284c4c3820461c4c3820be7c7c68208ba820a58c4c3820919c4c3820d7ec4c3820799c7c6820c7f820dd9c1c0c4c3820968c1c0c4c38201bac4c3820bc8c1c0c7c68209d5820dfec4c38202e7c7c68203d4820bfec4c3820542c1c0c1c0c4c382062ec4c3820a1ac4c3820209c4c382070ac2c123c4c38204eec4c382041bc4c382069cc1c0c4c3820643c1c0c7c682090c820a67c4c3820d76c4c3820d36c4c382047ec4c38209e4c4c3820792c1c0c1c0c4c3820684c1c0c4c382019cc4c3820c89c4c3820bbcc7c6820583820cecc4c3820c8ac7c6820245820787c1c0c7c68206be82073dc1c0c4c382052ec4c38202c9c1c0c7c68204fc8208d3c4c3820b53c1c0c4c3820e5ac7c68207d7820856c1c0c6c581f9820d98c7c6820530820cc0c1c0c4c3820a74c7c68203308208d9c4c3820cc6c4c3820bedc7c6820887820a8cc1c0c4c38207acc1c0c4c3820a99c4c3820db6c7c68205d0820e47c1c0c7c6820524820939c4c3820822c7c682051f820ad9c4c3820959c7c682057b8207b6c1c0c7c682013b8207a9c1c0c4c3820983c4c38209a7c4c3820aedc2c11cc1c0c4c3820bbac4c3820175c7c68207de820858c7c68205b4820a66c1c0c4c3820262c1c0c1c0c1c0c4c3820498c1c0c6c581b0820be9c4c3820b69c4c3820df3c4c3820495c7c6820627820ae9c4c3820a04c4c3820ad8c7c68203df820b83c7c6820535820b06c1c0c4c3820b12c4c3820c9ec1c0c1c0c1c0c4c38201b2c1c0c1c0c4c3820d86c4c3820d6bc4c382012bc7c6820d15820df1c4c3820a6ec4c3820df6c1c0c4c3820c73c1c0c4c3820116c1c0c4c3820248c7c6820697820d32c1c0c1c0c7c68202b0820ebac1c0c1c0c7c6820442820e49c7c68204b48207f8c1c0c4c382041cc4c3820c66c4c3820d4cc7c682053f820721c1c0c4c3820ed5c7c6820bc9820d9ec4c3820534c1c0c7c682015a820ae6c7c6820357820c83c1c0c7c6820774820e57c3c281d9c4c3820726c4c3820185c1c0c1c0c4c382086ec1c0c1c0c4c3820c3ec1c0c7c682057482089ec4c3820ee9c7c6820477820a16c7c68208ce820c78c4c38203e5c4c3820685c1c0c1c0c7c68208e5820af7c4c3820bbec7c68201f6820a95c7c6820395820ab2c1c0c1c0c7c6820a30820bb7c4c3820646c2c160c1c0c4c382097bc4c3820d5ec7c68204e1820e5cc7c6820e7a820ec1c7c6820b35820d84c1c0c4c38209eec4c3820182c4c3820be3c4c3820b60c1c0c7c6820c7b820c85c4c3820a4dc7c68202b2820ecec4c382016dc4c38205a9c1c0c4c382031ac4c3820b28c4c3820e03c6c58190820acbc1c0c1c0c7c68205b98209b7c7c6820d21820d69c1c0c7c6820249820ee4c1c0c4c3820872c4c38202dac1c0c4c38201cac4c3820f03c1c0c1c0c4c3820c05c1c0c4c382048cc7c68209d5820c94c4c3820490c1c0c4c3820c8dc4c382023bc4c3820f21c1c0c4c3820619c1c0c4c3820839c1c0c4c3820ad1c7c6820389820702c1c0c7c6820450820768c1c0c1c0c1c0c7c6820165820e0cc4c38202dec1c0c4c3820e11c4c3820e85c1c0c7c6820ed2820f23c7c6820a64820cc7c4c3820a4ec4c3820667c1c0c7c682042c820b59c1c0c1c0c4c3820df0c4c3820c28c4c3820d09c4c3820e97c4c3820c13c4c38203d8c1c0c4c3820da7c4c3820cb3c1c0c4c38209d8c7c68206df820a3fc4c3820510c7c68202cd820d2ec4c3820304c4c3820c24c7c682067082095ec4c38202cfc7c68206ce820d60c7c6820c07820d0fc7c68204d4820638c1c0c4c3820a7ec4c3820623c1c0c7c6820505820f1ec4c3820863c4c3820433c7c682082f820efdc7c6820708820e61c4c3820e61c4c3820b9dc4c38207a3c5c43b8208fac4c382051ac4c38208c7c4c3820d2fc4c3820222c4c3820739c4c3820881c4c3820591c4c3820bd8c4c3820ad2c4c3820c8ec1c0c7c6820a5b820ce9c5c42d8204a2c1c0c4c38205efc1c0c7c68206d7820892c7c68208d8820b54c1c0c4c3820815c7c6820353820b1ac7c6820d1d820dbac4c3820d10c4c3820e5bc4c3820ce8c4c38209bac4c3820bf4c7c682065582098ec4c3820eafc1c0c7c6820b4a820c5ac4c3820f01c1c0c4c3820880c6c581be8201d1c4c382098bc4c3820342c1c0c4c3820b6fc7c6820dd1820f3cc4c3820d19c4c3820d3fc7c6820c06820f21c4c3820dbdc4c3820ee3c1c0c7c68207e1820e81c7c68204268207fcc4c38207c1c1c0c1c0c1c0c4c3820249c7c6820a9d820ad0c4c38204b8c4c3820cacc4c3820372c4c3820778c1c0c1c0c1c0c4c3820393c4c3820f9bc4c38207cfc7c682033f820712c4c3820579c1c0c7c68201b68207cac7c68207b1820d73c1c0c4c3820c00c7c6820832820952c4c3820154c4c3820fb6c4c3820b01c4c3820f82c7c6820556820d31c7c68202b38209d0c1c0c4c3820948c4c3820773c1c0c4c38205d7c4c3820f1cc7c6820b9d820f3dc7c68205dc8208a3c4c3820fa8c4c38205bac1c0c1c0c1c0c4c3820f07c1c0c1c0c1c0c4c3820af0c4c38204abc4c38208c5c4c3820f4ac7c682072b8209c6c1c0c4c3820cc5c4c3820a4ac4c3820dddc4c38209a9c4c3820e4ec4c3820d41c4c382076ec7c6820bd6820c48c1c0c4c3820414c7c6820c12820e37c4c3820653c7c68202e1820c61c1c0c1c0c7c6820aa1820cffc5c47282062cc7c68209af820a83c7c68208778209bbc4c382023ac1c0c1c0c4c3820f22c4c38201b4c1c0c7c68208f1820b7ec4c38201c1c1c0c4c3820671c4c3820bc2c1c0c4c38202e0c4c382027fc4c3820bebc4c3820a13c4c3820c9ac7c6820ab3820ee1c1c0c4c382054bc4c38209e9c4c3820571c5c447820f0bc4c3820300c2c169c7c682078b820a5cc1c0c4c3820e24c4c3820c41c5c45d820854c4c382010bc4c3820833c1c0c4c3820a73c4c3820821c1c0c4c3820313c7c6820eb7820fbcc1c0c1c0c4c3820f3ec4c3820461c4c3820f64c4c3820186c7c68202f1820905c4c3820550c7c682076c820912c3c281aac4c38203cec4c3820635c4c38209a4c7c68207c2820881c4c3820348c4c382084fc4c3820a2bc4c3820f69c4c3820800c4c38209f0c4c3820827c4c3820377c4c3820a61c1c0c4c3820287c4c38204cec1c0c4c3820f6ec4c3820148c1c0c1c0c1c0c4c3820ba1c4c3820e01c7c682056982080cc1c0c1c0c7c68202008209f7c1c0c4c3820aa6c1c0c1c0c1c0c4c3820ab0c4c3820ffdc1c0c7c682041e820782c4c382093cc4c382044bc4c382098ac1c0c4c3820b19c4c3820b2fc1c0c4c3820f25c4c3820567c1c0c4c3820331c4c3820f7fc4c38208c6c4c3820c31c4c38201a4c1c0c4c38207a8c7c6820765820d07c5c40c820c88c1c0c7c6820523820631c4c3820880c3c28199c4c382078dc4c382048bc3c28189c2c153c4c3820286c4c3820b23c1c0c4c3820722c1c0c4c3820f63c7c682067c820e27c4c38203a8c7c6820178820c35c7c682012c820f52c1c0c4c3820b7dc4c38206a7c4c3820e74c7c682065a82105ac2c111c4c3820804c6c581ae820e06c4c3820397c7c68207e3820dccc7c6820714820903c4c3820a7ac4c3820a19c1c0c4c382054ac7c682046c820606c7c68206c5820e58c4c3820c99c1c0c4c3821052c4c3820be0c1c0c4c382092ec3c2818ec4c38202f1c1c0c7c682061c820f40c1c0c4c3820db2c4c3820c15c4c3820d42c4c3820eebc4c3820dafc7c6820ee2820fa7c4c3820b94c1c0c4c3820951c4c3820a4dc4c3820888c4c3820535c7c68207be82107dc4c382031ec7c68206d9821004c4c3820170c1c0c4c382060fc4c38204c8c1c0c1c0c4c3821009c7c6820854820b0ec4c3820995c4c3820e2ec4c382074cc4c3820440c3c28196c4c382093dc7c6820b9e820be5c4c3820e9ac4c3820caec4c3820f86c7c6820a5f820ec6c4c38204cac7c682051d82084cc7c682021e820a1fc4c3820c7ac1c0c4c38205f1c4c3820c73c1c0c4c3820ba0c2c102c1c0c1c0c1c0c7c682095c8209abc7c68202108207f9c7c6820e0f820f9fc4c3820577c1c0c4c382094cc7c6820c56820ff8c4c3820eaec7c68203de8205a8c7c68206248208e4c4c38206d6c4c38205a7c4c3820296c4c38209cac1c0c4c3820a2fc1c0c4c382106bc4c3820e2ac7c6820e18820f05c4c382087cc7c6820c17820de5c4c3821030c6c581ed820f4cc4c382091ac1c0c1c0c7c682070082109cc3c281cac4c38204ddc7c6820912821000c4c3820f28c4c38201b3c4c38201a7c7c68205848208b0c1c0c1c0c4c3820b95c2c14ac1c0c7c6820827820edbc7c6820e22820f4dc4c3820b37c4c3820598c4c382087ec7c682093f820c01c4c38207d1c4c382057dc7c68206fc820fe7c7c682016d8206a8c1c0c4c38209ccc4c38207efc4c3821007c4c3820b25c7c682077d820a3dc7c6820462820dc1c7c68201c6820747c1c0c4c3820cbec1c0c4c382017cc1c0c4c3821031c1c0c1c0c4c38206abc4c3820df9c4c3820f68c4c3821091c1c0c1c0c4c3820de8c4c382084ec4c38202d6c4c3820278c4c3820aaac4c3820d57c1c0c7c6820e53821059c4c382106ec5c4098205acc4c3821049c4c3820ec5c1c0c4c3820a4ec1c0c4c382064ac3c281e7c7c682020d820da1c7c6820c92820e77c7c68204e9820b70c4c38205f6c4c3820ea3c4c38204f1c1c0c7c68205f0820e5ec1c0c4c3820b1fc1c0c1c0c1c0c1c0c4c3820a81c7c68202ab82072fc4c382048ec4c3820e98c1c0c1c0c7c6820369820e4dc1c0c7c6820e97820edec4c3820acdc4c38205f5c4c3820fd1c1c0c7c682067d8210f9c7c68201498208a1c4c3820337c1c0c7c682018d8206dac7c6820556820740c7c68204a5820ff1c7c6820b51820eb5c1c0c1c0c4c3820d22c3c281e6c4c3820ce1c4c3820efcc4c3821027c4c3820c36c4c3820125c7c68203d1820fb8c7c6820837820c77c4c3820f53c4c3820ffbc7c68202b7820bf0c4c3820f11c6c5819d820900c7c682078a8209ecc1c0c6c58182820d58c1c0c4c3820ba2c1c0c1c0c4c3820940c1c0c1c0c4c382082dc1c0c4c38210fbc1c0c4c38202f6c7c6820743820a87c1c0c4c382023cc4c3820b44c4c38206e0c7c6820ce082112cc4c38209adc4c3820d3bc1c0c7c6820c82821088c4c3821097c4c3820b05c4c3820608c1c0c4c3820abfc4c38208c3c7c6820144821095c7c6820213820d30c7c68206df820febc7c68202ff820d43c4c382063ec4c3820332c4c3820e9ec4c3820c5bc4c3820db9c4c3820ed3c4c3820ffdc1c0c7c6820bf7820ecfc4c382104ac7c68205a2821111c7c68203838207a2c4c3820e70c4c38206d1c4c38205d2c7c6820c5c820e16c4c3820cd7c3c28192c4c382056ac4c3820383c4c3820716c7c6820dac8210f3c1c0c4c382107cc4c3820f8cc4c38204d6c7c68203ec820b7cc4c3820f31c7c682018082038bc1c0c4c3820840c4c3820f85c4c3820117c4c38202b1c4c382030ac7c6820dc382110dc1c0c1c0c7c68207cd820feac4c38201f1c4c38204dbc4c3820e94c7c6820ba7820f73c4c382102bc1c0c4c3820ed2c1c0c1c0c4c3820baec3c281b1c3c28182c4c3820943c4c3820647c4c38202b9c7c6820a90820cd6c7c6820708820e91c7c6820c59820fa1c4c38209f7c1c0c7c682020e82071dc4c3820d71c4c3820a58c4c38209c9c7c6820764820cbdc4c3820fa0c7c6820a05821077c4c382015ec4c3820c3dc7c6820271820d51c4c3820b6dc4c382066ac7c6820c0982117cc1c0c4c3820f0ac4c3820e41c4c3820c93c4c3820e5dc5c421820de2c7c6820deb820f09c4c3820f27c4c3820146c7c68207ec820916c4c3820a17c4c3820c6fc4c3820a22c4c3820710c4c3820251c4c3820c43c4c38204aec4c3820446c4c38209c7c1c0c6c581c38203adc7c6820ca882117fc4c3820c57c4c3820304c4c3820981c7c6820dc482104fc4c3820460c4c382112ac4c3820941c7c6820c3f820e4bc7c6820660820896c1c0c4c3820cafc7c682040182113cc4c3821145c4c382086ac4c38207c2c1c0c4c3820f70c4c3821082c1c0c4c382067bc4c3821066c1c0c4c3820424c1c0c7c6820bcc8210f9c1c0c4c38211cbc1c0c7c68202178210b1c7c68204138210e2c7c6820edf82119dc1c0c5c45f820c14c4c38209fcc4c3820fbdc7c6820273820fa1c4c3820c5ec7c68207b3820c31c5c41b820a0ac7c6820242820e66c5c44f820641c4c3821024c1c0c4c3820361c4c3821150c4c38204f2c4c38201e0c4c3820873c4c3820fc7c4c3820e6ec7c6820d3f82119ac7c68205c2820f59c7c68208b8820cf3c4c38202c3c4c3821060c4c3820951c7c682071c820ff2c1c0c4c3820b74c5c418820543c4c3820327c1c0c4c38204c7c4c3820cdbc1c0c1c0c4c38210ddc4c38202f8c4c3820afec4c38208edc4c3820ea2c1c0c1c0c4c3820e09c4c3820ff9c4c38210c7c7c6820c0a820cf9c1c0c7c6820edc8210cfc7c6820c32820ed1c4c3820ed6c1c0c4c3820954c4c38202c6c4c3820e72c4c3820ec8c1c0c4c3820b26c4c3820e48c4c3820159c4c38209bdc7c6820dca8211c7c7c6820e9c821028c7c6820581820b29c4c38207ffc7c682110a8211d7c4c3820767c7c6820528820dd0c7c6820f34820f45c4c382118ac1c0c7c6820b93821169c4c382060ac7c6820254820b8ac1c0c1c0c4c3820e7fc7c68205298208d6c4c3820741c4c3820a21c1c0c4c3820ffcc7c682101c82105dc4c3820f7dc1c0c4c38209c3c4c38204a0c3c281f8c4c3820600c7c68201608202cac4c3820dfec7c68205a782118fc1c0c1c0c4c38210b8c4c3821050c1c0c7c6820276820a25c7c6820af8820c29c1c0c4c382011fc7c68209758210dcc4c3820616c4c3820d78c1c0c4c382046cc4c3820cb5c3c281f4c2c14bc1c0c1c0c1c0c7c6820823820c90c1c0c4c3820a83c7c6820b7382109ec7c682101f821121c1c0c7c68201dd820592c4c3820cf0c1c0c4c38204fdc4c3820b9ec4c38203b2c4c382086fc4c382029ac1c0c7c6820b2e820b9cc1c0c1c0c4c38206c6c4c3821161c4c3820b39c1c0c4c3820a43c4c3821153c4c38211aac7c6820d18820e26c4c3820fa4c4c3820c94c1c0c7c6820bef820df5c7c6820aca820fc1c7c6820e2d82124ec4c3820be4c4c3820f54c7c6820268820d85c4c3820a86c7c6820911820f99c7c682030c820992c4c3820425c4c38211fdc4c382093ac1c0c4c3820798c1c0c4c3820bf3c3c281a1c7c6820cc0820f65c4c38210cdc4c38205b8c4c3820d40c7c682067a820b30c7c682077e820b2bc1c0c7c6820a97820bfac4c38208f2c7c68201a8820d3ac4c38201bec4c38211d8c4c3820375c4c3820c48c1c0c4c38204fec5c4518204d0c4c38203f7c4c3820328c7c68202b28211b6c7c6820bfe821280c4c3820f89c4c3820df7c4c382033ac4c3821264c4c3820317c4c3820988c4c3820c29c1c0c1c0c4c3820740c7c68204f4821183c1c0c4c38208fcc4c3820ac2c1c0c4c3820e13c4c3820c2dc7c68205888205d9c4c3821241c4c3820804c7c682109d8210edc4c3820c10c4c3821116c4c3821207c4c3821033c4c3820568c4c3820e7bc4c3820d54c4c382102fc1c0c4c3820615c4c38207ebc1c0c4c382055ac4c3820b81c7c6820f92820fe0c2c145c4c3820a68c1c0c7c6820467821122c1c0c4c3820486c1c0c7c68202ee820681c4c3820795c4c3820509c4c38206bcc4c38212b2c1c0c4c3820d5cc4c38209e9c4c382061bc1c0c4c382102bc1c0c4c3820fddc1c0c4c3820d50c4c3820289c7c682048f820de9c4c3820cabc4c3820a24c3c28188c7c68207448211dec4c38203b5c4c3820825c1c0c4c3820601c4c3820e04c1c0c4c38208b1c4c382081cc4c3820ca3c4c3820658c4c38208a7c4c38201dec7c68201c282109fc1c0c4c3820299c1c0c4c3820500c4c38204aac1c0c1c0c1c0c1c0c7c68208bb8212bcc7c68205d6820f20c7c6820338820a8fc1c0c7c68202af82099dc7c68206958210e5c1c0c1c0c4c3820edac7c6820bec820dc1c4c3820946c1c0c7c68203e582060bc7c6820cd282101bc4c3820a4bc4c3820d35c7c68208e38209a2c1c0c1c0c4c3820f30c4c3820ebbc4c3820bd2c4c382080bc4c38210cbc7c68208b78208c0c4c3820b54c4c38202ddc4c382065bc4c3820b49c7c6820b6682116ac4c3820d64c4c382090bc4c3820ec1c6c581b4820489c7c6820335820a54c4c3821034c7c68201a98203fec7c68203ba821010c7c68203e7821186c1c0c4c382089fc4c3820b6dc7c6820612821196c7c6820f51821022c4c38211e1c3c281abc4c3820d27c4c3820d2fc4c3820860c4c38208fac4c3820c34c4c3820781c7c682032b820ecbc4c3820f2cc5c420820bd9c4c3820583c7c682086b820ae4c4c3820e44c7c6820274820ab0c7c6820d6d821233c1c0c7c6820d508212d4c7c6820b178211abc7c6820a02820bc6c2c13dc4c38210f4c7c6820437820dadc7c68204a8820862c1c0c1c0c7c68205fb821280c1c0c1c0c7c6820a8d8210abc4c38210a6c4c3820c93c4c3820921c4c38203e4c1c0c7c6820e6f821231c1c0c4c3820ee8c1c0c4c3820970c4c3820f8ec4c3820780c4c3820c22c1c0c1c0c4c3821311c4c38212fac4c38210dbc4c3820ba6c4c3820739c1c0c1c0c4c38205edc1c0c4c3820f82c1c0c7c68203748204d8c7c68204e4820d1ac4c3820c99c4c382096dc4c3821194c7c6820826820aa3c1c0c4c3820e5dc1c0c7c6820977820ff0c1c0c4c38202aac1c0c4c38205c6c1c0c1c0c7c6820a5a820dc6c5c47b820f8ec4c3820b34c4c3820673c7c6820fad821217c1c0c4c3821038c7c6820746820d44c4c382111fc4c382046ac4c3820a6ec4c3820ad9c7c6820be8820d53c7c6820634820a52c7c6820bf0820cddc1c0c4c38208f6c4c3820949") +} + +func block8966() string { + return string("8966,01fa01b490fa01b48cc2c002c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c156c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c122c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c155c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c177c1c0c1c0c3c2819dc3c281c1c1c0c2c101c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c16bc1c0c1c0c3c281fdc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c2818cc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820106c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c28190c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820139c1c0c1c0c3c281bec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820158c1c0c1c0c2c162c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281d9c2c137c1c0c1c0c2c126c1c0c3c28187c4c3820122c1c0c4c3820129c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820107c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382013cc1c0c4c3820122c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281e6c1c0c1c0c1c0c4c3820140c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281b1c1c0c1c0c1c0c3c281aac4c3820197c1c0c1c0c1c0c3c281d6c4c3820168c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820191c3c281cdc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201c1c1c0c1c0c1c0c4c382017cc1c0c1c0c3c281e3c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281d4c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201e9c1c0c1c0c1c0c2c174c1c0c1c0c1c0c1c0c3c281bcc4c38201aac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c160c1c0c1c0c1c0c1c0c1c0c1c0c4c3820244c1c0c1c0c1c0c1c0c4c3820257c1c0c3c2818dc1c0c6c5818f820188c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382016ec1c0c4c382019bc6c58189820134c1c0c3c281f7c1c0c1c0c1c0c4c38201d0c1c0c1c0c1c0c4c3820154c1c0c1c0c1c0c2c179c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201d0c1c0c1c0c4c382018cc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820223c4c3820261c1c0c1c0c4c3820186c4c3820269c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38202bbc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820256c1c0c4c38201f8c1c0c1c0c1c0c1c0c1c0c4c3820208c3c281afc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201d6c1c0c1c0c1c0c1c0c4c3820277c1c0c1c0c3c281e9c6c581ce82014ec1c0c1c0c1c0c1c0c2c132c1c0c4c38201a4c4c38202e6c1c0c1c0c4c38202f9c1c0c1c0c4c3820271c1c0c4c38202f1c1c0c3c281f1c1c0c4c382025ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38202cfc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c17bc1c0c2c157c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c28181c1c0c1c0c1c0c1c0c1c0c1c0c3c281b3c1c0c4c3820105c1c0c1c0c4c38202a3c1c0c1c0c2c165c1c0c1c0c1c0c1c0c7c68201bc820252c1c0c1c0c1c0c1c0c1c0c4c38202c9c1c0c4c382020fc2c121c1c0c1c0c1c0c3c281e7c4c3820292c1c0c1c0c1c0c1c0c2c161c4c38201eac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c7c682027c820344c4c38201c6c4c382020cc1c0c4c382015dc1c0c1c0c1c0c1c0c1c0c1c0c7c6820327820363c3c24c5ec1c0c4c382025cc1c0c1c0c1c0c1c0c3c281f0c1c0c1c0c7c68201208202b9c1c0c4c3820361c1c0c1c0c1c0c2c14ac1c0c4c382029bc4c3820110c4c382022ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c2819cc1c0c1c0c1c0c1c0c4c3820156c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281d7c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820240c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c28183c1c0c1c0c1c0c1c0c4c38203bfc4c382027fc1c0c1c0c1c0c4c382011ec4c3820371c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820176c1c0c1c0c1c0c2c154c1c0c1c0c1c0c4c382012bc7c682012382029ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820118c1c0c1c0c2c12ec2c106c4c382019dc1c0c1c0c3c281b7c4c38203e7c1c0c4c38202a9c4c3820136c1c0c4c38201c3c4c3820153c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382032dc1c0c1c0c4c3820139c1c0c4c38203d7c1c0c1c0c1c0c1c0c1c0c1c0c4c3820261c4c382032ec1c0c1c0c1c0c1c0c1c0c2c13cc1c0c4c38202e1c3c281f3c1c0c1c0c1c0c4c38201abc1c0c1c0c1c0c1c0c3c28191c1c0c1c0c1c0c1c0c4c382020ac1c0c4c38203e4c4c3820412c4c38202e8c1c0c4c382034ec3c281efc1c0c4c3820142c4c3820172c1c0c1c0c1c0c1c0c4c38203ffc1c0c1c0c1c0c1c0c1c0c1c0c4c382022dc1c0c3c281f2c1c0c1c0c1c0c4c382028cc1c0c1c0c1c0c7c68201b18202e2c1c0c7c68203128203f4c1c0c1c0c4c3820357c1c0c1c0c4c3820148c1c0c3c281f0c4c3820187c4c382035ac1c0c4c382017ec1c0c1c0c1c0c4c382033dc1c0c1c0c1c0c1c0c4c3820457c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820362c4c382014cc4c382013ec2c176c3c281e8c1c0c1c0c1c0c1c0c4c3820128c1c0c1c0c4c38203acc4c38203d1c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38202f8c1c0c1c0c1c0c1c0c4c382041ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382039cc1c0c4c38202e5c1c0c1c0c4c382027ac1c0c7c682028d820446c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820292c1c0c1c0c1c0c1c0c4c3820236c1c0c1c0c1c0c6c5818c8201ebc4c382035cc4c38203fcc1c0c1c0c1c0c4c3820401c2c163c3c2819ac1c0c4c382015ac1c0c1c0c4c382026cc1c0c1c0c1c0c7c6820250820459c2c137c1c0c1c0c1c0c1c0c4c382041cc1c0c1c0c1c0c1c0c1c0c4c38202fdc1c0c4c38202d2c2c10ac1c0c1c0c4c38203a5c1c0c1c0c4c3820315c1c0c1c0c1c0c1c0c1c0c4c38203f2c1c0c1c0c1c0c1c0c2c153c4c3820295c1c0c4c3820260c2c17fc3c281a5c4c382045dc1c0c1c0c1c0c4c3820427c1c0c1c0c1c0c1c0c3c281ccc6c581c1820263c4c382030cc1c0c4c38203fbc4c38201e4c1c0c1c0c1c0c1c0c1c0c1c0c2c121c1c0c4c382029bc1c0c3c24450c1c0c1c0c4c3820275c1c0c1c0c4c3820260c1c0c1c0c4c38204b2c1c0c1c0c1c0c1c0c1c0c1c0c4c382043dc4c3820386c4c38203ffc4c38203f9c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382017fc7c682038482039fc1c0c7c68201818202b6c4c3820377c1c0c4c382044fc1c0c4c3820152c1c0c1c0c1c0c4c382033dc2c170c4c38201cfc4c382026fc4c3820175c4c38203e6c4c382025fc4c38202dfc1c0c1c0c4c382012ec1c0c1c0c1c0c1c0c1c0c1c0c4c3820126c3c28188c1c0c6c581e1820270c1c0c4c38204edc1c0c1c0c2c169c1c0c1c0c1c0c1c0c1c0c4c3820285c1c0c3c281b0c1c0c1c0c4c38203e6c4c3820227c1c0c1c0c1c0c1c0c1c0c1c0c1c0c6c581ab820297c4c38202f5c1c0c1c0c1c0c1c0c4c3820348c4c3820320c4c382020ec1c0c1c0c1c0c1c0c1c0c1c0c7c68203538203a0c4c38201fdc1c0c1c0c4c3820471c4c38202aac1c0c4c3820102c1c0c1c0c1c0c1c0c1c0c4c382056ac4c38204cac1c0c1c0c1c0c4c38203d0c1c0c1c0c1c0c1c0c1c0c4c38202b1c1c0c1c0c1c0c2c127c1c0c1c0c1c0c1c0c4c3820161c1c0c1c0c4c38204abc1c0c4c3820232c4c38203b2c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c6c581fe8202c5c1c0c1c0c1c0c4c38201bdc1c0c1c0c4c382014ac7c68201b88201e8c1c0c1c0c1c0c4c382032cc1c0c1c0c1c0c1c0c4c3820157c4c3820483c1c0c1c0c4c38201f9c1c0c1c0c1c0c4c38204eac2c109c4c3820133c1c0c1c0c1c0c4c38204cac4c38202fac1c0c1c0c4c3820439c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203f3c1c0c4c3820340c1c0c1c0c4c382017fc1c0c1c0c1c0c1c0c4c38205c9c3c281ebc1c0c1c0c1c0c4c3820488c4c382034cc4c3820319c1c0c4c3820467c1c0c4c38205abc4c3820198c1c0c4c38202e8c1c0c1c0c1c0c1c0c1c0c5c44282053fc2c171c1c0c1c0c1c0c1c0c4c382015ac4c3820341c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820155c1c0c1c0c4c3820124c1c0c7c682011182015bc1c0c1c0c2c159c1c0c3c281e4c1c0c4c382041ec4c3820201c1c0c2c11fc1c0c1c0c4c3820506c1c0c1c0c1c0c4c382050dc1c0c1c0c1c0c1c0c4c38204b5c1c0c1c0c2c10fc1c0c1c0c1c0c1c0c1c0c4c3820533c1c0c4c3820239c4c38201e2c1c0c1c0c1c0c1c0c4c38204a8c1c0c1c0c1c0c1c0c1c0c1c0c4c3820143c1c0c1c0c1c0c4c382052fc4c382025ec7c682015d820256c4c3820518c1c0c4c3820464c4c382038fc1c0c1c0c1c0c4c382051bc4c382052cc1c0c1c0c4c3820338c4c38205aac4c3820164c4c382013fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820431c1c0c1c0c1c0c4c3820193c1c0c4c3820552c7c682017782057dc4c38203b3c1c0c4c382023ac1c0c1c0c1c0c2c111c4c38201f8c1c0c3c28194c1c0c4c382053ec1c0c1c0c1c0c1c0c1c0c4c38205bbc1c0c2c133c7c68203b18204f7c1c0c1c0c1c0c2c15ac1c0c5c4648205cac1c0c4c3820175c4c382033fc2c116c1c0c1c0c2c12bc7c68201388202d0c4c382061cc4c382058fc1c0c1c0c1c0c7c68202798202c1c1c0c1c0c1c0c2c139c1c0c1c0c4c3820108c1c0c4c38204c0c1c0c4c3820296c4c382059cc1c0c1c0c4c3820253c1c0c4c3820462c4c38201b0c7c68201fc8204f8c1c0c4c3820634c1c0c1c0c1c0c1c0c1c0c1c0c4c3820341c1c0c5c43c8203a9c4c3820426c1c0c4c38202c0c1c0c3c281b8c4c3820401c4c3820615c1c0c4c38205edc1c0c1c0c1c0c1c0c1c0c4c38205acc4c3820410c4c3820583c1c0c1c0c4c38202e4c1c0c4c3820429c1c0c1c0c1c0c1c0c3c281b0c1c0c1c0c1c0c4c3820594c4c382028fc3c281d6c1c0c4c38204ecc1c0c1c0c4c3820676c1c0c1c0c1c0c4c3820205c1c0c1c0c4c382065fc1c0c4c38204fec1c0c4c382035bc1c0c1c0c1c0c4c382041dc1c0c4c382053fc4c38203cec4c38203b7c4c3820218c1c0c4c3820414c1c0c7c6820200820453c4c38204ecc4c382050ec1c0c1c0c4c3820690c1c0c4c38206b5c4c3820118c4c382010ac1c0c1c0c1c0c5c43682019cc1c0c1c0c7c682016582018fc7c682010982054fc7c68201bb820376c5c4638203e5c1c0c4c38203d4c1c0c1c0c6c581c68202f0c1c0c4c3820581c4c3820332c1c0c4c382027ec1c0c1c0c4c38203bbc4c38205bcc4c3820465c7c68205258205e6c3c281f8c1c0c4c382038dc1c0c4c382057dc4c382034ac4c3820391c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c2819bc4c3820592c1c0c1c0c7c68204728205fec4c38204d5c4c38202a6c4c38203e0c5c434820259c4c3820178c1c0c4c38205b8c1c0c1c0c1c0c1c0c1c0c1c0c4c38202bdc4c3820675c4c382050dc1c0c4c38205a7c1c0c4c38206a7c2c12ac1c0c3c281e5c4c38204e3c4c382057bc4c38203c7c4c38206ebc4c38204b9c1c0c1c0c1c0c1c0c4c3820542c4c3820229c1c0c1c0c1c0c1c0c1c0c4c3820202c4c38203b6c1c0c1c0c4c382021ac1c0c1c0c1c0c1c0c4c3820302c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38205d4c4c382060dc1c0c1c0c4c3820449c1c0c4c3820658c4c3820530c4c382072dc4c38201abc1c0c6c581f682028dc4c3820594c1c0c1c0c6c5819e8201b0c6c5819e820180c4c3820209c7c68205a88205c3c3c281d5c4c382061dc1c0c4c3820679c1c0c1c0c4c38206e2c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281c3c1c0c4c382032ac1c0c4c382019bc4c38204c7c1c0c1c0c1c0c1c0c4c382011ec1c0c1c0c4c3820321c4c382051cc4c3820515c1c0c4c38204abc4c3820639c1c0c1c0c4c3820387c1c0c4c382030fc7c68202ca8206b1c1c0c1c0c4c38205f7c1c0c4c382040bc1c0c1c0c4c3158195c1c0c1c0c1c0c4c38201e4c1c0c4c382066cc4c3820730c1c0c1c0c1c0c1c0c4c38206cdc1c0c4c3820618c1c0c1c0c1c0c2c10dc1c0c1c0c1c0c1c0c1c0c4c3820303c1c0c1c0c1c0c4c3820328c1c0c4c3820294c4c3820385c4c38201d6c1c0c1c0c1c0c1c0c7c68202ef820603c1c0c1c0c7c68205058206fec4c38203fac1c0c1c0c1c0c1c0c1c0c1c0c4c382060ec7c682011b82070ec3c28194c1c0c4c38206acc4c3820490c4c3820115c4c3820590c7c68206a38206a4c3c2819bc1c0c1c0c1c0c1c0c1c0c1c0c4c38205e4c1c0c1c0c1c0c1c0c1c0c1c0c7c682024582038bc1c0c4c382037bc1c0c1c0c1c0c1c0c2c13dc4c382071cc1c0c1c0c1c0c1c0c1c0c1c0c4c3820559c1c0c1c0c4c38204a3c1c0c4c382077bc1c0c1c0c1c0c1c0c1c0c1c0c7c6820370820782c1c0c4c38206c6c7c68201d48203adc4c382041ac7c682017982035dc4c382035dc4c38204e2c4c38203bcc4c382068cc1c0c1c0c7c682026a8204dec4c38206d2c1c0c4c3820117c1c0c4c3820190c4c38206d6c1c0c1c0c1c0c1c0c7c68203aa820705c4c382057cc1c0c1c0c1c0c1c0c1c0c4c38201a2c4c3820143c4c38207fdc4c38205bac4c382072bc1c0c1c0c6c5819d8207bbc4c3820736c4c3820149c1c0c1c0c1c0c1c0c1c0c4c382059bc1c0c3c28190c4c38205bfc4c3820596c4c38206b9c1c0c7c682040882072fc4c38205bbc1c0c1c0c4c382036fc4c3820719c4c38201f3c4c38203fec4c38204adc1c0c1c0c4c3820151c4c382051ec1c0c1c0c4c3820774c1c0c1c0c4c3820332c4c3820805c3c28199c1c0c4c382018bc1c0c1c0c1c0c2c13ac4c38205f3c1c0c4c3820267c1c0c1c0c1c0c1c0c1c0c3c281fcc4c38203abc4c38205f0c1c0c7c68204bc820724c1c0c4c38205c7c1c0c1c0c4c3820507c1c0c1c0c1c0c1c0c7c682042b8204d2c4c382024fc2c12bc4c3820458c4c3820735c4c3820317c1c0c4c3820793c1c0c4c38206d8c1c0c1c0c4c382031fc1c0c7c6820237820313c1c0c4c382070ac4c3820502c1c0c4c38203d8c4c38202ccc4c3820388c1c0c1c0c1c0c1c0c1c0c1c0c7c68205a98206e4c4c38201bdc1c0c4c3820367c4c3820255c1c0c1c0c7c68203ad8204a0c4c38201dfc1c0c1c0c1c0c1c0c1c0c2c147c7c6820624820693c1c0c3c281c6c4c38206dac4c38201fbc1c0c4c382060bc1c0c4c38201f9c1c0c1c0c4c3820754c1c0c1c0c1c0c1c0c4c38201d9c7c68202af8204b4c4c382070fc4c382078bc4c36a8197c1c0c1c0c1c0c4c382011fc4c38206eac1c0c5c45c82079cc1c0c1c0c4c3820807c4c38206f4c1c0c4c382043cc7c6820314820426c7c682043c820447c1c0c1c0c4c382066bc4c382086cc4c382017bc1c0c4c3820361c1c0c1c0c4c38202a4c2c143c1c0c1c0c4c38207e2c4c38204c5c4c3820693c1c0c4c382055ec4c3820845c4c3820599c3c2818bc3c281afc4c3820270c4c38207a4c4c3820127c1c0c1c0c7c6820238820857c4c3820572c4c3820524c1c0c1c0c1c0c4c3820219c4c38206f9c4c382088fc4c3820671c4c38201a5c1c0c7c6820226820286c2c166c4c3820632c1c0c1c0c1c0c1c0c4c3820566c4c3820733c1c0c4c382082ac4c3820669c1c0c4c38202e7c4c38205e0c4c382083bc1c0c3c281e8c4c38203d4c1c0c4c38207c7c4c3820771c4c38201f1c4c38204b7c3c281ccc4c3820626c5c47d8201f6c1c0c1c0c6c581f982010fc1c0c4c382064fc1c0c4c382042bc1c0c4c382068ac1c0c4c3820215c4c38208d2c1c0c7c68205498205dfc1c0c4c382085fc4c38204fac1c0c4c3820743c5c40182053ec1c0c1c0c7c68205cd820748c1c0c1c0c4c382087dc1c0c4c3820765c1c0c4c38206dec2c11cc4c38208aac1c0c4c38201cac1c0c1c0c4c3820235c7c682034e8205f2c4c382067bc1c0c1c0c4c38207cec1c0c4c38203aec4c3820276c1c0c1c0c1c0c4c382071dc1c0c1c0c4c382077fc4c382039ac1c0c1c0c1c0c1c0c1c0c4c3820479c1c0c7c68207c58207f6c1c0c4c38207d2c1c0c1c0c2c110c4c3820397c4c382033ac4c3820788c1c0c3c281ddc4c3820450c4c38205afc1c0c4c38206b4c1c0c7c68202dd8203ddc4c38206f8c1c0c4c38202cec1c0c6c581e282086ac1c0c7c68205db820685c1c0c1c0c1c0c1c0c1c0c4c38203f7c1c0c1c0c1c0c1c0c4c3820639c1c0c7c682087c8208dac4c3820785c1c0c4c382075cc1c0c4c3820792c4c38204a2c4c3820496c4c3820889c4c3820394c4c38208fec7c68202968205c8c1c0c1c0c1c0c4c3820142c1c0c1c0c4c3820128c1c0c1c0c2c14fc2c16ec3c28185c4c3820727c1c0c1c0c4c38201b5c1c0c4c3820539c1c0c4c382076dc1c0c1c0c7c68205608208f9c4c382047bc1c0c4c3820897c4c3820392c1c0c4c38207dcc4c38207b5c7c682044182079fc4c3820807c2c15ec1c0c3c281b3c1c0c1c0c1c0c7c6820221820804c1c0c7c68202cb820838c1c0c4c3820365c2c107c1c0c1c0c4c3820214c1c0c1c0c1c0c4c3820932c1c0c1c0c2c12cc4c38207e8c1c0c1c0c1c0c4c382019fc1c0c4c3820213c1c0c4c382049fc1c0c1c0c1c0c1c0c4c382068bc4c38204e9c1c0c1c0c1c0c4c382049bc4c38206b6c4c38205fcc4c3820739c3c281bdc4c3820810c4c3820959c3c281c9c7c682063b820755c4c3820104c1c0c4c3820468c1c0c4c3820425c1c0c4c38201c7c1c0c4c3820470c1c0c1c0c4c382064dc1c0c1c0c4c382076fc1c0c4c3820620c1c0c4c38208a5c1c0c1c0c4c382033ec4c38206d7c1c0c4c38203c4c1c0c1c0c4c3820879c1c0c4c38205ddc4c3820826c1c0c4c382030dc4c382018fc7c68203f2820513c1c0c1c0c4c38205a2c1c0c1c0c2c150c4c38207ddc3c281c3c4c3820430c4c3820991c1c0c1c0c4c38203d8c4c382068cc4c38208b6c1c0c4c38208a4c1c0c4c382087fc1c0c4c38203ecc1c0c1c0c4c3820544c1c0c1c0c4c382069dc7c68203b182094ac4c38206b8c1c0c4c3820795c6c581a282098bc1c0c4c38204dbc1c0c1c0c4c38205f7c1c0c2c10dc1c0c7c68208058208abc4c3820556c1c0c4c382014fc4c38207a5c1c0c4c38209b6c4c3820764c1c0c4c3820374c4c382060ec4c38207f0c7c682019882080bc1c0c2c102c1c0c1c0c1c0c4c38201f5c1c0c4c38208dfc4c3820972c1c0c4c3820121c4c38205ecc4c38209ecc4c38209cec1c0c4c38204e8c1c0c4c382024cc1c0c4c38206e3c4c382097dc7c682053782080ec1c0c1c0c1c0c1c0c7c682056182096bc1c0c4c3820451c4c3820775c1c0c1c0c4c3820297c4c38206d0c1c0c1c0c4c38208c0c7c6820477820696c4c38204aec4c382080dc1c0c4c38208e9c4c3820129c4c3820702c4c38208c5c4c382070bc4c38205e5c1c0c4c38207a2c4c38204e5c1c0c1c0c4c38205ccc4c382083fc1c0c1c0c4c3820337c4c3820205c1c0c4c38206f0c4c38208e1c1c0c4c3820436c4c382047ec1c0c2c128c1c0c4c3820411c1c0c4c38207d6c4c38202a4c1c0c1c0c4c382060dc2c143c4c3820641c4c382020ec4c3820147c4c382089ac4c38201a3c1c0c7c68202bf820770c1c0c4c38209bdc4c382054cc4c3820736c4c38209dac1c0c4c38203ebc1c0c1c0c4c382016cc1c0c7c68201928207b8c7c682058d8207bcc1c0c7c682030e82069bc1c0c4c38205cbc1c0c6c581ca8203a1c4c38204dcc4c3820475c4c38204dac1c0c1c0c4c3820336c4c3820225c5c4358206a9c4c38206bfc4c38203a7c4c3820166c4c3820797c1c0c1c0c4c382029fc4c38202b2c4c3820686c1c0c1c0c1c0c1c0c4c382086bc4c3820231c1c0c1c0c4c38205d2c7c68203308203d5c7c682069782099bc4c382066ac4c3820345c1c0c1c0c1c0c4c382061ec4c3820652c1c0c1c0c4c3820119c1c0c1c0c4c3820863c4c382099bc1c0c4c38208fdc1c0c1c0c1c0c4c38204d3c4c3820172c4c3820896c4c38207b7c7c6820335820602c1c0c4c38207efc1c0c1c0c1c0c7c682019282042ec4c382095ec1c0c6c581f382073bc4c3820a36c4c382099dc1c0c7c6820980820a54c4c3820112c4c38203bfc1c0c2c10ac4c3820925c4c3820718c1c0c1c0c4c3820757c4c3820955c1c0c4c3820633c1c0c7c682024b8202a5c7c682072f820a70c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820973c4c382058bc7c68203c182051fc4c382067ec1c0c1c0c4c382069ac4c3820448c1c0c4c3820116c4c38202e0c1c0c1c0c4c3820489c4c3820473c1c0c1c0c3c281d2c7c68205d8820656c4c38207aac4c3820896c1c0c1c0c4c38208fcc1c0c1c0c4c3820876c4c3820aaac4c38209e4c1c0c7c68203818207edc1c0c1c0c4c3820605c4c3820a4fc1c0c1c0c1c0c1c0c4c38203dec4c3820a1bc1c0c4c3820347c7c6820536820704c1c0c1c0c4c382026fc4c38203f8c1c0c4c3820613c1c0c1c0c4c3820640c4c38201e8c1c0c1c0c1c0c4c382089ec1c0c3c2818bc1c0c4c38203b5c1c0c1c0c5c469820925c1c0c4c3820266c7c68208ef82094cc4c3820766c4c3820a52c4c3820833c4c38206acc3c281dcc1c0c4c3820919c1c0c1c0c4c3820993c3c281eec4c3820590c7c682037c820ac0c4c38209c0c1c0c1c0c2c13bc1c0c4c38209dbc4c38201c9c1c0c4c3820709c1c0c7c68205e9820a7fc4c38207bdc1c0c1c0c4c38208f7c1c0c1c0c1c0c4c382034cc1c0c4c3820715c1c0c4c382026cc1c0c4c38206bac1c0c4c38206a1c2c130c4c38202bac3c28180c1c0c4c38209aac7c682038f8209d6c4c382047ec4c38207aec1c0c4c3820a78c4c382016dc4c3820a9dc4c3820965c4c382051dc7c68208608208e3c4c3820150c4c38209dfc7c682059a82080bc1c0c1c0c4c38202e5c5c41d820554c7c68208f9820956c4c3820a8fc4c3820378c1c0c4c382026dc4c3820278c1c0c4c382058ac4c3820723c3c281c8c7c682020b8209fec4c38204e0c1c0c1c0c4c38205f8c1c0c7c6820664820776c4c3820a60c1c0c4c382085dc4c3820517c7c682068f820998c1c0c1c0c1c0c1c0c4c382063ec7c6820419820992c4c382039ec1c0c1c0c1c0c7c68204a2820aa9c4c3820869c1c0c4c3820309c1c0c4c38207bac4c3820781c1c0c7c68201258207d8c1c0c7c6820817820922c4c38208d9c4c3820882c5c41a820364c4c3820406c4c3820641c1c0c4c3820169c1c0c4c382027dc7c68202b8820661c4c38206adc1c0c4c38205a5c7c682077f820969c4c38202b6c1c0c1c0c1c0c4c3820a5bc1c0c7c68201ce820309c4c38203c0c7c682053d82086ec4c3820832c1c0c1c0c4c382086ac4c3820687c1c0c4c382095bc1c0c7c6820904820a35c1c0c4c38208efc1c0c4c3820a11c1c0c1c0c4c3820232c1c0c4c38203a1c1c0c1c0c4c3820908c4c3820645c1c0c4c3820a88c4c38209a9c1c0c1c0c7c68206c88208c0c4c382040ec3c2818ac1c0c4c382094cc7c682037a8208a6c4c3820415c7c6820253820450c4c3820812c7c682021b8204eec4c382040dc1c0c4c3820a7ac4c38202cfc4c3820190c4c38206e3c4c3820a2dc4c3820745c1c0c4c3820867c4c3820a4fc1c0c4c3820267c4c38209cec4c3820b64c4c38208adc7c68204da8205e1c4c3820931c1c0c4c3820b80c1c0c1c0c4c3820753c4c382091bc1c0c1c0c1c0c4c3820760c4c38201d2c1c0c4c3820554c4c382043fc4c3820478c1c0c1c0c4c38203a4c1c0c4c3820984c4c3820a7cc7c6820120820ae0c4c382029ec4c3820542c2c107c1c0c1c0c4c38209f8c4c38206a6c4c3820940c4c38208eec1c0c1c0c4c38207e7c1c0c1c0c7c682027c8205a9c4c3820510c4c38207edc1c0c1c0c7c68209ec820bc9c1c0c4c382038ac7c6820398820474c1c0c4c382058fc1c0c3c281adc7c68201ef82066ec1c0c4c382054ec4c3820355c4c38203afc1c0c7c68206e6820940c1c0c3c281e5c1c0c1c0c1c0c7c68206bd8209a7c1c0c4c382092dc1c0c1c0c4c3820250c1c0c4c382068ac1c0c4c3820159c1c0c1c0c1c0c4c382021fc7c682046a820b18c4c3820902c1c0c7c6820908820b86c4c382045bc4c3820a62c4c38203c5c1c0c1c0c4c38209f5c4c3820a3dc1c0c1c0c1c0c4c38203b5c4c382066fc4c382031bc4c38201e6c1c0c1c0c4c38205e5c1c0c1c0c1c0c5c4788207c1c1c0c7c6820264820a6bc1c0c4c3820b3ac4c38207e0c4c3820938c4c3820a05c7c6820712820b26c4c382069cc7c682066d820c06c1c0c4c38208b3c4c382019ac1c0c2c13fc4c3820b9cc2c129c1c0c4c38205cac1c0c4c3820607c4c3820994c4c38207d3c1c0c1c0c4c382073dc4c3820637c7c682045682048ec1c0c1c0c4c38206e9c1c0c1c0c4c3820a93c1c0c1c0c4c3820618c4c38201c4c4c3820518c1c0c1c0c4c3820571c4c38205a4c1c0c4c38202c2c1c0c1c0c7c6820504820767c1c0c6c581868206c5c7c6820a02820be9c4c3820b01c4c3820112c1c0c1c0c1c0c4c38205acc4c382051dc1c0c7c6820468820bb3c1c0c1c0c1c0c4c3820965c4c38206bec4c382059fc1c0c1c0c4c3820865c4c38204fcc4c3820298c1c0c4c3820759c1c0c4c3820babc4c38204a1c4c382046ac4c38209fcc1c0c4c38208fec4c382044bc4c382082cc7c682061582084dc1c0c4c3820421c7c68208fd820a19c1c0c1c0c4c3820945c1c0c1c0c2c120c7c682010082090ac4c3820b3dc4c38204a9c1c0c1c0c4c3820517c1c0c3c281dec4c382017ac7c6820527820affc4c3820c48c4c38207e0c1c0c1c0c4c38205dcc4c3820135c3c281ffc4c3820c2bc1c0c1c0c7c682036f820844c1c0c3c281cfc1c0c4c382092cc7c6820756820c5cc1c0c4c38202c9c4c3820552c7c682046c8205fdc4c3820606c3c281d5c4c3820663c1c0c5c440820b58c4c38203a6c1c0c5c4758207b9c7c682077e820842c1c0c1c0c1c0c4c38209d8c4c3820461c1c0c1c0c1c0c4c38207e3c7c682065a820a09c7c68204738204ffc1c0c1c0c1c0c1c0c7c6820820820beac4c3820717c7c682040f820c84c4c3820c2ec4c3820ac9c4c3820953c1c0c1c0c4c38205b2c4c38208a8c1c0c4c38207bdc1c0c1c0c4c3820802c4c3820a06c1c0c4c3820986c4c3820798c1c0c1c0c1c0c4c38205b2c1c0c1c0c1c0c4c3820c94c1c0c3c281dcc4c382048ac4c38203f1c4c3820722c4c3820796c1c0c1c0c4c3820308c4c3820b0bc1c0c4c382079ec1c0c4c38207e1c1c0c1c0c4c3820557c7c682018a820744c1c0c1c0c7c682068982073ec4c382035fc1c0c1c0c1c0c4c3820aefc4c38206fcc3c281bcc6c5819382014bc1c0c1c0c1c0c4c3820cbdc4c37d818ec1c0c4c3820211c4c38202a1c1c0c4c3820695c4c3820239c4c382075bc1c0c4c3820379c4c3820be5c4c38202f3c1c0c1c0c4c382029cc7c68207c6820b03c7c68206fd82075ec1c0c4c3820339c7c68201f2820bd2c1c0c1c0c4c3820afac7c6820922820b06c4c3820968c4c3820393c7c682066b820c9ec4c38206fcc1c0c4c3820907c7c68202dc820a9ac4c38202dac4c382070cc4c38205bdc7c68203bc82098ac1c0c7c682050b820a68c1c0c4c3820b83c4c3820726c4c38208cdc4c3820775c4c3820874c4c38203dec1c0c7c6820c11820c26c1c0c1c0c4c382023cc4c3820754c4c3820856c4c38205b6c7c68207e88209cac4c3820638c1c0c4c3820963c4c38205c4c4c3820af3c4c3820984c4c3820c9dc4c38201adc1c0c1c0c4c38207a4c7c68205558207aac5c45f820392c4c3820ca8c4c38203a8c4c3820995c4c3820686c4c38207b6c4c382028ac1c0c4c38209d1c7c682068d8209b4c4c38208dfc4c3820375c1c0c1c0c4c3820baac7c682039d82075fc4c38204dfc1c0c1c0c1c0c4c3820af1c4c3820780c4c3820b64c1c0c1c0c4c3820cb6c4c38206adc1c0c4c38201c2c7c682096e820b96c4c3820636c4c3820d06c4c38209f1c7c68203268205ccc5c46d820b71c7c68206ec820cb7c4c38202b3c4c3820970c4c3820bbbc1c0c4c3820978c4c38202f1c4c3820a4bc7c68204b3820d13c7c6820915820a7dc1c0c4c3820429c7c68203ae8205c0c1c0c4c38208b4c7c682045c820484c1c0c6c581e28203e1c1c0c1c0c4c38206e4c4c3820536c4c382017dc1c0c4c3820adac4c3820b7ec1c0c4c3820227c1c0c1c0c4c38209d0c1c0c1c0c4c3820c80c4c3820a00c4c3820cf7c1c0c1c0c4c3820a34c1c0c4c38205fac1c0c4c38209aec4c382038cc7c6820481820cf0c7c68206b6820988c4c3820b34c7c682081a8209bbc4c3820532c4c3820aa4c4c3820526c1c0c4c3820208c4c3820694c1c0c1c0c4c3820c8cc1c0c1c0c1c0c1c0c4c38201e7c4c38209c5c4c3820d46c1c0c2c172c1c0c6c5819f820b2bc4c382028ec4c3820b09c4c3820784c7c682032b820b67c4c3820afac4c38205c7c4c3820acbc4c3820334c4c3820af8c7c682080c820a91c7c68202a78208f1c1c0c4c3820abbc1c0c4c3820c89c4c3820cd5c7c6820810820d6dc4c3820674c1c0c4c3820625c5c4178201f5c1c0c2c14ec2c15bc2c151c4c3820be4c1c0c4c3820b96c1c0c1c0c1c0c4c38207a0c4c3820d6ac1c0c4c3820c49c1c0c6c581f4820a1ec2c10cc1c0c1c0c4c3820152c1c0c7c68206a2820ae9c1c0c4c382054ec1c0c4c3820b68c1c0c7c68208c18208c6c4c38204e4c4c38205fcc1c0c4c38204b6c1c0c4c3820d11c4c382034ac4c38201ecc4c3820cbbc1c0c1c0c4c3820a63c4c3820911c4c3820cc5c4c3820d61c1c0c6c581ae8201e9c4c38201a6c2c16fc4c3820968c1c0c4c3820dadc1c0c1c0c4c3820ca5c7c682065e820b15c7c68205478207fbc4c3820857c4c382065ac1c0c6c581bf8206c2c3c281c4c2c17cc4c3820c0dc4c38201dac4c3820c9cc4c3820491c1c0c4c3820cf6c4c3820614c1c0c4c3820b83c4c3820be9c4c3820502c4c38207d7c7c6820249820c44c4c3820c56c4c38207e1c4c3820216c7c6820520820ac4c1c0c6c581ba8204e1c4c3820667c4c38201d3c3c281c8c4c3820879c1c0c4c382022cc4c3820d2bc1c0c4c38208bdc7c682025b820a95c4c3820268c4c38208ebc4c3820946c7c68202cd82064cc4c3820aedc6c581c7820b55c1c0c4c382026bc4c3820870c4c3820888c1c0c1c0c1c0c4c382039ec7c6820959820b5cc4c3820804c4c382072dc7c682075b820b6ec6c581c2820d2ec1c0c4c38207a8c4c3820d6dc4c3820503c4c3820d61c4c38204c4c4c3820de9c4c3820549c1c0c1c0c4c382062dc4c3820220c7c68203ed82081dc1c0c4c382011ac1c0c4c3820d4ac4c3820c84c4c38202a8c4c3820108c4c3820df3c1c0c1c0c1c0c4c3820c8bc1c0c7c6820400820848c1c0c4c3820c8dc7c68201c582031dc7c6820156820a80c1c0c1c0c4c3820c47c4c3820dd6c7c682048d8205c9c1c0c4c3820252c1c0c1c0c4c3820102c4c3820b1fc4c38208cbc1c0c1c0c4c38201ccc1c0c1c0c1c0c1c0c1c0c1c0c4c3820870c7c68206f3820cf2c1c0c4c3820544c7c68203da820c92c2c158c4c38208b7c4c382099ec7c68204c8820ca6c4c3820d45c4c38206a5c4c3820d20c1c0c7c682058e82071ec4c3820a17c1c0c4c3820524c1c0c3c281b9c4c3820da7c4c3820e44c1c0c7c6820836820b29c4c38204d5c3c281b4c4c38206c0c4c38205dbc4c38208a5c7c6820953820abdc1c0c7c6820514820d69c1c0c1c0c1c0c1c0c4c3820caec2c172c4c3820cfdc1c0c7c6820445820db8c4c38208cec1c0c7c6820352820d0cc7c68202c68202e6c4c3820b92c4c3820889c1c0c7c682049982061ac1c0c4c38206ccc4c3820121c1c0c4c38205a2c4c3820293c1c0c1c0c4c38205b3c7c68202b782097cc1c0c4c3820de3c1c0c7c68204bf82099ac1c0c4c382052ac7c6820543820760c1c0c4c382040dc1c0c4c3820349c3c281dfc4c382065ec4c3820b05c4c3820b24c7c68207e7820980c4c38204c3c4c38207f4c3c281c2c4c3820854c4c3820990c4c38201d4c1c0c4c3820179c7c68203e3820d47c1c0c1c0c5c431820d66c4c38204b1c4c3820869c4c3820668c7c6820ca1820d72c4c38208a0c7c682010c82078ec4c3820c02c4c3820aadc4c3820c05c7c682012e820ccec4c3820461c4c38202d6c7c682062a82097fc1c0c1c0c4c3820acec7c68208e6820d13c4c38207a0c4c38205c1c1c0c4c3820e32c7c682063a820cabc4c3820e18c7c682020b8203fbc4c38206aec1c0c7c6820650820df2c1c0c1c0c4c38206cfc4c3820da8c5c424820c2fc1c0c1c0c1c0c1c0c4c3820176c7c6820742820b13c7c68203db820c7fc5c47a820e54c4c3820b87c1c0c4c3820ce4c4c3820c83c1c0c7c68203ca820bebc1c0c4c3820a40c4c3820c36c7c682089b820ea9c1c0c7c6820a45820d8ac4c38204aac4c3820dd1c4c3820134c7c68203be820b0fc4c382028bc7c68201ee820aedc4c38209bfc4c3820301c7c682054a820800c4c3820883c4c3820e16c1c0c4c3820694c4c3820d23c4c38204c4c7c682011f820796c7c6820c13820e61c7c6820bbe820c2dc7c6820cd6820ce9c4c3820883c1c0c4c3820656c1c0c1c0c4c3820d91c1c0c2c12dc3c281cbc4c3820e0bc1c0c7c6820155820b85c1c0c1c0c1c0c1c0c1c0c4c3820334c4c3820dc8c4c3820ca4c4c3820928c7c682052d820bddc7c6820346820758c4c3820d5ec4c3820deac7c682046e820b25c4c38202eec4c382038cc4c3820823c7c68204b28205cfc1c0c1c0c7c682099c820ca2c1c0c4c3820eedc4c382076ec1c0c4c38204e4c7c6820380820663c4c3820cb1c1c0c4c3820e24c4c3820d12c7c6820706820afcc7c682021082049bc7c6820771820a84c4c3820da1c1c0c7c68201c3820e3dc1c0c7c6820e22820e6ec1c0c4c3820354c4c38202dcc4c382041fc7c68204ed820dd2c4c3820462c4c3820399c1c0c4c382043ac7c682022f8209a8c4c3820c04c4c3820d8fc7c6820aa0820e63c4c3820d74c7c6820ace820eb8c4c38201bec7c6820924820edfc4c3820932c4c3820180c7c682073c820885c4c3820e60c4c382071cc7c682058c820787c4c38209b8c7c6820914820e31c1c0c4c3820315c4c3820ef8c2c138c1c0c4c38203ccc1c0c4c38209cdc4c3820a9bc1c0c1c0c4c3820928c4c38209edc4c382038ec3c281c0c4c3820f29c4c3820eebc4c3820b5bc1c0c7c6820824820da7c1c0c4c38204c0c1c0c1c0c3c281c0c7c6820168820d71c4c3820347c4c38207dcc1c0c4c382044dc7c682081e820eb5c1c0c4c3820b4fc1c0c7c68209d4820d5ac1c0c1c0c1c0c4c38205e3c4c3820713c2c123c1c0c4c382048dc1c0c4c3820d0dc7c68202908203d6c4c3820cfbc7c68206d0820e73c7c6820937820e93c7c682056c8206e0c1c0c1c0c4c382030fc4c3820d86c4c3820847c4c382013bc7c682016182074ac7c6820b59820d99c4c3820ea2c1c0c4c3820814c4c38205f0c4c3820eadc4c3820ab4c4c38202d6c1c0c7c682093e820962c4c382022fc5c467820dd7c4c382092fc4c3820591c4c3820f5fc7c6820565820d19c4c3820712c4c3820369c7c68207a8820e0dc1c0c4c3820b1dc4c3820404c4c3820487c1c0c4c3820ad7c2c16cc4c3820d0ac7c682078e820cd5c4c3820e72c4c3820e40c1c0c4c38202b5c7c6820553820c00c1c0c4c3820cfac4c382099cc7c682059f820f23c4c3820275c1c0c1c0c4c3820350c4c3820bc7c4c3820721c4c382074bc7c6820ae1820b45c7c68207168207d5c4c382071bc4c3820a1ac4c38209bbc7c68206148208eec4c3820dddc1c0c1c0c3c281f6c7c6820a3f820ab2c7c682044c8208d3c2c119c4c38204d0c1c0c1c0c7c6820a01820ac2c4c3820dbbc1c0c1c0c7c68207808207b4c4c38206c2c4c38209eac4c382084ec4c3820886c7c6820543820f0ac1c0c5c41b820f02c4c3820d66c1c0c7c6820b08820e33c1c0c1c0c1c0c7c68204588206b9c4c3820a20c7c6820b6a820e9ac1c0c4c3820f9ec1c0c1c0c4c38205f9c4c382057fc4c382042cc4c3820ad4c4c3820d0ac1c0c4c3820ea8c4c38206e7c4c3820daac4c3820568c7c68209f2820b9ac4c382020fc4c38202d7c4c3820595c1c0c4c3820c2fc3c281fac1c0c4c3820802c4c3820d51c2c17bc4c382057ac1c0c1c0c7c6820be0820d95c4c3820a70c4c3820d55c4c38202e7c4c3820fa3c4c3820e64c1c0c4c3820b8bc7c682026e8205cfc4c3820d07c7c6820289820ce5c7c6820803820f6dc4c3820774c4c38204d2c4c3820509c4c3820d77c1c0c1c0c4c3820929c4c38206aec1c0c4c3820eacc7c68207da8208a1c4c3820cc2c4c3820dc6c7c6820e3a820f3bc1c0c1c0c4c382061fc4c3820307c4c382024bc1c0c3c281a9c1c0c1c0c7c682023c82054dc4c3820fc2c4c3820fa0c4c3820f3cc7c68201dc82037ac1c0c1c0c2c112c4c3820902c7c6820beb820fc4c4c38207ffc4c3820ad1c4c3820bd4c1c0c4c38202a6c7c68202f3820d0bc4c3820ed5c7c6820bbb820d8ec7c68206c0820f57c7c6820477820ed8c4c3820440c4c3820e6ec4c3820619c4c3820cb0c4c382096cc4c3820738c4c3820f11c1c0c4c3820e26c4c3820d83c2c17cc4c3820ec0c4c3820367c4c3820dcdc1c0c4c3820e1ac4c3820ae5c1c0c4c3820534c4c382037dc4c382063cc4c3820aaec1c0c7c6820ca5820e4ec4c3820a86c7c682084e820c3dc7c6820356820f5bc4c3820c0dc1c0c4c3820f90c4c382040ac4c3820647c7c6820ad6820d2ec5c464820b3fc4c3820e31c4c3820dafc4c3820c1ec4c38201e1c4c3820d39c4c3820a1ec3c2818ec1c0c4c3820f12c1c0c4c3820eb0c7c6820881820c36c4c3820a01c1c0c7c68201cb820d3bc4c382047ac1c0c7c68201f0820933c7c6820bf1821017c1c0c4c38209f2c4c382065dc7c68201838206e7c4c3820db9c7c6820631820c14c1c0c4c38203a8c1c0c7c68207b1821020c7c68205e2821031c1c0c1c0c4c3820d9ec4c3820456c1c0c4c3820e71c4c382022cc7c68201d1820f3ec4c38204dec1c0c1c0c4c3820c89c1c0c4c382096fc7c6820565820842c4c38209d9c4c3820963c7c682028382103ec7c682058c8205d7c4c3820da0c3c281acc4c3820f28c1c0c1c0c7c682071a820ed2c2c151c4c3820720c1c0c1c0c7c68206aa82092ac7c682089c820ed4c4c3820a0cc7c68208cd820a51c4c3820cb1c7c68204cb820a55c4c3820203c4c382085ec4c38208a6c2c128c4c3820f03c1c0c7c6820181820c09c4c3820834c7c6820d7a820decc4c3820d89c7c6820137820620c4c3820584c5c42f820f6bc7c6820c59820cbac4c3820a9ac4c3820dcfc1c0c7c6820e5c820ef1c7c68203cb820cbcc7c6820946820e29c4c3820f32c4c3820821c4c38202c3c7c6820783820de6c1c0c4c38207cfc4c3820325c4c3820deac4c3820288c1c0c4c3820adac4c3821000c1c0c1c0c1c0c4c382047cc7c6820f0782103cc7c68208c1820ddac7c682035f8205eac4c3821080c4c3820cc9c4c382036ec1c0c4c3820ffbc1c0c4c3820194c7c6820f4a82107dc7c68204c1820fdec1c0c7c6820c85820fb0c1c0c7c6820c40820fecc1c0c1c0c4c382095dc4c3820dcac1c0c1c0c4c3820ee2c7c6820d12820f38c6c581d38204acc7c68206c6820d87c7c68205a3820be7c4c382024dc4c3820475c4c3820f81c7c68207b7820cc6c4c3820b86c4c3820f27c4c38208f8c7c68204ae8207f0c4c3820617c4c38201c0c4c382055dc4c3820c4bc4c38203f6c1c0c4c38207a1c1c0c4c3821063c4c3820e88c4c382031cc4c38203dcc4c3820368c4c3820647c2c14dc4c3820c8dc4c3820ce8c1c0c4c3820a3ac1c0c7c682037f820b4ac1c0c4c3820d9dc1c0c4c38202a2c7c68201058210b2c7c6820299820b5bc3c281aac1c0c4c38209bfc4c3821099c4c3820219c4c3820f46c7c6820a08820ef0c4c3820ca3c4c3820f61c7c6820eed8210a2c7c6820683820df5c4c3821053c1c0c4c3820616c1c0c4c3820cd0c6c581ea820f66c3c281d1c4c3820c41c4c3820a58c2c118c7c682019682055bc1c0c4c38205a8c7c6820cc8820d3ec4c382086cc4c3820848c4c3820f45c4c3820fc0c4c38208a2c1c0c4c3820f0fc4c3820688c7c6820874820bd0c1c0c4c3820bf8c4c3820a64c7c68208d5821007c4c3820f81c6c581f7820d3cc4c3820e0ac1c0c1c0c4c3820850c4c382054fc7c682018b821032c4c382090cc4c3820f49c7c682063d8208b0c4c3820bf0c4c3820632c7c6820903820d9dc4c38208cec7c68206dc8209b6c7c68207ca820a6fc4c38202e2c4c3820c29c7c682051982087bc7c68206db820b84c4c38206ecc4c382096dc6c581da820412c4c38207eac7c6820d21820eaec1c0c7c682106f821092c1c0c1c0c4c38207e2c4c3820f06c7c68208bc820bc2c4c3820366c4c3820d6cc4c38210cac7c6820adb821033c1c0c1c0c7c6820733820e12c7c6820d08820f26c7c6820b30820c1bc4c3820fd4c4c3820556c4c3820167c4c3821099c7c682052e820f70c4c38209dcc4c3820e39c4c3820d33c1c0c1c0c4c3820d08c7c68201af820417c4c38205b1c4c3820ea0c4c38208a2c4c3820a9fc2c124c4c3820d64c4c38209bac4c3820b69c4c382029fc4c3820f7ec1c0c1c0c7c68205f1820a02c1c0c4c3821057c1c0c4c3820b05c4c3821096c7c6820fd0821052c4c382099ac3c281c5c1c0c4c3820c9fc7c68205c2820b45c7c68208bd820dd7c4c3820c59c1c0c4c382050cc7c68207c8820de8c1c0c7c6820cc0820cf4c1c0c4c3820cf3c4c3820c4fc7c682045b820d16c1c0c4c38210aec7c6820eea821049c4c3820923c7c68208b282091bc4c38208d4c1c0c7c6820ed9820f71c5c43b820679c4c3820563c4c3820872c1c0c1c0c4c3820624c7c68206f3820fcfc4c3820c3ac4c382110fc4c382018cc4c382089bc4c3821040c7c68208ba820a54c4c3821083c4c382077ec7c682036a82104bc4c38205cdc1c0c5c46f820d38c4c3820bb3c4c38204e7c7c68205e7820b0dc4c3820930c4c38204d1c4c3820f7bc4c3820e63c4c3820ee6c4c382098cc4c3820bc2c7c6820a71820d5fc7c682056d820e73c7c682056682062cc4c38209abc4c3820f76c4c3820b48c1c0c7c68208e082106cc7c682045a820ea4c7c6820245820290c1c0c1c0c7c68204ce820cabc1c0c7c682036e8210a4c4c38210d3c4c3820d5bc7c6820e648210d9c1c0c4c38201f7c4c382077dc7c68207d5821137c7c68210788210e9c7c68201d8820df9c4c3820bb2c1c0c1c0c7c6820f05821079c4c3820f96c1c0c1c0c4c38208e8c4c3820ea4c4c3820878c4c3820d48c4c38206bbc7c6820783820e8bc7c6820672821068c7c68210bf821107c4c382043dc7c6820f56821051c4c3820b07c7c6820d7b820feac7c6820a47821186c4c3820f50c4c3820b6bc7c68202b0820dc4c4c3820859c4c382074ec4c3820b58c7c6820571820952c6c581ec82031ac4c3820976c4c382047bc7c682058e821087c4c3820e0cc4c382057fc4c3821058c7c6820cac821036c4c3820b8fc4c382026ec7c68202bd820eddc1c0c4c38203a3c1c0c4c3820f53c4c3820e4bc7c6820e57820ebcc4c3820c1fc7c68210ff821148c4c3820fa6c4c382016dc7c6820662821114c7c6820829820f79c7c68203c38209d7c4c3820f76c4c3820aa7c1c0c1c0c4c3820da3c7c6820480820b3fc7c6820bbf820f1ec7c6820ae4820b40c2c14cc7c6820f25821055c1c0c1c0c7c6820bc5821199c7c6820f5d8210fec1c0c4c3820184c7c682088f8208d6c7c68202d7820f6ec4c38205d9c4c3820a21c4c38202c5c7c6820a49821100c7c6820982821193c7c6820f97821021c4c3820368c4c3820e78c7c682031882114ec4c3820eabc4c3820be2c4c3820435c7c68203c682059cc7c68203bb820ef4c4c3821023c4c382069cc4c382111ec4c3820fffc7c6820601820a49c4c3820bafc1c0c1c0c3c281fec7c68208d582108cc7c6820f7a820fdfc4c382042ac4c3821122c1c0c4c38202f4c4c3820354c1c0c3c28196c4c382015ec4c3820dcbc4c3820944c1c0c4c3820c82c4c3820668c4c3820dbdc7c6820114820596c1c0c4c3820fc6c7c68205d48209f4c1c0c7c6820ab7820b66c7c682101982103bc4c3820169c1c0c4c3820c8ac6c581a9820d28c4c38203eec4c38209f1c4c38211ddc4c3820b1bc4c3820610c4c38209d7c7c6820c50820dd9c3c2819ac4c3820a83c1c0c4c3820e71c1c0c4c3820772c1c0c4c3820776c4c3820682c7c68205e7820e58c7c6820768820f0dc4c3820fb9c4c3820fbac4c38204cfc7c6820cc7820db5c7c6820a0d820bf6c7c682049c820fe7c7c6820dc3821016c4c3820f44c4c3820d80c4c3820bb2c4c38211b2c4c3821202c7c68203ef820a68c7c68204858208d2c4c3820fa8c4c38205ebc7c6820a4d820cfac7c68202548208e3c7c6820816820cefc4c38208bfc4c3820da6c4c38205c1c1c0c4c38210b5c1c0c7c6820d2f8210a0c7c68208cb8211f1c1c0c7c6820c37821091c4c3820daac1c0c4c3821126c2c156c1c0c4c3820918c7c6820d79821160c4c382056fc1c0c4c3820a3ac7c68209a68210afc4c3820b1cc7c6820e66820f99c4c3821140c4c3820ea6c4c3820ffec1c0c7c68209278211f2c4c38210ddc4c38211d2c4c3820ed3c4c38208ffc4c38202afc7c6820711820ea2c4c382040ac7c68202f68204fcc1c0c4c382047dc4c3820fb3c1c0c4c382065bc7c68207a9820b39c1c0c4c3820646c4c38207d1c4c38210fdc4c3821234c1c0c7c68202208204e9c4c3821090c7c6820eb78211ddc4c3820644c4c382116ec1c0c7c682032e820cd8c7c68204148211d9c4c3820d9ac7c6820ecf8211cac1c0c4c3820a62c7c6820e2e8210d6c4c38211a4c4c382072ac4c382070fc4c382071ec4c3820313c7c6820b3b820e6cc1c0c7c68207b182112fc7c6820f25820fd6c7c682095a820de1c7c68207c3820a6cc4c38203b4c4c3820281c3c28195c4c382101fc7c682097e8211b5c7c6820fac8210f8c4c3820e82c4c3820553c4c3820fc5c4c3820d29c1c0c4c3820a06c1c0c4c3820cb9c4c3820f2fc2c134c7c682085b820f12c7c68205ce820d1ec1c0c4c3821173c4c3820d7fc7c6820c50820db1c1c0c3c28184c4c38209b2c4c3820d73c4c3820c86c7c68203c08203e4c4c38210a7c7c682085c820f1dc4c382120ac7c6820b8f8211b7c4c3820ab0c4c3820a2bc7c6820c25820e11c7c68203788210b3c4c3820f9dc4c3820ff3c7c6820d0b820debc4c3820d10c4c38201dec1c0c4c3820fe5c1c0c1c0c4c3820ba7c4c3820e67c4c3820300c4c382125cc7c682016c820e69c4c3820c7cc1c0c7c682043782076bc4c38211efc4c3820dcec1c0c7c6820c1082117ac4c3820dccc7c6820ad2820de5c4c3820f0cc7c6820ae38211c4c7c6820f6c821147c7c68202d5820cf6c7c68201fe820509c4c3821157c7c682121682128ec4c3820adbc4c382128fc4c382110dc7c68206ca820beec4c38210ddc7c68210ca821221c4c3820f2ec4c3820e5cc4c3820f30c4c38201a4c7c6820d218210afc4c3820ed5c7c6820146820b65c4c3820769c4c38201f4c7c68201af8210adc7c68203ea82090bc1c0c1c0c4c382056cc1c0c7c6820233820e13c4c38210fac1c0c4c382077cc4c382078bc4c3820e48c1c0c7c68208d1820e84c1c0c7c6820829820e07c4c3820e1ac4c38209dec4c382107ac1c0c4c3820a46c7c68206a5821278c4c3820a28c4c3820ffbc4c3820f7fc4c3820afec7c6820fcb821204c4c3820745c4c382112bc4c382035bc4c38205b0c3c28182c4c382125ec7c682078d8211c8c1c0c1c0c4c3820966c4c3820fb1c7c6820ae6820c79c7c6820e818212b3c4c3820a0dc4c3820888c1c0c7c682021582047cc4c3820281c7c68202d9820bb4c7c682083c8209b9c7c6820358820643c4c3821241c4c3820629c4c38207afc7c682097182122bc4c3820db1c4c3820af9c4c382126fc4c3820c43c7c6820d5c821042c7c6820b46820e8ac5c4658205b6c4c3820d4bc7c6820bc38212bac4c3820d60c7c6820b818210fac1c0c7c6820280820837c7c682021482119ac4c382095dc7c68210f6821269c4c3820d95c4c3821246c1c0c4c3820d76c1c0c7c68208c8821211c4c3820e96c4c3820d46c4c38211bdc4c3820fdfc1c0c7c6820144820fc7c4c38203c2c1c0c4c3820ccfc4c382127bc4c38212c6c4c3820b3cc4c3820278c1c0c4c3820d7fc4c38210c0c1c0c4c38211dfc4c3820b67c4c38204ffc4c3820f56c4c3820e37c7c6820786820bf3c4c3821072c4c38211cfc7c6820fd9821285c7c6820b32820dd5c7c6820b25820cddc4c3821155c4c38209b5c7c68201b482043ac7c68208f6820b90c4c3820b5ec4c3820d20c4c3820be1c7c6820365820890c7c68208f7820f59c4c3820ef5c7c68202ee820df0c7c6820e36820ee7c4c3820ee8c4c38203e0c7c6820589820ff7c4c382016bc4c3820157c4c3820204c4c3820ac7c1c0c4c382027bc4c382097bc4c382014ec4c3820b73c4c38206c1c1c0c7c6820c3e820fa7c4c3820f27c7c6820319820ba5c4c3820e15c1c0c4c3820691c4c3820e2ec4c3820834c4c38206a8c4c3820a13c4c3820126c7c682070d82089fc1c0c1c0c4c38209e8c7c68203b98206abc2c104c4c3820f33c4c3820ceec4c3820fbfc4c3820ff2c7c68208dd820b42c7c6820226820830c7c682017d820eb6c4c38203d1c4c3820fb7c2c173c4c3820231c7c6820df6821088c7c68203018212bbc4c382027bc4c38209c6c4c3820a13c1c0c4c38205e1c4c3820e68c4c3821325c4c3820777c4c3820fbec4c3821234c1c0c4c3821088c4c3820a74c4c3820522c4c38212a8c4c38207b2c7c682097b82133dc4c3820750c4c3820a25c4c3820d57c7c682020d82043ec1c0c4c3820b18c4c3820bfec1c0c7c6820655820bc4c4c3820bcfc1c0c4c38201a8c1c0c4c38206d3c4c3820611c4c38206b2c4c3820911c4c3820b56c4c3820c01c7c6820f368212d2c1c0c7c6820aea821346c4c3821336c7c6820757821368c7c682049e820d52c4c38204b9c7c682032f820b17c4c38212aec4c3820a73c4c38209efc7c68207c0820b01c4c3820944c4c38204f0c4c3820248c4c3820199c4c38202acc4c38205b7c4c3820621c7c6820f7d8212eac7c6820897820be1c7c682015b820a38c7c6820798820e30c4c3820f74c7c682053b82060fc4c38202ecc4c38203b8c4c3820a3bc1c0c7c68209f98212d9c4c3820bdcc1c0c1c0c7c6820573820954c4c3820665c1c0c1c0c3c281eac4c3821230c1c0c4c3820e1cc7c6820822821044c4c382138fc4c3820b33c4c38209f7c4c3821344c4c38205efc2c17fc7c6820d148210bec4c38208f4c1c0c4c3820f0ec4c3820558c1c0c1c0c2c14bc1c0c7c68209fb820d42c4c382087ec4c382135ec5c43a8201c8c1c0c1c0c4c38205c5c1c0c1c0c4c3820459c4c3821326c7c68211348213adc2c168c7c68204238208c4c1c0c4c38209a4c4c382057bc1c0c4c38209f4c4c3820c28c3c2818ac7c6820244820251c7c68203d9820547c4c38213a1c1c0c4c3820d17c1c0c4c38209fcc7c6820826820eebc4c38206e8c4c3820b8cc4c3820558c1c0c7c6820d9782137dc4c38206c4c4c38213c1c1c0c1c0c1c0c4c3820880c4c382040ec5c41e8209c2c1c0c4c3820b88c4c38203f0c7c6820193820cdcc7c68208758212c0c7c682084b820943c7c6820d5182129ac4c382101ec1c0c4c3820d8ec1c0c4c3820c72c4c382107cc4c3820eb4c1c0c4c3820681c4c3820bb9c4c3821392c7c6820acb820ee0c4c3820993c7c68203e3821181c1c0c4c3820c07c1c0c4c3821047c7c6820900820e1ec4c382025ac4c38209c6c4c3820a1cc4c382063ac6c581f4820f35c5c4268201ddc1c0c7c68202f5820358c7c682065b820faec4c38213e5c4c3820360c4c38209fac7c682090f820c70c1c0c4c3820575c4c382046bc7c6820d35820e3bc4c38207f5c4c3820383c4c3820ad8c4c3820bf7c7c682102f8212fec1c0c4c38212a3c4c38201d1c7c6820d4f820e51c7c6820141820e07c4c38204f6c4c382079bc7c682084f820884c4c3820442c4c38204b0c4c3820aa9c6c581bd821403c4c3820e03c4c3820bcbc4c3820e01c4c382131cc1c0c4c3820bd9c7c6820e2c82134fc1c0c4c3820723c1c0c4c38208bec1c0c4c3820921c4c382137bc4c382099fc1c0c4c3820bbec4c38209e3c7c68210fb82138bc2c13ec4c3820c38c4c3820992c4c38207d0c4c382136dc4c3820c6dc4c3820463c7c68209e0821253c7c6820ad0820f47c4c3820843c7c6820877820b89c4c3820700c4c382127fc4c3820577c1c0c2c10ec1c0c4c3820a61c4c3820f2ac4c3821169c4c3820a8cc4c3821010c4c38204a5c4c38207bfc4c38204cdc4c38209e6c4c3820e34c4c3820e75c1c0c4c38201bfc4c38209ebc4c382120bc4c382040cc4c38213bfc7c68210968211a6c4c3820b12c1c0c4c382055bc1c0c7c6821084821190c4c3820162c4c3820630c7c682080a82087ac3c281a0c4c3820225c4c3820188c7c68201d5820bfec4c3821041c4c38203f5c4c3820b78c4c382092bc4c3820d03c4c3820cb6c4c38213c9c4c3820485c7c6820369821200c4c3820cd0c1c0c4c3821073c4c3820f4dc1c0c7c68208c7821338c4c382069ac7c6820fc8821351c7c68210978211cec7c6820bbc820ee2c3c281adc7c68202a3820687c7c682027282036bc1c0c7c68210c28211d7c4c3820cb9c4c38204e0c7c68203cb820f21c4c3820cc3c7c68202ed820b91c7c68203a9820a2ec1c0c7c6820324820c6cc4c3820d53c4c38211f3c4c38212a8c2c161c4c38211c0c7c682023e820f7cc1c0c1c0c7c682031a8207e3c7c682054b82082bc1c0c4c3820a9dc1c0c4c382121fc1c0c1c0c4c3820fdbc4c382141cc4c3820a72c1c0c4c3820be2c4c3820967c4c3820b24c4c382139cc4c382108dc4c3820545c7c68204a4820710c1c0c4c3821425c4c38211dcc4c3821278c1c0c7c682117c821294c4c382080fc7c68202fc8203b7c7c6820871821346c7c6820305820761c1c0c1c0c4c382098dc4c38206d1c1c0c3c281bbc4c38212f0c4c38213e1c4c38204d4c4c382068bc4c3820841c4c3820c39c5c4058210d7c6c581a18213bac4c3820331c4c3820713c4c38209dcc4c38213a9c7c682139082140ac1c0c4c3820a61c4c3821250c4c3820598c4c382035cc1c0c7c682095a821109c4c3820962c4c38212a9c4c38211e6c4c3820df7c7c6820a93821158c4c382139cc1c0c4c38213dbc4c3820660c1c0c4c3820703c7c6820653820960c7c6820f2082145cc4c3820aecc4c3820bb1c4c38205dcc4c38206c3c1c0c4c3820e0cc7c6820d5882110bc4c38208c3c4c38206b3c4c382138ac2c125c4c3821008c4c38203cac4c3820529c4c3820be4c4c3821353c7c68205b9821463c4c382130ac4c3820ac1c1c0c1c0c4c3820bfac7c6820fe682100dc7c6820579820a88c4c3820c66c1c0c7c68206ee821364c4c3820ab6c1c0c1c0c7c6820654821466c4c38201eec4c38210e1c4c3821359c4c38202c7c7c682073d8212c4c4c382021dc1c0c1c0c1c0c1c0c7c682053c8213b6c1c0c4c3820f85c7c682096482145cc7c6820e06820f78c1c0c7c682065482141ac7c68203df8213a5c1c0c4c3820af2c4c3821109c7c68206078208cac7c682081c820a41c3c281a8c4c38203a3c7c6820c718214d6c1c0c4c382133dc4c38209c8c4c38214f3c7c6820850820d34c4c382086fc7c682012c820c03c4c3820e26c4c3820ef6c7c6820f8f82123ac7c68204118209fac4c382141bc1c0c1c0c4c38213afc4c38207d0c4c38209d6c7c6820c0c820c5bc4c38214a7c7c6820797820c57c4c3820f70c4c38205ddc7c682106782115dc7c6820e86820f52c4c3820389c4c38202e1c7c682049a8214e5c7c682026b820f86c1c0c4c38205d6c7c682076e821290c4c3820f33c4c3820773c4c38214cac4c3820170c1c0c7c682011c82139ec7c682144582147fc4c3820d4cc4c3820aa2c4c3820c39c7c6821028821477c7c682024e8211ccc7c682041e8214c7c4c3820433c7c6820444821054c4c382119dc4c3820600c7c68207418211e2c1c0c1c0c7c682030d821442c4c3820740c4c3820ce0c4c3820789c1c0c4c38206f5c4c3821394c4c38214b9c4c38202a0c4c3820755c4c3820b16c7c682051e82126cc4c3820975c4c3820104c4c38213a7c4c3820525c1c0c1c0c4c38214b7c4c382018ec7c68204cd8209fdc1c0c4c382131dc7c68205318210d3c6c5818082124fc7c6820ed682103fc4c382138cc4c38201aec4c3821428c1c0c1c0c7c6820892820b2cc1c0c4c3821549c7c682072282145dc4c382138ac4c3821352c1c0c1c0c4c38206d1c1c0c7c682066f820eddc7c68208258212d3c4c3820c91c4c3821522c1c0c1c0c4c38213cec4c3820c4dc7c68207c9820addc7c682090f820b94c4c38208f6c7c68205e882107bc7c6820305820729c4c382034fc4c382147bc4c3820b50c1c0c1c0c1c0c7c6820a7e82130cc4c3820790c4c3821497c4c38203f7c4c3820d02c4c38205efc4c38212dac7c6820345820e9bc1c0c4c3820b6cc1c0c4c3820435c1c0c4c382097dc1c0c4c3821564c4c382136bc7c682076b820cb3c7c68207da8209acc4c38206cec7c6820725820c47c7c682012c820a85c4c3821483c7c68201f28208e8c4c3820569c3c281d4c7c682061b82155ec4c3820440c4c3820a8fc7c6820642820d0ec4c382144cc4c3820348c4c38204ccc4c3821194c7c6820463821547c7c68201e082062fc4c382015fc1c0c4c382131fc1c0c4c3820b52c4c3820a6ac4c3820353c4c3820c6fc7c6820486820bd4c7c682042d82097ac7c68205918210acc7c6820ebf820f2dc4c38213a2c4c38214d6c1c0c4c3820c60c4c382059ec4c3821326c4c382149fc4c38213ddc4c382119ac4c38209ddc4c3820df4c4c38214e9c7c6820493821104c4c3821350c4c3821089c4c382156dc1c0c7c6820772821387c7c6820608820c32c1c0c4c382102bc7c6820981820c42c4c38214f9c7c6820858821483c4c38208b6c7c68208d78213f3c4c3820d62c4c38201b8c4c3821505c4c3820ab1c4c38208c6c4c3821348c4c3820398c4c3821225c4c3820b82c7c6820cc98212fec4c3821145c4c38203b8c1c0c4c382048fc1c0c4c382148bc1c0c4c38214b6c1c0c1c0c7c6820e4f820f13c4c38204b7c7c6820271820c19c7c6820cde8214d0c4c3820289c4c3820417c7c6820d25821029c4c38203a2c4c382140ac1c0c4c38214d7c4c382108cc1c0c1c0c7c682120982143bc7c68209698213d0c4c3820c4fc4c3821323c4c3820739c4c38203abc7c6820585820db7c4c38210c8c4c382092fc7c68205a0820b47c2c114c4c3820174c4c3820efdc1c0c7c6820b09821024c4c38213adc4c382064bc7c68202938212c9c3c281d0c1c0c1c0c7c6820f5c82101cc1c0c4c3820beec7c6820444820b74c4c3820649c5c46d820688c7c682063082149ec7c6820c628213e4c4c3820d77c4c3820c46c1c0c4c3820c9ac4c3820a89c4c38210b7c1c0c1c0c7c68202ff8210abc7c68201e2820b41c4c3820119c1c0c4c38208cac7c6820a6d820e08c1c0c1c0c7c68214ba8215bec7c682054882156ec4c3820f47c7c68206a082158ec7c6820ae8820c95c4c3821445c7c6820b638214a3c4c38208aec4c3820ce6c1c0c7c6820ccc821292c1c0c3c28187c1c0c7c68209e58215cdc7c6820d7d821322c7c68201458207c0c4c38205e3c4c38201bbc1c0c7c68213ee82145bc4c3821546c4c3820420c1c0c1c0c7c6820f7982159dc4c382124cc1c0c4c38213e9c4c3820947c4c38215dac7c6820d9f820dd8c4c3820788c4c38202c3c1c0c4c3820735c4c3821538c4c382097fc4c3820b38c2c15dc4c38203a2c7c682099d82125fc4c3820274c4c38215d5c4c38202d1c4c3821281c7c6820bc0821274c4c3820416c1c0c1c0c4c3820ebac1c0c7c6821108821618c7c6820a74821558c7c68201b782092ec4c3820a58c7c6820576821353c4c382136ec7c68213d582140ec4c3820876c7c682122582137cc7c68210f98213a5c4c3820b4cc4c3820b0cc7c6820311820ca7c7c682059d820676c4c38208d1c7c68205a08214dac7c68201ed82137bc1c0c4c3820608c7c682079a820873c7c6820d30821214c1c0c7c682096c8215a0c7c6820c99820ca3c4c38209b7c4c3820a7bc4c382094ec7c682084c821196c1c0c4c3820c56c4c3820e0ec4c3820c5ec7c6820ed78212cdc4c38205fbc4c3821136c7c682042f820f08c1c0c4c3821561c7c6820505820adec4c38205c8c7c682010b820422c7c6821362821427c7c6820bad820e9ec4c3821399c4c38215eec4c3820523c4c3820166c4c3820743c4c3820f67c4c3820c2ac7c6820e46821467c7c6820ab98212a5c7c68201a782158cc7c6820314821060c7c682085c8210f5c4c3820165c4c382136bc4c382142cc1c0c4c3821666c4c3820b4dc4c3820d1dc4c38214e4c4c382151ac4c382141cc7c682076a8213a6c4c38214bdc4c38208e7c1c0c4c3821444c7c682043f82060ac7c6821071821119c5c4228213aec4c382117fc1c0c4c38211eac4c382154ec7c6820cc7820d55c7c6820b518214a9c4c38201a7c4c382147bc4c3821264c4c382157bc4c38215a8c4c3821548c7c68210ea82152ec7c6820bf482134ac7c68209d4820d4fc4c3820da8c1c0c7c68213568215c0c7c6820c53820db4c1c0c7c6820f01820f39c1c0c7c68205ab820bd6c7c6821014821045c7c6820aa5821047c7c68206f282085dc1c0c4c3820355c4c382063bc7c682055a8207e9c7c68207f98211d7c7c6820b27821564c7c682072e821191c4c3820579c4c3820a30c4c3820bdec4c3821562c3c28191c4c3821622c1c0c7c682093d820c7bc4c3821529c4c3820909c4c38214e3c1c0c4c38213e3c7c6820559821440c4c3820492c7c68204f5821531c4c3820e2dc4c3820899c7c68215b082168cc7c682078f821026c1c0c1c0c1c0c6c58186820672c7c6820bd78214c1c1c0c4c38210c0c1c0c1c0c4c3821433c4c3820700c4c3820cb4c4c382156fc4c3820c27c1c0c1c0c4c3821391c7c68213fd82148ec7c682015f8208f0c1c0c4c382100fc4c382120ec7c682145e821470c4c3820373c7c682018582110cc4c3820b54c4c38206cdc7c68213938215f0c7c682061f821370c1c0c4c3820f73c1c0c7c682140b8215b1c4c382017ec4c38203e5c4c3821610c4c3820e98c4c3820af4c4c3820b66c1c0c4c3821500c4c3821629c4c38208a4c4c3820a03c1c0c4c3820852c4c3820337c7c68201ca820f37c7c682092b821512c4c3820791c7c68207188213a1c1c0c7c6820224821638c4c3820d1fc4c3821239c7c6820daf820db7c1c0c1c0c1c0c4c3821084c4c3820308c7c6820af082149bc4c38216a2c4c38206b0c4c3820637c7c682022e8211a8c4c3820849c4c3820813c7c682126e82132ec4c382105bc4c38214fac4c3820943c7c68204d78215ecc1c0c4c3820a30c1c0c4c38216e7c7c68201848214d0c7c68206ab821651c4c38206c1c1c0c7c6820b7f820e04c4c38216d6c7c6820ba1821303c1c0c4c3821658c1c0c4c3820a48c4c3820761c4c38213f2c7c682041f820c9dc4c3820a53c7c68206a8820926c4c38213cfc4c382021cc7c68209b0820a8cc4c3821545c7c6820a5d820e95c7c68213fc821482c1c0c4c3820bbac1c0c7c682032282084fc4c38214b4c7c682028f820501c6c581f58207d9c7c68206e6821089c1c0c7c6820466821175c7c6820e90820f98c4c3821155c4c3820de2c4c3820a95c4c3820535c4c38214c6c7c68201358215c1c4c3820dc0c4c38214cac1c0c7c68213a4821663c4c38205b4c4c3820d5dc7c68205be82170bc4c38212aac1c0c1c0c1c0c7c68208d3820b4bc4c3820d24c4c382167ac1c0c7c68213f88216b7c4c3820257c4c38207afc1c0c4c3821613c1c0c1c0c4c382042ec7c6820cbc82169bc7c6820626821124c4c3821575c4c3820194c4c3820206c6c581a78206fbc2c12fc7c6820ef28213bbc3c281e1c7c68204f98214ecc4c3821647c4c3820961c7c6820209821180c6c5818282123cc4c3820cc8c7c68214ee821644c7c682021e821690c1c0c4c3820532c4c3821652c4c38214ebc7c682070b820af6c4c3821727c4c3820e27c4c3821175c7c682056382134fc4c38206b7c7c68201b2820216c4c382025bc7c68205ce820d36c4c38209cfc1c0c4c38216d1c1c0c4c382013bc4c382073ac7c6820ac58214b3c4c38206afc1c0c4c3820742c7c6820a2f821643c1c0c1c0c4c3821339c4c3820addc4c38203d2c4c38209f6c5c446820779c7c6820f8d82137ec2c10cc4c3820947c4c382048ec7c68204eb820f64c7c68203f5821598c7c6820bd58212e0c7c682090e821604c4c3820aacc1c0c4c38209e1c7c6820f7a8211dac4c3820613c7c682094982148ac4c3820e4dc4c382096fc1c0c4c382039cc1c0c4c382069fc7c6820306821318c4c38205ebc5c441821257c1c0c7c6820fa5821146c4c3820864c4c382161cc4c3820ccac4c3820c80c4c3820ec6c7c6820938821332c4c3821631c4c3820167c4c3820d17c7c682072a820c33c1c0c4c38205bec4c3821352c4c3820685c1c0c7c68213858214d8c4c3821110c7c6820493820519c1c0c4c38211ebc4c38214c3c7c6821499821702c4c38213bcc4c3820de9c7c682014d821153c4c3821437c4c38210dfc5c408820441c4c3821374c4c3820605c1c0c4c3820f4ec1c0c4c3820e21c4c382031cc1c0c4c3820b97c7c6820f9f821794c4c382151bc4c3820a57c6c581d88201c2c7c6820a3782159ac4c3821698c7c6821492821766c4c3820873c4c382150cc7c6820bf8821521c4c3820b2cc4c382137fc4c3820657c7c6820a5082101dc7c6820396820b32c4c382177ec4c3821661c4c3820342c7c6820ecd821577c4c3820cecc4c3820c64c7c6820806820fe2c4c38214ebc4c3820828c1c0c7c68213fe8216b0c1c0c4c3820fc9c4c382178ec4c3821595c4c382158fc4c3820a3bc4c3821082c7c68204f3821516c4c382094dc4c38209c9c1c0c4c38213d8c4c3821796c4c3820e7cc4c3821736c7c68202fb820fdbc4c3820fa8c4c38215e4c4c3820758c4c3820751c4c382135dc4c38210bbc7c6820328820a19c7c68206598215fdc4c382163fc4c3820545c4c3820c3ec4c3821222c3c28198c7c682016b820c20c7c682021c8216eec1c0c4c38203e8c4c3820364c7c68208c9821098c7c682024a82175cc4c3820d57c7c68214ac821709c4c38212e5c4c38216ffc4c382088bc7c68207318217c3c4c38215c0c4c3821417c4c382031ec4c3820e70c4c3820a1bc4c3821453c7c6820147820c0ac4c382050fc4c382164dc7c68204a7821316c7c68208e2821798c4c382153cc4c3820801c4c3821528c1c0c7c682105c82160fc7c68216e982179ac7c68209578217e8c4c3820ab2c4c38207c7c4c3820895c1c0c4c38212bcc7c6820a6682139bc4c3820fe0c4c38216f1c4c38213ffc7c6820c1c8215b2c1c0c1c0c7c6820ca9820d54c1c0c4c382170cc4c3820fb6c4c3820a52c4c3820bcec1c0c7c682078a8210b4c4c3821471c2c10fc1c0c4c38216e2c4c3820855c7c68211218216afc4c3821671c7c6820828821722c4c3820a44c4c3821378c1c0c1c0c1c0c1c0c4c3821186c4c382074dc7c68205b8821006c7c6821310821542c4c3820cdbc7c6820a4a820c1cc4c38203f6c7c6820212820996c7c6820316821578c4c38208c2c1c0c4c38213fac4c3821598c4c382178fc7c6820bc1821486c4c3820a2ec4c3820a37c1c0c1c0c4c38214d5c4c3820eb3c4c38206e5c4c3820432c7c6820a998213b1c1c0c1c0c7c68211ba821300c1c0c7c682046f8207cbc3c281b5c4c382140bc4c38203acc7c68207c28211fec4c38217a3c4c38202f7c2c15ac4c38213a8c4c38210d6c4c3820851c4c382118fc4c3820bd1c4c382116cc7c68210948211a9c4c3821244c1c0c1c0c4c38217d5c4c382182fc4c3820186c4c38208e4c4c38217f3c1c0c4c38215c7c4c3820eb5c7c6820f10821801c7c682024782088cc4c3820a97c7c682070782127cc4c3820b04c7c682093f821354c7c6820a258217dbc4c3820e11c7c6820759821420c4c38208f3c1c0c7c682074a820d27c4c38201ffc7c682139d821833c4c38201cdc4c3820d00c1c0c4c38216bfc4c382118ac4c38215f2c7c6820a14821777c4c3820383c4c3821701c7c6820aee820c40c1c0c4c3821622c4c38209c7c7c68201b7821832c4c38203a5c4c3821544c1c0c7c6820234821729c4c382164cc4c382165ac7c682077382159cc7c6821085821491c4c3821062c4c38207cec4c38213b7c4c38217ebc5c429821759c4c3821706c7c68207288213f8c7c68205ed82145fc4c38213b2c4c3820f84c7c682023b8211eac5c41c8201fdc4c3821681c4c382095cc4c38215fcc7c682013e821712c4c382012fc4c38217d0c4c3821361c4c3820887c4c3820af4c7c6820e178211f0c1c0c4c382183ec7c682098982183bc7c68202d8821380c4c3820ab3c4c3820362c7c682051f821885c7c682078c8208fbc7c682121a8217cdc7c6820a6582134bc7c6820b5e821273c7c68206b8821281c7c6820dfe820e50c4c38217acc4c3821674c4c382176ec7c6820fcd821665c7c68203f082144dc1c0c4c3821891c1c0c7c68216648217f0c4c382182ec4c38203edc4c3820b0ec1c0c1c0c4c38216b2c4c382146dc7c6820230821386c7c68213638216d5c4c3821562c7c682098d821892c1c0c7c6820ad6820c7ac7c6820f248211e1c7c682019a820fefc7c6820e28821123c4c38204e5c4c38206c7c4c3820934c4c382153dc7c6820b2a8217c1c4c38217bec4c3820a90c2c11ec7c68216ad8217d7c4c3820282c4c3821637c7c682150b8217e0c4c3820aa7c1c0c1c0c7c6820b80821854c7c68206b3821408c1c0c4c38204bbc7c68213c982182ac4c3820320c4c3820763c4c3821589c4c3820c3ac7c6820a18821849c4c3820415c7c682010e8217f9c4c38205d3c7c6820d22821608c4c38210e2c4c382023bc1c0c4c3821636c4c3820381c4c38214ccc4c382102ac4c38204c2c1c0c4c38208bec1c0c7c6820c5282182dc7c6820c428214e2c4c38204f5c4c382180ac7c6820b19821264c4c38204a6c4c382082ec4c3820f34c7c68209458218e5c7c68205518218b2c7c68208878213cbc7c682046d821807c4c38204bcc4c3820c65c4c38207e9c4c3821301c4c38210a6c7c682148c821878c4c38204f1c4c382142fc7c6820ed08210b1c4c3820151c4c3820424c7c682081b820c3bc4c38216a0c7c682152b821650c4c38216dcc1c0c7c682167d8216c9c7c6820792821575c4c382095cc4c3821413c1c0c1c0c4c38218bec4c3820655c7c68203ba820c62c7c68205ff821708c4c38216b6c4c38216c7c7c6820f2f8211dcc7c6820ba28215d4c4c3820c6cc4c3820343c4c3821563c4c3821634c7c6820649820bc6c4c3820b7dc7c68204558215bfc3c281fdc4c3820c41c7c68203048210bac4c3820785c7c682085b820ec4c5c447820c67c4c3821406c4c3820ad8c4c3820bd2c4c3820b5fc4c382152ac4c3820d8bc1c0c4c38202ffc4c3821595c4c38211d9c4c3821297c5c402820e94c7c6820c1a82169fc1c0c7c68218518218c2c7c682028a8209adc4c3820ba3c7c68203cd821366c4c38217afc7c68204af820c0fc7c6820d9b82112ec7c6820bec8215b0c4c3821809c4c382107bc4c382063fc4c382167ec4c382151fc7c6820a24820f0fc4c38217cac7c682012d821899c4c3820da9c4c38216acc4c38217e4c7c6820cd7820f35c4c38204f7c7c6820c9b820ef3c4c3820c3cc4c3820eecc4c382087cc4c3821357c4c3820762c7c682025a8214fcc4c382166cc4c38203e9c1c0c7c68209058218a9c4c3820c66c4c38218bec4c3821065c4c3820c31c7c682011482100ec4c3820756c4c3820915c1c0c4c382062bc4c3821603c4c38203dfc7c6821861821897c1c0c7c6820dbc8216bac7c68204ba820ec9c3c281cec4c382137ac4c3820bb6c1c0c4c3820e19c4c3821736c7c68210258211aec4c3821223c1c0c4c3820298c4c382188cc2c153c7c6821662821836c7c6820e44821673c4c3820724c4c3821806c6c581be8215cfc4c38216aac4c3820863c4c3820f61c4c3821111c4c3820f19c7c682037c820d4dc4c382154fc4c3821452c7c682036c8216edc4c38217a1c7c6820bfb821923c1c0c4c38204c9c4c382151ac7c6820529820c32c7c6820d7b821586c4c382121cc7c6820866821971c1c0c4c3821530c4c3820e7dc4c3821863c4c3820201c3c28192c4c38210a1c7c68206f18211ccc4c38217a0c4c38201b3c4c3821168c4c3821319c4c3821864c7c6820770821658c4c3820749c4c38210b0c7c6820a078217fac4c382053bc7c68203918214c8c4c38216c2c7c68213b48215bac4c3821989c1c0c4c3820ce7c7c68202bc820c5ac4c38209ffc7c6820d6b820f3fc7c682094b821450c7c68206698212adc7c68213ef8217a2c4c3821105c4c3821345c4c3821557c4c38211e3c4c382135dc4c3821904c7c682077a820b31c4c3820dabc7c68213bd821546c1c0c4c3820677c4c3821740c1c0c4c382192dc4c3820b4ec7c68202f0820e5ec4c382165dc7c68202de8217b9c4c3820a1cc4c3821371c4c3820b91c4c3820f98c7c68212be821635c7c682021e82196ac7c682083a8215e5c4c38216c2c7c68213a78213cdc7c68216b98218dcc4c3821008c7c682021782185dc4c38205b3c1c0c4c382146cc7c68215db82191cc4c38213bdc7c6821265821824c7c682142c821614c7c68208ac82143cc4c382188fc7c682091d82192cc4c3820699c7c68208fc82198ac4c3820997c4c3820ca7c4c3820b9bc4c382159ec7c68202ce82083ec4c382185bc7c682056b820c55c4c38202edc7c682071182197dc1c0c4c38210c9c1c0c7c68216c9821713c1c0c4c3821775c7c6820bff821597c7c68215968218e5c2c15cc4c3820860c4c3820cd9c7c6820c0c821950c4c38205e0c4c3821340c4c3820fd6c7c6820c7782134ec7c6820a92821158c4c3820c97c4c382148dc4c38218abc4c3820cadc4c38213d3c7c6820951820f07c4c3821233c1c0c7c6820ae7820d85c7c68206f4820f6bc4c38216c3c7c68208dc820d29c1c0c4c382025cc3c281f8c7c68215478218f4c7c68214188218d5c4c382181ac1c0c4c382197cc7c68202fa8218fbc4c38218a4c7c6820cf58214afc7c68206d4820a16c7c6820b7e820e91c7c682161682174cc4c3820dd2c7c68215da8217b5c4c38203bec4c382075ac4c382079dc7c68215f58217f2c4c3821625c7c68218ac821908c7c682031f8219c3c7c68215738217c2c4c3820d8bc4c3820476c7c68201008204bec1c0c4c38213b4c7c68213ab82144ec7c68213448216cdc4c3820482c4c3821434c4c3820164c7c68214e6821832c7c682103d8215ffc7c6820c2b8216f2c7c682032f82139ac4c38218fac4c3820541c4c3820269c4c3821879c7c6820b6f8215adc7c6820b38820c70c7c68202eb8207fac2c16bc4c382143ac7c68216548217dec4c38219d7c7c68203c4820fb4c7c682050c82057ec7c6820e6282193dc4c382174dc7c682027282170ec7c68213ae8216b8c4c38218e2c1c0c5c46282024dc1c0c4c3821691c7c68201b2820399c4c382153fc7c682048382147dc4c3821549c4c3821307c4c3820670c7c682143f82199dc7c68217ae8219e2c7c682174a821755c4c3821385c7c682085a821416c7c682012a820cd1c1c0c7c68213f98218f6c4c3821161c4c38202f7c7c6820d9882153ec4c382142ec4c3820877c7c68204c6821196c7c68203fa821330c4c38216a1c7c6820507821894c7c682163b8217cbc4c38211e3c4c38215bac7c682035e821898c7c68204a98219f1c1c0c7c682088c8217bdc4c38202bec7c68212fb82193dc4c3820522c4c38205e6c4c382148bc4c38206cfc7c682031e821722c7c6820b8c821880c7c68209ca820d24c4c3820ccbc7c6820efc8211a5c4c382134dc7c68210808212cbc4c3820145c1c0c4c38215c2c7c68205d38214e0c4c3821185c7c68209d9821a1ec4c3820fd7c7c68202db821833c4c3821480c7c68207d7821481c4c3820adcc7c682117e821930c5c468821381c4c3821821c4c38214cfc4c38214f1c4c3821173c4c3820b6dc7c6820ac8821341c7c6820b928212edc7c6820c1e820ce1c1c0c4c3820b11c7c682134c8214d9c7c6821936821a14c4c3820c45c4c382139bc4c38211fac7c682019582041cc7c6820d0382164fc4c3821299c4c382169ec7c682049d820808c4c3820f00c7c68204698214efc4c38212a6c4c38215f5c7c6820c6e8219fbc7c6820b0082190ac7c6820e49820e4ac4c38208c7c4c3820ea7c4c3821982c7c682173b821933c7c682089d821484c4c38217cfc4c3821929c7c682050e821394c7c6820c83821a1cc7c6820c1d82193fc4c38215a6c7c682087e821887c7c6820983821776c1c0c7c682168b821958c7c682025f821459c4c3821963c4c382104bc7c6821208821432c4c3820d0fc4c382124ec4c3820258c4c38207f9c7c68208f2821251c7c68201a5820aa6c4c38216b1c7c6820242820d1fc4c3820dfac7c6820372820732c1c0c4c3820921c1c0c4c38218fcc7c68203b38209f3c7c6820d4782177bc4c3821830c1c0c4c38212a0c4c3820a73c4c38203c2c4c3820e87c7c682117d82153ec4c38216e0c7c6820bf982163dc4c3820569c7c682183a821a52c5c413821024c7c682139d82191ec4c3821a5dc4c3821621c4c3821611c7c68216a98216e4c4c38201f1c4c382198cc7c68219d78219e4c7c6820e8e821294c7c68219a5821a60c3c281e6c2c15fc4c3820b4fc4c3821700c4c3821a16c4c3821133c7c68202bc821868c4c3821064c7c68206c582070ac4c3820b7bc4c3820d56c7c682097c820ebdc4c382155dc7c682176d8217d6c4c3820a16c1c0c7c6820e5b8215d7c4c3820407c7c682063e820fc7c1c0c7c68206fb8207abc4c3820132c7c682067d8216fbc4c3820898c4c3821a57c4c3821525c4c38218e7c7c6820bd882160bc4c38216dac4c3821a7bc4c38206ddc4c382113ec7c682124282126ac4c3821624c4c3820359c1c0c1c0c4c3821665c4c38213a8c4c38213cdc4c38204eac4c38219b2c4c382186fc4c3821a57c4c3820e10c7c68205da821918c7c68212458215dec7c68212ee82174dc4c38209a3c4c3821458c7c682103d821ac3c4c38202eac7c682116e821455c4c3821368c4c3821633c7c6820c19821895c4c382064ac4c3821350c4c38219f7c4c38219d1c3c281b6c7c682144f8218e6c7c682116b8212dbc4c3820206c4c38216f5c4c38219eec7c68201508216c4c7c6820f228215c6c7c68202ae820533c5c4148219b9c4c3820deec7c68210cc8215f7c4c38219ffc4c382101bc5c44f8205aec7c682164c821a88c7c682059b820811c4c382100bc7c6820da2821ae9c4c3820312c7c6820c5282158cc4c38211f6c1c0c4c3821685c4c382107ec4c3821a9cc7c68202d08206bbc4c3820eafc4c3821a61c4c38212d2c7c6820a98820ed0c1c0c4c3820b6dc7c68216c5821805c7c6820a7f82142dc4c3821411c4c3821973c7c6821116821a50c4c38212f2c1c0c4c38215a4c4c3821343c7c682107a82171ec2c110c4c3820dcec7c6820ce2821452c7c6820ba88215afc4c3820f83c1c0c7c68208c582101ac7c682044e82064ec4c38209fbc4c38204afc4c38204d9c7c6820862820d8ac4c382118bc7c68205ba821393c4c382047dc4c382083dc7c6820da48212d0c7c6820be3820c85c7c6820406820880c4c38214e2c7c68201c9821115c7c68210cb821217c7c6820c938210f0c7c6820893820d0ec7c682107782129ec7c682102e821a47c1c0c4c3820734c4c3820c20c4c3821a19c4c3820adfc4c38210bbc4c3820243c1c0c4c3820698c7c682030a821759c7c6821953821adfc7c68216c6821b30c7c6820b238211fbc4c3821688c7c682093c821a1ac1c0c7c68217b8821895c4c3820efac7c6820c8a8218b3c5c477820a0bc4c3820318c4c3820601c1c0c2c140c7c682181f82194ec4c38203cec7c68202e9820cfcc7c6820173820a0ec7c68209a5820cbdc7c6820c88820ed6c5c458820572c4c38212d4c4c38217fcc7c68201d7820c7ec4c3820839c7c682042d821845c7c68218f182197ec4c38215a2c4c3821444c7c682093f820d99c4c38215f8c4c38211d6c4c3820accc4c38211cdc7c6820b1b820ef9c7c6820838821710c7c68201e0821811c7c682109e8212e7c7c68205c5820985c7c68214e58216f7c7c68206db821457c7c68214778214adc7c68214f98219e8c5c4528218e8c4c3821844c4c3821606c4c382161cc7c6820480820698c7c6820d4c8218c8c4c38210f7c1c0c4c3821686c7c68202fb8216bbc7c682051682186cc7c6820fe3821a1dc7c6820f3e8219f2c7c68210218214ddc1c0c7c6821498821941c7c6820a5c820cc6c7c6821a02821b2dc4c382115ec1c0c4c3821541c1c0c4c38217a6c7c68201b9820d3ac7c68202ab8219c6c7c68209e28210d0c7c682168b821a35c4c3821007c7c682111c82183dc4c38217c4c7c682194c821a7ec7c6820acc821794c4c3821773c7c6820bf382161dc4c3820bcbc4c38204f2c1c0c4c382177dc1c0c4c382189ec4c3820224c4c3821250c7c682061c820e75c1c0c7c682044f82159ac7c6820eb9821b72c4c3821261c7c68213b08219dfc1c0c7c68208e4821a43c7c68210428215f9c4c3820d39c6c581dd820cffc4c3820a8bc1c0c7c68201bc820b8ec1c0c4c3820666c7c6820a91820b9dc4c3821a4cc1c0c7c68201a082041dc7c682078f820a55c4c38210b9c7c6821304821539c7c6820a1f8216ccc4c38217a2c1c0c1c0c1c0c7c682105b8215c2c7c68201c582143ec4c38204ddc7c682016a82172cc4c38205aec7c6820ab58211abc2c148c4c3821a36c4c3820d48c7c682112a82187bc7c6820fc9821ab3c7c682064882143cc4c38212cbc7c6820e2d821666c1c0c4c3821116c7c682013c820160c4c3821863c4c3821357c7c6820678821295c7c6821a80821af9c7c68207c48217d8c7c682024f82048bc7c68211888214aec1c0c4c3820c6ec4c382102cc4c38217ecc4c3821a61c4c382064cc7c68208e7820aa8c7c68211c1821b18c4c3820fbcc4c3820cf8c4c38218edc3c281dbc4c3820e38c7c682081182113cc7c68212198218a0c4c3820fedc7c682104182171bc7c68209f082107ec7c68205ee8210ebc4c382045ec4c38216e9c5c455820a99c7c68205c6820b44c3c281b5c4c3821470c7c6821339821670c7c6820710820dc7c7c6820a7a8211fdc4c38202adc1c0c7c682143d8215d3c7c68209a9820d2fc4c3820b72c4c382145dc7c68201138210c7c4c38211aac7c6820a7c8215dcc7c6820f9a821594c1c0c7c68203b0820e6bc4c3821b29c7c6820e92821803c5c42c8219d4c4c382110cc4c38208d8c4c3821ae8c7c682030c820f82c4c38212b2c4c38202bfc4c3820af3c4c3820cbec4c3821b85c7c682090d820a07c1c0c7c68210a782117cc7c6820d5d821248c1c0c4c38216fac1c0c4c38217e6c4c3821865c4c3820fb5c4c38209c3c7c68217bf82184ec1c0c4c3820d4dc7c682044382170ec4c3821720c4c3820abcc4c38215f4c7c6820d7082156bc4c3821a6ac7c682113582179ec4c3820651c4c382014cc7c6820c61821bcac1c0c4c3820936c7c682132b82195bc7c6820e9d821ba1c7c68204a582191cc7c6820c698216d8c7c6820faf8210c3c7c6820b10820ee1c7c682182f8218aac4c3820dffc4c3820b36c4c38212a1c4c38218fdc3c281d8c4c3821bd1c4c3820b93c4c382011bc7c682079b821474c4c3820284c1c0c7c68215b5821b3fc4c3821499c4c3821a7cc4c38218aec4c38206f0c2c109c4c38214cdc4c382052dc7c6820778821402c4c3821b78c7c682112d821449c7c6821a1b821ba7c4c3820c58c4c382110bc1c0c4c38214f3c4c38206e9c4c38217d1c7c68210e08218ffc4c3821976c6c581a7821644c4c382075dc7c682080c821ae7c7c68217e38217f4c7c68203c9821581c4c382145ac7c6820fae821b0ac7c6820495821ae5c7c682045f820e2fc7c6820d0282102dc7c6821939821b4dc4c38208ecc7c6820ecb820fb9c4c3821617c4c3821232c7c6820cd682133cc4c3821753c4c3821220c4c38216cdc4c38219f6c4c3820335c4c382147ac4c382199ec4c382066ec7c68215ab82187dc7c6820752820816c7c6820133821c28c7c682154a821b61c7c6821835821bc0c4c38202f2c4c382178dc4c3820500c1c0c4c3820c5cc7c682055f82113bc7c682061b820b11c4c382180dc4c3820979c4c3820b29c7c6820ba4821502c7c6820996821336c4c382131ec7c682066682153bc4c38203c9c7c68209be8216b4c7c682062e821271c4c3821585c4c3820a31c7c682069982076cc4c3820d75c7c6820fab82104ac4c38215d1c7c682164b821657c7c6820692821ab8c4c3820c91c7c682045f821314c7c6820262821135c4c3820c5ec4c3821aaec7c6820b7d821814c7c6820a328211b7c4c3821756c4c3820732c4c38202fcc7c6820bc882163ec7c68210db821baec7c68205f682118dc7c6820ab98210edc4c3820f42c4c3821574c4c3820fdcc4c3820a46c7c682098c820ba9c4c3821987c4c3821258c4c3820526c4c382052fc4c3820800c6c581f28218c4c1c0c1c0c7c6820a4882199ac4c3821ab5c4c38206a0c7c6820979821925c7c682081a820ec7c4c38212aec4c3821427c7c68205ad8211f4c7c68214d2821ac6c4c3821990c4c38215d8c4c3821981c7c68215e1821ae5c4c3821bc1c4c3821c2dc7c68204f0820dd5c4c3820f4ac7c6821715821be2c4c3821657c4c3821062c1c0c7c682011a820e0ac4c3821748c4c3820586c7c68202a2820916c4c3821c0bc4c3821317c4c3820323c4c3821abcc4c3821c34c4c3820837c7c68206b4820c81c7c6820ea082171dc7c68201c1821461c4c38212d8c7c6820222820c96c1c0c7c68206f7820e53c4c3820d65c4c3820d92c4c38216f4c4c3821b93c4c3821376c7c6820f15821786c7c6820233820a34c7c6820970821966c7c682023f821772c7c6820c618218a2c7c68215f1821a4fc7c6820b2f821aa8c7c682128c821b4ec4c38203dac1c0c4c38213dac7c6820e6f8219dec4c38201fac4c3820fffc4c3821310c4c3821952c7c6820fec82166ec4c38202b0c4c38219bcc4c38214efc7c6820a79821583c4c382173cc4c3821875c7c68205748218a8c7c682010e8216aac7c6820658820e19c7c6821489821ca1c4c38211d3c7c682183c8219f4c7c6820a7682180fc4c3821251c7c68202da821594c5c4768205a4c1c0c4c3820dc3c4c38207d6c7c682121a821c12c7c682091f820e2ac4c3820604c7c68204d6821667c1c0c4c38215e8c7c6820903821bfdc7c68212de8217bec1c0c7c6820cc1821bb4c7c682114f821448c7c68202ad8212b5c4c3821735c4c38217d1c7c6821574821659c7c6820c51821b6ec4c3821c85c4c38211d2c7c6820c72821c1dc4c3821220c7c68201a28214e1c4c382197ec4c3820b5fc7c68202dd821676c7c6820e478216f6c4c3820b51c7c6820b4c820d7cc1c0c7c68210f18215e2c4c3821cd4c1c0c7c68209cb8215a1c4c3821b77c4c3821b3ac4c3820cc5c7c68209ba820e23c4c3821bf4c7c6820c63821829c7c6821790821826c7c6820189820831c4c3820dfdc4c38219dcc4c382066cc7c68210a9821821c7c6820e8f821bfcc1c0c7c68214ac8216e8c5c4188216c8c4c3821716c4c3820929c4c382105ec4c3821b9ec4c3821b3ec7c6820d73821bb0c7c6820be8821c03c7c6821891821a1dc4c38219f5c7c6820c78821587c4c3820ba0c7c6820c688213b0c7c68208b5820c82c4c3820ab7c7c682142a821951c7c682033b82181dc7c6820c768214f8c1c0c7c6820b0782185fc7c6820baa820d68c4c3821975c4c3820e9dc7c6820b3d821944c7c6820900820c31c4c3820a33c4c38213dbc7c6820640821cc7c1c0c7c682172f821788c7c6820643821b85c7c682174f821a9dc4c3821906c4c3820127c7c6820e83821adcc7c6821a71821cc3c7c68203b6821739c7c682078a821142c4c38209a1c7c6820d59821c28c7c682014082092ac1c0c7c682168e821af1c4c38218e9c1c0c6c581cb821a0bc7c6820528821c63c7c6820894821c6fc7c6820b43821105c4c3821293c4c3820853c4c3821438c4c3821b27c7c6820ee0821b48c7c68206ff820ddec7c6820f628210c9c7c682010d820582c4c3821cfec7c68204ee820ec8c1c0c3c281c5c7c682121382153ac4c38212e0c7c68201fc821bcdc4c3820b60c1c0c4c38214b1c4c3820df1c7c682113f821320c7c68201bf820c23c7c68204ef820cf9c7c6821bee821c09c7c68219c5821a5ac7c682120c821999c4c3821187c7c6820f1f8211aac7c6820b5a821996c7c6820dbc8210f3c7c6820fee82125dc4c3821b76c2c142c7c682088b820b49c7c68219bb8219f8c7c68205f6820901c7c682030e82178dc7c68211c7821a11c7c6820e02821cadc1c0c4c3821a79c4c3820d81c7c6820caf821c75c7c68201fa8203ecc1c0c4c38216fbc7c6820c60821c62c4c382074dc1c0c7c6821208821cf4c4c38207a5c7c68216fc8219c7c4c382062bc1c0c7c682152d821ccbc4c3821d36c7c682104d821496c4c3820675c4c3821a50c4c38212b0c4c382140cc1c0c4c38218cec4c3820e8ec4c3820ddcc7c68217ea821b37c7c6820701820d1ac7c682062d8206b2c4c38215b9c4c3820bb7c7c6820ad7821662c4c38203dcc7c6820b35820d8cc7c68203248213fec4c3820f3dc4c3821843c7c6820820820ea1c4c38217adc4c3821d78c4c382189bc4c3821ad7c4c3821aaac7c6820d40821127c4c3821749c7c68214f882174ac4c3820fc1c7c6820f87821b1ec7c68209a28219efc1c0c4c382155fc4c382123dc7c6820d528212cac4c38216efc7c68207a38210c8c4c3820c9ec5c403820f05c7c682104c821b24c4c3821c09c7c68207ac82179ac4c382130ec6c5818d820d6bc7c6820bec8219cbc4c38208a3c4c3820f94c7c6820484820e0bc4c3820d4ec7c6820a5f820e35c4c3821404c7c6820550821570c4c3821d3dc4c3821582c7c682130d821369c4c3820b4bc7c6820f178219fac7c6820eb4821262c4c38214c4c4c3821057c4c382123bc7c68209578218b7c7c6820207820dc1c4c382151fc4c382178ac4c38208c2c4c3820907c4c38216bdc4c38202bec7c6820154821273c7c6820c748215e4c6c581d3821070c1c0c4c38212b6c7c6820f43821189c4c3820b06c4c3821bbbc7c6821533821c37c4c3820c76c4c3821227c4c3821d20c4c3821390c7c6820a6e821b86c4c38212a4c4c382024ac7c68219a0821af4c4c3821ad7c7c6820171821d95c4c38215ebc7c6820fe1821689c7c68210b38212c8c7c68219f3821c8dc7c6820ffa821779c7c68210bc8210c7c4c3821347c7c6820f5182110ec4c3820a64c4c382122ac4c38204c1c4c3821377c4c3821d29c7c6820a7b820c63c7c6820d3b821182c4c3821524c7c6820e12821485c7c6820374820e88c4c3821c26c7c68205aa820f75c4c38210c1c4c3821a9ac7c682053a820e23c7c6821771821bf8c7c68209e6821b9cc4c3821782c4c3821d2cc7c682018582036dc4c3821aecc7c6820f1b8214a5c4c3820f78c7c682195a821ddcc7c6820ee8821426c4c382124dc7c6820b28821c33c1c0c6c581df821cabc4c382063cc7c68209568216bec7c6820b60821bf9c4c3820535c7c6820a1a82124dc7c682079f821831c7c6820cd182122cc4c3820c2cc4c3821928c4c3820d11c7c682034d821b75c7c6820dfe8210e7c7c6820a0982178bc4c3821b7ec4c3821329c7c682085282132dc4c38218dac1c0c7c6820af082193fc4c3821893c4c382198bc4c382050ac7c6820de2820f65c4c3820b37c4c382166ec4c38212bbc7c68216d3821a3dc4c3821461c7c6821af6821b3cc7c6821bf8821c4dc4c382165bc7c6821004821b95c7c682161382165ac4c3820e42c4c38217e1c7c682030b821bfcc4c38217d4c4c3820a26c7c6820fe9821d53c7c6821c48821c55c4c38214b3c4c38219b0c4c3821c1fc5c43d8211e5c7c68214e6821c84c1c0c7c6821590821deec7c6820c908215f6c2c16cc7c68215ec821970c4c3821602c1c0c7c6821909821dc2c7c68219b3821b3dc7c682069e821703c7c6821922821ad0c4c38210d7c7c6820a658219eac7c682028c8216cfc7c682122f821d5dc7c6820c75821501c7c6821815821938c4c38204f4c4c38203efc7c68210e6821340c7c6820da582192ac7c6820e538219f2c7c6820fb2821128c7c6820c6b821950c7c68210a58214dfc7c682048782108bc7c682165e821b55c4c38212b4c7c682184f821e16c4c3821bb3c7c682100b821ddec4c3821d12c2c119c7c682104f821b16c7c682022b8214b4c4c3820434c7c6820a968213c6c7c6820e9f8215dbc7c6820dbb821a98c4c3821101c7c68219a88219c5c7c6820708821a0cc7c682010382076fc4c38201ddc7c68209138214a0c7c682044e8211f8c7c6820ba5820ef7c4c3821730c7c6821867821dfac7c682162e821d32c7c682131482187cc1c0c7c68209e5821cd1c1c0c1c0c5c449821c7cc7c6820f69821babc4c3821311c4c3821487c7c6820717820d2dc4c382198bc4c3820478c7c68215ac821c92c4c3821db5c7c6820268820d43c7c6820db68211a9c7c682101d821252c7c6820fbd821c3dc7c68204c2821bb5c4c3821655c4c3821ba0c7c6820e298212b2c4c382131ac7c6820a63820d1bc4c38218fbc4c3821519c1c0c4c382027ac7c6820a15820d2dc7c6820726820b10c7c68204d1821c8ac4c382197cc7c6821307821d5fc1c0c7c68219f6821bdac4c3821a24c7c6820e15821d6ec7c6820c73821502c7c68203d3821903c4c3821e15c4c38216f7c4c38217e9c7c6820a69821d05c4c3821847c4c3821ae2c7c6820b858218e4c7c68212fd821c0cc4c382157fc7c682092c820aa2c4c38215a7c7c6820667821093c4c382125ec4c3820567c4c3821c6bc7c6821633821d11c7c6821684821d27c7c68213cf82164ac4c3821259c4c38210ecc7c68207de8217d6c4c382153ac4c382104ac4c3820aaac4c382191ec7c68202b5821da6c4c38219f7c4c3820d82c4c3820d88c7c6820455820ad9c4c3820c74c4c38210f5c7c6820c5b82188ec4c3821afbc4c3820c01c7c6820a2d821ba6c4c382178ec7c6820546821509c4c38213c3c7c6820fde821b57c4c3821433c7c68215e5821abfc7c6820321821d12c4c3820750c7c68207c98209d2c7c6820bf082171bc7c68207ac821c5fc7c68207958211edc7c6821b43821e0ec4c3821382c4c3821b5cc7c6820e5a82162ac4c3821de1c7c68217fa821915c1c0c7c682197d821dc0c7c68204038216dbc7c6820d26821ea0c7c68218ec821b70c4c38218e8c4c3821d2bc4c3821c69c7c682169a821e8dc7c6821180821d5bc7c6821179821b00c4c3821715c4c38213a2c7c68201a68201a9c7c682065c820719c4c3821843c4c3821907c1c0c4c3821e1dc7c682021b821de2c1c0c4c3821e33c4c3820f2cc7c6820b8a821816c4c3821a3ac7c6820c75821421c4c382121bc7c6821a34821a83c4c3821d90c4c38210c4c7c682091882188bc7c68201f782122ec4c3820c6bc4c38213e7c7c68207f38213e6c7c6820b70820c6dc4c3820512c7c68201aa820d6fc7c68213f9821eb1c7c6820e5f82158ac4c3821d5ac4c3820c7fc7c682162482177ec4c382199bc7c682067a821861c4c382196fc4c3820d16c4c3820e4ac4c3820d37c6c581db8216f9c4c38203b4c4c382184ec4c3820cd3c4c382103fc7c6820405821813c4c3820171c4c38218d5c4c3821912c7c68218458219fdc4c3820a72c1c0c4c3820d2cc7c6820279820f0bc4c3821c40c7c682069b820b79c7c6820d808210d9c7c682084982191ac7c68213738214c0c7c68216218217edc7c682156f821c58c4c3821149c7c6820cd2821288c4c3820bb5c4c382166fc4c38217b5c4c3821c40c4c3820950c7c68215848217fbc4c3821920c4c3821086c7c6820fac821b58c7c68204528209d0c4c3820a42c4c38215d4c4c3821214c1c0c7c68218df821ba2c7c6820ddf821a21c4c382181ec4c3820627c7c682088e821d34c4c382136fc7c68205408214d1c4c3820d87c4c3821e65c4c38218e3c4c382081bc7c6821535821bd0c4c3820d7dc7c682093a820bcdc4c3821bd6c7c682038a821be5c7c6821949821a1bc4c3820d65c7c6821b2e821b8ec7c6821b0f821f0dc7c6820211820c54c4c382109ac7c68208de8211bec4c3820ee9c7c68209cc821e45c4c38214c3c7c6821921821b63c5c449821219c4c3821ef2c7c68201068203c8c7c68209a4821abac4c3821c19c1c0c4c38213dec7c682140e821c8bc7c6820498820844c7c68209ee8210b8c4c38219ccc4c3821acec4c38201a0c4c3821513c4c3820f9cc4c3821c5ec7c6821b34821e22c7c68212ff821a9bc7c6820bac821028c6c581a48212e8c4c38207a9c4c3821e71c4c3821553c4c3821db2c7c6820ad382149ac7c6820f9382112ec4c3821d65c4c3821ea3c4c3821ea5c4c3821aacc4c3821776c7c6820b3e821b8dc7c6820abf82167dc4c3821c45c4c382121dc7c682132f821b97c3c281c7c7c682090e8216efc4c3821747c4c38215c3c7c6820c6f821770c7c6821959821ef7c7c68214b782166ac4c3821288c3c28192c4c38212b8c4c3820a98c7c682074e821249c4c3820a12c7c68201e7821923c4c382184dc4c3820682c7c68213da821cdec4c3820f36c7c68207e6821054c4c3821f25c4c3820e55c4c3821c82c7c682130a821c29c7c6820bca8215d3c4c3821d02c7c68209a7821cc7c7c6820af582175cc4c38211afc7c6820f6382172dc7c68201fb8214f4c4c3821dcdc7c682169b821e1bc4c38214bcc7c6821419821741c4c3821678c1c0c4c3821bbfc7c6821030821d5ac7c6820366821b83c4c3820602c7c6820496820abcc4c3821365c7c682022b821504c7c6820247821ee0c1c0c4c38213f7c4c382172ec4c3820a86c4c38206d5c4c38218f8c7c6820465820635c4c3820f18c7c68214d3821cb7c7c68205b5821f66c4c38219d6c7c68218ab821c5cc7c6821183821b92c1c0c7c68202b48218f9c4c3821a01c7c682029d820e02c5c427821cc0c4c3821f47c7c68204bb821774c7c68205ad821f2fc1c0c4c3820423c7c6820930820dfdc4c3821b3bc4c38208ddc4c38214f2c4c38213c5c4c3821112c7c6821184821cacc4c38205d9c4c38211adc4c3821c2ec4c38212fac7c68201d2820a9cc7c6820695821e2fc4c38209c4c5c41b821e12c7c6820684821679c7c6821c7f821cc9c7c68201c7821362c4c3821165c7c68209c782109cc7c6820b22820ff8c4c3821da4c4c38218d8c7c6820cc08214b2c4c3821983c4c3820df5c4c38212e2c7c68212cd821370c4c3821d31c4c38207ebc7c6821239821a85c4c3820146c4c3821876c7c682059782123fc7c6821571821ccac4c38207dbc7c6821199821889c4c38210a8c4c3820e84c7c6820e2a82163cc4c382146cc4c3820388c1c0c7c6820721821e5cc7c6821478821a26c4c3821cecc4c382114bc7c6820bab821aebc4c3821243c7c682132f821a87c7c6820a0582157ec7c6821397821619c7c6821429821e21c4c3821f79c4c3820f32c7c6820e8c821d44c7c6820266820950c7c68204e88213dec4c3820c23c4c3821a0ac7c6820f5d821002c7c682020d821a70c7c68214c2821b0ec7c6820ccb820f86c7c682151e8217bac7c682116f8212c7c4c38219d0c7c68208bf821095c4c382126fc1c0c4c38219b7c7c6821906821a07c4c38206a1c4c3821623c4c3820117c7c6820217820588c4c382133cc4c3821e23c4c38206bdc7c6820665820c5dc7c682082d821ec5c7c68217a782185dc4c3820b81c4c3820b37c4c3821b53c4c3821a0cc7c68206ed821d8fc4c3821fbcc7c6820402821fb3c7c6820737820f7bc4c3821d01c4c3820884c7c682125b821e46c4c38215a8c4c38217cdc7c6820ec4821c9cc4c3821926c7c6820dc9821b13c7c682142b821e03c7c6820fd8821342c4c3821f39c4c3820767c4c3821c62c2c175c7c6820403820dccc4c3820871c7c6820ca082123fc4c3820d83c7c682197a8219cac7c68204f1820c6ac4c3821a4dc7c682064a821f3bc4c3821942c7c6820207821171c7c682056f820cb8c7c6821a5b821cf7c4c3821b7dc7c68216ea821adac7c6820db382106ec7c6820df8821b23c1c0c7c6820b70821a49c4c3820c65c7c6821aff821f70c4c3821961c7c6820d3d821d2fc7c6820c5f8214e3c7c6820310820a10c7c6821d08821d1ec4c382166cc4c3820bb1c7c6820ff9821738c7c682120a82176cc4c38214e9c7c6821c51821d59c4c3821fd6c7c682080d821d01c7c68212fa821dc5c7c6821734821f79c4c382154ac4c3821defc7c68212bf82146bc7c6821576821dc0c4c3821087c7c6820568821579c7c6820b9f82190bc7c68204a4821c07c4c3821766c7c682014a82109dc4c382120fc7c6820bff821a89c7c6820c71821cd1c7c6820de7821934c4c38214bec7c6821aef821f08c4c38211a2c4c3821eedc7c6821086821bcdc4c3821f39c7c6820f088219fcc7c682133e821d8dc1c0c7c6821ced821ec0c4c3820c2cc4c38219c0c4c3821522c7c6820562821c0dc4c3820de0c7c6821988821d8dc6c5819f8209d5c7c6820cb58211bfc4c382184cc4c3821ba4c5c43e82091ec7c68212e9821a92c4c3820f9ec7c682181b821e8ec4c3821615c4c3821ad2c4c3820191c7c6820d8f820ed4c7c6820815820a84c7c6821b8b821e5bc4c3820d76c4c3820768c7c682122e821fa4c4c3821846c4c3820b88c4c3821a25c7c68212e9821a82c6c581ab8204fdc4c38202b1c7c6821267821f3fc4c382108bc4c3821acfc4c38209bcc4c3821027c4c3821120c7c6820f3f821f83c7c68213ed821a08c4c382154dc7c6821a27821a53c1c0c4c3821c53c7c68219e1822022c7c6820867821526c4c3821293c4c3820c5ac4c3821df0c7c6821011821f02c4c38218d0c4c3821420c7c6820e2b821e45c7c6821d99821f9cc4c3821d9ec4c38211e8c4c3821424c4c3821f72c7c682083c820da5c7c6820c57821c6dc4c3822031c4c382039bc7c682011d8202cdc7c6821834821feac7c6820976821f68c4c38217d2c7c6820494821c59c4c3820b9fc4c3820199c4c382100ec4c3821aa0c7c6820bb0821a40c4c382131fc4c3821bcac4c382198ec7c68201a1820a33c4c3820c14c4c3822053c7c6820d9682182bc4c38204a7c4c3821412c4c3821d4ac6c581a682135fc4c3821018c4c3820b78c4c3821ef2c7c68205a58213aac7c682067f821773c7c68210a5821222c7c6821978821b6bc4c3821191c3c281edc4c3821c7ac4c3821207c6c581fc8201b6c1c0c3c281b7c7c682024c820833c7c6820dac8211bbc7c68214b182163cc7c6820f1582179dc7c682032b820b5ac7c68219d4821fe5c7c6821618822041c7c6820584821ef4c7c6820e818212d0c4c38210b9c4c3820cf3c7c6820fc1821d5ec7c6821753821780c7c68212c1821869c7c682186d821959c7c682073c821207c7c6821bd3821ff5c7c68204fb821ee4c7c68201a18211b1c7c682044a821d1ec7c682050482128fc7c6821abb821fa6c7c68205c4822077c4c3820d49c7c682170f821df7c7c682037d820d85c7c6820448820e89c4c3820541c7c68212df821b41c7c68210da821eebc7c682039b820fd2c7c68208b0821e75c4c3820b14c4c3821e0cc7c68213d6821d15c7c6821726821dd9c7c68202008205eac7c6820b0a8215dec7c6820413821d49c7c6820228821d7ac7c68212cf8214f4c6c581e7821dfec7c6820c88820eaac7c68206df8217e5c4c3820febc7c6821862821ca0c6c581d082189ec4c3820ff0c7c6820325821aebc7c6820c7c82165fc7c6821e34822027c5c445821257c7c68215ee821bdcc4c38211b3c4c38212fcc7c682032d820aeec4c3821284c7c682017b821231c4c3821cf3c1c0c4c38202cac7c68204138214a2c4c382095bc7c682106a8218d8c4c38208ccc4c3821d4ec4c3821bbcc7c6821237821270c7c682035a821fe7c7c682010982186dc4c3821d1bc7c68201be821985c4c3822081c4c38201cdc7c682044b8212c5c7c6820375821163c7c6821496821c01c7c6820efe820f9cc7c682126d821aaec4c3820241c7c6820cf1821837c7c6820cea82127dc7c68211cb821f45c7c682021a821ee6c7c68211178219f4c7c6820977820c97c7c6820350821d0cc4c3821752c7c68204b8821423c7c682031b820dbec7c6820ce3821a8ec1c0c4c38215d9c7c68211d182185fc7c6820d3e821210c7c6820333820951c7c6820ca6821311c4c3821b0ac7c6820885821c0ac7c68202368220b1c7c6821ae0821ce7c7c68202d48219fcc7c68212f4821cb2c7c68211d0821ed7c7c6820213821398c7c6820d4982203bc7c68206f28206f5c7c6821ed98220c9c7c6820e5182164ec4c3821cb1c4c36781a6c7c6821072821388c7c68211db822024c7c682011d8220afc7c682012d820d32c7c68220308220f0c7c682138d821ba1c7c6820e958220afc4c382013ac7c68212af821edec4c3821dbfc7c6820e76820ebbc7c6820a2c821d35c7c68218c5821fabc7c682049e821c27c4c3821b52c4c3822023c7c68211bf821240c5c45b821e9bc7c6820329820bfbc7c68204b8822026c4c382070cc7c68212188220a9c4c3821c67c7c6820f04820fbdc5c4818481b6c7c682043682103cc7c68202f9821992c7c68201ba821c4bc4c3820abdc7c68208098209abc6c581b1821913c7c68204008206a7c7c6820e01821947c7c6820107821babc7c68203b082137dc4c38211afc7c68204d88218eec1c0c5c44d820f3ac7c6820832820e80c7c6820ad1821de5c7c68211f782131bc4c382123ec7c68209cb8220ecc7c6820c12820e82c7c68203b2821ba9c7c6820e85821d30c7c6820452821081c7c6820570821f7ac7c68215c982200cc7c682023e820794c4c3820c55c4c38212b9c7c68216e3821b28c7c68201828215e9c7c682190d821b13c4c382119bc4c3821ac5c6c581ec8204d0c7c682031d820eefc4c3820d6ec7c68211d4822070c7c6820b19821a7dc4c3820a0bc7c6820bfd82102cc7c6820eca821d3ac7c68205fa8219c4c7c68211d8821ddfc7c6820ae1821280c7c682038b8216c8c4c3821de0c7c68206c9821c9dc5c466821cb8c7c6821f72822119c7c6820a8a820effc4c382172cc7c6820d1b821f69c6c581ac8216cec7c68208cf822022c7c682165e821c1cc7c68203aa8219c3c7c68207ad820f63c4c38218cac4c3820673c7c682039582123cc7c682040f8218a8c7c68211f782211dc6c581c4820b21c7c6820a7d820f52c7c682033682180cc5c41f822144c7c682102a8215c9c4c3821a06c4c3821389c7c682011582160dc7c6820aeb821205c7c682013682103ac4c3820ec3c7c682138e8220f1c7c6821e7282205fc4c3820c86c4c3820ca1c4c38207d1c4c3821800c7c68220f2822124c7c68203bd820707c4c3821dffc7c68217df821ad1c7c6820af88212cec7c68215808219a3c4c3821f5ec4c382107fc7c68203fc820f8cc7c6821888821db6c7c6820f348216fcc4c3821115c7c68201a98220c3c7c682022d821c9fc7c6820f09822013c7c6821fb58220d3c7c6820ae4821813c4c3821df4c4c3821a80c7c68203e7821e8ac7c682101982195dc7c6820b988212c9c4c3822099c4c3820f03c7c68205068220c8c7c6820c08821802c7c6820c45821cf0c7c6821cc1821d51c7c6820dcd821e18c4c3820bbcc7c682035282206bc7c68206f68220dac4c3821291c7c6821a94821dffc1c0c7c68203fd8215b3c7c6820feb8213b9c7c6820f89821facc7c6820425820d41c7c682133f821bb6c7c6821baa821fbec7c6821c9a821c9fc7c68215ce821cefc7c6820f64821aafc4c3821018c7c68210b5821c30c7c682024e820b5cc7c68202f2821db7c7c68213c2821e66c1c0c7c68204db821e26c7c6821192821b44c7c682022e8213ecc7c682128282199bc4c38211f5c4c38219d5c7c6820f40821da8c7c6821c88821f73c7c6820359821d2ac6c581e9820d56c7c68206468207cfc4c3820e3ec4c3822190c4c3821ff2c4c3820b77c7c6820edb821c2ac4c3821fbbc7c682054d821b94c7c68211a1821e1fc4c3822173c7c68203ee821167c7c68208b1821d94c7c682041b820781c7c6821640821efac3c281a4c4c3820e9cc7c6820f8e821ccec7c682104582111ac7c68203a682156cc7c68214dc821f14c4c382206fc7c6821c00821fc4c4c38211c0c7c6820db0821aa7c7c68205f482100cc7c682108f821412c4c3821cc2c7c68203c5821ff0c5c406821b9dc7c6821d6d821d9ac7c6820ac58210e4c4c3821e02c7c6821201821cfbc4c3820fd0c7c6820c90821ca9c7c682032a8214bcc4c3821be9c7c68212ab821c4ac7c682038d82041bc7c682129e8212b1c7c682020c822110c7c68210f4821172c7c682062e82213dc7c6820e50821094c7c6821d7f821f87c2c148c7c682105e821ab4c7c68213058216bac7c6820288820e54c7c68202598220a4c7c6820e1c8220ccc7c6820137820f9dc7c682176b8219d0c7c68203d6821e0fc7c6820e748220c7c4c3821dd8c4c38210fcc7c6820bdb820f8fc7c6820895821dadc7c68213d182201cc7c6821308821676c7c682012f8216e5c7c682116c821eb8c4c3821188c7c6821e55822084c7c68204b6820a44c7c6821b46822091c7c68207048218ccc7c682019f821c9ac7c6821d3e82212fc7c68201fe820bbdc7c68201e18220b2c4c3821495c4c3821c03c7c68208e2821551c7c6821268821eb2c7c682014b820ce4c7c6821cda821edbc4c3820a1dc7c6821554822164c7c6820edc821b77c6c581b4820c4bc7c682066a821ca7c7c682119f8220dec7c6820622821e04c4c3821f98c4c3822011c7c68203728215fbc7c682143b8219b2c7c68211e6822113c7c6821c6b821e78c7c6820786821781c6c581888216ecc4c38203f9c4c3821d40c7c68209b2820e10c4c38202d3c7c68203d082200dc6c581e0821d82c7c68203a4821e61c7c68221568221ddc7c68202d9820300c4c3821240c7c6820b4882214ec7c6820a1582205dc4c38211ebc4c3821d66c7c6821c77821d97c7c6820396821a90c7c6820891821462c7c682094282115bc7c682155982215ec7c682094a82106bc7c68203868221e8c4c3820bf5c7c682171f8220a9c7c68212ac8217a8c7c6821c44821ea7c4c3820851c7c6820130821334c7c68207cc821df4c1c0c6c581a5820823c7c6820893822122c6c581ed821a66c7c68212ce8212e4c7c682180b82209cc4c3821fb4c7c6820ff9821f88c7c6820304822002c4c382135ac4c3821c4dc4c3821ee0c7c682038082171cc7c6821a65821e57c7c6820b98821767c4c3822206c7c68201ef8212eac7c6820aae820ad5c7c6820839821ba5c7c68211e7821b1bc7c68206068221eac4c382090cc7c68203dd82117bc4c3821f92c7c6821fad8221aec4c3821c56c7c68203cf820a1fc7c6821166821395c4c382220dc4c38221dfc7c68211518221b9c7c68202c88211f4c7c682077c820872c4c3820fe0c7c68212f9822094c7c6820edf82172ec4c3821d26c7c6821768821825c7c68202ae820cd4c7c6820d6a821272c4c38219dec7c682176e822040c4c3820248c4c3822166c7c6821cb08221dcc4c3820f88c4c38210c4c4c38213f6c4c3820277c4c3821e62c7c68208c382210ac7c6821c17821cd8c7c6820d4e821d09c7c68212bc822205c7c682175e822161c7c68221048221fdc7c6821540821e4dc4c38212a4c4c382096dc7c6820511821ce0c7c6821043821912c7c68203ea820593c1c0c4c38218dec7c6821256821ca8c7c68203a0820f26c7c6821d078221d6c7c68207c2821c41c6c581d7821159c7c68202f682168fc7c68218e6822140c7c68204308220f7c4c3820249c7c68203cf822025c7c68212b482221fc7c6820103821a63c7c6820b2d821dd8c7c68203e2821eb9c4c3821f31c7c6820e80821f7bc7c68210cd8221b4c4c3821c15c7c6820de78211e9c7c682033b8221f6c7c682039d820824c7c6820437822131c4c3821ba8c4c3821034c7c682013d820a2bc4c3820c1bc7c6820e7782124cc7c6821c69821eabc6c581ee821b1ec7c6820f588213f0c4c38217d3c7c6820a5e8214cec7c6821889821ee3c7c6821677821eb6c7c6820234821215c4c38216c1c7c6820afc821bf2c7c6820163821382c4c3821a38c4c38212d9c7c6821b8c822076c7c68207a3821386c7c682075a8220cbc7c6820aa1821ab1c7c6821038821570c4c38202f4c7c68202d482171cc7c6820379821b00c7c6821adb821ae0c7c6821246821b03c7c6820202821f04c7c6820ceb8218c6c7c6820b938210abc4c3820b79c7c6820cc48220a1c7c6821787821e19c7c68212ad8214c2c7c6821c71822184c7c682084a82105ac4c3821ea3c7c68219be821c17c7c6820fe8821469c7c682016a821cccc4c3820982c5c47e82176fc4c38212d7c4c3821cd4c7c6820a5c821165c7c68205b98211b6c7c682146b821a4ec7c6821c618221dfc4c38202d5c7c6820fbe8220bac4c3820612c7c6821ce58221a7c4c3820b62c7c682020a821ee1c7c68206a38212a5c7c68220f6822201c7c682153f821d50c7c6820c8e8214c7c7c6820d148212ccc7c6821cff822213c7c6821e36822130c7c68204398209c0c4c3821e4bc7c6821a76822177c4c3821e9cc4c3820ec6c7c68216f1822195c7c682027d821ba8c7c6821d24821f59c7c6820531821806c7c6820b6c820e25c1c0c4c3821e75c4c3821873c4c3821be5c7c68201e5821f10c7c682044a820ef0c4c38202dfc4c3821048c7c6820a29822005c4c38211e2c7c6820da1821e5fc5c444821048c4c3821e37c7c6821d798221a9c7c682034b822294c7c6821b0282207bc7c6821056821d1bc4c3821cd8c7c6821985821b1dc7c68212f4821784c7c68211de8216d2c7c68203db82220fc7c68201cc8214ffc7c68208f5821d5fc7c6820af7820cdac4c382201ac7c682129b82221fc7c6821fae822169c5c405821e29c1c0c7c68206b5821d6ec7c68214908221bec7c682065c820ae9c7c6821154821256c7c6820b7c8220c3c7c682010c820eeec7c68212d182136cc7c6820d3f821b53c4c382206ac7c6821c058221f9c7c6820d09821265c7c682042f821e4bc7c6820427821b64c4c3820a69c4c3820fbbc7c6821068821e39c4c382211ac7c6820229821335c4c3821216c7c68201ad820a4ec4c3821e72c7c6821235821f5cc7c6820a23821e51c4c3820a21c4c38222bcc7c682112d822277c7c6821d0c821e28c7c6820b03820d01c7c68202a78219c8c7c68210d28221a0c7c682019d821ff1c7c68207f7820a75c7c6820bdf820e74c4c38202c1c7c6820432821e0cc7c682200982229dc4c3821ea0c7c6820f55821c66c7c68212d68220d6c4c3821d71c7c6821395822270c4c3821d3bc7c6820f48821014c7c6820174821383c7c6820df1821655c7c6821568821981c7c6820f92820ff1c4c3821f1bc7c682040882069dc4c3822136c7c6821c00821cbbc7c682028e8202e4c4c382123bc7c6820674821cf4c7c6821f388222c7c4c3820e3fc7c6820ecd821009c7c68204df8204f4c7c68211e182207ac7c6821d2b8221b3c4c3822200c7c682015c821479c4c3821cdcc7c6820f8b8215c4c7c6821a3182213ac7c6821d0b8220ccc7c6820e21821743c7c6821afa822004c7c6821291821866c4c38203d7c7c6821fbb822306c7c6820189820e37c7c68203338220cec7c6821e6c82226fc4c38220b9c7c6820ccc821435c7c68221818221dec5c43882230ec4c38206c8c7c6820446820e41c7c6821138822274c7c6820dcb8222afc7c6820c7a8212c6c7c6820428821d19c4c38204cec7c6820306821d70c7c68201388221f1c7c6821039821270c7c682022282141fc7c6820575821ec3c7c6821162822125c4c38211a1c7c6820101821af3c7c682038e8219f5c7c6821e4e822287c7c6820c348218aec7c6820254821e78c7c6820c24820eefc7c68202aa821f24c4c3820988c4c3822279c7c6820748822123c4c3821df5c4c38212e4c4c38220f8c7c68211e482127ec7c6821fd78220a5c7c6820349821febc7c682089d8221c3c7c6820990821b86c4c38214b0c7c68204678206ffc7c6820acf821b8cc7c682193182230dc7c6820a4c821c5dc7c68204fa820e17c7c68202b7820882c7c6820a2482109fc4c3820b1ec7c682017c82130bc7c68212628216dcc7c6821f5a822070c7c682010d82127cc7c68211df822151c4c3821ec4c7c6821511821ea6c7c6820ea3821b0cc7c6820dc6820eeec7c6820159821999c4c38215a1c7c682215c822276c4c3821cf1c6c581fb820e45c7c6820482820faac7c6821ceb821f98c6c5819382220cc7c6820c26822170c7c6820c178222acc7c6820cfe822152c7c682105c822039c7c682045e8215a9c7c6820c8c821f4ec7c68222958222d3c7c6821d68822308c4c3820ebec7c682161d821840c4c38213e5c4c3822017c7c6820397820a0fc7c68213f4821f27c4c3822151c7c682027f822109c7c6820705822335c4c3820ea3c7c68202d1821bd3c7c68207a2821dd9c5c425820e0fc4c38220fac4c3822136c7c682199e8221f7c7c68205bf821fd2c7c682033a821c80c4c3820160c4c382078dc5c4238204cbc4c3822237c7c6820fca82171dc4c38208bac7c6821491821820c7c68212cf821af1c7c6821d698221f6c7c68213ea822332c7c6820efa821035c7c6820b1f821189c7c6821130821d7bc5c4328210f6c7c682041882173ec7c6820790820e4fc7c6821fe2822310c7c68205008221bcc4c3821e57c4c38221f1c7c68201ea8212b6c7c6820bd38221a7c4c38222f3c6c58196821255c4c38215c6c4c3821266c4c3821d31c4c3822311c7c6821194821bbdc7c682015e8222e3c5c445821dfcc4c38221f0c4c38211a0c7c6820404821227c7c6820f2282219ac7c6821bb0822180c7c682105082154bc7c6821748821bf3c7c6820d7882115dc7c6820d04821c0fc7c68218108219eac7c6820baf821719c7c68212038215a6c7c6820891821126c7c6822185822261c7c682111b821286c7c6820a03822134c4c3821728c7c6820fcc821d59c5c404821703c7c68219b5822202c4c3821d97c6c581ca8218b6c7c6820197821ed5c7c6821232821a25c7c68201b5821d03c7c68201d38213f6c7c6821097821a5cc7c68209488213f1c7c6820ed7821f2cc7c6820163822315c7c68206dc821bfdc6c581ba821169c7c6821430821ee2c7c68214e7821cadc7c6820bfd8221adc4c3821f29c6c581ef82012ac7c68208cc820caac7c68204908214e7c7c6820237821139c4c3820912c4c3822241c7c6820323822233c4c3822259c7c682040b821000c7c6820a00820fd2c7c682124b821afac7c682100d821cdfc4c3821eb4c7c68201c0821c6cc7c6820e6d821ca6c7c6821283821accc7c682202782237bc7c68219c2822005c7c6821c60821cd5c5c479821cd6c7c68220e78222a0c6c581da822308c7c68205ee820d32c7c68204fb820f8cc4c3820cb3c7c6821511822247c7c68212c28222f2c7c682044d82232fc7c6820124820ddfc7c682033c8212e2c7c682089a820c00c7c6821c7e8220d2c6c581bf820ed2c4c38212a9c7c68211638222aec7c6821d28822082c7c6821a678222cdc7c68206f7820e65c7c6820e1b821a6ec7c6821956821f0fc7c68207cb820f7cc7c6820428820c8bc4c38221e5c4c3821460c5c47e8207fdc4c38213dcc4c3821468c7c6820f4b822359c4c382175dc1c0c7c6821ba682215ac7c68205f5820a3fc7c68201b48221b8c4c3822052c6c581cd821ab6c7c6821dd382202bc7c6820752822362c4c3820f23c4c3822128c7c6820678820d89c6c581f182191bc7c6821519821f6bc4c38208d6c7c68203f8821b2cc4c3821708c7c6820868820d3fc4c382069ec7c6820cb0821031c4c3820dacc6c581d9821e49c7c6821af3821d71c7c68202658203e2c7c6821942821954c4c3820ffac7c6821a158223bfc7c6820a4e82211ac4c3821fc0c7c68202878206e0c4c38216dec7c682233b8223dbc7c6820afb820dd3c7c682033e821beac7c6821d63822334c4c3821244c7c6821bed82225cc7c682219b8222ccc7c68201de820673c4c3821de9c4c38217fbc7c6820ee5820f4dc7c6821049821ba5c7c68202c78206bcc4c38212a7c7c6820fb7821700c4c3822246c7c6821cbc821ebbc7c68202c6821fc9c4c38217f1c4c3821d0bc4c382119ec7c682129882227ec4c3821ebac4c3821e2cc7c68204fe8210c5c4c382113dc7c6820d15821d53c7c6822126822289c7c6820e568211c9c7c68220fe822296c4c3821dedc6c581f5821131c7c6820d30822098c7c68219e3821ca5c7c68210bd822258c4c382193bc4c38223cdc7c68204c6821f26c7c682148f821bb3c7c68201ba8220f4c7c6820344821d9dc7c6820e86821527c7c68217bc821f0fc4c382181bc4c38215d7c4c3820183c7c6821bba82222ac7c68209e9821f91c7c6820d44821ff4c7c68216d2822173c7c682159f8221fdc7c6820356821da3c7c68209e7821a77c6c581eb821139c7c68208eb8213d6c7c68204e682222fc7c682106f821e4cc7c682157f822095c4c382109bc7c6820d00821ecac7c6820cdf822226c7c682148a822390c1c0c7c68215b98217c7c4c38208f1c7c68215c8821cbcc7c6821bfb8221acc7c682093382209ec7c6820a17821177c7c68209738211a7c4c3821153c4c3821c57c7c68205f9821f76c7c6820203821955c4c3822410c7c68222d18222eec4c382179bc7c68216df821e2ac7c6820684821f38c7c68202848222b6c7c682014d821eb2c7c6822099822257c7c68219a482216ac7c6820cd7821b69c5c413822243c7c68203af821edbc7c68210e2821cb9c7c68202d28212c2c7c6820b698211d3c7c6820d4382177fc4c3820d88c1c0c4c3821404c7c6822212822303c5c4358220ebc7c6821b79822416c7c682100582173fc7c682067c821dcec7c682110f82220ac7c682201e8223d6c7c68217a6822159c4c3821253c4c3821c20c7c682186b82203fc7c6821a30821d3fc7c6821206821b64c4c3821b7ac4c3822349c4c3821edec7c682018a820b3ac4c38220bfc7c682025d8223b6c5c4158212afc7c68210b6822413c7c6820e52822113c6c581fa820a82c4c382170ac7c6820402822294c7c68210d1821994c7c6821f0c8222aac7c6820dee822256c7c6820d05821526c4c3821857c4c382238dc7c68214c98222b6c5c471821e5dc4c3820af7c7c6821001822087c7c6820bef821cbec4c3822413c4c3821ed6c7c682101e822035c7c68203bd8217ffc7c6820d72820f4ec7c682210b8221edc7c6820799820841c7c68216fd822207c7c68213a0821c8cc7c6820ced821ca8c7c6820327822402c7c682084d821948c4c38205d0c7c68215eb8219aac4c3822378c7c6820e5d822024c7c6820859821a15c7c6822161822371c7c682149a821911c7c6820db2822398c4c3820bf4c7c6821091822389c7c68204108212b3c7c6820f9b821e46c4c3821f96c7c68211ec8223ebc7c68202b3820c7ec4c382122dc7c68213678223ffc4c382126cc7c68210758223c8c7c68212e68214a6c7c6820ee4821f12c7c6821bf2821f3ac7c68204d982227bc7c6820238820b0fc7c68209958223f6c7c68207ec821f3dc7c6820d05821213c7c6821e14822304c4c382225dc7c6820c43821aeac7c682048b821a95c7c6820aad820fd5c7c6820e24821296c7c6821224821ae8c7c6820d1d821fd9c7c68202ef822123c5c42d820f3ac7c68204dd82104ec5c43f821bb1c4c3822382c7c6820a43821859c4c3821e7dc7c6821093821c1ec7c68211718222c8c7c6821d3b822283c4c3821e82c7c6820262820eafc7c68211be821855c4c3821689c4c38214eac7c68216e7822478c7c682191d821b96c7c6821b8b821dfec4c3820b0ac4c382210dc7c68203f18223bdc7c6820a94821215c7c68203738222e3c7c6820e7a821dc9c7c6821a2c822210c7c6820363822271c7c6821c30821f01c7c682122c82231ac7c6820aa38223bbc7c6820a81821632c4c382101ac4c38223c0c7c6821969821c76c5c47882155ac7c68202428211d0c7c68206e18222a6c6c5818f820b1ac4c3821762c7c682114b822232c7c6821ad682235ac7c68201b1822199c7c6821f478221e0c7c682128382184bc4c3821fa0c4c3820e60c7c6821c668222d8c5c4578216d7c7c6821b0e82249ac7c68217c482209ac4c3822196c7c6820f29821f37c4c3821a7fc7c68203268211e7c7c682025d821a48c7c682044c82235ec7c6820cf5821331c4c3820221c7c68203178217d5c7c6821ace8221b4c4c3821c06c7c682103a8216dbc7c6820393820919c7c6820b3b821b17c4c3820f85c4c38208c9c7c68207e48215fcc4c3822100c4c3821507c4c3821e37c7c6820346821f85c7c682103982112ac4c3820fddc7c6821fe082224ec4c3820311c7c6820b9e8222e2c7c6821add821bf5c4c3821cd2c7c6820c37821e56c7c682034b821c16c7c6820a8a821b88c7c68221c382230bc7c68201ce820e87c7c68210ee8223fdc7c68201a3820ce0c7c68202d382112fc7c6821c238222a5c4c382085ec7c682161f8222f6c7c6821827821b60c7c6822266822464c7c6820989820acac4c3820e1fc7c68211b2821c72c6c58198820806c7c6820a7e82128bc6c581a2820fa9c7c68207f68209c5c7c6821db3821e6cc7c682026d8208a7c7c6821933821f5fc4c3821f49c4c3821d03c7c6821ef3822487c7c6820fc08215e6c7c68212b782207dc7c68213be821c18c7c68220dc822435c7c6820afd821a8bc6c581a3820148c7c6822034822203c7c6820445820d7cc7c68215fe821e1cc7c68216c7821f4cc7c68209178212d1c4c3822300c7c6821120822376c7c6821f898224a4c7c6820e79821c9cc7c6821baa821d9ec3c281b8c4c3822266c7c68201b9820501c7c68209d8820afec7c682119e82193ec7c68211748214a2c4c3822298c7c682181982195cc7c6820964821e85c7c68213848221b1c7c682099e822427c4c382177ac6c581c9821980c4c3821d6cc7c68202cc82252ac7c682222a822233c7c682134982216ec4c3821921c1c0c7c6821abd82242cc7c68207f8820b15c7c6820113821b46c4c3820ecac7c682236d8223a5c7c68204b4820b1ac7c682114a821321c7c6820395821b83c7c682136c822263c7c68210c38213b9c7c6820ec08222d9c4c3820ff5c7c6821ef7822006c7c68219268219b0c7c6820d81820e58c7c6821913821f59c7c6820aab820eccc7c6820e1d821cacc7c68224198224f8c7c6820ee182252fc4c3820afbc7c68202188202f8c7c6820f14821ec6c4c38223eec7c682053d822211c7c6821de0821eb3c7c682207d8224efc7c68201778211edc7c68214f08216dfc7c6820622821ac5c7c6821bde822443c4c3822451c7c6820447821c9dc7c6822078822319c7c68212ef821cffc7c68211a082143ec7c682091c821d16c7c682033c821ab6c7c682015c8207ffc7c682117f822384c7c68203298220dec5c45982227bc7c68202e9820c24c7c6820ef6821dd7c7c6820f608224d8c7c6820265821fe4c7c6820fb6821005c7c68207db822444c7c68221d5822327c7c6820d908212b5c7c6821a2c8222b0c7c682127f822521c4c382230dc4c3820a77c7c68201f6821e31c7c6821d668222c7c7c6821e568222f4c7c682027e8211acc7c68220d9822567c7c6820edc822249c4c382238fc7c6821687821848c4c38211d8c7c682106b8223a2c7c6821100822516c7c68222ff8223e6c5c4398216e6c7c6820d9b821071c7c682048c821accc7c6820d94821460c7c68213df822443c4c38218cbc7c6821015821c83c4c382133bc7c68212f6821313c7c682074782255dc4c3820528c5c47a8208a7c4c3821ca5c7c6821bc48224fdc7c68209a0821e26c7c6821cae8222efc7c682110682211cc7c6821fdd822047c7c68207f482114dc6c581ae82244bc4c3821b2bc7c6820a9e8215f3c4c3822479c7c6820276820ccfc7c68201d882140fc7c68212098212d8c7c68224a6822556c4c38222f5c4c382191dc7c682202d822550c7c682013a8224abc7c68206428217f6c7c6820dbf822117c7c68208ea82242ac7c6821ab1822489c4c382254bc7c682188e821910c7c68201ec821dccc7c6820116821befc4c3821fb4c4c38220b0c5c4708223d2c5c412822422c7c682030b8218c8c7c68207f282229fc4c3821b0bc7c682033982062cc7c6820f5382207ec7c6821e32822367c4c38214b2c7c682013f820baec7c6820e85820fb5c4c3821fa8c7c68221058224f3c7c682067e8216cac4c3822090c7c6821be4821f10c4c382175fc7c68208648210dec7c68214ae821c63c4c382216fc7c6820210822480c7c6820280820e6bc4c382216cc7c68207948210efc7c6821cdf822293c7c6820bc88222e7c7c6820c448222a8c7c68204538211b5c7c68222208224aec7c682114c82253cc7c68218bc821ffac7c68207e5821d4dc4c3822054c4c3820f90c7c68202ba822120c7c6820cd2821ec1c7c6821b4c822459c7c682012b8224f0c7c682108a8224e0c7c68219c0822180c7c68201df821bbcc4c38207bbc7c6820251821279c7c682115282205ac4c3820d7ec7c6821398821c65c7c68223dc822591c7c682088d821e01c7c68202a5822578c7c68216c1821c4ec7c68205b1821d56c7c6820d9a822544c4c3821e3cc7c68220288225d3c7c6822217822324c7c68201b6821d90c7c6821d4e822230c7c6821701821eb5c7c6821218822368c7c682042c8220c1c7c682100f821de7c7c682151582248fc7c68201f0821bb6c7c682051a8209e3c7c682028b822545c7c682065182195ec7c6820aaf822433c4c382219dc7c6821d0082228dc7c68208a8820fa1c7c68206a482251dc7c68209b1822242c7c68216de82214dc7c68201dc8211cac7c6821a69822293c7c682232f822593c7c6820e1482225ac7c6821af08222adc4c3821b31c4c382167ac7c68211b48220b8c7c6820f1e820f68c7c6821c71821df2c7c6821b1d8222d0c7c6822248822338c4c3821cd6c4c3821dbdc7c6820f318225b6c7c6821e0682219fc7c68207b0821182c7c68204c88207adc7c6820144821650c4c38224e5c4c3822574c7c6820394821103c4c38224c9c7c6821f7782253bc7c6821eb88223dfc7c68202a88221f3c7c68207eb821fe8c7c6820ede820f5ec7c68209a3821d75c4c38209b5c5c460821569c7c682138d821a4ac7c68203d2821b4bc4c3822525c7c68217e7822331c7c682145982214cc7c682126b821b98c7c6821591822536c7c682201d8221d0c7c682120b821f58c7c6821e9782202ac4c3821a74c7c68220c6822561c7c6821241822112c4c38210a3c7c68216278224b9c7c68201f3821233c4c382215bc7c6821d5d821f69c7c682081e8222dbc7c6822436822517c4c3822457c7c682205b822343c7c68201c6821f6ec4c3820dc8c7c68219a7821a2fc7c6820307822415c7c68201d7821c18c7c6821d6482227ec7c6820ff4821beac4c382118fc7c68218558223f7c7c6820b9c821fa2c7c682172d821f60c4c3820e56c7c68211bc82223ac7c68203cc821fe2c7c6820433821f76c7c682156982231bc7c68209358211a6c7c68202998225c3c4c3822148c7c68218da822146c7c6820f8d82221dc4c3821b4ac6c581a3821de5c7c6821e99821fa1c7c6820d67821b56c7c6821e42821e85c7c6820287821d96c7c6821f7a8223f2c7c68202b28211c2c7c6821974821a20c7c68204c782111fc7c6820141821c93c7c6820149821b7ec7c6821f9d8222d2c7c6820d69821012c7c68220898222ccc7c68212458225afc7c6820809821bbac7c682061e821178c7c6821bb7821c5ac7c6820153820dd0c7c6821630822627c4c382231bc7c68217578223cec4c38223bbc7c6821e308221eec6c581e08212dec7c6820fd1822524c7c6820101821ef4c4c38215a3c7c682100a822344c4c3820f51c7c6821718821d5cc4c38225cac7c6822261822363c7c68212d78221bfc4c38222fdc7c682158f822148c7c6820cd482261dc7c6821c45821d38c7c6820fc482115cc7c6820338820f41c7c6820c53821aeec7c6821448821d42c5c433821fa4c7c6821aad82212fc7c682026a820b41c7c68222528222dec7c682238782249ec4c3820ae2c7c682171f822292c7c68212dd8224e2c7c68220ea822421c4c3821f34c7c68207cd82212cc4c38209b8c7c6820f5a821e92c4c3821ed3c4c382109cc7c682104e821c8fc7c68201958222d4c7c68201788221b7c5c403820cb7c7c6821c338224ddc4c3820c9ac7c6821a58821ab7c7c68206a982079ec7c68208b7822481c7c68205f5821f9bc7c6820cf7822028c7c6820da38218a1c7c68204a882248ac7c68204318214cbc7c6820557822495c7c6822231822577c7c6820310821ccac4c382247bc6c581e3820a9ec4c382213ac7c6821af2822430c7c682042a82115fc7c6820d93820e1dc7c6820e9682257cc7c6821c59821df2c6c58181821d3dc7c68202b4821e86c7c6821497821ccec7c6821d218221fec7c68201da822174c7c682088a82264ac7c6820ee6821bdfc7c6820bac8220dfc7c6821b2282252bc7c6820c8f8220d8c7c682151382224cc7c6821156822605c7c6820c2182111dc7c682216e8224c3c7c6821758822549c7c6820766821469c7c682029d820586c7c6821be0821e06c7c6820eab820ebac7c68223dc822617c4c3822431c7c6820ea6822142c7c68211a8821f4fc7c6820fe4822291c7c6820ba1821daac7c682210f82242bc4c3820ce5c7c682108d821befc7c68210328221dcc4c382250bc7c68204e68225bcc7c6821379821dddc7c6821fa3821fc9c7c68217a8821fefc7c6821cf6821f2ac7c68202ab822390c7c6821d57822342c7c6821711822406c5c4418222cac7c6820d1c82163ec7c682208f82221cc7c682040782243fc7c68211188220fdc7c6820cf182215dc7c6820935822455c7c6820854822189c7c6821cfc822315c7c68220598223abc5c44e8218d4c7c68203d3821f1ec7c6820c3f8216bec7c6820bb8821aa9c7c68203e1820bb4c7c6821cb58223c2c7c6820e08822135c7c6820d84822176c7c682235b82264fc4c3822235c7c68222f9822336c7c68212388225f1c7c6821d6c8224f8c7c6820230821a8ac7c682206a8222b9c7c682216a8225c5c6c581d1821b5fc7c6821174821586c7c6821a72821e5bc6c581fb822009c7c68201e5822314c7c68207a18223d9c4c3820418c7c682144f821dd6c7c6821141822558c7c6822414822604c7c68224f68225abc7c68221d88225f3c4c3820b53c7c68218238219cdc7c6820e06820f0cc4c38225c3c7c6821669821e84c7c6820e098210eac7c6820f4f821065c7c6820dbe82205bc7c68215f382217cc7c6821345822430c7c6820cf88212e3c7c6821c10821cdbc7c68214058225dec7c682036a8221c8c4c3821cb9c4c382088dc7c68218dd821cf7c5c430820eb0c7c68203038225ecc7c6820da0821724c7c6820853821d88c7c68220dd8225fec7c6820340821f9fc7c6821515822145c7c6821bb8822079c7c68207be8225b4c7c682037b821a03c7c682037f8225b9c7c68223f08226a1c7c6822115822234c7c6821289821fa5c7c68207fc8225edc7c68220408226cbc7c68212be821b78c7c6821bc7821df6c4c3820d54c7c68217b2822205c7c68210aa822270c7c68203f3821f97c7c6820130822282c7c682036d8206dfc7c682208882246dc7c6821b09822668c7c6820173820952c4c38220eec7c682203e82236bc4c382257bc7c682094f822288c7c68203c682137cc7c682084682239cc4c3820c18c7c68218b58218efc7c6821efc82223bc7c682162c8221b9c7c6820f43821e67c7c682181882254ec7c6820599821bf1c7c682248c822603c7c682112c821728c5c417822068c7c6820f168224aac7c68222a28223afc7c682146a821f93c4c3821e96c4c38226b1c7c68209bd8221efc7c6820738822633c7c68222cf822615c4c38206c9c4c382220bc7c6820283821117c4c3821d9dc7c6822257822540c7c68203c38208d4c7c68222e182240ec4c3820604c4c3821ea8c4c3822476c7c68211f9821f28c7c682253382257ec7c6821279822098c5c431820ffdc7c6820f4082266ec4c382228cc7c6820295822361c7c6820534820ad2c7c6820b7a8220dcc7c6821f5b8225b8c4c3820e66c7c68201cb822675c7c68204058224f1c4c3822086c7c6820671820d09c7c6820ac98215afc7c68202c8820befc7c6821900821ff7c4c3820617c7c6820196821ea2c5c41d822493c4c3820f65c7c6820d6c821f50c4c3820631c4c3822280c7c68211cb82207cc4c3821508c7c6821b4c82203cc7c6822655822656c7c682127582255ec7c682118482124ac7c6821485822662c7c6821719821c08c4c3822240c4c38218d2c7c6821426821607c7c6820eb1821969c7c68210508225b7c4c38222c4c7c6820cce82194ac7c68207ec82129dc7c68210ce8220ecc7c68201828225dcc7c6821c99822153c4c38218b3c7c682109b8213e0c7c68220e4822701c7c682168f821ff4c7c68209398218f9c4c382183dc4c3821a11c7c682043482046ec7c6821284821fbec7c6820e3e821b98c7c6821b21822692c7c682116a8224a3c4c382242bc7c682185a821b59c7c68202a1821125c7c6821cf1821d13c7c682105f821d96c4c3821debc7c682013d82204ac4c38224abc7c68222c58226b9c4c382216cc7c682180d821a3cc4c3822156c7c6821cf28225ccc7c682052b822360c7c6820d63821ef5c4c3822540c7c6820847821b67c7c6821a5e821ff5c7c6820e7e822760c7c682240b82242ec4c3821fd4c7c6821cf282237dc7c68213ca82246ec4c382184ac7c6821d49821d73c4c3820cf0c7c6820f828221d2c7c682063d8221a3c7c68211cd8222d6c4c3822491c4c382241dc7c682118d8226ebc7c68210a382257ac7c682203182240ac7c6821541822771c7c6821b2782246bc7c682160082258dc7c6820f728210f2c7c68212d4821fcfc7c68203fd82215cc7c6820cad8220f9c7c682111d82200ac7c6822311822576c4c382181cc4c38226a8c4c3821c3fc7c6820dc2821277c7c6820f99822623c7c68201ac821872c7c6820514821ff1c4c3820ab8c7c68217ef821fedc7c6820503821d69c7c68205bd8225f9c7c6821dbe82250cc7c6822127822425c7c68202e38216a5c4c3821480c7c6821d508225f9c4c3822528c7c682224282261ec7c682084a820f6ac7c6821b0d82249dc7c68214868215bbc7c682085682234ec7c68204ef820db3c7c6821f04822695c7c68210e8821c56c7c682115b821cb7c4c38226a4c7c68204e382263ec7c6820581820d07c7c68224838225b8c4c38204bdc7c6820582821481c7c682122682223fc4c38225edc7c682046c8204e1c7c68220978224a2c4c38218ecc4c3821fb7c7c68203cd821960c7c682055e8226d3c1c0c7c682052e822526c7c6820555821d86c7c682050b8225cdc7c68207ee82155fc4c38217d4c7c682052b821b6fc7c682153b821c79c7c6821e36822551c7c68207b38224a7c7c68204c5821a30c7c682018d820562c4c38224ecc4c3820a96c7c6821c7d8222fdc7c6821fe5822366c7c6820c5f822790c7c6820ab5822770c7c6821cd0821eefc7c682234b822627c7c682233d82263ac7c6821bbe822704c7c6820fb2821017c7c6821da08223e9c7c6820e05821b1bc7c682054a82136ac7c6820511822611c6c581de821e6fc7c68207ba8217fec7c682269a8226bac4c38209a2c7c6820e0382206fc7c682236a82250ac4c3821eaac7c6820fa4821a5cc7c6820539822221c7c682032c8218e2c7c682129c821b35c7c6821a7d822688c7c68212a3821b66c7c68204e2821b60c7c682140c82167cc7c68204eb8212f1c7c68213fb821742c7c68217548225d9c7c682071a82123ac7c68209f0821458c7c6820d25820f30c7c68206b082152fc7c6821055821758c7c68204bf822335c7c682232b82245cc7c682048a822400c7c6820e4c822165c7c68204d8821f11c4c3821b43c7c6821f51822411c7c68217dc821d46c7c68216a0821b7cc7c6821e0a822268c7c682183c8221b5c7c6821a70821bd4c7c68205238225cac7c6820f13821dcec7c6820d0f821bcec4c38213b8c7c6821593821ebfc7c68204c3820f92c7c68215b5821795c7c68220ad82274cc7c6821422821e97c7c6821e808227e4c7c68209c1820a87c7c682245c822608c7c68201f4820f2bc7c6820a2a82272ec7c682050a820ecec7c682048c8226b0c7c682053a820e09c7c68204748206cbc7c682049182259dc7c68210468222f1c7c68218bb821f48c7c682047a820991c7c682055f82243ac7c682055d822673c7c682066482249bc7c68205c0820dcac7c68218bc821c94c7c6821ec4821ef6c7c6820f45821db8c7c68205748227edc4c3821eb1c7c68204aa820eecc7c6820499821efcc7c68205df82255bc7c68213e28222b5c7c6821d2a82206cc7c6820d7e821f30c7c682056a8227a1c7c68205208221a2c4c38218b8c6c581d282149dc7c6820b42820bdac7c682047f8218bdc4c3821ffec1c0c7c6820b94821324c4c38213b6c7c682269c8227b8c7c682056d82277fc7c682058d821f60c7c68204b1821aedc7c682206d8226c7c5c40b820c78c7c682075d821915c7c6820a2f82281cc7c682074482267bc7c6821e2782269ec7c6820b35822122c7c6821447821577c7c682059582230ac7c6821a33822375c7c6820808820abec7c68204d3821be8c7c68205928224fac7c6820131821896c7c6821bd18222f0c7c6822752822768c7c6820f42822410c7c68205d082127dc7c68223398223e8c7c68204dc821567c7c68220cf82228ec7c682049c82255ac7c68220a082243ec7c682125a822187c7c6821c70821ffcc7c6821438822720c7c6820291820623c7c68204818220f5c7c68211f0821c53c7c6821d9b82270fc4c3820110c7c68204a3822824c7c68205c2821e2ec7c682153482222bc7c68204f8821f31c7c682199f821e9ac7c6822075822798c7c6820489821d06c7c68211b8821bbfc7c68205fe8214cfc7c6820585820ccac7c6820228822579c7c682067d821b8ec7c68221ea822838c7c6820f1a822054c4c382068fc7c6820488821f6ec4c382202fc7c68223fd822689c7c6822497822750c7c6820fa2822760c7c6820466821172c1c0c7c6820f77821883c4c382080ac7c682054c8213bbc7c68204cf8206cbc7c6821ee58223cdc7c68219b4822490c7c6821dd78226edc7c6820c35821a4cc7c68205488212bdc7c6822352822495c4c38217f4c7c6820486822533c7c6820a11822198c7c6820e1f8213a3c7c68225c48227a4c7c6821c3c82281ac4c3821532c7c68211148226afc7c68216c0821716c4c3820ac6c7c68225238225c0c7c68204f2821dc6c7c6821360821bd8c7c68205d2820e76c7c6821e98821f6ac7c682045a8222ecc7c68205b0822253c7c6820597821f9cc4c3821cedc1c0c7c6820b20821729c7c6820589821305c7c68205fb821c86c7c6821106821acac7c682049f8224e8c7c6820d688226bbc7c682045c820e22c7c6821bfa822814c4c38224edc4c3820c81c7c68215ea822012c5c4208204bdc7c68208af820e35c7c68204978225f7c7c68205138226f5c7c68204a0821fdcc7c68215b7821a29c7c6820878822678c6c581a8821f8ac4c3820469c4c38219b1c7c6820db0821694c7c682057c8226a1c7c682056b821841c7c6820537821d73c7c682055082273cc7c6820fce8214e0c7c68205608223dec7c6822726822826c7c68206d9821eaac7c6821761821a75c4c38215cac7c6820ea78221f3c7c68202b88213aac7c68223d48225c9c7c68209b7821a97c7c6820540821617c7c68217ee8218b1c7c68221aa822347c7c68205de8224bdc7c682160a82170cc7c6820df9821fb9c7c6820c2a821c68c7c68206bf822807c7c68221478226fbc7c6820ebf8225a8c7c682240482253dc7c682051582268dc7c6821bde821f6dc7c68217fe821b8ac7c6820f4c8212d6c7c6820efb8226e6c7c68212bd8224a0c4c3820791c4c38208dbc7c68204b3821b35c7c682057a821c83c7c6820c10821066c6c581bb821a00c4c3821fcac7c68220fa822227c4c3821bfec7c6820570822574c7c6821061822580c7c68204fd820e67c7c68213c7821af6c7c68209df820c02c7c68204ac821ff0c4c3821b10c7c68208f3821afec4c3821372c7c6820494821237c7c682052a822749c7c6820f0d822284c7c6820593820f37c7c68204a6821802c6c581b28213edc7c68204ba822085c7c682046f820fdac7c68205108225a2c7c682047082164bc7c682051a82260cc7c68204a1820b2dc4c38219b6c5c41a821690c7c6820abe821637c7c6821108821e7ac4c3821ffbc7c6821db38225d1c7c6820bfc82223bc4c38214b9c7c68209c1821b1cc7c68219f9821e7cc7c682064f821d8ac7c68208ed821e43c7c68209af821dfdc4c3821356c4c3821fbdc7c68201db821944c7c682068d822108c7c6820e3f820f6cc4c3821ff9c7c6820123820906c4c3822789c4c38214b5c4c3821aa8c7c68216118218a5c7c682089e821609c7c682029a82080fc7c6821588821a37c7c682094b821ae1c7c6820b8d822186c7c68213e882192fc7c68205ff8223cbc7c6820c69821aabc4c382279cc7c6820d6082235ec7c6821b87821f6fc4c3820111c4c3820316c7c682018e82092ec4c3821917c7c6821381821745c7c6820241822528c4c3821506c4c38215d2c4c38228e8c7c6820f94821f8dc7c68207d8820a0ac7c6821ab2821fc8c4c3821e70c7c68216b38226ffc3c281a0c4c3821b33c7c68207d48219c1c7c6821789821dfbc7c6821879821f20c4c38228d4c7c68205f8821b0fc7c6821faf8228f0c7c68203308227a6c4c38206b7c7c682079a8215e7c7c6820a22820d31c4c3821df8c7c68210ef8214a7c7c68208fa821638c1c0c7c6821882821989c4c3821400c4c38217d9c7c6821972822901c7c68207fe820a5ac7c682010a82187fc7c682139f8228c3c4c3822438c7c682029a8228f4c4c382153cc4c3821b5bc7c682029c821892c4c38204bec7c68218148218f0c7c68217c18228eac7c68201588208d7c7c682061a821b17c7c68220a2822737c7c68215a5821ad2c4c38228d8c5c474821debc4c3821e13c7c682132c821f65c7c68201e38227cec6c581998228e1c4c38215fac6c581cf821910c7c68201318218ebc7c68209108228c1c1c0c7c682020482037ec7c6821928821f18c7c6821e52822839c7c6822403822877c7c68202de822074c7c6820861821e7bc7c68204d78216c0c7c6821717821ea9c7c6820bf78216e2c7c6820adc821fc2c7c68203c7820bdac7c68203d5821f42c4c38213c1c7c68207ee8228afc7c68214c582173ac7c6821781821eb3c4c3821e91c7c68215998218e0c7c6820479821aa3c4c38216a5c7c68205cb821b47c7c6820a59821e69c4c3821744c7c6821894821b36c7c6821366821886c7c68218d78219fec7c682023d821d80c7c6821afe8223f3c7c6821a96821c49c7c6821518821d1dc7c68224018226cfc7c68219bd821a62c7c68213c6821bffc7c68216518228c8c4c38228ebc7c68205a38222b7c4c382169dc7c6821a418228f7c4c3822356c7c6820125822796c7c682162882166dc4c3822917c7c6820a56821775c4c38228f6c7c68215d0821f7ec7c682054b822879c7c68204d4821db0c7c6820a22821940c4c382281dc4c3820c77c7c6820ac882197bc7c6821bc58221efc4c38202bbc7c6820c0b821e4ac7c68209aa821464c7c68207f1821bb5c7c682019e821f36c4c3821b33c7c68205c3822855c7c6820a67821589c7c682035e821c46c4c3821e79c7c682089f821793c4c3821ab9c7c68213d48227a7c5c408820bd6c4c3821f75c7c682021f82281dc7c68228e3822946c4c3821e43c7c68217728218bac7c68205e882191fc7c68205168217e2c7c6820421821d61c7c682099882161ec7c6821556821760c7c6820302820495c7c6821a5d821be6c7c6821ee982291ac7c682170d8219e6c7c6820bc98219adc6c581978227c6c7c6820351821ad0c7c68215aa8217b3c7c6820f1c82188ac7c682095f8219acc7c68217aa822949c4c382036bc7c68215cc821887c4c3821b07c7c68217b8821a32c4c3821c0ec4c38213b7c4c3821765c7c6821bd28222a6c7c68207b58228d5c7c68225398226c7c7c68205a1820b56c7c6820627821a91c7c68208988222c9c7c682199c8219e5c7c68218fe821cfdc4c3822979c4c3821a99c7c6821bcb821c24c7c6821543821a3ec7c68219c9821f20c7c6821ac4821bcbc7c682274182288ec7c68205d78228d1c7c68215bd822973c7c682019e8205a7c7c68201db821908c4c38218a5c7c6821ac1821c55c4c3822954c7c68202c0821544c7c68206038208e6c7c68224c88228d1c7c68215ad821620c4c3821691c7c6820ade821ffbc7c6820609821ac4c7c68224a982277cc7c6821d22821f7cc7c68218f7821fc8c7c6821ad382259ac7c6821caf82284ac7c68226f6822854c4c3821bacc7c6820342821a0dc7c6820bcc821cb6c7c68202cb820abfc7c68204c98220acc7c682037782186bc7c682022a821732c7c6821a4e822388c7c6821446822072c7c68216e8821b75c7c682016f821ea4c7c6822880822896c7c68218f2822875c7c682273082292bc4c3821799c7c6822730822930c4c382058bc7c6820d31821f95c4c3821518c4c3821634c7c682176a821a4bc4c38227e7c7c68224c782261cc4c38228e3c7c68208d882145fc7c6820546821446c7c682174b821991c4c382147dc7c6820212820899c7c6820530820835c7c682069f821507c7c6821537821a71c7c6820acd82176dc4c3821b2dc4c382186ac7c68208b9820926c6c58183821ee8c7c68219a2821d81c4c3821903c7c6820438821979c7c682157d8227dac7c682038282296fc7c68217cf821fb8c7c6820c6a821f40c4c3821733c7c6820746821707c7c6821dec821e90c7c682157d8220cdc7c682086d8228d0c7c682198d8229a6c4c3821c21c7c68218e1822010c7c682160382293ac7c6821671821785c7c6820246820351c5c454821e8fc7c68202b9822761c7c6820f00822058c7c68204728218d0c7c6821e63821fd0c7c6820294821a36c7c68215298217ccc7c682061982263bc4c382284fc7c68201d98217b9c7c6820a43821379c7c6821328821fc7c5c4168229d6c4c382079dc7c68215b4821fffc7c68215bf821cc6c7c68201c4821858c7c6820dae821f41c7c6820460822906c7c682174e8225a4c7c6820b2e821961c7c68214678229a1c7c6821407822566c4c3822007c5c40e82292ac7c68218c0821e4ac7c6820be6821e5dc7c68204e7821e54c7c68217c38229dac7c68211278224dac4c3821e53c7c682084382181ec7c6820e78821f21c7c68218aa821f1ac4c382188fc4c3821628c4c3821636c7c68216b5821f86c4c38216e0c7c68202db820934c7c68201ae820aeac7c682099f821a9ec4c38213e4c7c6821605821dafc7c6820471821f1dc7c6820c4a821ab5c7c6821505821a6cc4c382142fc7c682150f82247cc4c3821a93c7c682179f822935c4c3821c58c5c40b822061c7c6820c68822904c7c682078982161ec4c3820162c7c6820ba6821c86c7c68202c282078cc2c111c4c3820967c4c3821d95c7c682173d822981c4c38221abc7c6821536821fa7c7c68216cb8229d8c4c3821b5ec4c382090dc7c682014f820578c7c682043b82134ec7c6821a8c821aa4c6c5819c820a83c7c68205e9820cd3c7c6821474821efdc7c68201eb82189fc7c6820c7d822811c4c38201a8c7c682158d821b73c4c38219d2c4c382292ec4c3822918c7c6820c938225e0c4c382184dc7c68201d5820cbfc4c3821ebec4c38228d8c4c3822a0cc4c3821675c7c6821443821f80c7c6820728822986c7c6820132821588c7c68202858229edc7c6820246820343c4c3821793c4c3821983c4c3821363c4c3821c4fc7c6821454821896c7c68203c882133ac4c38206c4c7c68227cb82284fc7c68217a4822964c7c6820987821ba2c4c38213b3c7c68215e3821eddc7c68213f2821488c7c68202d8820b7bc4c3820a35c4c3821d87c7c6821d10821fb1c7c682176582204cc7c68214b88218c7c7c6820c4c821325c7c68205648207fec4c3821e89c4c3821991c7c68205f482171ac7c68205ec821ec5c4c38228f5c4c3821ae1c4c3821885c7c6820616820d9ec7c682034d8216edc7c68216c3822a2bc4c3821874c7c6821439821c64c4c3822a39c4c3821b2fc7c682039f821d61c7c682027382162fc4c382294cc7c682115982267ec7c682170b82198dc4c3821333c7c6821cfa821db4c4c3820936c4c3820170c6c5818982068ec7c6821e348225e8c1c0c7c6820c3c822919c4c3821359c7c6820243822984c7c682060a8221cfc7c68204768219cfc7c6821b63821fa9c7c6821656821a9fc6c581b2821e17c7c6820c16821aa4c7c6822a59822a62c7c682060c8214d4c7c6821d1f822951c7c68213b5822a3ac4c3820ce7c7c6821f7e8229a7c7c682093c821e0dc7c68201cf82295fc7c682145b82154ec7c6820258822a17c4c38214f5c7c6820696822769c4c38218d1c4c3822a56c4c3822461c7c6820999821a6bc7c682150e822a6ec4c382067fc4c3821a96c7c68215ae8217b4c5c44b820498c7c682167c821764c7c682137182157cc7c6821760822a42c4c3821f00c4c382197ac7c68205d6822395c7c6820240821b31c7c6821df78229b4c5c4738229adc7c68202ac821d1ac7c682083d82249ac7c682023f8229cac7c68205d8822062c7c68208bb821d0fc7c6821c48822944c7c682182c821fa6c7c6821626822902c7c6821a3b822974c7c6820ac682167ec7c6821cfd8228a4c4c3820424c4c38226b8c7c682016f821c97c4c3820a97c4c38206c7c4c3821769c4c3821c85c7c682019c8219bac7c6821348821b80c7c68220cd8229dec7c6821d62822532c7c682200e8228f3c7c68216ee822994c7c68209208229d7c7c6821f338228dcc4c3821583c7c682138c821ccdc7c6820cbb822703c7c68213808219f9c4c3820b73c7c68218a3821c50c7c6821d27822a4cc7c682030a8218eac4c382144bc4c3822a25c7c682027482175ac7c682091c822336c7c6821d348224b1c7c68207df82157ac5c452820bf9c7c682048f820623c7c68209ef821839c7c68206ef820a04c4c3821fecc7c6821f71822aa0c4c3820680c7c6821c478228f9c7c68214de82281ec4c38229b6c7c682098e822982c7c68216478229c5c7c682058a8223d5c4c382037ec7c68216a28228a3c4c3822a18c7c68206108207d2c7c6820633820dffc7c6820ab18228fdc7c682146f821f00c7c682023d822a3cc7c6820390820941c7c68205388215a7c4c3821ae4c7c6820255821532c7c682169d822a17c4c3821a41c7c6821977821a05c7c68217a5822494c4c38206fac7c6821b30821f64c7c6820291821dc7c7c6821e03821f67c7c68206be8219abc4c38218a3c7c68210d48224fec7c6821e948228d7c7c68229918229d4c4c3821afcc7c6821d48821fb8c7c68209a1821bfec6c581f9821fbac7c6820b6182146dc7c6821409822a45c7c6820709822064c4c3820690c7c68205de8225fbc7c68218d7822a95c7c6820384821a02c7c682036c82141ac7c68215608228e1c7c682055c821b37c7c68209ff821b62c7c6820a6782299dc4c38214a8c4c38219a6c7c6821f5d8228cec7c6820bba8214fbc4c38211f8c7c6821cc38229e6c7c68217da821d39c4c3821f0dc7c682091e8216d5c5c446820451c4c3821a39c4c38229cdc4c3821ec7c4c38217edc4c38228e4c7c68217c5821c49c4c3820a5bc7c6821710821efbc4c382161ac7c682010f82281cc7c682189f822023c7c682082f8215b1c4c38229c8c7c68201c8821ca2c7c68215ef821ce4c7c6821365821391c4c382083bc4c382132ac7c68201e3821bc1c7c6820263822a81c4c3821a42c7c6821db982290bc7c6821881821ed2c7c68205b4821c77c4c3822a46c5c436822ad4c7c682065d821013c4c38213b8c7c68205a6822907c7c6822975822ae4c4c3821572c7c682296d822a08c7c68213e1821dacc7c6820fa98223ccc7c682011c822ae2c7c68226f08229f9c7c6821c5b822abcc7c6821723822058c7c6820235821a01c7c6821927821e41c7c6820f41822236c7c682056e821f36c7c6821f158229b5c7c68219fb821bd6c7c68216e18228cac4c38229c9c7c682023a822b12c7c68202c4822045c7c6820b0e822a0ec7c6821745822a7cc7c68203a7821d2ec7c6821f848222ebc4c3820273c7c68220148228c5c7c6820fab822224c7c682022382292bc7c6821c8482291fc7c6820578821b10c7c6820a89821f32c7c6820264820390c4c3821e9fc7c68228dd8229f6c7c6821fc7822a33c4c38213ccc7c68201ac822a10c7c6821bff821df8c4c382132bc7c6821d14822515c7c68203578229fdc7c6820c58821142c7c68213d4822b03c4c3820512c7c682194c822909c7c68218db822020c4c3821763c7c68206fa822a98c4c38216d1c7c682116182286fc7c682063482189ac4c3821cf8c4c38218cdc7c682163b822932c7c68218d68218dbc7c6821d9882202ec4c3821bd7c7c68202fe821ed8c7c682043e821696c7c6821431821f52c7c682082c822b26c7c6820954821a97c4c3821ed2c7c682173c822334c7c68215ac821ff7c7c682286d8229e0c7c682177c822946c7c6820376821e83c7c68205e2821fc4c7c68207f8820fddc4c3821b45c4c38228d3c7c6820e94822b31c7c68214a3821f67c7c682064b8229bdc7c6821b42821cc9c7c6822a23822b51c7c6821a7582290cc7c682123d821f06c7c682017a821874c7c68214f582214ac7c682219d822889c7c6821901822961c7c682016e82177cc7c6820644821ef6c7c6820ca08224e5c7c68201b38228a8c7c6821c54821f66c4c3821e09c7c682141b822a64c7c68228f2822ab4c7c68229fa822a46c7c6821a26821bf0c7c68228d5822b12c7c6820b89821a88c4c3821953c7c682150a822736c7c68216f38219afc7c68216a782196bc7c68215438227aac7c682165b822931c7c6821cf6821f29c7c6822a0f822a70c6c5818582139ac4c38227a6c7c6820b5d8217b0c7c682059a82206bc7c6820508821a59c4c3821e9dc7c682197f822860c4c3822862c7c682192b821a3cc7c682081f822b50c7c6820a3d821a27c7c6820600820ac2c7c6820c2282296ac7c682221e822739c7c682144a8228e9c7c682073f8214e1c7c6821bec822accc7c68201e6821e1ac4c38206d9c7c6822924822a0dc7c6820c5d821df0c7c6821984822a7ec6c581b9821807c7c68201ed821972c4c3821b26c4c38202ebc7c6820638821c90c7c68215ae82298bc7c68202e3821a6dc4c3822addc7c68205fd820ac0c7c6821979822968c5c44a8219bbc7c6820409822b10c7c682074f821901c4c38215cdc7c6821e3e822b36c7c6821c3b822ac7c7c68202a0820840c7c6821cea822a58c7c682060f821d88c7c6821704821ecbc4c3822a84c7c6820ad9822825c7c68208d9822a95c4c38229bcc7c6821e998224cac7c68229a9822a27c4c3821612c4c3821641c4c3821c91c7c6821bd9822af6c4c3822a49c7c6821bb98229e8c7c6820650822853c7c6821b87822a42c7c68208a9822a02c7c68205e482289dc7c6821ecf822a06c4c382188ac7c6820360822b01c7c6820c4a822b29c7c68228c58228cfc6c581e48228fdc7c68228e9822b60c7c6821a098229a2c7c6820645820c13c7c6820aa882141ec4c3821f75c7c68206218207b2c7c6821660822ab2c7c6821a10821b45c7c6820670821e9ec4c3820bd8c4c3822aaac4c38215d2c7c68202c482261fc7c6820da68212ddc7c682188d822b55c7c68208e5821d55c7c68209108228e6c7c68206d4820818c7c6820454822a6ec7c6821ce8822b54c7c6820f588215e2c7c6821eb7822b00c7c68215ed821b07c7c68219e2821f22c5c46a822a8ec7c68214d28217e4c7c682169c822906c7c6820bde82261fc7c6820680822ba1c7c6821d458228ffc7c6822432822595c7c68213ac822923c7c68202e0821ce8c4c382082dc7c6820521821a55c6c581a1821c50c4c38229e3c7c68214f68215fac7c6821277821823c7c68204098216a3c4c3820652c7c682288f822a63c7c682208a8226e4c7c6820d0c82226ec7c68229ac822a1ac4c382064ec7c68229e58229f9c7c6821c0b822a9fc7c68211c88219d3c7c6821cc8822a15c7c682049a822b1fc7c6820416821643c7c682086f8229fbc7c6821416822b1ec7c68229ad8229e4c7c68211ff8224b3c7c6820c0b8217e1c7c682192e821d29c7c682082b8214a4c7c6820d8d822187c7c6820dd482285ec4c3822a76c7c6821db4822ae9c7c6820a60822409c7c682033f82143fc7c6821c87822af7c7c6820387821e8dc4c382299ac5c46e822790c7c6820a79822ab1c7c68205f18228b8c7c6821625821f62c6c581ff822b9fc7c68207a7821831c7c6821a2a822ba8c7c6822a74822b46c7c682010b821860c7c6821c04822914c7c68215e3822a1cc7c6821a49821efbc4c3822bd6c7c68216d682194bc7c6821ae6822b0bc7c68215f8822731c7c6820286821a40c7c6820ddb820e6ac7c68225f5822ad9c7c6821e70822956c7c68214938219b8c7c6820660821c87c7c68214db821626c7c68201ff82081cc7c6821e2382297ac7c682062f821516c7c6821b6c8229fbc7c6822a1b822bc7c7c6820a41822a12c7c682299a8229b0c7c6820bcf8228a4c7c682018d821f09c7c6822920822a3cc7c682175a822b58c7c6821503822ae5c4c3821431c4c3821924c7c68205878228dbc5c45d8208dac7c6821327821769c4c3822ab7c4c3821f7fc7c68202a9821d87c7c6822a7b822b8ec7c6821edc821fdac7c682295c822a47c4c3821b3bc7c6821c25822a91c7c68208b8821e11c7c68229338229efc4c38219e7c7c682021d8219bac7c6821333822a18c4c3821dd0c7c68217c8822c25c7c682018782138bc4c3821584c4c38217c6c7c6822b07822c1fc7c68209d3821436c7c682142d822619c7c6821abe821f4ec7c6820e3c82285bc7c6821a34822b84c7c6820e20821b02c7c68221f58226d8c7c6820e0082262dc7c6820b958215ffc7c68223d58226e6c7c68206f18220d3c7c6820708822364c7c6820c0e822567c7c682126b8223edc7c682067c8210d4c7c6820ed1822548c7c68206f682135fc7c68206d7821a18c7c68223c582281fc7c6821c95822302c7c68206a28224f9c7c6821bbb82217bc7c682071b820b9ac7c682244c822767c7c682044982146fc7c68224488225fcc7c6820e9c822800c7c6821eeb8222b7c7c6822191822496c7c68206598222b8c7c682208d8224ecc7c68214fa822722c7c68210f782234fc7c682069282266dc7c682182b8221bfc7c68207478226cdc7c6821cfe82250ac7c6821e1882210cc7c6820aba8226c9c7c6820422822b64c7c6820443822bc0c7c68206d38218b4c7c6820689822120c7c6820629821a18c7c68206cc822154c7c6820f9a8225acc7c68206838208a3c7c682172b822501c7c6820a268218f5c7c68203f4822ac0c7c68219e1821cbdc7c6821cf0821f89c7c6820a3682209ac7c682189b8225e5c7c6820c998210e6c7c682179f821f1cc7c68220bb8220e8c7c68206fd821387c7c6821d4f822582c7c682067782269bc7c682201d82245fc7c6820625822338c7c68219c6821fd1c7c68206e1822590c7c68215fb821d19c7c6820ce282204fc7c6822601822899c7c68206b182280cc7c68219468222c1c7c6821812821e32c7c6820fa1822453c7c682072b82256fc7c68213bc82198ec7c682229e8228a5c7c68204f6822a2fc7c6820c9282231cc7c68220b78221bbc7c6821098822c62c7c6820eb18225c6c7c6821c0f822317c7c6820e3282271cc7c682073a821402c7c68203fe821631c7c68211018226bcc7c6820f84821107c4c38215bdc7c68207b68222f8c7c6820454821c34c7c68203b9820904c7c68206aa820feac7c6821144821c5dc7c6821f3582257dc7c682074c822440c7c68206d8822c78c7c6820636821f16c7c6820eb2822bbac7c68211a382271fc7c68206ea8227f6c7c682145a822402c7c68213ce8216f8c7c68206818220d5c7c6820648820f95c7c6820a6c822733c7c682205c8229e6c7c68206a6821a89c7c6820725822039c7c6820ea58220e9c7c682254e8226f9c7c68221d18223d0c7c682136a822b8fc7c68219928226f2c7c6820d79821fcbc7c682081282235ac7c6821687822b28c7c682065f8215a9c7c6820737820cafc7c68206d282246ac7c68203ba821b9fc7c682179182276fc7c682041982200ec7c68203eb8229d0c7c682162d822b72c7c682081482254fc7c68203e9821f6cc7c68206dd82223ec7c682064d8222b9c7c6821830821afcc7c6821f83822c99c7c68206ca821183c7c6821daa822101c7c68208198226bfc7c6820714821c0ac7c6820a57822667c7c68206ba8226b0c7c6821b51822b26c7c68227138227f9c7c6820d44820dfbc7c68215928228fac7c682237c822715c7c68206bc821095c7c68207168226fdc7c6821f06822471c7c682207a8223f8c7c6820741822636c7c682112182245dc7c6820662821d4cc7c68206128223b9c7c682076d821d76c7c682150a821b1fc7c68215ea8225c0c7c68206d6821856c7c6820c07821ec9c7c682196b822960c7c6821762821805c7c6822405822441c7c68209de82189cc7c6821c1b8221d4c7c6820b5782158dc7c6822520822c7dc7c68225a0822772c7c6822854822857c7c682077b822853c7c6821212822887cac982210082262a8227b6c7c682114a822306c7c6821bad822323c7c6820763821937c7c682088e821514c7c6820ccd821414c7c6820cc48211fcc7c68206f8822cdcc7c68206e5822699c7c68207658222c8c7c6820762822b5cc7c6820d38821b3ac7c682075e822c59c7c6820c4e821eb9c7c682077d82208bc7c68206eb8215f1c7c682074b822c94c7c6820bd9822286c7c6821bf7822375c7c682217f8222b5c7c6822175822549c7c6820b6e821d39c7c6820701820927c7c6820855821197c7c68212548225cfc7c6821b2c822b5fc7c6820fd98227e8c7c68208ff820d2ac7c68207788212aac7c68221ec822bdfc7c6820787820f7dc7c682070e821030c7c6820793821febc7c68222e58228b6c7c6820706821c06c7c682211f822144c7c6820e8d821995c7c6820f0e822bbdc7c68211bb82241cc7c6820f7e822c4ec7c68207298225a0c7c68207208221dbc7c6820799822357c7c68207cd8221e7c7c6820e0082135bc7c6820cfb822734c7c68219948227d7c7c68207b9822967c7c6820a40821d2dc7c682154b82265ec7c682073082236fc7c68207b3821787c7c682237f8227f3c7c68207b4821e2ec7c6821c1582298dc7c68207ae8220ffc7c6820ddd820e99c7c68207b0822514c7c682213e822668c7c682105d821a8fc7c68223558226c4c7c6822170822725c7c6820d788223d3c7c682072e82104cc7c682110a822738c7c6820dbf8220abc7c68207c8822216c7c68211a58222edc7c68207bc821997c7c6820aba8226ddc7c682151c8220d0c7c682268e822c5ac7c68221d9822671c7c6820dd0822ac2c7c68207c6820f02c7c68208ec821aaac7c682073e821b3ec7c68207ca82151dc7c6822833822c60c7c682075f8208bcc7c682222f822267c7c682174e8224d3c7c6820f4b82260bc7c68224cc822c48c7c6820fcd822687c7c68207f2822c8dc7c68207e482262bc7c6822214822773c7c6820769821003c7c682184f82282ec7c682075c822c96c7c68220d4822cf2c7c6821da5822035c7c682266b822a28c7c68207c48224c7c7c6822780822cc7c7c6820ddc821582c7c68210ba822588c7c682078482265fc7c68208d0820fafc7c6820d3d822512c7c6820cfd8221a1c7c6820b39820f48c7c682175f82248ac7c68221b08222f1c7c6821a548226edc7c68223be822d29c7c6820dc08224a5c7c6821c818227bec7c68220ed822397c7c68207d9820c9bc7c6821566821b04c7c6820e98822c9cc7c682093d822a60c7c682098f821eb6c7c6820fa28223d7c7c68203d982096bc7c6820961821f78c7c6821ba082268bc7c682296c822a6bc7c6821bce821f43c7c6821bf782266dc7c6820803821b90c7c6821ead822ab4c7c6820e3a82255ac7c68216eb821d75c7c6820df3821d28c7c6821fa28224fdc7c6822369822534c7c6820ecb822934c7c682080e820ceec7c682294e8229d7c7c6820b13822b53c7c68208258210b8c7c6820c98820fa7c7c6822223822c35c7c6822816822cabc7c682118e8215dfc7c68218c1822a6cc7c6821bc6821f1ec7c6822977822a58c7c682227a822673c7c6820817822869c7c6820b8e8219eec7c6821e54822a39c7c6820ff6822634c7c6820caa821804c7c68209dd821c73c7c6821259822239c7c68225d6822c81c7c68224858226fac7c6821a9f822c1bc7c68229b2822b19c7c682082a820beac7c6820420821c5fc7c6820c648217c8c7c6820715822a73c7c682280f822acbc7c6820331822c64c7c6821735821962c7c68214b8821e6ec7c6820819821bb7c7c6821e60821f62c7c6820818821e3ec7c6821a4b821d7ec7c6821156821d65c7c6821f2a822983c7c682049d822073c7c68218418227e6c7c6820821820a32c7c68205bc822c12c7c6821b9b821bc6c7c682097e822481c7c68224178224e3c7c6820e188222b1c7c68225c8822c50c7c68207cc822d31c7c68212dc821873c7c6822611822667c7c6820573821e79c7c68215e08228c9c7c682055c822851c7c68207c3822106c7c682200f82297fc7c682084682279dc7c6820cbe821074c7c68208368223a4c7c682083f822739c7c6820fcb8222d7c7c68220a882263dc7c68207be821962c7c682222b822500c7c68221fb82279ac7c682127a822d2cc7c68207c1820b0dc7c68224d58225d6c7c68207e5822c5ec7c682084c8217f5c7c6821fbf822706c7c6821f9a8223dac7c682086282244dc7c6820de5821914c7c6820d0682273fc7c6822747822c4fc4c382270ec7c682274a8229f5c7c68222078227cac7c68219bc822519c7c682051b820577c7c6820bb9822b7bc7c68208868223cac7c68207dd822817c7c682056e821c21c7c682266e82287fc7c6820a6b8214eec7c682058882068ec7c68204f9822c10c7c68208758209a8c7c682278a822bf3c7c68224298226e9c7c6822a67822acdc7c6822822822c7fc7c682050f82163fc7c6820b8d8215d0c7c6821819821f6cc7c6821ac98229bac7c682076c821fe6c7c682040c822b00c7c682071f822b4fc7c68214d9822881c7c6820b498216d0c7c68218dc8219e9c7c682299c8229bec7c68204d6821134c7c6821cd7822c65c7c68204b0821b80c7c68207f7822650c7c682039a822b7ec7c682292a822be4c7c682169482227cc7c68207e6821d77c7c6820b998222bac7c6820464822971c7c6821f0b821fb6c7c68205278228f5c7c682052c821f7cc7c6821c36822cdfc7c68203c1821e89c7c682091d820c67c7c6821a14822daec7c6821a16822b4cc7c68207a68214aac7c6820628822a32c7c68208688223b3c7c6820460822b6ac7c68219e5822abac7c6821ce182290ac7c68206ce82291cc4c3822b9bc7c6821e94822b2dc7c68209c28229efc7c68205d1822a40c7c68220938227fcc7c68213c88228efc7c6821ad4822db5c7c68206ef821d8cc7c6820580821a83c7c6821a23822bd7c7c68209b38228eec7c68213cc821721c7c682053c822b55c7c6820561821ff8c7c6821e00822b43c7c68218a2822b88c7c6821c31822daec7c68203e88217a1c7c6820fd7821a2bc7c6820508822461c7c6822ca9822d5ac7c682169c822a59c7c68205648219a8c7c682081d820ddac7c6821673822a57c7c682245a8226e0c7c68229d2822a07c7c68222f6822cdbc7c68208fa8217efc7c6821a77822729c7c6820890822d2ac7c6821f46822341c7c68227a0822a05c7c68202fe822950c7c6820382821ca4c7c68204b582293cc7c6822507822d04c7c6820815820917c7c68204f3821706c7c68215d6821e74c7c68227228227d0c7c6821af8822cf9c7c682072c8219dac7c68218c9822aacc7c68202ea822af1c7c68204388218b2c7c682090a822bbcc7c6822987822a80c7c68214728220e0c7c6822b39822b71c7c6821898821afbc7c6820822820fcec7c68215308216abc7c6822848822ab3c7c6820587822a1cc7c68216468228b7c7c6820691822c06c7c68215ca8216f2c7c682046d821adfc7c6821c11822926c7c682086e82242ec7c6821e9d822ab8c7c6821bd8822a14c7c6820567821b2ec7c68216998217b7c7c6820371822c15c7c68205d1822b2cc7c682067b820751c7c682181a822a3ac7c682294d822c2ec7c682046b82196ac7c68204ad821d48c7c6820827821bb4c7c6821dc1822be7c7c6821d16822629c7c68202fd820813c7c682096a8227b5c7c6821e6f822abfc7c682093b822da7c7c6821750822a43c7c68204cc8218f6c7c682051c822c2cc7c682087b8223e7c7c6820b2f82190fc7c682043b8228eac7c6820f968222bec7c6821a22822c89c7c6821e8b8220bdc7c6821465822c4cc7c682057e821abac7c6821f2d822d71c7c682037082160ac7c6820cff820de4c7c682055a821835c7c682086b82124fc7c6821d7e822a33c7c6822a1e822b07c7c6822988822e0ec7c68213ff822ac4c7c682290e822936c7c68214bb822b6dc7c68216458219afc7c68204578214b6c7c682067a821fd0c7c68205b58229c1c7c682045d822dafc7c682149f822dcbc7c6822b44822b95c7c682172582298ec7c6820fbf822b48c7c6820e5f82106ec7c6820db482210ac7c6820881821b05c7c682044282160ec7c6821bb9821f8ec7c6820492822e31c7c6821e07822a7dc7c68218eb821bc0c7c682091f821b2ac7c68203228206e8c7c6820827822abec7c6820389822919c7c6821b508229a5c7c6821a44822b5ac7c682034f822be1c7c6821c89822d16c7c682143a822aa7c7c6821aa5822adfc7c6821e8c822af1c7c68202ec82198fc7c682038582177dc7c6820551822bb3c7c6821587822b29c7c6820521822863c7c68213a6821b1ac7c68213bf822990c7c6821b89822b2bc7c682149e8218dfc7c68206da822adac4c3820538c7c68206af8206c3c7c6820a39822301c7c6821578822ab6c7c68208b18210cbc7c68221c782225ec7c6820576821b84c7c6822aa2822dd9c7c682252f822858c7c682220b8227e6c7c682094e822835c7c68208b8821d9cc7c68211c382207ec7c6821b8a821d67c7c6820831822abec7c682059e821dcfc7c68217118221cbc7c68208b5821125c7c68213c5822d6bc7c682059d821714c7c68205c6822b16c7c6821601822044c7c682060b821d80c7c68208b3822becc7c6820628822d62c7c6822ba0822ba6c7c6822896822a92c7c682298f8229c2c7c6820c30822b33c7c6820598822963c7c68229f3822be2c7c6822581822729c7c68214bf821d43c7c68217ba822964c7c6821aff822876c7c6820764822ac9c7c6821779821ddac7c68205b7822519c7c6822650822cb1c7c6820c278228fbc7c68216f0822e7ac7c6821b2282274dc7c6821b5d8229e0c7c6821efe822c1ac7c68215f0822a11c7c6820635822814c7c6822c24822d81c7c6820f5b821b4fc7c68205d5822bb9c7c6820fa38226e1c7c68205d5822e16c7c6821d74822acac7c68205f3821a81c7c6822b69822e04c7c68205a1821ac2c7c68206098207efc7c682061d82139fc7c68205a6821c5cc7c682116d822794c7c68205f2822bcec7c682062a8229b8c7c68205da82060cc7c68227e7822ea1c7c68214d482295dc7c68205af821eb7c7c68216978229a2c7c6821400821efdc7c68209858218b9c7c6820f3d821131c7c68219ab822e65c7c6820611822ad0c7c6820941821b61c7c6821ca382264ac7c68229b1822e26c7c6820801822d79c7c6821922822c8fc7c6820e3882226fc7c68218a7821acfc7c6821a53822e84c7c682115082218cc7c6821bac822ea9c7c682079c8214c5c7c682292e822e8fc7c6820661821f0ec7c682157b821fccc7c68208c8822281c7c6821bd4822ad3c7c6820861822048c7c68218c28229a4c7c682239a822e70c7c68222ad822486c7c682066d8206f9c7c6821cf9822e2cc7c68209a582182ac7c6820845821e8ac7c68214fd821b19c7c68219ef821c2ec7c68206de82070dc7c682083e821af0c7c6820eae82226cc7c6821e738226cac7c682297a822ea2c7c682164082244ac7c6821536821fc5c7c6821ea182239cc7c6821965822493c7c68214f7821ebcc7c682063f82294bc7c6821d0e821f32c7c6820a0f8225b5c7c6822c33822ea9c7c68209f6822bc1c7c6822cb2822e9ac7c68214ec8228e6c7c682287a822c43c7c682065382082fc7c68208ea822d32c7c6821560821653c7c6820b21820ffec7c68208cf8221cbc7c682181782185bc7c6821dfc82271ac7c6822bbc822e40c7c682156a821f25c7c6821902822e62c7c6820583822ad7c7c68228c3822accc7c6821f61822914c7c6820e7f82219ac7c6821fdf822e1bc7c682131e822cfec7c6822b45822bddc7c6820657822e4ac7c682237f8225b0c7c68222fb8227c1c7c68227af822c0ec7c682069782297bc7c6821d35822d30c7c6822842822981c7c6822db5822e1fc7c68216bc822e50c7c682077982135cc7c68208de822036c7c6822b87822bf2c7c68206d5822a93c7c6822ac7822deac7c68206fe820c03c7c6820b4a82256cc7c6821870822d75c7c6821550822a3fc7c6820901822676c7c68218528221bac7c6821a17822687c7c6821a8d822280c7c6822196822346c7c6822eb7822ebfc7c68229b4822a99c7c68206ed821fb2c7c682114682270bc7c68206e28229c6c7c68224e982269fc7c6820dfc822d64c7c6822680822821c7c6822e63822e6ec7c6820958822e15c7c6822350822c79c7c682071d822e1bc7c68208a0822630c7c68206ee822c1cc7c68216308221aac7c6821ec38221a5c7c682088a82208ec7c6821a4282293fc7c6821c78822da9c7c6820a18822a19c7c6820702822bf5c7c682089c82221bc7c682090b821267c7c6821e9c822ce0c7c68222d282243ac7c6822271822a86c7c6821ac0822b5ac7c6821355822bf4c7c6820916822d9fc7c68208a18223aac7c68212a28225cec7c682242f822534c7c6821e3a822acfc7c682233a822414c7c6820b71820d4ac7c6820ec58220b4c7c6820703822d8bc7c68210f0821166c7c682291f822ab5c7c6820924822723c7c6822aef822ec5c7c682219c8226d1c7c68223518227dbc7c6820914820ef5c7c68229e3822adec7c6820913820eb8c7c682105d8218e4c7c68208ac822202c7c68208af8225dfc7c682209d8225b2c7c682072c822de1c7c6821b54822983c7c6820727821f3cc7c68223e0822602c7c682141782160ec7c682114e822203c7c6822478822aafc7c68221eb822418c7c68225e48228aec7c6822141822e4fc7c682071f821851c7c6821822821b9fc7c682074f821838c7c6821c35821f05c7c6820ded820ffcc7c682115a821f87c7c6820b0c821abbc7c6822988822e1cc7c6820740822bddc7c6821303822711c7c6822a5e822b08c7c682205582283ec7c68228e58229ecc7c6820931821301c7c6821ee7822c1ac7c6822860822dafc7c68208aa822802c7c6820749822a9ec7c68208b482277ac7c68207318214bec7c6820734821c31c7c6821629821964c7c6820f11820fd3c7c6821661822e7dc7c68227b7822bf8c7c68208b2821b44c7c6820746822e4ec7c6821bd582284ac7c682073f82247cc7c6822ad7822e87c7c682073b821abfc7c68216a3821ae4c7c68208ab821b06c7c6821909821e40c7c6820753821e95c7c6820a3c822bb5c7c68224ea8228e2c7c6821447822d26c7c6820db8822527c7c6821a0d822bccc7c682278f822afdc7c6822354822560c7c6820e5a822ca6c7c682093e820958c7c68220be822ed6c7c682176a822948c7c6820782821af4c7c68208e182228bc7c682076a821fa7c7c682077a822bb0c7c682077782290dc7c6822a54822bfac7c68223ef822da4c7c6820aff82152cc7c68213128215b3c7c68220028224e2c7c68217b2821da6c7c6820948822015c7c682095e8226c6c7c682225982244fc7c6820949821d89c7c68218d4821b39c7c6822b84822d8ec7c682249c822995c7c682091a82147fc7c68207ab8228a9c7c6820cfc8212cac7c682155c8221dbc7c6821ac7822eb5c7c6822815822e0bc7c68208dc822d99c7c68211b9822865c7c68213f7822a14c7c68224b2822f66c7c68208d0821cbfc7c6820a27822a8bc7c6820f8b822c5fc7c6821746822426c7c6822008822e27c7c6822a4f822bd8c7c68216f8822beac7c6821dea822401c7c6821ef1822357c7c6821fce8228fcc7c68208e0822158c7c68209668228acc7c68221c4822c5ec7c682208f822784c7c6820971820f0bc7c68207b8822ab3c7c6821129822424c7c6821c88822ed1c7c68207a6821f09c7c6820db9822f7ec7c6820977821866c7c68207a7821b65c7c6821fd98222bdc7c6822be3822f63c7c6821dee8227d3c7c6820975820a9cc7c6821409822e46c7c6820912821205c7c68226b28226cec7c68207bf82083ac7c682119582287dc7c682125b822f3fc7c68208f28221cac7c6822504822632c7c6821f90822333c7c68222ab822dfec7c6820e7b822e15c7c68220a3822d83c7c6821ec88229c6c7c68208f882156cc7c6821919822e4ac7c68208f5820b8bc7c682148d822d73c7c682194a822097c7c682098f822d9dc7c6821eed822314c7c682136d8224dcc7c6820986822cc8c7c68207d4822bdec7c6820cd98222c2c7c68207ea822a4dc7c68224e7822f02c7c682098b8217e6c7c68207f38216e1c7c682232982247ac7c6821dae822c39c7c6820923820b1dc7c68207d3821721c7c682128a8224c9c7c6820a8e822d01c7c6822de3822eadc7c68226ee822db0c7c6820bbf8224ffc7c68207f5822b14c7c682092d822b63c7c68209a682261cc7c6820ee3822596c7c6822382822ec1c7c682243d822ceec7c68209a0822c57c7c6820ae7822fa0c7c682165d822f32c7c68209ae82147ec7c68209ad82244bc7c6820afd821cd3c7c68225e3822cedc7c682292d822bdcc7c6820f4f8211e0c7c6821853821860c7c68209b48220c6c7c68207fc8229d5c7c6820a3e8226f6c7c6820937821ed0c7c68209b9822d24c7c68207df822f5dc7c68207de8216a8c7c6820b8a821a6bc7c682111f8225a9c7c6821698822deec7c6820f198221afc7c6820974821c96c7c68209398225aac7c6821c2f822e9bc7c6820942821692c7c68209e48224bcc7c68207f1822ea0c7c682093a8220c4c7c6821479821871c7c6822b94822d8cc7c6821774822a78c7c68209be8210aac7c68219dd822182c7c6821f3b822f3ec7c68218fd8224a0c7c68209d1822cc2c7c6820ac3820b7fc7c6822ea8822f1dc7c68209d5821206c7c682219082244ec7c682095582208ec7c68207fb822063c7c6822a48822bb3c7c682144a822bc6c7c6821d1d822c10c7c6821c8a822c83c7c6822118822460c7c682149c822ebec7c682094f821318c7c68207fa82152fc7c6822a09822e24c7c682081f822e2fc7c68213a982244dc7c6821dc38228dac7c6821375822373c7c6820e4e821ccfc7c68209e88225a7c7c68209e782248ec7c6820ba28217c0c7c682096e8218f8c7c6821693821e4cc7c682097882263cc7c68225528225dec7c68220bc8225dcc7c6820b3c8211f5c7c68225db8226fcc7c6822b06822b4ac7c68209e0822762c7c6820972822458c7c682082e822c30c7c6822e59822ed7c7c6820974821ffdc7c6820f288223b4c7c6821da8822996c7c6822639822fa1c7c682129982252cc7c682187e822b02c7c682115f8212e8c7c682243f822d36c7c68209f5822546c7c6821ae3822fa2c7c68209948225e1c7c6821324822bd6c7c682257f822852c7c6820830822be2c7c68220d8822396c7c6821be78222d6c7c682121c82134bc7c6822bef822d6fc7c68229f7822c9bc7c6820d18821de1c7c6821a08822a73c7c68209ee8227a4c7c6821306821cb2c7c68218d9821dbac7c6822463822d38c7c68209838222dfc7c6821c74822e47c7c6820c4e821e64c7c6820f71822ed1c7c68227fd822d87c7c6821cd28224d1c7c68223fe822759c7c6820997822f36c7c682083582297dc7c682096a822e13c7c68216dd822dcfc7c68208408213b3c7c68217f1822f76c7c6820a20822702c7c682085a8217f2c7c6821113821c1ac7c6821a09822ae8c7c682116482298cc7c682085f822b67c7c6820865822e34c7c682098a822925c7c68221a6822850c7c6822480822fb8c7c6820a10822f28c7c68221f0822fecc7c6821adc8221ffc7c682101f8224a8c7c68209b1822452c7c6820866822a16c7c68208588208e5c7c682084b822adcc7c6821ba3821db5c7c6820a23822698c7c682087a822be4c7c68209bc82242dc7c6821f94822d80c7c682086d822a98c7c6820a2a82279dc7c6820ec2821d47c7c6821dc2822c0dc7c6820a3e82210cc7c6822418822c93c7c68222628222f5c7c6820bdf8222c3c7c68214fb822a2ec7c682087d821341c7c682193c821a3dc7c6822610822b77c7c6820f8a82234dc7c682105382261ac7c6821495822d4dc7c6820b90822c88c7c68209ac820aafc7c6820a31822562c7c6820a39822591c7c68213158225a3c7c682087f821aa2c7c6821a468224eac7c6820894822f3cc7c6821a9a8227f5c7c68227a382288fc7c6821c7b82240fc7c6820a42822ce4c7c6821413822b24c7c6820a9082119cc7c68209cc821c8ec7c68220aa822cd2c7c68209f8821351c7c68216398229d9c7c68209d28223e5c7c6820892822244c7c6820b6f821fe6c7c68220e5822c72c7c6820a458213f5c7c68209cd823043c7c682281082295ec7c6821b25822eb4c7c6822405822e02c7c68217eb822dc0c7c6820bf2821554c7c6820ede821bc9c7c68218bf821da9c7c682155a821b39c7c68217b6821f82c7c6822467822f99c7c6820a4b8223a3c7c68209e282223cc7c68224a2822ccfc7c6821858821eadc4c3821fd8c7c68215d1821c99c7c6821c438221d2c7c68228d48228d9c7c682125f822238c7c6820bdb823084c7c682262d822a1fc7c68210708222a1c7c6821d7b82237ec7c68221bd8221cdc7c68222c0822c9dc7c6820b338221e7c7c68218bd822770c7c6820c11822a66c7c6820f598224cfc7c6820b26821261c7c682192d8221ccc7c6820a2982121ec7c6820e04822f84c7c68215f4822e35c7c682208d82258ec7c682160c822bb8c7c682176f8220b3c7c6821b68822ecac7c6821b24822132c7c6822897822f93c7c6820bd582256ac7c682125482282bc7c6821dbc822837c7c6821110821ffac7c6821130821ceec7c68225fc822e75c7c6821e048221fac7c6821ac8822a54c7c682117082238ac7c6820c0882216fc7c6820b40820bc3c7c6821ef38220f3c7c6821d3a822609c7c6820e5d820f5fc7c682269a823045c7c68212c5822043c7c6820f1782262ec7c6821a8a821e3dc7c68212e582134ac7c6822d22822d46c7c68212ed822d3dc7c682179c822779c7c6822624822659c7c68211b68211dbc7c682102d8227f4c7c6820f20822812c7c682120282171ac7c6821a3f822095c7c6822b25822d20c7c6822832822c63c7c68220008223c9c7c6820fb0821b58c7c6821f2f822d28c7c682118e821cd9c7c6822535822cb4c7c6821a86822cb9c7c6821b40822cf8c7c68226c28228b7c7c68220f8822cd6c7c68222188222e6c7c68215d8821820c7c6822004822c82c7c6820d5b821296c7c6820bf5821a68c7c68225738226c5c7c6822686822fe2c7c6820ae3822291c7c6820b95822182c7c6820d3a8226f1c7c6821272822541c7c6820f4482302ec7c6821be18223eec7c68210d5822737c7c682195e822137c7c6820b6a821c75c7c6821252822c69c7c6820d04822f2dc7c68212f8823094c7c68220b7822da2c7c6821217822f2cc7c6820bb5822143c7c6820c05822d33c7c6820cf9822c3fc7c68220d782273fc7c6820ad4822d13c7c6821313821369c7c68215258220f9c7c6822d1d822d8dc7c6821a688222ffc7c6820dcf8211c1c7c682121f8219bfc7c6822691822ee9c7c6820b43821b7dc7c6820eaa821bbdc7c682108e822d66c7c6821bc4822f71c7c6821e50822572c7c6820a75821a3fc7c68220aa822757c7c682130b823012c7c682118a822384c7c682261382269ec7c68223a6822745c7c68224708225b4c7c6820acf821968c7c6821f9b822fcac7c6821289822e76c7c68223a182267cc7c6821952822267c7c68211b48220e6c7c6821052821b8fc7c6822c53822fb4c7c6821af582235fc7c682130c822f09c7c6821a248224acc7c6820b9e820dc1c7c6821136821274c7c68219808224b4c7c682215e8225fac7c68211c382224ac7c68210098230b8c7c682111c8224bec7c6820e7f822642c7c6820d7082287cc7c6820cea8213f1c7c6820af5820e7bc7c68210e38222c6c7c68212a1822cd2c7c6822eda8230b2c7c6820e1482304ac7c68221f8822fe9c7c6821f5b82283bc7c68219fa8225b9c7c68226168226d4c7c6820e49822fdec7c6822e68823021c7c68219d88227adc7c6821803822d7cc7c6820f5482253ec7c68210b78221a1c7c68215208223c7c7c68210b2822086c7c6820dfb8228c7c7c6820ae2822395c7c68220a7822fa7c7c68221418221ccc7c68211b0822362c7c6820f0982270cc7c682275e823117c7c6820d28822f40c7c6820b7a822ca1c7c68212f28230d6c7c68228c1822e5ec7c6820ec58228a6c7c6820b99820ed8c7c682113a82254ac7c6820ba9821947c7c6821c6a8223adc7c6820dae822d6bc7c6820a7182135bc7c6822d06822d1ec7c6820ca982240ec7c6821fbc822f90c7c6820b23822d89c7c6820aab822cbcc7c6822032822c73c7c6820dd4822ba3c7c6820f2d821be0c7c68212358221c9c7c682275a822f39c7c6822bae822c43c7c6820dd18223fbc7c6821a958230bec7c68210b68222bbc7c6820b72822094c7c682246f8224b5c7c6822b3f822b82c7c6820a94821b4ac7c6821f9d822874c7c68208bb822b05c7c68214ab82310cc7c68211c6822f30c7c6820f7f821dd2c7c68210b4822890c7c6820e97822dd4c7c68211a78226d4c7c682238c8225f0c7c68211d4821f07c7c6821d338227fdc7c682126a822470c7c6821004822507c7c6820e4c822321c7c68212f88224fec7c6821c168221e2c7c68221098228bbc7c6820e5e82274fc7c6821be4822d03c7c6821154821bf6c7c68220ca82234dc7c6820bfc82113bc7c68222858225adc7c6820ff7821cdac7c6822834822a61c7c6820ddb8215c8c7c6821b05823116c7c6821cba8221b2c7c682233d823110c7c6820da48220a0c7c6821cd5822333c7c6820e45822fe5c7c6820ff4822da0c7c682120e823030c7c6820c51820fd3c7c68225e2822664c7c6820e89821888c7c682227d823013c7c6820f57821063c7c6820ed98230dbc7c68212368230aac7c6820d01820f14c7c6820dbd82216bc7c68211ba822cc3c7c6820d968210dec7c68221788223e6c7c6821eec823080c7c6821d2f822707c7c6821eda82244ac7c6821c32822fbdc7c6820a2c823020c7c6821224822581c7c6821ef08225f4c7c68212da822679c7c68226b482306cc7c682191182306dc7c68223018225aec7c6820d7a822846c7c6821cbd822fa9c7c6821b54822e2ec7c682106d822381c7c68224bc8224d1c7c68220fd822282c7c6821dcc822188c7c68217ca82292dc7c6820bd082128cc7c68225bf823095c7c6820d5f8225d0c7c6820f8a821beec7c68217d08219aac7c68210c582162bc7c68215f682272dc7c68222b8822feac7c682115e8230cac7c6820d458229b7c7c6820de4822351c7c682143482226ec7c6820cc2821286c7c6820dc482226cc7c68226d9823137c7c6820d8d82108ec7c6821d98822997c7c6820be88224e0c7c6821263822f75c7c6820b04820f67c7c6821e4f822ba3c7c6820c8e821d17c7c68212df82311bc7c682128e8230bac7c6820dfc822195c7c682122f823157c7c6820ad08230c9c7c6821e2a822843c7c6821b32821e42c7c68223cf822723c7c68223648227d4c7c6820bca8226a2c7c6821a2b821c94c7c6820c8f822cc8c7c6821a6582277dc7c6821319822779c7c682261b8226f7c7c6820dab822c42c7c6822766822f24c7c6820ee58221b2c7c6820e57822d55c7c6821957822cbec7c6822c6e822ca3c7c6821302822618c7c68210e8822442c7c68217f7821a51c7c6820ebd821e58c7c68224bf82285dc7c68211c4821306c7c682224d822765c7c68212ec821ad1c7c6820c21821029c7c682166882226bc7c6820a1d8221e3c7c68225b7822cf6c7c6820ec7822124c7c68213218221f4c7c68211f1822fb9c7c68218378221b1c7c6821f54821feec7c68211a282264cc7c6821f0a82275fc7c682113a82142ac7c682279b822e9ec7c6821176822781c7c68210df822f04c7c68226a9822f08c7c682119d821905c7c6821ae9821ecec7c682129f822104c7c68225828230fbc7c6820a4d8223d1c7c6820e978219edc7c6821ed48225a2c7c68211ae821430c7c68224fb822e09c7c6820f3882275bc7c6822821822c54c7c68212c08226abc7c68214ea821dc8c7c682131d8230a2c7c68224f0822bb7c7c6820d9382106dc7c68211c5822695c7c6820e99822c70c7c68209fe821aeac7c682276e82278ec7c6821176821fd5c7c68211818223f4c7c6820a9f822cf7c7c68225fd823191c7c682302f82308bc7c6820b7482105ac7c68211fe8212efc7c68215c582297bc7c6820c9c82310fc7c68212e18220e1c7c6820c94823133c7c6821c1b8223b4c7c682245a822546c7c682207c823050c7c6820e6182272fc7c6820ccd8219ebc7c6820fad822c48c7c6822755822d3ac7c6820a82822756c7c68212d5822260c7c6820dd68230ebc7c6820f88821de6c7c682313982318bc7c68211d5821d93c7c682127a823090c7c6820b978210fdc7c682274382310bc7c68221c082272bc7c68210588225acc7c682129582247dc7c68212b18223d2c7c6820fb382238ec7c6821d08822b80c7c6820fb882225ec7c6822116822ce7c7c68212a282253fc7c682264c82318ac7c68212988214c8c7c68220d9822f6ac7c6820a8d821cdec7c6821d7d822693c7c68223c18231b6c7c6820d2a822139c7c6821414821ef9c7c6820e308213d9c7c68211f382250cc7c682130e822171c7c682110e822d7dc7c6821a76821df5c7c6820f01821300c7c6821c3282314ec7c682226b8222e0c7c6820a6d8231cec7c68212f9822065c7c6822393823067c7c68211ef82301fc7c68217fc8228adc7c68224a7823111c7c68225068225d7c7c6821bdb8220fec7c6820f4982304cc7c6820ff5822552c7c6821025821236c7c6820deb822c91c7c6820df7822ef2c7c6822dc6822f96c7c6821d238226bbc7c682257782272fc7c68224398228dfc7c68219e082232bc7c6820dec822b5cc7c682212b8230fcc7c6820f5e822caec7c6821da7822370c7c68223a282279fc7c6820ff18231c2c7c6821fe7822658c7c6820ab0820db2c7c6822f61822fa4c7c682233e822703c7c6820ea5821795c7c682114382226ac7c6820dad821473c7c68211b88230c9c7c682106a82279ac7c6820c188231a1c7c682114d8222bac7c682272a82307fc7c68221c48230dfc7c6820c3f8230a1c7c6822d1c8231d4c7c6821475821cd9c7c6820f2b82201bc7c6820d90822acec7c68218dd8225f6c7c682180b82274ec7c6820ae58230e6c7c6820d26822709c7c6821287821e48c7c6822fc78230fac7c68212698230a9c7c6820db6822197c7c68221128227edc7c68211ee822eb9c7c6820e908231b1c7c6820b87820c04c7c6820d15822681c7c682115a822f77c7c6821856822c6ac7c6820a0e821798c7c6820ab4822873c7c68223a78228b5c7c6820c878222fcc7c68210138226f3c7c6821bcc823231c7c6822c40822f81c7c682204982315dc7c682211482315ec7c6820f6d822faac7c68221c78223bac7c68210eb82181dc7c68212858226acc7c6820fb18211bdc7c6820fd5821026c7c682131a822452c7c68226e48227eec7c6820bf28220e1c7c6820a5182232dc7c6821f9a82267ec7c68219a9821c36c7c6820e0d822c54c7c6820c0e8220e2c7c682246982270ec7c682221882259ec7c682121d821920c7c6822df88230cfc7c68211fd8223dac7c6820e2c8226efc7c682212e8221b3c7c6820bce8221fec7c6822433822b37c7c682223c822cc4c7c6820f8082239ec7c6820f0a82276ac7c68212a7821399c7c6820c33822229c7c682221b822878c7c6821287823219c7c68223ae823257c7c682105f82289ac7c6821fd6822199c7c68210cf821ad8c7c6822c5d822f8dc7c6820d58822572c7c68226d78227d9c7c68221ec822c44c7c6822ce1822ce7c7c6820aa58222f7c7c68211fb823203c7c68221f7823153c7c6820de8823159c7c68226d3822f1bc7c6821d9f822f21c7c68211228221bcc7c6821b0882302cc7c68210208231b3c7c68210b182303ec7c6820d3c823211c7c68210238231e3c7c6821a9b82263ac7c6820f89821077c7c6820fba822726c7c6821078823144c7c6820be6822a1fc7c6820e13822407c7c6821419822a28c7c682120d822640c7c6820f6682317ec7c682229782312cc7c682283d822d2ec7c6821102822435c7c6821acb8224f7c7c6820ef4822d39c7c68215ce82216dc7c6821649823200c7c6820eb78224f7c7c682100c821797c7c68214fe82258dc7c682283c823150c7c6820fc6822dc6c7c682224582265bc7c68210dc821dcbc7c682248b8231d8c7c6821684822a4dc7c68223bc823292c7c6820ee9822fc7c7c6820d9f82109ec7c6822243822ca7c7c68225258230d3c7c682110a8225a6c7c6821aa18224e1c7c6821451822457c7c6821705821a73c7c68222a78223d9c7c6820fc58228c6c7c682206282237ac7c6822d66823297c7c6820f8e82319cc7c6820fd48231acc7c68219a1822cf3c7c682125d82255dc7c68210cd8225d8c7c68228698231dcc7c6820a08820d50c7c6820b84821148c7c68216c5822e47c7c6820b53822763c7c6820f2e822042c7c68227d882300bc7c68219f08227a5c7c6820f1b821275c7c68221fc8228a7c7c6821f5e822698c7c6820ec1821f74c7c6822934822d41c7c68218b5821ffcc7c68221598230b5c7c6821223821473c7c68211d1822d0ac7c6821b498220c9c7c682180e822f15c7c6820eda8223afc7c6820b558226e9c7c68228c0822b4dc7c6820cb8822e8dc7c6821090822d37c7c682128282149cc7c6820cdf82246ec7c6821104822cbcc7c6820aca8222a4c7c6820ce8822799c7c68217dd8223c9c7c682158b822861c7c6820f3c8221f8c7c6820cd8821bc2c7c682293f822a16c7c6820d9c8224ebc7c68224cb822671c7c682205f82234ec7c6820bae8222e9c7c6820c35822f88c7c682104f8231ccc7c6820e6f8221dac7c6820ca2821bdcc7c6821a69822198c7c68211fa8228b0c7c6821eff82320bc7c6820f2a82273ac7c6820b36820faac7c6822ce9823190c7c68213eb82238ec7c6820a288226a3c7c6821da2822738c7c68230178230ddc7c6820b348220b3c7c682274c8229f5c7c68221b582279ec7c6820b088221a0c7c68211e082243bc4c3821076c7c6820ff3822cd3c7c68212c8822fe6c7c6820cf28225c9c7c6820fee8218d3c7c682278c82321bc7c68224d7822f95c7c682214b822867c7c6821e3f823106c7c6820f5c822139c7c682250d822df0c7c6820d1e821827c7c682284e822cd5c7c6822c8d82323cc7c6820f54822744c7c68222e482260bc7c68215dd822d9fc7c6820ff8822179c7c68210a1821eecc7c6821d58821ed7c7c6821f7d8232d5c7c682131b8228a0c7c68221da822c3dc7c68208c4822e42c7c6822841822d70c7c682278b82286bc7c682131c823002c7c6821308822843c7c682100182222cc7c6822472823152c7c68211118231cbc7c68209e9822327c7c68214d3823081c7c6822446823162c7c682275f822d90c7c6822052822ffac7c68221ca8230c0c7c6822801823164c7c6821141821dc8c7c6821027822084c7c6821d40823128c7c682122982219cc7c682246a822f48c7c6820d37822d9ec7c68223ce8232a3c7c6822774823173c7c68210c28232efc7c68229a8822f1ec7c6820f1f8232d2c7c6820e43822943c7c6822498822ec7c7c6820b6b822d5ec7c6821228822742c7c6821260822827c7c6820ceb8221ebc7c68212a6821c13c7c68211c7822d37c7c6820bcd8226b9c7c682305f82307ac7c68225188226f4c7c6820fbc823120c7c6820aa182289ec7c682127b82262bc7c682245182304dc7c6820ff0823203c7c6820e48822f9bc7c6822643822d53c7c6822b938232cdc7c6820dde82122dc7c6821783823137c7c682166f8227e3c7c68211f6822219c7c682223a82313ac7c6822146822d18c7c6820f6e822f2ac7c6820d948212fbc7c6820f1d8222f7c7c6822d05822d44c7c6821247821415c7c6821a5f821e48c7c68224098225bbc7c6821f688224cec7c682186e82192ac7c6820eff821677c7c6820aa0821129c7c6821ea1822652c7c682122b821b99c7c6821e5f8226f7c7c682242f82297ec7c6820b0b821396c7c6820de1821eb5c7c682231d822c66c7c682119f823337c7c6820ee38220c2c7c68222df8225e9c7c68214e8822801c7c682247782277bc7c6820df68224b4c7c682124782288ec7c68220f68230aac7c6820f6f823089c7c6820cdc82259cc7c68212018223cac7c6820f18822134c7c6820e628214a6c7c6820b46822d11c7c68230e8823212c7c6821035822462c7c68210ac822565c7c6822cb7823099c7c68210fc821d46c7c682224b822579c7c68225698230b9c7c6821c8182330ac7c6820f10822222c7c6820e838226e3c7c682128a822302c7c682119c822bd3c7c6822da08230fdc7c68226f28230cac7c6820c2d820fa0c7c6820d5a8220efc7c68211ab822445c7c6821b818231d6c7c68225f8822912c7c6821d3e8220c0c7c6820bf6822bf0c7c68224c8822cfbc7c6820e6a82326cc7c6820b02822f9bc7c6820ce3821857c7c682191b8231f5c7c68211b08216fdc7c682259b8230acc7c68210d8822ebec7c6820b9b82209cc7c6820f958218a1c7c6820d8c821d6bc7c6820e52822249c7c6820fe7822cf2c7c68226d1822ff0c7c6820c09821514c7c6821b9c8227a2c7c6820e77822473c7c6820a8d823114c7c6821d41822372c7c6820de38210e7c7c6822757822ceac7c68210b0823152c7c6820c348232b2c7c6820d428230e6c7c68215b8822092c7c682274082324dc7c682118b8230f6c7c68230a7823282c7c6820dc582334cc7c6820def82258fc7c6821043822df2c7c682285f823287c7c6820aa4822563c7c68212ab8220bec7c682209b82309dc7c68225108227b3c7c6820a4c82239bc7c6820b20820b2bc7c6821caa823309c7c682164e823150c7c682254d822d93c7c68210f3821dfac7c6821167822ebbc7c68212f7822d4ac7c682305b8232adc7c6821337822c5cc7c68218cf8228b4c7c6822503822b0cc7c6821fe18228d2c7c6821132822c71c7c6821552821f2cc7c6821317821d47c7c68211858227b0c7c6821b2b823109c7c6820c12821a20c7c68210ae822d69c7c682115782321cc7c68229f2823204c7c6821f548230b3c7c682218b822d53c7c682110d8230d0c7c6820f398226d6c7c68227568231aac7c6820c4d82177ac7c6822326822d5bc7c6821b1282214ec7c6820a66822f4bc7c68210cc8226e8c7c6820a27821c7dc7c6822545822864c7c6820f55823299c7c6821258821e3dc7c682127182270cc7c682108f822795c7c6820ee48217a9c7c6820ca48230e2c7c682308782313ac7c68210108212e3c7c6822f0e823334c7c6820bc5823197c7c6821dd2823029c7c68224fb8232b8c7c6820d5e82223fc7c682129a823366c7c6820ec28225c8c7c6820bc7821f55c7c6821bb282320fc7c682123e822163c7c6820f6f82147ec7c6820fe5822121c7c682280e822cecc7c6821e02822f3ac7c68228a2822c6fc7c6820e6d82274ac7c6822586822799c7c6821113823149c7c68226bc8230a6c7c682218a823295c7c6821967822318c7c68221838232e2c7c6822bf182312dc7c6822508822efcc7c68232be8232e2c7c6822e1982328dc7c68210d182151cc7c6822377823183c7c6821229823166c7c6820ac4821b4ec7c6821ca7822747c7c6821a4a8220a6c7c6820f468227c4c7c682178c82318fc7c6820c3d8221f4c7c6820fdc822fa1c7c6822f2e823134c7c682256d822fcbc7c6820c79823063c7c6822cd9823398c7c68215c3822fd2c7c6820e4b821b23c7c68221d7823346c7c6822d3b8230d5c7c682212a822614c7c68228858230c2c7c68218c3822350c7c682231982258fc7c6822d21822dacc7c68216e38220b1c7c682204b82205dc7c6821524823228c7c6820bad823307c7c6821c7f82329bc7c682167f821f2ec7c68232c48233b8c7c68218e382322ec7c682318e8231a5c7c6820ce6822791c7c6820f3182327dc7c6820d108231ffc7c6821ec2822cebc7c68210e5822be0c7c682217a82331ac7c6822fa8823151c7c682129082256fc7c6820ad3821160c7c682234c8232c2c7c68211198233e4c7c68218b1822a5ec7c6820c15822138c7c682272d822fd4c7c6822aee823281c7c6821074822f7ac7c682149d822e61c7c68210928226c6c7c6821cdd8233d0c7c6820def821103c7c6821aef8232d9c7c6821fb38224e4c7c68228618230c3c7c6821ce382307ac7c6820f168224f5c7c68213168223c6c7c6820dd8822374c7c68224df823390c7c6820ef9823349c7c68222d98233fac7c6820a6f822e49c7c6820e3d822c90c7c682282f823362c7c6822ecd823370c7c6821de38230c4c7c6820e65822ce5c7c682267a822d4fc7c68217f5821ee2c7c6821b748230fbc7c682251b822564c7c68212b0822473c7c6822db082332ac7c68220db82313bc7c6821a64822c49c7c682218182327bc7c6822606823396c7c6820efc822f5fc7c6820ba8822509c7c68222c38231c3c7c68217178229b9c7c6821683822426c7c6821263822cf3c7c6820fc8821e4ec7c6820ec3822500c7c68220788227d1c7c68212ec822325c7c6820eac822d9cc7c682260d822ee6c7c682203382339ac7c6820b6882212bc7c6821150822846c7c68226698230ddc7c682196e82328fc7c6820aeb82303bc7c68225e882324ac7c6820d9882247dc7c6821daf822a2ec7c6820cde82313ec7c682306a8231a0c7c6821079823286c7c68218248221c9c7c682112b82251ac7c6820a0c823317c7c6821b4b821dd4c7c6822e95822ec7c7c6820d198223f0c7c682148782224ac7c6821f07821fadc7c6820d22821ee6c7c6821059822383c7c6822412822b78c7c6820f2182326dc7c68212d3822b28c7c682107382218ec7c6820a47822102c7c682263c822c0ac7c6820c96822200c7c68218178225b2c7c68222dd822d6dc7c68226698232a8c7c6820fef821b49c7c6820b28822735c7c6820dc28223bfc7c6821bc9822c3bc7c68230c78233e7c7c6821dc982334cc7c68230a5823252c7c6821e7e822db1c7c6821771822208c7c68212f3822225c7c68232c08232eec7c68222ae822f44c7c682265b822bdbc7c68212438222e1c7c6822e5182316cc7c6820de0822fcdc7c6820dba821228c7c68210dc82334bc7c682122682282ac7c68216d4822d33c7c6822e75823356c7c682129f8213b2c7c6821f58822d0bc7c6820a8e8227d5c7c6820b1e821b67c7c6822015822240c7c68208ae8209c9c7c68223408233f0c7c68210bf82302ac7c6820e8c8233d3c7c682210d822c5fc7c68213d7823290c7c6821968822d38c7c6820ba7822aa6c7c6821893822c52c7c68211ee822003c7c68222ed82324ec7c6821f8c8225d2c7c682101282185ec7c6821022823466c7c6820abb8210e3c7c6821f4482343dc7c6821aa78230a1c7c6820a81823148c7c6820ec9821407c7c682213f822776c7c6821cc582332cc7c68220eb823215c7c6821df98223f1c7c6820d0d8233bdc7c6821b148220f7c7c68210618230cec7c6822d06822ec3c7c682104d82322dc7c6820c3882335fc7c6822372823192c7c6820ebb822d45c7c6820b62822697c7c6821e19821e3cc7c6821d1c821e73c7c6820cac82173ec7c6823261823347c7c6820a50822788c7c6820a80821f49c7c68211e9822529c7c682260f8226d0c7c6821c7e82346dc7c6821c1d821cc4c7c6821a13822111c7c682218c822447c7c682254c8231c4c7c6821f4c82231ec7c6820e688222bfc7c6820eb28233a5c7c6820fb48221d8c4c38217d2c7c68212c3822d9bc7c6822b09822debc7c6820ebc821bb8c7c682130f8223d1c7c6822437822fbbc7c68210c6822c51c7c6820bbd822ae0c7c6821bcf8226d8c7c6820e7a821ddbc7c6822645823395c7c6822f148231fec7c6820c29822727c7c6820fe1822d2dc7c682235582248fc7c6821e018228c4c7c6820e398225f7c7c6821fa182302cc7c68229eb822b59c7c6821118821e6bc7c68213e9821a1cc7c6822f9e823135c7c6820fc38210f8c7c68212dc8213fdc7c68226dc822892c7c6821ca3821dbbc7c6820ae6822807c7c6822121822283c7c68231c0823322c7c68219b582320dc7c6821f3782289bc7c6821523821b56c7c682121e8230afc7c68222ca8224adc7c682151d82289ec7c6821899823040c7c6820be0822f65c7c6820aef82229fc7c6822127822192c7c682224b8231ecc7c6820cdd823104c7c6821ded8232b7c7c682107582310bc7c682116a82338bc7c68210a28233efc7c6821b14823111c7c6820e0f8216ebc7c68211c28225a8c7c68227858233a9c7c6821905822686c7c682129c82248dc7c682344a823475c7c6822b528231bbc7c6821551822dfdc7c6820ae8821904c7c6820f728232edc7c68220ff82340cc7c6821a638228f6c7c6820de68219d3c7c68212fd821cf5c7c68213eb822713c7c6820a12821231c7c6822563823310c7c682244f823379c7c68219f08222a7c7c6821a04822085c7c6822800823446c7c6820ad5822f92c7c68222ea82282cc7c68211028224b0c7c6820f73823205c7c6820bd38233e1c7c682101c822c41c7c6822631822e7bc7c68231bc823450c7c68228098230b4c7c682241d8225dbc7c682130982329ac7c6820ef8821248c7c6821a9482268fc7c68212ee82320ec7c6820aa3823459c7c68211e4822889c7c6820bf1821dd6c7c68224dd8231a4c7c6820d6f82196ec7c6820e058221adc7c6820a5f82262ec7c6822d78823481c7c68211d6822042c7c6820e9a82336bc7c68226a0822d98c7c6820d1c82117ec7c68232ac8232e0c7c6820bb6823386c7c6820a8b820e43c7c682144182239dc7c6821cc0823332c7c6821ff38224fcc7c6820dfa822953c7c6821221821f55c7c682337c8234d8c7c682274d82305dc7c6822065822d6cc7c6820a5e822602c7c682129782313fc7c6821e77822be5c7c6820e20821d00c7c6822bb78234a7c7c682113c8230d5c7c68209f9821187c7c68210c6821565c7c6820c9f82311bc7c682103882347cc7c6820fcf8234abc7c68219db82297ec7c6820c2e821489c7c682107c82211cc7c68230ff823191c7c6820c17823245c7c6821d41822662c7c68221288233dbc7c6822264822509c7c682102b822b09c7c68230fc823264c7c6820e9182218ac7c6820b028232e8c7c682268c822cbbc7c6820fad821eacc7c6821c51821eeec7c68221c6822353c7c6820cec823305c7c682175282269bc7c68217b68234d8c7c68227e582308fc7c682168d8216cec7c6820d40822102c7c682108a8212e6c7c68224348227d6c7c6820b5982204ec7c6821034822834c7c6820d978221e9c7c6820c9882299bc7c6820b9d821addc7c6821059822585c7c6821138821dabc7c6822d70822d85c7c68210bd82332dc7c6820fda82226ac7c6821bf6822f7dc7c6820d82821d44c7c6821015821f16c7c6821123821a84c7c68212048220d1c7c68211938234b8c7c6820b30823364c7c6822fae823339c7c68211518231c4c7c6822c7b822d5cc7c6820e59823093c7c6820be5822358c7c6822069822612c7c6820f0682143dc7c682213c822a4bc7c6820d278232e1c7c68211f98233f4c7c6821e8282207fc7c68210e98230e3c7c68210ee8224f4c7c68219378220eec7c6820b658231e2c7c6820e3b8231a7c7c6822ace823360c7c6821bf182315dc7c68223d882269dc7c6822d6082339fc7c682216682305ec7c6820edb82213dc7c6822872823123c7c6820cba8224c6c7c68209f3822429c7c68210ed822485c7c68211958212ccc7c6821a4f821f3ac7c68220c182225fc7c682191682237ecac98207c5820d50822da3c7c6820c258231f4c7c682117882300ac7c6820cda823219c7c6821164822011c7c6820fcc8215e8c7c682310a82344ec7c6820e8b822513c7c6821002823464c7c6820c1f822aeec7c6820b2282307cc7c682124e8230f5c7c682201882310ec7c6820be78230e0c7c6821003821255c7c6820b7c8230f9c7c682225d822a8dc7c6821e39823187c7c6820cb4822541c7c6822fee8232ecc7c6821081823052c7c682256b822cfcc7c6820f04821767c7c6820c4982231dc7c68217b78231cec7c6822d358234eec7c68229ee8234c9c7c68209af822bd1c7c682113f8234ebc7c6820e8f8234a4c7c68224a5822d68c7c68213f58232ebc7c682091a822f4cc7c682233c823457c7c68211c582214dc7c6822172822e88c7c68231898234e8c7c6821d9c8220a7c7c6820d368234b6c7c682289d823120c7c68214688217e9c7c6821afd8231a5c7c6820ce9821210c7c682236f82342ac7c68212f582356ec7c6821730823447c7c68211b982310ac7c6820c158226cbc7c682118c821d8ec7c6821e598223aec7c6821f6b823061c7c6822396822f51c7c6820f9b822cd4c7c682196782350bc7c682109a8230b6c7c6822265823367c7c6820bdc8233f5c7c68221dd823384c7c682227f822638c7c6820ff6821914c7c682240c823524c7c682296f822c8cc7c6822717823365c7c68223b8822464c7c6820fe2821b95c7c6820dba821192c7c6822051822d2fc7c682240882284cc7c6822290823412c7c6820bdd82334fc7c6820d33822887c7c682208b8234bfc7c68221e482221ac7c6821c4e82200dc7c6820b77822c08c7c6821be3822ce6c7c68221ce82357ac7c68229d8822bdac7c6820f69822e69c7c6821f9e82223dc7c68230158231b1c7c6820f688224d9c7c6820fb8822068c7c682204a823391c7c6820d1882321ec7c68227d78232dec7c682117082358cc7c6821051821d82c7c68220438227eac7c68228b882320ac7c682112482245fc7c68223fa823408c4c3822856c7c6822c49823446c7c6821b9d822a50c7c68225598230fdc7c68220c48231afc7c68209ed823151c7c68219a9822cecc7c682217a8231f6c7c6820a7882314bc7c6821c2c8233a3c7c68225d58232afc7c68212c482338fc7c68217ff822883c7c68221018230d8c7c68212038224fcc7c6820af18222b3c7c6820eb9822f7ac7c6820d4182116fc7c6820f4c8230bdc7c68211ec822651c7c68210cf822313c7c6820bd782225ac7c6821085821b47c7c68211978230afc7c6821eaf823207c7c68211a48224d4c7c682109d82353cc7c682189d82267dc7c6822556823027c7c6820df88227b6c7c6821016821fb9c7c6820fd1823324c7c6821973822454c7c6822d1c8231dbc7c6820bc68226bac7c6821e768227cdc7c68224a1823165c7c6821df982262cc7c6820fa4822487c7c682257a823164c7c68211e88233ddc7c6822c938232aac7c682266f8235bbc7c6820e1e822ff2c7c6821a8d822f61c7c68210d8823263c7c68226828226cec7c6822d2c8231c8c7c6821c98823231c7c68228058233e9c7c6820fd882148fc7c6821667822968c7c6820be3821db1c7c682195f823217c7c6820ab38218afc7c6821064823104c7c6820eea8215e9c7c68227ff823181c7c68227f7823342c7c682125c823591c7c68233ac823574c7c6820fca8230dac7c68219bf8230edc7c682214c822e56c7c68234bc823573c7c6822c848233ffc7c6822f188235b1c7c6820f3b823337c7c6821f1182304dc7c68223208233b6c7c68224e382304cc7c682128d8234ecc7c68217ab822d1dc7c6821ca9823336c7c6821ecc822751c7c68223ac8227f3c7c6820ac382126dc7c6820af9822827c7c68220ad8227c4c7c682112c821e86c7c6820e25822fdfc7c6822c40823532c7c6820c95822da4c7c6821033822efcc7c68230f982356cc7c68230dc823193c7c68211cf822393c7c6820fa6822657c7c68211778231dfc7c68208a9822e17c7c68224de8226b7c7c682165c8233f1c7c6820d9c8222dbc7c6821931822850c7c6821133823469c7c6822013822126c7c6820e3c8210adc7c6821930823450c7c682121b823563c7c682288682335cc7c6820dd3822cfdc7c682106682107fc7c682349d8234eac7c6820e8a82319bc7c6820ce1823242c7c6821d70822c92c7c68228c4823569c7c68211b18232d4c7c68210d082306fc7c682264782323dc7c6820ba382316bc7c6820f6082312dc7c682240f8233b4c7c6822774823407c7c68211a382337ec7c6820fa58231d1c7c6820f93822df4c7c6820b4d822fc8c7c682241e823474c7c6820c1d82304bc7c68225dd823240c7c6821c96822dc7c7c6821693821e51c7c6822d5182353ec7c6820f91823065c7c6822511823135c7c6821a9c8223b9c7c68211328230eec7c6821b7b822744c7c68233778233abc7c6822d39823576c7c682127e821654c7c6821044822666c7c68211fc823533c7c6822fed82317fc7c682246f822c4ac7c6822d1982338ac7c68230ec823220c7c68216cf822a9dc7c6820c548225cec7c682120c82270bc7c682359c8235b3c7c68210d28230cfc7c6821a8582229bc7c6822640823497c7c6820bc0823214c7c682241a82349ac7c68212768230d9c7c68213128230aec7c6820b1c822943c7c68222f9823030c7c6820f6a822450c7c682263b823535c7c682317c8235bcc7c68220b58235f7c7c6821a478230bfc7c682125a822d5dc7c68223078226bdc7c6820e418234adc7c6822e66823445c7c6822cc1823333c7c6820ecf8233fac7c6820a9b823525c7c68210a882332bc7c68230e282312ac7c68218ca82352bc7c682280c8230c8c7c682256d823588c7c68210e1821c2cc7c6820fe4822162c7c682137882305ec7c6820fbb823423c7c6822450822f92c7c6822117823188c7c68211ff822fd6c7c6821c42822efbc7c6821ec68232d3c7c68210408234c9c7c682230982269dc7c682264b823596c7c682107d822f0fc7c6821147823341c7c682318d8235f0c7c6821f8f82361bc7c68212e7822d91c7c68223b382323fc7c68223a98233fcc7c6821e658230a3c7c68224c5823319c7c6820e8d823416c7c6821be3822160c7c682116d822542c7c6820a59822828c7c6820b1482315ac7c682130f82256ac7c68230e3823469c7c682199082224cc7c6820dc5823286c7c68214e8822440c7c6821ed1822afcc7c6822c678234bac7c68230f582343cc7c682167b8222e0c7c6822147822803c7c6820e288228b5c7c68221e18226b3c7c6820d71823185c7c68211e58219e0c7c682100682228bc7c68227c98235eec7c6820e34821d37c7c6821ad8822103c7c6821dad822cb4c7c6821069822578c7c6821f4b8227d2c7c6821778822131c7c6822454822505c7c682324c823548c7c68210f18223ebc7c6820c0a822a67c7c68226f4823436c7c68219b7823650c7c6822710822cefc7c6820b63822976c7c6820cb28231e2c7c68233508235c2c7c68215a3821c0cc7c6820efe821eabc7c682355482356dc7c68231ab82332fc7c6820e92821f81c7c68211ac8235a6c7c68210468233b0c7c68230b7823492c7c68215cc822fe4c7c6820d84822155c7c6821bc3822ef1c7c68212f6822e8ac7c6821ab2821e9ac7c6822cbb8235cec7c6821573823066c7c682362982365bc7c682186a822950c7c6821585822db6c7c6820acd821358c7c6822a72822b81c7c68233af82358ec7c6820eb68231dec7c6822c468232c7c7c6821d60822b57c7c68213e28234f7c7c6820d35820e59c7c682141d822a7fc7c6821268821939c7c6821067823021c7c68227b48234a2c7c6822f6582359dc7c682111a823417c7c6820e7e821c65c7c68234678235bac7c6820cae82245bc7c6821e6d822dbbc7c6820b4e822f6bc7c68222168223ccc7c68226498231a6c7c6822168823607c7c682266a822689c7c68228448234ecc7c68210db823369c7c6822f788235ccc7c6821c788226d0c7c68228b4822e04c7c6822933822bd4c7c68228718236a9c7c68225ec823043c7c68212ac82359ec7c6821334822d9ec7c6822aa8822e77c7c682141182276dc7c6821179821cb8c7c6820e938231ffc7c6822c6182348bc7c682292c822f8bc7c68220ce823615c7c68225ba8226dac7c6821d92821e81c7c6821aad823596c7c6820cb58229eec7c6822087822cfec7c682206c822460c7c6822df5822e96c7c6821e87822ddec7c68218fa8229ddc7c6821ea2822fecc7c68224d08233c7c7c682337d8235ecc7c68233b1823634c7c6820d1a821d6ac7c68217688234fec7c682126682253ac7c68230e5823661c7c6820ced821f3dc7c6821211822d40c7c6821dcb822829c7c6820e69823395c7c6821a7f822570c7c682192e822893c7c6820f24821c1ec7c6820efb82222cc7c682162f8235d8c7c6820c4c822e65c7c6822a74822ffbc7c6820ea1821ddac7c682224e8235c4c7c682124b821feec7c6822afa822ff3c7c682127682348ac7c68218ba82295cc7c6821da5822352c7c6821343822a41c7c68223e382253ac7c6821641822a12c7c682220c822d1bc7c68212e1822e01c7c6820ba0822997c7c6822b6182362cc7c68233ef8236c7c7c68210a6823210c7c6821601823696c7c6821cab82362ac7c6821436822c20c7c6822f7b822fdbc7c6822840822cffc7c6822be6822c0cc7c6821b59823211c7c6820e47822c60c7c6820fed822d0ac7c6821465822787c7c68208db822de8c7c6821d20822496c7c682128d822f71c7c6821852822d15c7c68225ee822b06c7c6822e41822ef1c7c6820ece821456c7c682113e823194c7c68221ff823143c7c68224af822e3cc7c682209b82247fc7c6821337822ab9c7c68219658236bfc7c682122a822c6fc7c68208fb820c16c7c6821aab822b9ac7c68218908227b8c7c68224b18232b9c7c68214f0822344c7c68227be822e83c7c6820d74822d18c7c6820f62823431c7c6821732822e5bc7c68226e2822c5dc7c6822990822da6c7c6823124823570c7c6820df0822626c7c6822d54823393c7c6821b018220b4c7c68210fe822318c7c682190e822937c7c6821749822d0cc7c682251382263fc7c68212a0822d5ec7c682291e822a19c7c68217188235b9c7c6822927822b23c7c68219b9822377c7c68222d5822628c7c682118c8234c0c7c6822f4d823597c7c6822a2c823062c7c6821fb0822fc4c7c6821956823220c7c68228a0822b13c7c6820d64822a77c7c6821d91823091c7c6821292822637c7c6822a78822e07c7c6820ca88232a5c7c68211bc822449c7c682114082277ec7c68210a082362ec7c682111b821200c7c6821e5e822ff9c7c682218b822cf1c7c682186c8219fec7c682288d822dbcc7c6821dbc8227b2c7c6821872822a88c7c6820fe9822f98c7c6820e2b82250fc7c6821beb82363bc7c6822e3e823448c7c6821859822f6cc7c682161b822bacc7c6820dc9821ab7c7c68211f2822cdcc7c68211d5823145c7c68223b88230f8c7c6820d62821978c7c68225378236fac7c6822a51822aa5c7c682103e821c13c7c6821327822bcdc7c6821143821e64c7c6822251822d0fc7c6821037823587c7c6822796822d4bc7c68229aa822ef9c7c68217de82276cc7c68208b9821816c7c6822a0a822b8ec7c6821750822e46c7c6820ed38233bec7c68212f0823547c7c68213df8227a9c7c682102e823193c7c682133f823417c7c68209c8821c79c7c682111e8233aec7c6822fb68232fcc7c6820ea882217dc7c682345582368ac7c6822bfb822cbfc7c6821c9e8229afc7c68219468223b0c7c6822f15823525c7c682202c823373c7c682114f8235a8c7c6821853822b64c7c682311582369fc7c6820a0a8236d9c7c6821604821761c7c682198f822b56c7c682111282268bc7c682141e8217fdc7c68216ae822ae2c7c6821656822dfbc7c6820c48823058c7c6821727822da8c7c6820ecc823544c7c68209c3820bc4c7c6821510822045c7c68216458228f7c7c6822bcf82373cc7c6821740822bc0c7c682109f82253cc7c6822a5b823718c7c68218b8822d52c7c6821616822215c7c682141082202ac7c6822ab9822e25c7c68219be82257cc7c6822823823213c7c682144b822a7fc7c6820eb3821d18c7c6821e478229c0c7c6820cdb822f0bc7c68214e4821d2ec7c68218ed821dc1c7c6822342822cddc7c68213548230b1c7c6820d4b8222bcc7c6821763822decc7c68208ed822a0dc7c6821943821945c7c6821ac7822944c7c682181f822aa9c7c6821e38822ccbc7c682317982332bc7c6822903822cacc7c6820c7b821edfc7c68232e4823506c7c682236e82300ec7c6822855823217c7c6821509821548c7c6821466822b6fc7c682147682339cc7c6820c228213abc7c68213ef821b71c7c6820ba6822c02c7c68227e48227f2c7c682120d821e68c7c68217cc8228dac7c68230578236b1c7c682198c822baac7c68218088228e0c7c682161b821ff9c7c6822aad822edec7c6821ffe822aa8c7c68216238217bfc7c682180f822a56c7c6821ed8822ffdc7c6820bed822ff6c7c68223488226a6c7c682105682347dc7c6822f3982354ac7c6821e148231eac7c6822c17823032c7c682168082192bc7c6820f978227d8c7c6823034823716c7c6822806822f63c7c68217db822d74c7c6820bfa821590c7c6821d04822a13c7c6822daa823427c7c6821517822f69c7c6821389822dc2c7c682220e822844c7c6821dd182294ac7c68219e6821d7cc7c68229058237a9c7c68214bf822e08c7c6820d2c822c52c7c68218708237a1c7c6820b54821424c7c68219c1822fdac7c6820c06822e7fc7c6822915822a0bc7c68229e1822c23c7c6822e12822e8bc7c6821764822b8cc7c682095f821528c7c682230f8228edc7c68218ea822f23c7c682257d822ad6c7c68227b9822ffec7c68213af8228efc7c68218cf822f59c7c68215ed821fd8c7c6822a34822f23c7c6821751822bf7c7c682192782345bc7c6822d52823004c7c6821fd3822d6fc7c6821a93822a38c7c68219dc823056c7c682124a823470c7c6821c93823371c7c68236df82378ac7c682158e822dfcc7c6821c7082336cc7c68215b68235f3c7c6820d59822641c7c6821882821e20c7c6820ffd823398c7c68229f08236d4c7c6822498823248c7c6821e44821e7fc7c6822bed822ecbc7c68229cf822d6ac7c6821c68822922c7c6820e9e822758c7c68216bf822b3cc7c682150f8215a2c7c68227a7822a2ac7c68214b5821ec7c7c6820e3382340ec7c6820bd18214d7c7c6822e79823760c7c6822ece8237d6c7c6822941822b5ec7c6821edf8229c2c7c6822945822e0fc7c682126e823481c7c6822ac3823003c7c6822204823732c7c6820db58224aac7c6821f1d822b22c7c6820d53822809c7c6822935822b89c7c68212eb822428c7c6821a7e822c31c7c6822b0b8237dfc7c6822b67823561c7c682162d82182dc7c6821401822a35c7c6820905820c73c4c3823794c7c6821083822fefc7c6822f0c823702c7c68224ee822c41c7c6822b45822ee0c7c68210fb822866c7c6821a05821e25c7c6820d2382170ac7c68221af822efbc7c68229d0822a55c7c68210ff823060c7c6820cf48230bbc7c6820ef2821b6ec7c682151b8237d6c7c68212f182172bc7c6821bdd8227fcc7c68215aa822afcc7c6822a3e822c97c7c68210ce82229bc7c6821acb822d1fc7c68209b3823035c7c68216f3821c22c7c68213d2822870c7c6821bc3822a22c7c682196d821b25c7c68211b3821652c7c6820c1a822ef3c7c6820dc782379ec7c6821714822a7cc7c68213758234fdc7c68215c18216aec7c6822a528236f8c7c68228bf822ac3c7c682293d822a13c7c6821a8b822d7ec7c68213e88219e8c7c68223f78235c0c7c682097a821a73c7c6822e1c823764c7c6821442821a12c7c6820ffc823413c7c6822ab2822e41c7c68214db822868c7c682324e8235c1c7c68215ab8216b9c7c6821ade822f0bc7c6822185822d12c7c6821464821d11c7c6821615822c27c7c68214aa823817c7c682180a8228ccc7c6822ef9822ffdc7c68229c4823776c7c6821936822dbec7c682199c8219dac7c68219ad821c0dc7c682193a822f0cc7c6820c3b8219cbc7c682224f8226aac7c6821baf821c1fc7c6821421821df3c7c68229cb822abdc7c6820b318228a8c7c68211de822cb8c7c6822902823814c7c6822792822e1dc7c682199d82203fc7c6820ef382343ac7c6821e38823743c7c682157a822e2ac7c68210be8226c1c7c6820d34822efac7c6822976822ef6c7c6822aa7822c4cc7c682139e822a68c7c6821849822954c7c68214de823796c7c68218a6823694c7c6822d7782381ec7c682133b822ac4c7c6820981822c31c7c6820f1c822dd1c7c6822ca6823256c7c6820bc1820bedc7c6821713822bffc7c68213c88214d8c7c682273d822cc9c7c682369b8237aec7c68216968219a0c7c6822f43822f7fc7c68223c68227c0c7c6822d11823542c7c6821bd0821f56c7c68213d2821ad9c7c68229108237b5c7c68216fa822aa4c7c682186482384bc7c6821609822fe7c7c682194d821e5ac7c6820a5a822ec5c7c6821b11822e5fc7c6822c7c822fafc7c68218e9821ae3c7c682166b822e80c7c6822a5c822b5dc7c6820b17820c46c7c6821493822ee5c7c68217ee822a36c7c6822bfa822c20c7c6820c28822c28c7c68210e5823357c7c6820bcc821e53c7c6821ef8822dbac7c6822af5822b89c7c6821b2f8229e4c7c6821b848227b5c7c6821330821a3ac7c68216fe8229ccc7c682140d822bafc7c6822def822e00c7c6822c7e822dc0c7c68232b9823647c7c6821f2d823747c7c68212f3822c55c7c682197b822815c7c6822ac5822db1c7c68216a6822febc7c682119b823456c7c68216cb822b0fc7c68213098224dac7c6821963822ea5c7c6822b2d8237abc7c68215e78237b2c7c682182c8236ecc7c6820f9f82249cc7c68211ce823186c7c6822900823790c7c682174b823865c7c6821fc68227cbc7c6821ee982369bc7c68228e78229c0c7c68214a1822b0dc7c682375c8237c3c7c6820f91821c7bc7c6822adb822e90c7c68217318229a4c7c6820a56821a5ac7c6820bb7821d26c7c6820a38822038c7c6820ac7822e96c7c6820987821709c7c6823235823434c7c68213298216adc7c68228e88237d1c7c6822921823593c7c682150e8229e1c7c682385a823880c7c6822bf9823069c7c6821e938236e7c7c68228e0822f8ec7c682156a822e22c7c6822300822831c7c6822a05822b2cc7c68217d78229ffc7c68216d08228f9c7c6821b408225bec7c6822b6f822db8c7c6821c428232f4c7c6820c7d822fb7c7c682180882384dc7c682290d822a15c7c68213dd822dc1c7c6822f4e823071c7c6822e8582375bc7c6822b6b823891c7c682166b8229dec7c68217f8822e3ec7c6821a7a822952c7c68219b1823843c7c6821dd58232a4c7c6821145821f23c7c68222e28223eac7c6820e46823854c7c6820c30822e84c7c68216da821b51c7c6822993822b75c7c68228728232e7c7c682129b823254c7c68218ad821c92c7c682185082383fc7c6822019823687c7c68209da823712c7c6822f16823355c7c68210a98225a5c7c6821940823878c7c682117b82186ec7c6822c21823832c7c68218818218afc7c68213d8822b94c7c682179c822025c7c6822279822d27c7c6821361821439c7c68213c78237f0c7c6821958822008c7c6822c2d822c86c7c68213cb822a63c7c682151e8229c5c7c6821476822589c7c68219e9822d61c7c6820b2e821e25c7c6823092823721c7c6822c21823035c7c6820e408234b9c7c682137a821998c7c682313682331cc7c682287e822b5bc7c682180482318fc7c682199a821a06c7c6821b5e822bd0c7c682373c8237a3c7c6822aae822e98c7c68218ff8227a5c7c68211c982385cc7c68229fe822df9c7c6821a3e822ea3c7c6821454821a2dc7c6820bb0823820c7c68221b8822705c7c6822bd98237d0c7c68217778229f4c7c682133a822fcfc7c682114482326ac7c6822f2b8238d4c7c6821639821b11c7c68228c2822c2ec7c682138f82178fc7c68217ea823764c7c6822ad8822bb9c7c68227c8822a68c7c682299e822c8fc7c68229a5822e85c7c6821a628238b9c7c682136f823747c7c6821572821a28c7c6821a0e822e82c7c6820b75822bcac7c6821796822a8cc7c6822aff8237a0c7c6820a76821b36c7c6820b52821a35c7c6822b83823818c7c682101b823834c7c68216d982380ac7c68221748234f5c7c68213f38238d9c7c6822b01822e6ec7c6820e7982380ec7c682132d822a6fc7c682326d823602c7c68231a882331ec7c6822fd1823884c7c6821734821b71c7c6821a288237c4c7c68215f2822e11c7c6822b0e8237c1c7c6821755822e17c7c68233828233b7c7c68218868229bcc7c6820ef182289cc7c682151282301ac7c6820e0e82373dc7c68216a6822010c7c682135c822c2cc7c6822e93822fb6c7c6821ae2822f52c7c6821f1f822cf5c7c6820efd82344ac7c6820b00822b2ac7c6821a128229cbc7c682187e821935c7c682195b822f33c7c6822a70823047c7c6821a7a822ddac7c6820b1682169fc7c6822adc8238e6c7c6820ee7823438c7c6821418822f1fc7c68217ce822b30c7c682192c823886c7c6822ad4823703c7c6821918822f42c7c6822e3f823866c7c6821977821993c7c6820cc1823300c7c682141d823906c7c682303a82372fc7c6822a20823907c7c68214f2822a01c7c6821747822c34c7c682329182379bc7c682293d822b4fc7c6820ba4822de4c7c6821b2082376ac7c6822aae8236c5c7c682148c82362cc7c68218a78238d6c7c6820906822de2c7c6820ded822d5bc7c6820960821809c7c6821a7b821d81c7c6820f2c823655c7c6822a00822e38c7c682194d8238f2c7c6821d078232d4c7c68217c6822ff5c7c6821664821744c7c6822c75823872c7c68213d38237a0c7c6822761822fbec7c6820d868214abc7c6821ad98226b8c7c6821828822915c7c6820b57822c1cc7c68216b58229cdc7c6820b47822b2fc7c6821392823899c7c68219d28228ddc7c682134d821954c7c6821a1f822e53c7c68210e482356ac7c68229cc82378cc7c68218b0823121c7c68213228235b0c7c682093b821c5bc7c682199f823837c7c68229ae822b4ec7c68235e282371ac7c68232fa82350fc7c6821488821c0ec7c682255382327ac7c68231958236e9c7c68219a6822e9bc7c68210118235d0c7c6821a2e823627c7c6822d5d82309ac7c68212b982366ec7c682132e823783c7c682250b8236bec7c682169a822db9c7c682153182159bc7c682211982265ac7c68212ba8227f8c7c6822a01822dbcc7c6822848822907c7c6822379823658c7c682215a822c7ac7c6820b5d822c15c7c6821ebd8237d8c7c682302a8231d9c7c6821a2f82388cc7c68221e88232b6c7c68219b38227e1c7c6822d4c823161c7c6820e2782312cc7c6821be88227bbc7c6821fc182319cc7c6821d8e82203ac7c6821a6a822b92c7c682211b822ce1c7c6820d2b821907c7c6820c0f8218acc7c6822773822e48c7c6822a9b822bfec7c68222e9822aa6c7c68214af8221fbc7c6820e36823319c7c6820f80821fe4c7c68227ab822cc0c7c68216ab823593c7c68223168227d4c7c6821ff8822989c7c68218f3823963c7c6821679822833c7c68213028234b2c7c6821168823238c7c6821ce2822b47c7c68211498217a3c7c6821dec8229d3c7c6822345822625c7c6822871822f8dc7c6823176823729c7c68216928230d6c7c6822a9a822d7bc7c6820e16822fa8c7c6821de88221a8c7c682291d822937c7c6821338821723c7c68214c0822a76c7c6821498821fc5c7c6820d6382217ec7c6820ab6822beec7c6822cc5823639c7c682153d823871c7c6822780822e0cc7c682314d823419c7c6820ed1823342c7c682205782305fc7c6820d6e822178c7c6820f1a823093c7c682301c8235d5c7c6822e498236c3c7c68214418233bbc7c6821799822962c7c68222698234cfc7c6823476823479c7c682253782271dc7c68230f782344fc7c68225ee822a82c7c68228888234fac7c6821ebc822c32c7c6821916822688c7c6820cef8227efc7c68212c38233a4c7c6820fe38234eac7c682194f821d8cc7c6820d5c822802c7c6820e4d8225bac7c68231a382342ac7c68215d5822dd6c7c68211c6822204c7c68233e28233e8c7c6821f02823393c7c68222cb82317dc7c6820f5a8222a5c7c68219d882356fc7c6821b90822fcac7c6822557823167c7c68227b782298bc7c682273a8238fac7c6822612823144c7c68236b98237cac7c6823453823729c7c68216dd8228a3c7c682191982378ac7c6821036823225c7c6821596822b19c7c68226db823235c7c6821acd821ea5c7c68211988234b0c4c3821e8cc7c682170d8236b7c7c6821069822692c7c68217c9823081c7c682150c8229f8c7c6821152822763c7c682149b822e6ac7c68223e482398dc7c682207382387cc7c6820f50823958c7c6821a23821acdc7c682345a823568c7c682175b822a6cc7c68223ec822cc2c7c6821e0882329ec7c6821230821d2cc7c6822135823170c7c682298f822de4c7c6821b3d822e31c7c6820fc2823006c7c6822c2d822c34c7c682183f821ec9c7c682291c822fe4c7c6823515823639c7c68236ee823741c7c68224e6822665c7c6822dc98237d1c7c6821b6d823079c7c682103b8226a6c7c6821d7c822a5fc7c6821376823735c7c68233ee823726c7c68228ec823976c7c68227d682397fc7c6821238822323c7c6822e378231e1c7c68210da823666c7c68226ec823626c7c6821c2f822a81c7c6821a81822ef8c7c6821aa28228fbc7c6821f1f823237c7c68216d382391cc7c6822719823600c7c68214da822e4bc7c6820d928224c5c7c68210d5822cf0c7c68210a4823730c7c68210ec82189cc7c682212d8224b8c7c68214f7822862c7c68212b8821be2c7c6821f2482395ac7c682192f8236f3c7c6821198822fb0c7c68212ff822554c7c682100a821553c7c68212d58224aec7c68217708238eac7c6822787823883c7c68214a9822fe7c7c6822d9b82321fc7c6821fc3822228c7c6821453821bafc7c6822ea3822f79c7c68212c1821f99c7c68222878229e9c7c68232098233dec7c68216f0822bcec7c68216b18237c2c7c68225eb823439c7c6821494822bd4c7c68218388238d3c7c6821660822c2fc7c68216ff822a49c7c68234048236fbc7c6820e9f822765c7c682200182353bc7c682293b823922c7c6821932822b8bc7c6821581823868c7c6820e72821a43c7c68215bb8239f4c7c6822c30823804c7c6821533821941c7c6822db9823a02c7c6820e7c823780c7c68217a7822e0fc7c6822817822f34c7c6821842823779c7c6820df482353cc7c6821242823293c7c68227f882341fc7c6821cb3822e52c7c6821a878227dec7c682286c822ec0c7c6821868823853c7c6820df2822fe6c7c682144c822e57c7c6822a4a822e2dc7c68213158235e7c7c6820e7d82284cc7c6822aa4822da8c7c6820da2822ecac7c68211288224cfc7c6820e5b822f3ac7c68210c1823828c7c68229ce822b41c7c682117d822707c7c6820adf8238a7c7c682183f821e15c7c6821ba9823748c7c6821212821c72c7c68238a08238e7c7c6820e42822644c7c6821db082377dc7c68226908231e8c7c6820b82822ebac7c68211ad82268dc7c68234d98235bec7c68209b0822ffbc7c68228d6822ddac7c6822a3e8237c1c7c682154c8229fdc7c68235d6823779c7c68212bf823488c7c6820f878239d2c7c6820da9823280c7c6821b18823178c7c68210bc8231aec7c682237c82380ec7c6821dca82300ac7c6822fc9823606c7c68228478237b7c7c6821472822d78c7c682284b8228bcc7c6820ea9823622c7c6821fc08222fcc7c6820e6c8230bdc7c682327982378dc7c6820ead823625c7c68234fc823643c7c6821884822c37c7c6822368823778c7c6821fdc823124c7c682296a8238f4c7c6822af8823960c7c6822fad823971c7c68216bd822610c7c6820cfe82333ec7c6821a8c822a4ac7c6821d838236ffc7c68212608221e4c7c6822998822c0fc7c6822ae1822c13c7c68225f282398cc7c68214948237b5c7c682238f822c96c7c6821534822bc2c7c6821964822ea5c7c68216a4823071c7c6821a488225cbc7c6821f5f8224d6c7c6822ff482386ac7c68211da82257bc7c6821670823a27c7c682155b8219ecc7c6821280821bc7c7c6820aec8229f1c7c68229668237e8c7c68209998237eac7c6820b5082307bc7c68227f1822926c7c6820a4a82294bc7c6821ac0822929c7c682155b82164ac7c68217078239c0c7c68232e68235bfc7c68209eb821a3bc7c6822cad822ea4c7c68233f2823a5ac7c68219d9821c4cc7c6822f4a823932c7c6821d54822a97c7c6822be882383ec7c68215568239c3c7c6821bc8822f2fc7c6821ad4822b15c7c6820a87822bffc7c6821aa6822f62c7c6822a9c822db8c7c6823078823a12c7c6822e7d8237c7c7c6820a6a8215b2c7c6821a6c8236e1c7c682094d82390cc7c682173a8237afc7c68215d6823909c7c6821592822f57c7c68213d0822a5dc7c6822d4b8237b8c7c6822bc98234c7c7c68214a1822f8ac7c68235568238fcc7c6821a45823098c7c68215048216fec7c6822982822d74c7c6822903823a5dc7c6821e0b821fbac7c6822071822bf2c7c68231f1823298c7c6821c38822962c7c68235cb8237fac7c6822b888237b1c7c6822ba4823070c7c68213728238e1c7c6821877821fedc7c6822d48823760c7c6822ae9822dbdc7c682294f822ec4c7c6822b8f8235a5c7c6820af6821fd3c7c68217aa823911c7c6822c0682382bc7c6821e40823827c7c682293c822ddcc7c6821403822b77c7c6821538822e0ac7c6822fe1823696c7c682163d8226b6c7c68214438237b4c7c68215fe8238c7c7c6821a1a82338dc7c6821ab98238c5c7c6823789823a42c7c68219a2821e07c7c6821e63822e34c7c6822d7b8236edc7c6821d1a823991c7c6822305822c91c7c682152a822e7cc7c6821422823a1ac7c6820ab8822b97c7c6821c4c8228dbc7c68213ee822cbdc7c6820b12822b11c7c68213e6822aa2c7c6822de6823767c7c68220388239cdc7c68220b68239e5c7c6821373821789c7c6822f07823a9bc7c68209d3822bb5c7c6822b3d8237d0c7c6821702822b60c7c682214b8221c1c7c68213fc822ec4c7c68220e38227e5c7c68229d1822e0dc7c6822e54823016c7c682204d823aa0c7c68231778236a1c7c6822938822a91c7c68214408229bfc7c6822dee823795c7c6820b27822b9bc7c6820ae0822e91c7c6821545823038c7c68209db8237e8c7c6821935822fabc7c68209e1822972c7c6822b6c82374dc7c6821d0d82306ec7c6822911823920c7c6820fe8822ec6c7c6821429823160c7c68213ac821cf9c7c682165c822894c7c6822928823070c7c6821751823856c7c68215cb821902c7c6821aa5822f6bc7c68219d6822eabc7c6822db78232c8c7c68210e0823279c7c6821ccd823792c7c6821614822bbfc7c6821680823418c7c6821844823a7cc7c6822af7822ee0c7c6821d83821feac7c6821506823a22c7c6822fd382300cc7c6821c6f823952c7c6822ed5823a4bc7c682116b823283c7c6820b3e821f86c7c68219a18232a9c7c6822bca822f56c7c682389f823acec7c68217ae822ef5c7c682159b822be1c7c68234ac8237e2c7c68233998234dac7c682329d823a73c7c6821ba4821c91c7c68213c08239dbc7c6822a9382393cc7c68213ba8216b0c7c68220fb823841c7c6822b98822e54c7c6822aa1822b4bc7c6821c11821c24c7c682373e823856c7c682144e821a91c7c6820f75822d3ec7c6821742823401c7c68214ba8217adc7c68219718219e7c7c6821a0f821d74c7c6821e108227a8c7c6822c22822e0ec4c38229fac7c682190f8229abc7c68208f48239a3c7c68228be822d77c7c682152c822ad8c7c68229fc823957c7c68237b6823846c7c682132c823914c7c68214f6822c16c7c6821022823345c7c682175b823786c7c68208f08239c1c7c6820a048218fcc7c68234c5823771c7c6821610822e5cc7c6822f35823abdc7c6820a53823932c7c6821b38822b91c7c68209c4821d8ac7c6821558822bbbc7c6822e03823acac7c68217f882392ac7c682098e821c02c7c68209cf822922c7c68231848236eec7c68214fc822e8ec7c682196c823a82c7c6822ba9823a5ec7c6822aa3823823c7c6822f7082304bc7c6822fbc82386dc7c68217378238bcc7c682292c822f83c7c68238fd823ad9c7c6821790822f2bc7c6821705822e80c7c6820909821fcec7c68228e7823ac5c7c682160c822cb2c7c682169e82297cc7c68229bb822b10c7c682198682397ec7c68219ce822c18c7c682170f821811c7c6821737822ba5c7c6821fe9823a85c7c68237b9823ac7c7c6821b38822bc7c7c68229e2822b08c7c6821d0d822ef0c7c6822ea7823ae3c7c6822dcb823b0bc7c6822e97822ea6c7c6821c04822965c7c6822bf7822dd7c7c6823801823840c7c6822b4a823b1cc7c682137f8229a3c7c682370c823abac7c68217e3822dc4c7c68213b5823aa4c7c6820aac823a88c7c6821374822b9ac7c682190e822eafc7c6822ec2823462c7c6821675822c01c7c6822df1823ae5c7c6820a92823811c7c682117a821557c7c68214a88238a6c7c68214c6823a70c7c6822de78237aec7c68215bc822a80c7c6821ecf823765c7c682147a823b0ac7c682134c8214d5c7c682115c8227c9c7c682168282278ac7c6822d61822de0c7c6822d7382392fc7c6822e58822ecfc7c6822b50822f83c7c6821e0e822fdbc7c6822b18822dadc7c68214bd823940c7c6820a85823affc7c68210f2823654c7c68216858238dac7c6820ff2821bc8c7c6822ef8823b1ac7c6822af3822f49c7c6820aa6820b44c7c68218cd82394bc7c6822a64822acac7c68215cb821ce6c7c6821a6f82291ac7c6822a0f8237edc7c6821ff6822fd5c7c6821672823a24c7c682174c8217bdc7c6820fc3822155c7c6822b3a822dbdc7c68209f7821917c7c6822b998237ccc7c68219ca822d67c7c6821a0e822a5fc7c6821ad382375ac7c68216058229f1c7c68213c0823753c7c6822987822b68c7c682297482387bc4c3821328c7c68216b2821d4bc7c6822016822b49c7c68213e3821f71c7c6822ebc8239ffc7c682196c821ec0c7c6821731823adfc7c6822b7f823b0cc7c6821e7d822d59c7c682141082154cc7c682146e823a87c7c682161a822ed4c7c68230048233f2c7c68218a982299ec7c6822548822677c7c68223898223a0c7c6822697823119c7c682289f822addc7c6822e5d823a72c7c6821a0a823a80c7c6823857823939c7c68237bc8239b7c7c6821fca822b34c7c682154f8215cfc7c6822db38238adc7c6821943822e67c7c6820bb8821401c7c6822a028237dac7c682154d823b26c7c682092082377ac7c6820e1b822ed6c7c6822f3b82387fc7c6822b71823b56c7c682157c823a90c7c6820b61822f80c7c68229e7823b4ec7c6822afd82394ec7c682161282377ac7c68209fd822b2ac7c6821550822e1ac7c68210f482292fc7c6821ac8823b75c7c6822c5b82388ac7c68228a9823b85c7c68216b8823051c7c682146e822f27c7c6821b26821f17c7c68216a18239b5c7c682275c822ad3c7c68237b482389bc7c6822999822febc7c6821dcf8236c4c7c68214cc821500c7c6821e1c823a61c7c6820a14822978c7c68228cd822ae3c7c6821e8f8239bbc7c6820b76822b95c7c682390482390fc7c6821733822a6dc7c682173d822d6ac7c6822e78823b24c7c6822ba5823010c7c6822e8c823796c7c682391a823af2c7c6822e32822f79c7c6822e5f822e81c7c6820af2821986c7c6822060822adec7c6820a3c823ae2c7c6822e33822f3bc7c6820e2f82309cc7c6821561822db7c7c682216b8227f6c7c6823952823a54c7c682295b823873c7c6821b6a823a66c7c6820b75823915c7c68218288237bfc7c6820ac1822515c7c68208e9823b79c7c68218fe823b4fc7c68218ad823b93c7c6822e7a823803c7c68213a482142bc7c6822acb822bcbc7c6822eb1823841c7c682293882374dc7c68223c38232a7c7c6822c8c823aa9c7c6823888823b3ac7c6822ddd8236c6c7c6822ac88238c8c7c68236978236eac7c6822b3b822dd7c7c682162e823bb7c7c6820b2a822e11c7c6821620823a22c7c682114c823710c7c682189a82308dc7c682287e82370ac7c6821d22822a35c7c682156d823bafc7c682287b823528c7c682161f821cbec7c68222fa823ac2c7c6822a3282384bc7c6821642821ea4c7c682340b8234fbc7c6820f8382208cc7c68213fa823a86c7c682149282397bc7c6821539822a84c7c682166d821b82c7c6821d8f822ac1c7c68219ac823b38c7c68227e98232d0c7c6820dd9822e01c7c6821672822fd5c7c6821edc8229b6c7c6821432821f21c7c6821785822875c7c6821e95823933c7c682273e822fc9c7c6822ef282357cc7c682234a823353c7c6821137823308c7c682102f8233aac7c68217a0822ab1c7c6821f3e823b16c7c68212b78219ebc7c682340f8236a9c7c6821037823769c7c6821a4d822743c7c682182e823694c7c68211908230eac7c6823208823595c7c682120f823727c7c6820ef782338cc7c68217c082295dc7c68217ac822e5ac7c68220f0823927c7c6821f8b82346fc7c6822e3d823b8cc7c68216a8823987c7c6821a17821d72c7c6821a5482384ac7c68217ce823ad4c7c682371e823938c7c6822aec823a54c7c6820f778223bec7c682234c822629c7c68222cb82280dc7c682130d821d0ac7c6820cc3821720c7c6821364822eefc7c68218bb822a09c7c6821ebf823125c7c6822046822898c7c68222eb822f82c7c68236d7823babc7c68216bc823a9dc7c68224b78232cec7c6820cb282106cc7c6821d0f822403c7c682181082302dc7c6821738822836c7c6821a78823695c7c68226d582392dc7c6822be9823ae9c7c68231c682329cc7c6821686823b5ac7c68213318226fdc7c6822aa382380dc7c6822d62823690c7c6821e44823a77c7c68214fe821c9bc7c682150d822221c7c68215e1822fe8c7c68214ed822dc5c7c68227938234f4c7c682195c822c01c7c6822822823358c7c6822ae7823b07c7c6822423823620c7c682168a821f53c7c68218c7822c26c7c68235b5823c14c7c682298a822c1dc7c6822aeb823b08c7c68235738239b1c7c6821739823a46c7c6823742823a6bc7c68218f7823979c7c68229f3822af2c7c6823adc823b77c7c6821b5b823a96c7c6823b4c823b98c7c68228d3822b4bc7c68214f1823bcac7c68223f3823863c7c682132082232ec7c68219cf822a21c7c68238db823a1dc7c6822ad1823086c7c68212db82348ec7c6821a7c823860c7c6821e7b823519c7c6821682821f52c7c6822037822f2dc7c68222cd8226bdc7c682170482214ac7c68216d7822f82c7c682156882238bc7c6821fb6823818c7c6821a78822a26c7c6821801823a2dc7c682135e822f46c7c68216c6822a9fc7c68217f3822016c7c6820eda823325c7c6822940823ba2c7c6821663822a9cc7c6823572823584c7c6821df1822ba7c7c6821b06822388c7c6823a11823a74c7c68213a0823126c7c682354b823b68c7c6821725823b75c7c682159e823824c7c6820e9b8232f1c7c6822a9b822f41c7c6822255822927c7c68214848234b1c7c68215a5822e16c7c6822d948234a3c7c68210828228f1c7c6822b8a823583c7c682160f823625c7c6821d6f821ee8c7c682203e8239ecc7c6822f6f823372c7c6822acd823bc5c7c682279c8228bac7c6821508822343c7c682213082348bc7c682140d82369ec7c6822a07822ad1c7c6821db7822c85c7c6820e558232a2c7c6821428823767c7c68216498223d7c7c6821eef821f61c7c68213e7823434c7c682148e82180ec7c6821674822eadc7c682129d821527c7c682327e823284c7c68221c68226e5c7c68225d48235fcc7c6820d91823a05c7c6821e248220a3c7c6822bd2823500c7c6820fe68232e3c7c68223c5823691c7c68234e982371cc7c6821d068222fec7c682159d823140c7c68217d3821da1c7c6822b0e823820c7c6822c6b8236b2c7c6822d2f823572c7c68214498216b6c7c6821e92823c18c7c68215998215a4c7c68212f58213bec7c6821e6282337ac7c6822163822694c7c6821ac2822b32c7c68219a5822bfdc7c6823abf823b73c7c68217d8823316c7c6821e2c822278c7c68236b78238e5c7c68217b182260ac7c682193a822fbac7c6821425822936c7c6822646822cfcc7c6821b15823b8dc7c68227c38228acc7c6822225823616c7c68213b182383dc7c6822670822f86c7c68230e0823a78c7c682288c8237abc7c682296d823803c7c682145e823b7bc7c6822b3682397ac7c68216ca823426c7c682173b822d6dc7c6821695823323c7c6821ac982396ac7c682141f823493c7c68216bb822f37c7c6821f65823669c7c6821699823c65c7c6821648823676c7c682361a823684c7c68214a5822cdfc7c6822a4e8236fdc7c682181c822397c7c6823ae0823b76c7c6823a71823b54c7c6822dd28237c0c7c682172a821a1fc7c68216068239e0c7c6822425823c4cc7c6821d7782307cc7c68217b4823821c7c6821e118223a1c7c68229d5823b3cc7c68217c5823047c7c6821b5a822b76c7c682190c821dc3c7c68238fe8239ebc7c682134782396dc7c6822eb0823582c7c6821571823422c7c6821a2d822c1bc7c68217f0823b6bc7c682188d823baec7c682152b82241cc7c6821ca1822223c7c6822e1e823ac6c7c6822ed0823c73c7c682296b823b73c7c6823b37823b9fc7c682389a823b63c7c6821f0e8228cec7c68219ae823bcbc7c68217cb822b90c7c682343e82393ec7c682138e8222e6c7c68218a682394fc7c6821fdb82276dc7c68217bc823992c7c68230018236d1c7c682155c8216d4c7c6821a22823b5ec7c6821800821c44c7c682143582308cc7c68223b1823994c7c682390a823aedc7c6821475822824c7c6822f4b823020c7c6821a59822b42c7c682191f823706c7c68223cf82361cc7c6822dec82375ec7c6821995822505c7c68219fd821ecbc7c682318082361bc7c6821993823b87c7c6821e30822ce2c7c6821b6d8232abc7c682132a8227cec7c68216488236dac7c68213238239d6c7c6823899823af5c7c682167f823a3cc7c68215f98233f3c7c6821517822dc4c7c6822a47822dedc7c682250282349bc7c68228ec823aedc7c6821a21823831c7c6821c39823353c7c68217268225ffc7c6823c0e823c39c7c6822aab823c9fc7c68215dc82341cc7c682152e8233bac7c682281b8239b4c7c6822bc4822fd9c7c682213882323dc7c6822e89822eb2c7c6822eb8823b9fc7c6822fcc8233c0c7c68214ce82345ec7c682250f8235f0c7c68217dd8230f1c7c68231b7823461c7c6822d67822d72c7c6821642822f52c7c68239b0823c7ec7c68224d682325cc7c6822aea8239adc7c682185c823993c7c6821342823961c7c68216e48227aec7c68214908232b8c7c6821f5a822fe9c7c68238c38239c3c7c6822e3f823791c7c68238bc823c7fc7c68213fb8234a8c7c6821791821b19c7c68220bb8232b4c7c6821360823b7ac7c6821b72822543c7c6822158822620c7c6822f3d823bbfc7c68234cd823a3ac7c68213ec823bf9c7c682188c822358c7c68238cf823ba0c7c6821678822f2ac7c68216af8236cac7c682179e822f64c7c6821b41823c2fc7c68229858229b3c7c6823a85823b05c7c68230268232abc7c682163a8236adc7c68215fd822e8ac7c68224ad823589c7c6822984822d63c7c6822b3c823b42c7c6821c3a8236dcc4c382351dc7c6821724823b3dc7c68222398232a8c7c682339e8233d2c7c682286d8239e1c7c6823c91823c9ac7c6821c208231fbc7c6822d28823a2dc7c6822a31823a7ec7c68229db822f7bc7c682168a82360ec7c682163282315bc7c6821a9d822ae6c7c68217b18237e1c7c6821c3e823233c7c68217928226ccc7c6822663822c3cc7c68214158225b3c7c68233e382364ec7c6822b6e8233c3c7c68224398236a5c7c68218788236e6c7c68219828237a2c7c6822dfc823aecc7c682154082259bc7c6822f73823456c7c68213ea822586c7c682191a822e98c7c6823249823326c7c6821b48823c71c7c68213968224cdc7c68217c2823758c7c6821e98822957c7c68217c9823c70c7c68218e7822ed4c7c68226c38235cfc7c68224568225c1c7c6821b29823a35c7c68219c982294ec7c6821c3a8232aac7c682171e823a21c7c682317e82342dc7c6821e17823930c7c68216f4823bdcc7c68216b4823315c7c6821576823c05c7c682306b823989c7c6821681821f4dc7c682210e822d30c7c68215d98223b5c7c6821697822d72c7c6823695823ca5c7c6823821823a9ac7c6821743823005c7c682303d823b19c7c68213778237dbc7c68230ea823b49c7c682180c8234e5c7c68217b0823b24c7c68234b482379ac7c68234e4823807c7c682164f821ef5c7c6822cb6823307c7c68217bb82267ac7c6822e7482331ac7c68213838237fcc7c68216a9821dfbc7c68215e0822ae3c7c6821482821a39c7c68216d882194ec7c68231ec8239cbc7c682309182327fc7c68221c58227d5c7c6821ab882228ac7c6823166823485c7c68239fe823af0c7c6822a40823996c7c6821da78238b6c7c682162c823542c7c68215b7822ccbc7c6821e3a8237e4c7c682179b823aeac7c68213848231acc7c68214cb823383c7c6822607823311c7c68214d1821d8bc7c682175e823679c7c68235dd82369cc7c6822958823792c7c68216d98217ecc7c6821bdb8239aac7c68216b7822b99c7c68215c7823503c7c682168e82366ac7c6821a58822f98c7c68216f9822e57c7c68227ac823d4ec7c68216cc8221a5c7c682178c82364fc7c6822367822782c7c682226d823a0bc7c682205082235fc7c6821a00823046c7c68217dc821c5ac7c6822963823b7ac7c682137e8235f8c7c68213d98239a7c7c6821f51823201c7c682156582370ec7c6823299823a4dc7c6823513823586c7c68226d2822d55c7c6823926823d16c7c6822be8823b8ac7c6821818823728c7c68214c982314dc7c6821d9f822cedc7c6822f94823b45c7c682179782322bc7c682166a822ebfc7c68213e08234cac7c68234a0823d52c7c682255e823a08c7c6821a6f822cbdc7c682288482323ac7c6822dff823b15c7c682198a823cc4c7c6822c008239b9c7c6820a6e823c9cc7c6821780823a2fc7c6823949823d72c7c6822920823aa2c7c6822d578233d5c7c6822603823a10c7c682146a823c69c7c6822b7f822b81c7c6821f63822f5cc7c682158a823559c7c6822482823519c7c68215b8823c5cc7c682152d822ff9c7c682177b82395fc7c6822ac5822e53c7c68222738233b6c7c6821e618232cfc7c6821c67822188c7c68217e8823890c7c6822d2482333fc7c68233c4823560c7c68217da823c14c7c682231a823b35c7c682177882306ac7c68238c9823ba5c7c6822b6c822e9cc7c682336d823d93c7c682212982264ec7c68215b4823c7cc7c6821555821f93c7c682258b823d20c7c682174f8238d3c7c68214df8227fbc7c682211e8231e7c7c68214058234a3c7c68220518233cac7c6821da9822e45c7c68215c582393cc7c68217e2822e5cc7c68227ca8239a2c7c682301c8238b7c7c68216a7822f25c7c68213a38227bbc7c6821beb82306dc7c6822536822facc7c682165f821aa1c7c682179d8223a9c7c6822941822f9dc7c6821437823564c7c68223928223f2c7c68223f982346cc7c68222ea823003c7c6821c528221d1c7c682160782245ec7c68213d58236e1c7c68230de823736c7c6823a88823ab6c7c682380c8238e2c7c68225bc823686c7c682334f8234f6c7c6821e8382285ac4c382241fc7c68220b8823d64c7c68231ba823d01c7c6821388822fbcc7c6822a55823d67c7c68227da823ac0c7c6822c4f823985c7c68213498230ccc7c6821408823c40c7c6822d7f8234c7c7c68213c282252dc7c6821f15822bf5c7c682219782287ac7c6821c43823435c7c6823871823c46c7c6821335823dc1c7c6821e84822092c7c682145082157ec7c682133e8224e6c7c6822928822b66c7c6821dbd8229fec7c682158b821f74c7c6822f0d823dbfc4c3823265c7c6821cf382351cc7c6822277823c56c7c6822e598239f8c7c68216e68238a1c7c6823338823d84c7c6822d1a823000c7c68229e282392cc7c682160d822cf1c7c68215528232c3c7c6822001823d1dc7c68217df822f7dc7c6822ed9823a9dc7c68220808231dec7c682239f82266cc7c682135a821a64c7c6822ed7823b71c7c6821842823dc3c7c6821462822f37c7c68228b98231c2c7c68219f8823b2dc7c6822b23822e36c7c6821e7a8238b5c7c6821d52823186c7c68226ac8234c3c7c68215f78220c8c7c682155d82320bc7c68217a9823c11c7c6821b82823bcec7c682162782398dc7c68218f2823bbac7c682289f822908c7c68213f0822304c7c6823c99823cafc7c682229a822700c7c682265c8232dac7c6821f7382224fc7c6823a03823c26c7c6822571822f04c7c682178a823940c7c682150d8230b0c7c6822615823a81c7c682241a822849c7c68225648233a9c7c682219e82335fc7c68217b3823b44c7c682160b823056c7c6823a29823b4fc7c6822fcd8231e9c7c68213dc822522c7c6822520823970c7c682281a823460c7c68227ba823a48c7c682168c822e77c7c6822ee5823adac7c682195f8232dcc7c6821d02823c01c7c68214c4822ca3c7c682172a822b8dc7c6821355823d33c7c6822f878235dbc7c682159c823889c7c68213588236b1c7c6822948822eaec7c6821e298232a1c7c6821688822859c7c68213d7823d55c7c6822725823128c7c6823c1b823c74c7c68215678238a9c7c6821efe822a36c7c6822ee68234c1c7c6823338823cd3c7c6821659821d67c7c6821754823438c7c68214718234dfc7c6821423822021c7c6823073823cdec7c682167b823c05c7c6823c4b823c89c7c6821c3b823c9bc7c68230c5823d6bc7c6821c4182368bc7c6823223823744c7c682205e8235a2c7c6823032823b4dc7c6822a30823a59c7c68214c18228bdc7c68233a28234c6c7c68216358231e8c7c6821f14822ccec7c6821782822bb4c7c6821520823a16c7c6821756823301c7c6822d10823306c7c68216b38225adc7c6823733823ab2c7c682326782355cc7c682159f823371c7c68226de823cbec7c682218d823079c7c682286e822980c7c68213ca82354dc7c682376882387bc7c6821d1c823737c7c6823de0823de2c7c68213c48234e5c7c68224eb823421c7c68227eb8239f3c7c6822ca9823b9bc7c6821d3c823c61c7c6821a4582290bc7c6821457823937c7c6821e3f823139c7c68232188238a2c7c682310e823154c7c6821563823babc7c68223ad822634c7c6821580823d27c7c6821451822674c7c682273e82331cc7c682136e8238ecc7c6823451823a2bc7c68215df821867c7c6823a7b823b03c7c68216ea821b08c7c68222a3822e19c7c68215238224e7c7c6821a67821f94c7c6822214823550c7c6821668823c7bc7c6822de0823bbac7c682246282370fc7c6821fc382222dc7c6821c0882311fc7c68214cd82331ec7c682159182238cc7c6821e1e8236f9c7c682333a823c5fc7c6823b69823e37c7c68213d18231c7c7c682168d82185ec7c68235318235aec7c682173f821741c7c6822399823248c7c6822a50823232c7c6821249823757c7c6821be18233cac7c682201e822179c7c6821521821a92c7c68236888236bfc7c6821822823842c7c682247e82357bc7c6822f60823310c7c68215dd822f9ec7c6823d40823d7fc7c6823c77823d2dc7c6821b168237fcc7c6821f4a822888c7c682330c823d65c7c682300f823c12c7c682305c823674c7c6821503822acfc7c6822edd823105c7c6821d588221c0c7c68215e6823284c7c6821332823b74c7c68217ab8231fec7c68232a08235d4c7c6822969823b94c7c68214a48237f2c7c68238e3823a93c7c6822209823e6ac7c68217e7823705c7c682290a823a4fc7c68217f6822cfac7c6821e3b823a25c7c6823c6c823e54c7c6821f57823736c7c6822171823175c7c68227f0823929c7c6822a1d822f4fc7c6822b7a823a43c7c682233f823250c7c68212eb823a37c7c6822471822ccdc7c682299d823aa1c7c6822eeb823d67c7c6822ddd823b9cc7c682302882383cc7c682187a822732c7c6821f7d823668c7c6821f4d821fafc7c6822e03823afec7c68214dc821d99c7c6821da1822eeac7c68217af823911c7c6822af0823bb6c7c6822b73823449c7c6821faa82353bc7c6823786823b06c7c68227bf823096c7c6823a7c823af8c7c682156b8218cbc7c6822d448233cdc7c6821a37822eb7c7c682381a823827c7c68223c482298dc7c6821456822ff1c7c68214fd823eb1c7c682362d823b88c7c68216f5822f16c7c68239b58239f3c7c6822067823e84c7c6822c138238a0c7c6821559821a51c7c6821a60823793c7c6821c4f822ef4c7c68222378238c6c7c68214b0822c3fc7c6821478822a89c7c68226fe82351bc7c682251a8225f0c7c682159382324bc7c68221cd823812c7c682297c823e00c7c682296e822a97c7c6822fc18238b3c7c68235088235fdc7c6821b50822b76c7c682244282352ec7c68216c4823d9fc7c6821938823589c7c6822488823d98c7c6822f9a823caec7c68229c7823bd9c7c6821b0b822c3dc7c68214ed823e31c7c682136782365fc7c682291b823d69c7c68225b1822f88c7c68218e1823e8bc7c6822e3882390ec7c6822ccc82329dc7c68233ac823520c7c68230e782374ec7c6821de48234ffc7c68220e482332ec7c68227fe8235e0c7c6823b09823e66c7c68218348237acc7c6821455823d6cc7c68219dd823bfac7c6821555823c08c7c682144d8237eec7c682113d8233e3c7c682150b822b68c7c68227168236b6c7c6822c6d8234c4c7c68217a4822a0bc7c6822788823840c7c6821998823776c7c68216f68235cac7c68229d4822c32c7c6821c7a8226d9c7c6822fc082321ac7c68217d9823d25c7c6822391823327c7c6821597823874c7c68225ea822ccac7c68237f9823aebc7c68226c5822884c7c682162b822385c7c6821406823496c7c68216a482391bc7c68217e0823787c7c68235118236c2c7c68218718233b0c7c6822089822d76c7c68236aa8237a5c7c682128b8236cbc7c68224b9823468c7c682377c823a8cc7c682201b822f05c7c6822e5a822f57c4c3822810c7c68217f9822940c7c6822d818238ecc7c6821eb08230f3c7c6821eba82341bc7c6823a04823ddcc7c682196d822ddfc7c68236cc823b6fc7c68214a082332fc7c6821757821f2ec7c6822b86823e51c7c6821786822446c7c682160082334dc7c68215c4823e60c7c6822f448235b7c7c68235cd823e80c7c68213f48219c7c7c68238a8823e7fc7c6821f56823c67c7c6821d368233d8c7c68236e7823b15c7c682297d822e79c7c6821ce6823bb4c7c6822a21822cd1c7c682240d822641c7c68217a5821f46c7c68214dd8227b4c7c682153582225fc7c6821712823b95c7c68237de82388bc7c68215a082284ec7c682236c822794c7c68217e5822d23c7c68217f78230bcc7c682240d823788c7c6821463822503c7c6822742823c92c7c6821579823bdfc7c682275b823a3bc7c6822165823de3c7c68217bb8233dbc7c6821f288232d7c7c6822b18822f5ac7c6823912823bb5c7c682194882307dc7c6821815823e91c7c6822b358235d5c7c682316e8233dec7c6823775823a55c7c68217fd823aecc7c68218c98219e4c7c682178b821f8fc7c682140f82371fc7c68216838225a9c7c6821ac1822e67c7c6823c8f823d08c7c6823b0d823e07c7c6821501822877c7c682262c822d27c7c6821566823cc0c7c6821a90822088c7c6822ec9823b23c7c68215b6823451c7c682370482376fc7c68231a9823657c7c68225d8823cf2c7c68227668231b5c7c68216ec82339dc7c68225bd8238a2c7c68239e7823a38c7c6821510822f53c7c6821a52823cc9c7c682176c823ec6c7c68227b0822f73c7c6821646823d49c7c68214ad821eeec7c6823915823a5ec7c68218b08218f4c7c6821542822472c7c6821cee823d0fc7c68222a9823348c7c6821669821acac7c6823934823d96c7c68216e5822504c7c6822a7e822b1bc7c6821a9e823b0bc7c6823ad8823ec8c7c682215f822b40c7c6821ab3821b7fc7c6822c56823575c7c68237a68237b9c7c6822b6a822dabc7c6821784822380c7c682074c823dddc7c68231b68234bbc7c6823d8b823f00c7c68231f8823e5dc7c6821a6d822ee8c7c6822d84823329c7c6821783823d46c7c682184c823129c7c6821653823c1dc7c68233e0823c41c7c6823bfc823c28c7c682290f822af2c7c6821c9b823378c7c6821e2b823351c7c6821ce082398bc7c682162a8226ffc7c6822caa822cfbc7c6822f2e823414c7c6821e2d823775c7c68218ee823812c7c68219d5823117c7c68215ef822096c7c6822474822823c7c682251b823364c7c68238ce823a7fc7c68214bb823ee0c7c68219ce823743c7c6823d53823daac7c682203a823009c7c68238a4823c15c7c68217c782398bc7c6822a96822ec0c7c682155e822717c7c6821a5f823dbbc7c6823433823cb7c7c6821a56823623c7c6821dc4822ff7c7c682311c823329c7c6823c68823f81c7c68223e1823984c7c68218258233f6c7c68218b78238dac7c6822a85823cc7c7c6822c988234cac7c682172f8234dcc7c68235858237e0c7c6823230823f12c7c6823a58823b57c7c6821850823bd0c7c6821f35822ff5c7c682175d82264dc7c68218c08238bec7c6821a1e823363c7c6823206823b88c7c682274e822751c7c6822e23823491c7c6823343823c7dc7c6821a0b822786c7c682311d823e67c7c6822900823c21c7c6823aa1823da7c7c682337e823e2ec7c6821f0a823717c7c682200a82229cc7c68220e3822522c7c6821a19823742c7c6823813823a5fc7c68218e082388ec7c68222508226e7c7c682142e8239bdc7c68231128231b8c7c68225fa8235a1c7c6821957823aaec7c68208ad823befc7c6821abc821d86c7c6821876823650c7c6822d948231abc7c6821d5e82328ac7c6822735823700c7c6822cf582346bc7c682201f822029c7c682374b823bd8c7c682237b8237f5c7c68232bf823e04c7c68220f5822c57c7c68239df823cb9c7c6821eae823e97c7c6821ac68228cbc7c6821f978233c1c7c6823c2a823eb8c7c682147c823167c7c6822bc1823b34c7c6822380823a50c7c6821602822436c7c682183e823038c7c6821304823917c7c682116282156ec7c68219558232b7c7c6821b12823259c7c6821a44823fb2c7c6821b7f822e56c7c6821e6d823908c7c6821b89821e6ac7c68227dd823cbcc7c68219c8822f74c7c68214ff8232ddc7c6822e29823ee0c7c6822de982399ac7c6821934822cc3c7c682177f8233a7c7c682163a823241c7c6821aa6822afec7c682279582373fc7c68239dd823a36c7c68234e9823d1ec7c68225028236d2c7c6821a33822831c7c682192582230bc7c68228128234a1c7c68219498237e9c7c682229c8235dac7c6821aac8239efc7c6822b7d823ce3c7c68222288230d2c7c682190c823c0bc7c6820ebe8238edc7c6821c3f823ec1c7c6821a998228fcc7c6821a31823540c7c68237ec8238b4c7c6821ed48224bac7c6821a98823f44c7c6821846822f2fc7c6821ade82264dc7c682188b823c10c7c6822189822e6dc7c68230d4823c63c7c68219a7823a9fc7c6823881823cf5c7c682183a821af7c7c68232e5823a00c7c682244782361fc7c682195d821b66c7c6822952823935c7c6823ef0823f56c7c6821847823f88c7c682288582354fc7c6820d75821ed9c7c682315f8235f1c7c6821c14822e82c7c68235298237c9c7c6822676823cc5c7c6823017823e6fc7c6822c12823b89c7c6822bcc8237f4c7c6821d9b8232ebc7c682274b822874c7c6821a5e822385c7c6822cc1823245c7c6821a86823a0ac7c682285782307ec7c682372c8237ccc7c68219878232e1c7c6822c09822d7ec7c6821a5b823e24c7c68223e1823867c7c682190a823b1bc7c682195a823f43c7c68219d182219fc7c682193c8220fcc7c6821d4b822ac6c7c682189d823482c7c682370c823987c7c682208c823e39c7c68218f1823662c7c6823e5d823f0bc7c682228f82241bc7c6822376823fb8c7c682192482390dc7c6823f2c823f33c7c6821976822551c7c68218d6822fb7c7c6821f8b823262c7c68227bf823455c7c6821a048226aec7c6822cda8234b5c7c6821988823f40c7c68219cc823a06c7c68222cf823065c7c68230e78230efc7c68219848239eec7c6823e1b823eadc7c6821c2b823e96c7c6822f31823f6ec7c6823832823f74c7c6822af982369ec7c6822cab823761c7c68224bd823dd5c7c682283d823df1c7c682328a823e77c7c6822d12823125c7c68221c5823f92c7c6821ac38234e0c7c68225808227ccc7c6821fae823f73c7c6821aaf8225dac7c68218c3823e3cc7c68239f7823f1cc7c68232d98233ccc7c682262182282dc7c68218f5823c7cc7c68218a48233e7c7c68220c5823969c7c6823472823611c7c6821a848236f7c7c6822956823b08c7c6821840823213c7c68226de823f50c7c682353682364cc7c6822a24823f1cc7c6821abd823a0dc7c6821865823dbac7c68234158235f5c7c6821829823eccc7c6822bd1822c07c7c6823539823631c7c6821a3882350dc7c6822f07823906c7c68236af823e94c7c6821ab0822942c7c6821974821a13c7c68228298238cbc7c68221bb822672c7c6821a29823d89c7c6822d468235dfc7c68218cc8224a1c7c6821ad6821b42c7c6822d4d823df3c7c68218698230edc7c6821b01821d7ac7c6823141823e62c7c68234db823d26c7c682187b8220cbc7c6822a2982384ec7c6821edd823862c7c682185c821e77c7c6822882823b7fc7c682400f824042c7c6822ae5823872c7c682396a823f31c7c682319582373ac7c6821c108224ffc7c68227f7823edec7c6823c3e823c81c7c682189082307dc7c68212c7823ff0c7c6821b6282377bc7c682384f8238e1c7c6821a2e82287dc7c6823b21823cc9c7c68219ed823eefc7c682253282388ac7c68221c18239a0c7c682184b822670c7c6823c19823dc9c7c6821ab0822a26c7c68210f9822b51c7c68222758222c0c7c6822f8a823e9ec7c6823c5e823dfac7c6822746823430c7c6822dc3823fb1c7c6822e06822f6cc7c6823e6c823ffcc7c6822c2b82405cc7c6823c07823cd0c7c6822fbf822fd7c7c6821a03823f93c7c68231bf82335dc7c68219a4823e78c7c6821ece82203dc7c6821839823c8ac7c68239a1823cbec7c68218d282279ec7c682372c82382fc7c6821a79823c17c7c6823ab1823b06c7c682268482269fc7c68224b68234a1c7c6822d88823edac7c6821862823c6bc7c6821a5582406fc7c682311682321ac7c6821abe822db4c7c682206d822499c7c6821975823ca1c7c68219db8236dbc7c68219b68229c8c7c68231ed8239e8c7c682237d8231a7c7c682190b823f01c7c6823b60823c9bc7c6821fdd8230b9c7c682331b823f34c7c6823b5f823bc4c7c6822c24823892c7c6823d11823ecbc7c6822d64823408c7c6821ad58236fec7c68222c2823eb9c7c68219a3823c8fc7c6821a66823d80c7c68218b6822ca7c7c68218c1821f1ac7c68227ea823c31c7c6821a56821adac7c682183b823902c7c68218848239f1c7c6820f74823e91c7c68220b0823be2c7c6822eb6823a98c7c6820ec88231c7c7c6822194823411c7c6821f1382310dc7c6822cb3823ab4c7c6821a8e822491c7c68225a1823300c7c682196f823d5dc7c6823ec7823f76c7c682187f82385fc7c68220c5823340c7c682378f823fc2c7c68224c2823320c7c6821c6e82283fc7c6821929823f71c7c6823671823844c7c6822f50823becc7c6821ee3823875c7c6822e72823636c7c68229c382380cc7c682210b8233ebc7c68230198234e1c7c6821a32822b8cc7c6823f2b824004c7c6821a0f823f04c7c6823555823dbbc7c6820d67822775c7c6821997822386c7c6821ce9823db6c7c6821854823ef0c7c6821ebd823960c7c68230f082349cc7c6823101823cb9c7c6823d45823ee9c7c6821a10822dfac7c6823271823a69c7c6822e44822f3dc7c68235a2824097c7c682194b8237ffc7c6821836823143c7c6821a7282329fc7c682326a8232e3c7c6822324822c45c7c6821c9082312bc7c6822f50823ebbc7c68234258237e5c7c6821e7482377ec7c6822972823d24c7c68225df823d43c7c6822b1d8238c1c7c6823ce7823dabc7c6821d188239a3c7c68226468238b1c7c6823531823d57c7c68232d18240b3c7c68239ac824060c7c6821ab4821da3c7c6821ad5821b57c7c68223e78223f8c7c6821ae6822de3c7c682187c8240b5c7c68234cc823598c7c6821e12823f07c7c6821f6f822828c7c682328882344dc7c6821cb5822e21c7c68222fe822edcc7c6823e85823f47c7c6821c6e823480c7c6821966822cd8c7c6823d07823f95c7c6822b70823e10c7c6822a08822a53c7c6823f0f82406dc7c6821f858234a9c7c6822c368238f0c7c6823092823e53c7c6821060823efcc7c6823855823bfbc7c6821e6082385ac7c68218758239e9c7c6823bf2824016c7c6823bb0823c3bc7c682289a82395fc7c68218c68235d2c7c6823ffb824064c7c68219c4822325c7c68218f3822263c7c682185a8233dfc7c6821a8f8226b5c7c68238fb823ce2c7c682248d82360ec7c68220bd823e08c7c6822e6d823719c7c68219d9822f12c7c6822705823266c7c68219cd823d31c7c6821d25822f54c7c682184a8227dbc7c6821e6b82369cc4c38240f5c7c68237b0823ba6c7c68218d38232a4c7c68218b482333bc7c682186f822684c7c68218ce823f7ac7c68218b9822845c7c68223378235bdc7c6821a748225ddc7c6823a5f823afdc7c6821f19823b83c7c6821ea6823f72c7c6821897823882c7c6822f74823581c7c6822ac1823f29c7c682195182272ac7c6821880823059c7c6821a828232ccc7c68238b98238edc7c682369f8236dec7c682193e8239cec7c6821f8882327cc7c6822e60823076c7c68223b78237fbc7c68218de822d96c7c6821826824054c7c6822238824032c7c68227a28234c2c7c68233608239dec7c682380f823af7c7c68218838240f9c7c68218ef8235fac7c6822f85823e29c7c6821aa9823f60c7c6821a2a823b29c7c682213f822dc8c7c6822c9c822eacc7c682368c8238fec7c6821848823ab0c7c68233ff823fb9c7c6821bb182328bc7c68219bd822f59c7c6823cff823d9ec7c68237ed823ba3c7c68225cf82274fc7c6820e708236c5c7c6822f62823d30c7c6821ec2823de6c7c68221678240f5c7c6821bed8236c9c7c68226968240f2c7c68225d2822644c7c68219ae822a5dc7c6821900823b14c7c6822754822d2ac7c68219b48236abc7c6822ff38238c9c7c6821fd182395ec7c68235dc823bf1c7c682250d823e05c7c68216ac822c07c7c6823255823fbec7c68221078230bcc7c68218c4823778c7c682193b822d09c7c6821b998230c6c7c68221e6823473c7c68219f1822813c7c682187d821cfac7c6821a07823402c7c6821c8b823809c7c682223e82336cc7c68224d78233fec7c6821d238235d7c7c6823c85823e74c7c68237d7824122c7c6821c528227c2c7c68237d38237ebc7c68231558240dac7c6821b92823458c7c68233688233dcc7c6821e50823e3ac7c68218d1823f75c7c6823734824109c7c682381b824151c7c68238f9823f85c7c6822d7a824085c7c68219608236f8c7c68234448239f5c7c68219ec823d76c7c682187a823efac7c6821b6b821c76c7c6821b5f822fb1c7c6823a30823be4c7c6821c01822675c7c68222a0822ceec7c6821b96821d64c7c6821cb4823da6c7c6823886823abcc7c68222dc823b47c7c6822deb823e54c7c6821a46823756c7c68230d8823fd0c7c68231f0823567c7c6822709823ef1c7c6822e13823798c7c6822a6b823dc4c7c68219c2823e5ac7c682197f823f5ac7c682190d8232d1c7c68232368237a5c7c6821df68226a5c7c6821e52823ea7c7c6822939823bb6c7c6822797823385c7c682310582374ac7c682319e823e58c7c6822cb7824168c7c6821d84823ac1c7c6823794823f4cc7c68218f08238cdc7c6822efe82309fc7c6823905823f3cc7c6823c80823cf7c7c6822d95823b09c7c6821fd4823fbdc7c6823a4e824110c7c6821b9b822891c7c6822e7f823b2ac7c6822475823da1c7c6821af58236f0c7c68219968223c1c7c6822272823d6ac7c6822970823bd0c7c682299c823815c7c6821da4823e2ac7c6821af2823751c7c6821bcf823693c7c682258a8240e0c7c6822b79823b67c7c68224d08225d5c7c68223b5823099c7c6823b97823fe7c7c68219e382239ac7c6821877822e3cc7c682314282412cc7c6821c4a823358c7c68218bf8237fec7c6821d858235edc7c682356d824010c7c6822c22823c37c7c6821b68823fccc7c6821f7f822e36c7c6821a6e823f0cc7c68218d9823afcc7c6823f1e823fbcc7c6821aed823fb4c7c6821dfd823d91c7c6821b32823445c7c6821c61821cccc7c6823678823848c7c6821b3f82408bc7c6821b79821f53c7c6821b6f8227fac7c6822620823e05c7c68218a0823f3dc7c6821bf382352dc7c6821afd82267bc7c682253b82346cc7c6822420822d7dc7c68228b2822a69c7c68219f3823b79c7c6822a8f82383bc7c6821b8882418ac7c6821bcc8236d0c7c68230ef8237d4c7c6822547823bd4c7c6821970823a62c7c6821b91822beac7c6821b9e821cefc7c6822683823bc2c7c682210e823738c7c6821932823058c7c6821dc6821fb1c7c682263d82335cc7c6821c5e822b42c7c6821ed6823854c7c68221e58227cfc7c6821746822758c7c6821bae8234d6c7c6823f9a824108c7c68228de823fdbc7c68219458227f2c7c6822841823468c7c6821aa0822a71c7c68232708235a4c7c6821bdf823e2cc7c6823cfa823d04c7c68224bb823fa4c7c6821aa38240afc7c6821608822d0ec7c6821f0b821f18c7c68221f2824048c7c68220dd8222f8c7c68219b8823e21c7c6822360822ccec7c682194f823fc1c7c6823b2d82405dc7c68230d0823453c7c68231f2823e1dc7c6821b5d822e55c7c682263e823edac7c6822c8282323ec7c6822f5b823da9c7c6823e6b824122c7c6821b098230bac7c6823b968241cfc7c6821de9823f6dc7c6821bb28237d9c7c682398e823d7cc7c682366f82418dc7c6822ebd823f9ac7c6821bf9821fe3c7c6821ae7824195c7c6821af7823c58c7c6821b0d823e59c7c6822172822754c7c68228f882401ac7c6821b1f823ba7c7c6822622823d1cc7c6823b3d824139c7c68229718229edc7c68227b1822f42c7c6823552823e63c7c6822c7c82417fc7c6823443823f20c7c6822a2a822e71c7c6823893824081c7c682288a823a34c7c6822f788236a3c7c6821af88232b3c7c6821b7a822fc0c7c682239d823f57c7c6821ba7823b82c7c6821b91823b65c7c6821b558226e5c7c68238d7823ed3c7c6821bd78238f5c7c6822d4e823825c7c6821b8182249bc7c6821dbe824019c7c6821b0c82201cc7c6822ec6823156c7c68220ae823fadc7c6822d82822e9fc7c68237108240d5c7c6822eb8823860c7c6821b6982254ac7c6823b49824091c7c6822eab824143c7c68223fc823c55c7c6822845823b36c7c6821e90823a6ac7c682201a8238ccc7c6821bfb823654c7c68222b4823fe8c7c682209f823e93c7c68225148238a3c7c6821bc582330ac7c68238dc82408dc7c682319d82331dc7c6821b5c824025c7c68240dd8241cdc7c68225be8225c7c7c68239e2823e23c7c6821b0382326ec7c68222b3823420c7c6822a3d823b97c7c6821812823feac7c6821788822531c7c6821bad823e69c7c682241e822538c7c682406a824167c7c6822ad58231d1c7c682206e8241fdc7c6821b20823cf4c7c68230618233fbc7c6822dc8823e0ec7c6823d81823de3c7c6821e5a82390fc7c68223228234c2c7c6821b7682311ac7c6823685823afac7c68226428239d3c7c6823039823c56c7c6821aee823f7cc7c6822f978239dbc7c68228b3823e2bc7c6822712823948c7c6821b4f823e83c7c6821b4d823e53c7c6822593822fa6c7c68220df8240b1c7c6821b52823845c7c6821b04823f03c7c6821b1c822eb1c7c6821b8d822b9fc7c6822f60823590c7c682328d823660c7c68232838236cec7c6821b2a823f56c7c68223dd8239dfc7c6821ba382357bc7c68227ba822959c7c6823064823a28c7c6821bc2823eedc7c6821bbe82400bc7c6821b8f823fdac7c68230d7823168c7c6823441823cd6c7c682206682423ac7c68234de823f91c7c6821bfa823a89c7c68229cf822a87c7c6823b93823c24c7c6822fb28231e6c7c68235b882360bc7c6823a568240d8c7c6823e5f823e99c7c6821b6c821becc7c6823a94824205c7c6822518822d50c7c6821b9382280bc7c68225e3823597c7c6821c07822c19c7c68232bb82336ec7c6822839823eb6c7c6822764823fa3c7c682207f823521c7c6822e86823ae9c7c6821b288238f1c7c6823027824216c7c682235b82361dc7c6822bac823a5bc7c6821aec82366ec7c6821fde82256cc7c6821c298237e7c7c6822adf823e1cc7c682164d82387dc7c6822d238241d9c7c68222068226dbc7c682309b823bf9c7c6821fb08239eec7c6821b7b823228c7c682209f822804c7c6821b73821be6c7c682352f823ebcc7c6821c058233f8c7c68222558228abc7c682361d82403ec7c682263582395cc7c6821c6a8225f6c7c6822cda823d35c7c6823ecf824228c7c682383c823c87c7c6821b15823db6c7c68224b0823333c7c68232d682358fc7c68220f2823708c7c6821b748226c8c7c6822346823d68c7c6821b1a821ce2c7c6822ae0823ff9c7c6822d6e823735c7c682225b82412fc7c6821d2d823c76c7c68226f0822ebac7c6821bda822bdcc7c6822e6082388ec7c682161982377ec7c6821c9e8238f3c7c6821d3782285bc7c6822dc2824143c7c6821b6a823bb2c7c6821b21823feac7c6822e88823895c7c6823715823d14c7c68233dd8236d5c7c6823d5682414cc7c682341a823f69c7c6822fc88240dfc7c682308582354ec7c6821bf082401cc7c6821d0a8232aec7c6822bcb8237aac7c6822cd08233d9c7c682396682396cc7c6821c3e822f17c7c6821b978240a0c7c6821bf582322bc7c682382682382ac7c6821b7c823e00c7c682399c823f68c7c68222d78236a4c7c6821b3c823b84c7c6821b65823e88c7c6822029823669c7c6821f45823130c7c68225a3823281c7c6821af98239cbc7c6823aeb8240a9c7c6822d7f823cccc7c6821e5c8241b3c7c68229ba8238c8c7c6821bd28241d9c7c682323082348fc7c6823953823a64c7c6823c38823d75c7c682236e8235b2c7c682260d823459c7c68228668239cdc7c68220a182415bc7c68238e6823ae7c7c68229b582373ec7c6821b94823620c7c68235a1823bccc7c6821be9823a53c7c68225838231ddc7c6821bd5822c14c7c6822cb8823d2bc7c6822b9d823176c7c682280b823e86c7c6821c74823073c7c6821c4b82422dc7c682354882387dc7c682313d82314ac7c68233f7823c88c7c6822e18823ae7c7c6821d9a823313c7c6821bd9823b36c7c6823806823b37c7c6821c60822224c7c6821ddf823e75c7c6823750823f65c7c6822149823c30c7c6821b9a823bb9c7c6821b70823b39c7c6821f2b823526c7c6822fbe823bd1c4c3821be7c7c68225f1823949c7c6821fc1822f54c7c6821b5a823e4fc7c6822e4e8239d5c7c6821c268224cec7c68239a6823a1ec7c6821e05823859c7c6821eaf822330c7c6822b258238c2c7c6821eac821f3fc7c68221508232c1c7c6821c3c82325ec7c6821c57823077c7c6823317824140c7c6820c8782347ac7c68242838242d0c7c682179282324cc7c682271b8242d2c7c6822ea1822eaac7c6822ae6823f53c4c3823558c7c68223918242d6c7c68234958239a7c7c682307f823d7bc7c6821c02823e8dc7c6821c6c822df6c7c68238b0823b02c7c6821c23823addc7c6821c39822f81c7c6822939823836c7c68230b182423dc4c38233b8c7c68240ce8242e1c7c6820a7782333cc7c6822fed8242e3c7c6822b7c82378bc7c6823805823ee5c7c6823a1f8242c3c7c6822c7d823215c7c682250e824181c7c682276c8242e9c7c6822b6e823f5cc7c68225948242ebc7c6821ee48234fac7c6823a23823da2c7c6822c1e824263c7c6822ec2823675c7c6823d4f823e71c7c68229ff824249c7c68221d982419fc7c6822592822ac2c7c68241978242f4c7c682176b823dbec7c68239368241abc7c68232428233c3c7c6823c348242f8c7c68227e1822f4ac4c3822050c7c682302b8242fbc7c6822c6d823d0ec7c682326b8242fdc7c6821c3d822289c7c6821e00822bc8c7c68237de8238f3c7c6823429823d30c7c68231ee8235f7c7c6823d36824303c7c6821bf4823b48c7c6823714823d63c7c6821c1c824120c7c68233a38240e6c7c68240bb82413ec7c6822321823bc3c7c6823dfa82430ac7c6823498823e36c7c6823bdf82430cc7c6822295822babc7c682361282430ec7c6823dcb824236c7c6822588823369c7c6823ba38241aac7c682224d8241d0c7c6822d48823a8ac7c6823773823934c7c6822ce8823a78c7c6822b04823d0cc7c6822f00823a64c7c68212f78242b0c7c6822ce0824319c7c6822003822cd4c7c6822aeb824135c7c6822048823ccac7c682047f822f1ac7c6822e3082431ec7c6821c2a823de8c7c6821c7c8227cfc7c68224c482264ec7c682416c8241f8c7c68223dd8228a6c7c682393b8241f3c7c6823aef823f1bc7c6821c64823566c7c6821e55823f26c7c6823a34823d75c7c6821c278235aac7c6822e9d82401fc7c6822e728234aac7c6822033823642c7c6822eec823effc7c6822c8e8239e2c7c682338d823883c7c6821c2d822cefc7c6821c1282249fc7c68234008241fbc7c6821fe3823896c7c682249282311ec7c68231aa823483c7c68227e08235e6c7c68227b1822893c7c6822992823ccec7c68221fc8231c1c7c68231d7823ea1c7c6822b59823d9bc7c68221b6823586c7c682325482420ac7c6821e4f823568c7c6821ea7821f8dc7c6823f988242a6c7c6822d02823563c7c6821f508234cdc7c6821d94821ea8c7c6822a10823b4ac7c682251e823205c7c6822f118237c5c7c6821eea822e28c7c6822b7d823bfec7c6821c25823ca5c7c68232d3823d8ec7c68222fb823d97c7c682364a8238c6c7c68234f1823a4cc7c682321b8232f3c7c6822f4f823c79c7c682342182373fc7c6821c6d8242f2c7c6822fe282408ec7c6823bf6824309c7c68236e0824354c7c6821c7382385fc7c682255f822babc7c6821c1a823d4bc7c68224f682333fc7c6821c828220a5c7c6823829824061c7c682259f822736c7c6823eea824107c7c6823bf3823ea3c7c6822049823668c7c6821c378233f7c4c3824230c7c6823246824361c7c68226e7823dc2c7c6821c2b82383ac7c68231c3823c03c7c6823eae824365c7c68234848242b7c7c682200b8223d8c7c682215b823fdfc7c6821c14821fdac4c382223dc7c682279382436bc7c682283a823e4dc7c6823b86823cb0c7c6821c19824066c7c6820ec182396bc7c682433e824370c4c3824358c7c68235aa824372c7c6821dbf82304fc7c6823e40823fc8c7c68222c6823698c7c6821e588234ebc7c68230fe824377c7c6821cae824265c7c68227bc8241b9c7c6821f1b8238acc7c6821c38822bd5c7c6822c8b823ca9c7c682260e823209c4c3820497c7c68240bf82437fc7c682207982428fc7c6823dd7824381c7c6822a378238aac7c6821c54823a6cc7c6821f90822ca0c7c6823dfd82437fc7c68236b9824386c7c6821c8c823cdfc7c6821e76822da3c7c6821f77824204c7c6822e1482401cc7c682213b8234e8c7c6822806823ad0c7c6821c35823908c7c68228c08230a8c4c3823414c7c68221d6824390c7c6823e57823f40c7c6821d63823d2ac7c682315882347bc7c6822ce4823343c7c6821c47823b33c7c68239d5823ed4c7c68215be824384c7c6821d8b823c4dc7c6823b0f824399c7c6821c22823e3dc7c68236bd8242c1c7c6822d1782439cc7c68212fc8236f6c7c68239a682439ec7c6822d3b8241d5c7c6821d798243a0c4c38235f9c7c6823c858243a2c7c682309e823e4bc7c6822c698243a4c7c68237188240cac7c6823352824395c7c682147c8235f2c7c6822c688243a8c7c6821c46822ea2c7c6822701823f6bc7c6823ff48243abc7c6823f70823ff7c7c6821fb5824071c7c68232fb8242a5c7c6821f30823667c7c6823652824276c7c6823fc78242a0c7c682255b8231d3c7c6821e3b8236f5c7c6822328822819c7c68232a082339bc7c68232b282429cc7c6822732822c84c7c6821f0182329ac7c6822041823f91c7c6821fcf8235d6c7c6821e4982316bc7c6821e8b8239e7c7c6821cba82326cc7c6822e6f8239edc7c6821e4d823588c7c68233d48239e3c7c682237382343cc7c68224a9823a17c7c6823df0824151c7c6823c6b824274c7c6821dd4822e0cc7c68231948240d4c7c68221e1823107c7c6821f27824328c7c68220838230f4c7c6822762822cf7c7c6821c958243bbc7c68222dc822cbec7c6821db68242f3c7c682309c8233dfc7c6821d728241b5c7c682379882410ec7c6823cfe823df1c7c682306b824346c7c6822d3f8233a8c7c682374482429ac7c682202f82434ac7c6822b38824050c7c6823a33823ed9c7c682276a823fc4c7c6823429823f71c7c6822b65822cadc7c6821d4f824365c7c682243c823a39c7c6821cc4823d23c7c68222da823fecc7c6821f998225efc7c68221a682405ac7c68239c4823c8ac7c682254d822e4fc7c6822f72823ff3c7c68231bb824255c7c6823114823624c7c6821dae823bf6c7c68229aa823b65c7c6822a20822ecfc7c6821caf823900c7c6822783822c29c7c68223a88234e7c7c6822d3c82429ec7c6821f4b8220aec7c6821d45823895c7c6821f968226adc7c6821d3882270dc7c6823d4b824003c7c682363082409fc7c6821d0e822ee1c7c6821d10823c9cc7c6822133823707c7c6821e20822074c7c68227f082438bc7c6823f8e823fe1c7c6821cd3823a59c7c68223bd822d98c7c6822c0b8243dac7c68223fb823c72c7c6821fa98239f6c7c6822162823536c7c6821fbf823406c7c6822622823243c7c68234cf823f32c7c6822d8b822ddec7c6823ac3823b18c7c682202682434bc7c6821d4c823e41c7c6823fe182439fc7c6821cd7823aaac7c6822061823ab7c7c68222ef822678c7c68226cd823df8c7c6822b178240d7c7c6822047823165c7c6821d9382368dc7c68234a58241dac7c6821fd7823ea4c7c682268582431bc7c6822044824156c7c6823c368241c7c7c6821f788233c2c7c68233a6823f3dc7c682200782385ec7c6821dba823bd2c7c68224ee822d34c7c682298a824138c7c68237d28241c0c7c68231c682329fc7c682200682271ac7c6821c97823791c7c6822718822c0ac7c6821eb0823cefc7c682318c823ac0c7c682205e822637c7c682231f8238a3c7c682202c823d1fc7c6823f3f823f5bc7c682215382395cc7c6823a948241f7c7c6822b62824254c7c6823e11824144c7c6823048823c0ec7c6821e0a823db8c7c6822adb824008c7c6822386823403c7c68226ee823fc4c7c682218f824182c7c6821e598235f4c7c68237ba8240f4c7c6823927824246c7c68237538242a7c7c6821dcd823de7c7c6821e5e822630c7c6822a8d8231b9c7c6821c898236d6c7c6821f70823909c7c68227ec82333dc7c6821ef08231f7c7c682323c823e45c7c6821df1822f31c7c6823383824423c7c6822e4d8238f7c7c6821ce58224b2c7c6823d44823dd6c7c68228bf824012c7c6821d9282315ac7c682385d823955c7c682324482416fc7c6822241822778c7c6823c91823ccbc7c6823119823c10c7c6823790824198c7c6821ff28238ebc7c6821e21823a47c7c682280e82335dc7c6823d9e824051c7c6822dcc822dd1c7c6821d54823b8dc7c6821f8482426fc7c6821eca823044c7c682233082289cc7c6821f238233d5c7c6821d6a823c2fc7c68228fe824156c7c6822b738237c6c7c682203c8235dac7c682233f823486c7c6821e938229f4c7c6822c74823636c7c6822037823fb6c7c6823721823e64c7c6822f8b822fc1c7c682333182366dc7c6821eae821f3cc7c6824065824203c7c682206782420dc7c6821e69823bbec7c6821d2582323bc7c6823c3f82424dc7c6821efa823ed7c7c68241b18242b8c7c6821ca0824316c7c6821e1d824170c7c682357d823ca0c7c68239fb823f18c7c6824091824297c7c682295a82372ec7c682206e823406c7c6821dc7822a89c7c6822056823f0bc7c682284b8236dcc7c6821adb822590c7c6821d628227aac7c6821f4482320fc7c6821d89822c70c7c6821f3e823b90c7c6823a84823aa3c7c682273c823c11c7c68236c382415ec7c6822724823663c7c6823bb8823f21c7c6821f6d822310c7c6823c308240dbc7c6822c808236cbc7c6823198823665c7c68234378243c4c7c6822d4982325dc7c6822069823376c7c6821d218233e6c7c682229982417dc7c6823c20823fc1c7c6821fbd823f30c7c68218c5822dfdc7c6822000822ca4c7c68232438235e6c7c6821e0d824153c7c6821e7c823d17c7c68241c282421dc7c6821e1e823f64c7c6822297823084c7c6823cbb824092c7c6821e878238a6c7c6821cdc822648c7c6821de4823aa8c7c6821d158232cdc7c6821fc2822a9ec7c6821d17821fdbc7c68236f1823978c7c6821ddc8243c2c7c6821dab823327c7c6822a7b823accc7c682296e823bf0c7c68222e7822cb5c7c6821dd1823b86c7c6821d24821f43c7c6823c418242c3c7c68225aa823edbc7c6821d528231b0c7c6821caa82410dc7c6821fab82242cc7c6822aad823b12c7c6821d7f823c66c7c6822253823388c7c68226df82444bc7c6822e6f823fdec7c6822f4382418ec7c6823ae6823b6ac7c6821de682345dc7c6822a03823a0fc7c6821fa3823bdec7c6821db28244a6c7c6821f57822c68c7c6822e94824419c7c682271982336dc7c682203682393fc7c6822211822d4cc7c6821e81823289c7c6821e318243c5c7c6821f33822ecec7c6823850823cf4c7c6823971824278c7c68231698233f9c7c6822f09823488c7c6822ad6823745c7c68232b5823f4fc7c6822618823c08c7c6821ca4821e6ec7c6821e9b8241ebc7c682205a82341ac7c6823600823bc8c7c6821f8182378dc7c6821cfc823ab0c7c6823c04823c0fc7c68232618242fbc7c6823bd782418bc7c68221a4823239c7c6823c63823ee3c7c682376d823b2cc7c6821db8823ff0c7c68237c2824134c7c6821d058232e9c7c6822a8a8237bdc7c68232ba823674c7c682265d8234e2c7c6823392823440c7c6822f21823055c7c68243448243b1c7c6821dc58234aec7c68222b282312ac7c6821cb1823517c7c6821f8c82355dc7c68241248241f2c7c6823c268241bfc7c682350c823efbc7c6821cc6823724c7c6822ebb823dcec7c682399e8241a6c7c682211d823dffc7c6822dbf823c60c7c6822020823ba8c7c6821e9682380bc7c68231d0823ed7c7c682379a823c29c7c6821e08823f7cc7c682288b82384fc7c6821eff823422c7c6823858823cbdc7c6822ebc823d38c7c6821e888240b0c7c68236da82394cc7c6822326824281c7c6821ed3824265c7c6822dca822e8fc7c6823036823269c7c6821d42823412c7c6823ab38242b5c7c68221118241cac7c6822468822649c7c6823d50823dcac7c68238cd8240bac7c6823fa6824430c7c6821f40823dd6c7c6823375823452c7c6823db4824200c7c68220fb823a8cc7c6821ff3823297c7c6821d098237f9c7c6822c76824070c7c68237018241a3c7c6822b9e823962c7c682405382428bc7c6821e33822fd7c7c682429182444cc7c6823c93824269c7c682204d823b30c7c6821f03823ec0c7c6822465823601c7c682400b82428cc7c6822c2b822dcec7c6821c8f822ce2c7c68219ff823f15c7c6823db282417ac7c6821e8e823ed0c7c6821cdb8227c7c7c682276f823d0fc7c6824183824410c7c6823517823954c7c6821cb3823bbdc7c68236d2823c6dc7c68224a88233f1c7c6821e2d822ef5c7c6823bdc8243aec7c6822f01823d12c7c6821ebb8231d6c7c68226f182312ec7c6822093822fffc7c68222c1822608c7c6822cc4823d47c7c68221c2823c43c7c6821ca282435bc7c6821ee58235a0c7c6821cea82406cc7c6822ea8824476c7c68222d58236dbc7c68222d08233d6c7c6822f19823bcbc7c68229dc823b7bc7c68235cd82418cc7c6821e35822cd7c7c6821e6882314cc7c6821ebe822dadc7c68233558242afc7c682202e8237b3c7c68223fc8231fdc7c6821f82823eddc7c682247982286bc7c68244bb8244c6c7c68237c3824300c7c682215782443cc7c6821fcc822dcfc7c6822c3a8235b4c7c6822c36823516c7c6822be78244bac7c6821f8082427ac7c682278d8242c2c7c68226c88230dec7c6821e358240d6c7c68236d18243d3c7c68239a5823e15c7c6823d94824074c7c6821ce1823f2ac7c6821cec8237f8c7c68220648228f0c7c68231fa82337fc7c68228c9823c50c7c68234ed823efbc7c6821e9f823f14c7c682251f822587c7c6821faa823ffac7c68228478244a1c7c6821eb4823b5fc7c6822b6d824020c7c6821cbb823f3ec7c6822c0c823082c7c6821fef824259cac9821b34823dcb823fe9c7c6821cf582233ec7c6822484824049c7c6823782823926c7c6821e7f822e8ec7c6822989824229c7c6823085823ee4c7c6822b21823afdc7c682217e82280ac7c6821f63822de8c7c6821dd382357ec7c682321c823d3cc7c6821de8823a31c7c68228198238a8c7c682219282333ac7c68231848234fdc7c6823dd8824363c7c6821fa58221b6c7c68233b58234f3c7c6823e44824063c7c682218d823623c7c6823154823a51c7c6823cf882425ec7c6821da0822d43c7c6821e1a823d8cc7c6821ef18241b7c7c6821d51823234c7c68233bb82448ec7c6821ce78230e9c7c682397c823ebac7c6823ebf82446fc7c6823fac8242bcc7c6821ceb82388fc7c6821d78822b49c7c6821df38238a4c7c6821f918220bac7c6821f5c824056c7c6822072823a5bc7c6822056823eb2c7c6823c3b824095c7c682272882405ec7c68220d482417bc7c682354782454fc7c682376e8242d5c7c6822e738241d6c7c6821fb2823121c7c682243e8229f8c7c68237aa823df7c7c6821e4182222ec7c6821ddb822427c7c682251c822584c7c6822be5823919c7c682379c823d60c7c68220e2823303c7c6821f26823db0c7c6822ad282371dc7c6821ef982399fc7c6821e10822c26c7c68234968239e6c7c68221e6823cadc7c682296982436fc7c6822993823f8dc7c6823d8282400cc7c68221f5824340c7c68231718231f3c7c6823938823dd3c7c682266c82309fc7c682343a8243cdc7c68237cd8239acc7c6822476824201c7c682204f823afbc7c682200c8243a9c7c68243ac82449ac7c6823b2282442bc7c6821d1482389dc7c6823f4e82404cc7c68231b98236b5c7c6821d688239bfc7c68232f28236d3c7c6821d438236b2c7c6821e7e823c53c7c6821fe182314cc7c6822019824258") +} diff --git a/core/pevm_processor.go b/core/pevm_processor.go new file mode 100644 index 0000000000..0dfa15b121 --- /dev/null +++ b/core/pevm_processor.go @@ -0,0 +1,403 @@ +package core + +import ( + "errors" + "fmt" + "runtime" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/metrics" + + "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" +) + +type PEVMProcessor struct { + StateProcessor + allTxReqs []*PEVMTxRequest + delayGasFee bool + commonTxs []*types.Transaction + receipts types.Receipts + debugConflictRedoNum uint64 +} + +func newPEVMProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *PEVMProcessor { + processor := &PEVMProcessor{ + StateProcessor: *NewStateProcessor(config, bc, engine), + } + log.Info("Parallel execution mode is enabled", "Parallel Num", ParallelNum(), + "CPUNum", runtime.NumCPU()) + return processor +} + +type PEVMTxResult struct { + txReq *PEVMTxRequest + receipt *types.Receipt + slotDB *state.UncommittedDB + gpSlot *GasPool + evm *vm.EVM + result *ExecutionResult + originalNonce *uint64 + err error +} + +type PEVMTxRequest struct { + txIndex int + baseStateDB *state.StateDB + tx *types.Transaction + gasLimit uint64 + msg *Message + block *types.Block + vmConfig vm.Config + usedGas *uint64 + useDAG bool + value int // only for unit test +} + +// resetState clear slot state for each block. +func (p *PEVMProcessor) resetState(txNum int, statedb *state.StateDB) { + statedb.PrepareForParallel() + p.allTxReqs = make([]*PEVMTxRequest, txNum) +} + +// hasConflict conducts conflict check +func (p *PEVMProcessor) hasConflict(txResult *PEVMTxResult) error { + slotDB := txResult.slotDB + if txResult.err != nil { + log.Warn("execute result failed, HasConflict due to err", "err", txResult.err) + return txResult.err + } + // check whether the slot db reads during execution are correct. + if err := slotDB.ConflictsToMaindb(); err != nil { + log.Debug("executed result conflicts", "err", err) + return err + } + return nil +} + +// executeInSlot do tx execution with thread local slot. +func (p *PEVMProcessor) executeInSlot(maindb *state.StateDB, txReq *PEVMTxRequest) *PEVMTxResult { + slotDB := state.NewUncommittedDB(maindb) + 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 = slotDB.GetNonce(txReq.msg.From) + } + + slotDB.SetTxContext(txReq.tx.Hash(), txReq.txIndex) + evm, result, err := pevmApplyTransactionStageExecution(txReq.msg, gpSlot, slotDB, vmenv, p.delayGasFee) + txResult := PEVMTxResult{ + txReq: txReq, + receipt: nil, + slotDB: slotDB, + err: err, + gpSlot: gpSlot, + evm: evm, + result: result, + originalNonce: &on, + } + return &txResult +} + +// 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 *PEVMProcessor) toConfirmTxIndexResult(txResult *PEVMTxResult) error { + txReq := txResult.txReq + if err := p.hasConflict(txResult); err != nil { + log.Info(fmt.Sprintf("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex)) + return err + } + + // 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) + return fmt.Errorf("gasConsumed != result.UsedGas mismatch") + } + + // ok, time to do finalize, stage2 should not be parallel + txResult.receipt, txResult.err = pevmApplyTransactionStageFinalization(txResult.evm, txResult.result, + *txReq.msg, p.config, txResult.slotDB, txReq.block, + txReq.tx, txReq.usedGas, txResult.originalNonce) + return nil +} + +// wait until the next Tx is executed and its result is merged to the main stateDB +func (p *PEVMProcessor) confirmTxResult(statedb *state.StateDB, gp *GasPool, result *PEVMTxResult) error { + checkErr := p.toConfirmTxIndexResult(result) + // ok, the tx result is valid and can be merged + if checkErr != nil { + return checkErr + } + + 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()) + return fmt.Errorf("gas limit reached") + } + + 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) + if err := result.slotDB.Merge(); err != nil { + // something very wrong, should not happen + log.Error("merge slotDB failed", "err", err) + return err + } + result.slotDB.Finalise(isByzantium || isEIP158) + + delayGasFee := result.result.delayFees + // add delayed gas fee + if delayGasFee != nil { + if delayGasFee.TipFee != nil { + statedb.AddBalance(delayGasFee.Coinbase, delayGasFee.TipFee) + } + if delayGasFee.BaseFee != nil { + statedb.AddBalance(params.OptimismBaseFeeRecipient, delayGasFee.BaseFee) + } + if delayGasFee.L1Fee != nil { + statedb.AddBalance(params.OptimismL1FeeRecipient, delayGasFee.L1Fee) + } + } + + // Do IntermediateRoot after mergeSlotDB. + if !isByzantium { + root = statedb.IntermediateRoot(isEIP158).Bytes() + } + result.receipt.PostState = root + p.receipts[result.txReq.txIndex] = result.receipt + p.commonTxs[result.txReq.txIndex] = result.txReq.tx + return nil +} + +// Process implements BEP-130 Parallel Transaction Execution +func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { + var ( + 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) + } + + misc.EnsureCreate2Deployer(p.config, block.Time(), statedb) + + allTxs := block.Transactions() + p.resetState(len(allTxs), statedb) + + 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) + } + statedb.MarkFullProcessed() + txDAG := cfg.TxDAG + + txNum := len(allTxs) + // Iterate over and process the individual transactions + p.commonTxs = make([]*types.Transaction, txNum) + p.receipts = make([]*types.Receipt, txNum) + p.debugConflictRedoNum = 0 + + for i, tx := range allTxs { + // parallel start, wrap an exec message, which will be dispatched to a slot + txReq := &PEVMTxRequest{ + txIndex: i, + baseStateDB: statedb, + tx: tx, + gasLimit: block.GasLimit(), // gp.Gas(). + block: block, + vmConfig: cfg, + usedGas: usedGas, + useDAG: txDAG != nil, + } + p.allTxReqs[i] = txReq + } + p.delayGasFee = true + if txDAG != nil && !txDAG.DelayGasFeeDistribution() { + p.delayGasFee = false + } + + // parallel execution + start := time.Now() + txLevels := NewTxLevels(p.allTxReqs, txDAG) + buildLevelsDuration := time.Since(start) + var executeDurations, confirmDurations int64 = 0, 0 + err, txIndex := txLevels.Run(func(pr *PEVMTxRequest) (res *PEVMTxResult) { + defer func(t0 time.Time) { + atomic.AddInt64(&executeDurations, time.Since(t0).Nanoseconds()) + if res.err != nil { + atomic.AddUint64(&p.debugConflictRedoNum, 1) + } + }(time.Now()) + + if err := buildMessage(pr, signer, header); err != nil { + return &PEVMTxResult{txReq: pr, err: err} + } + return p.executeInSlot(statedb, pr) + }, func(pr *PEVMTxResult) (err error) { + defer func(t0 time.Time) { + atomic.AddInt64(&confirmDurations, time.Since(t0).Nanoseconds()) + if err != nil { + atomic.AddUint64(&p.debugConflictRedoNum, 1) + } + }(time.Now()) + return p.confirmTxResult(statedb, gp, pr) + }) + parallelRunDuration := time.Since(start) - buildLevelsDuration + if err != nil { + tx := allTxs[txIndex] + log.Error("ProcessParallel tx failed", "txIndex", txIndex, "txHash", tx.Hash().Hex(), "err", err) + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", txIndex, tx.Hash().Hex(), err) + } + + //fmt.Printf("ProcessParallel tx all done, parallelNum:%d, txNum: %d, conflictNum: %d, executeDuration:%s, confirmDurations:%s, buildLevelsDuration:%s, runDuration:%s\n", + // ParallelNum(), txNum, p.debugConflictRedoNum, time.Duration(executeDurations), time.Duration(confirmDurations), buildLevelsDuration, parallelRunDuration) + + // len(commonTxs) could be 0, such as: https://bscscan.com/block/14580486 + var redoRate int = 0 + if len(p.commonTxs) == 0 { + redoRate = 100 * (int(p.debugConflictRedoNum)) / 1 + } else { + redoRate = 100 * (int(p.debugConflictRedoNum)) / len(p.commonTxs) + } + pevmBuildLevelsTimer.Update(buildLevelsDuration) + pevmRunTimer.Update(parallelRunDuration) + log.Info("ProcessParallel tx all done", "block", header.Number, "usedGas", *usedGas, + "parallelNum", ParallelNum(), + "buildLevelsDuration", buildLevelsDuration, + "parallelRunDuration", parallelRunDuration, + "executeDurations", time.Duration(executeDurations), + "confirmDurations", time.Duration(confirmDurations), + "txNum", txNum, + "len(commonTxs)", len(p.commonTxs), + "conflictNum", p.debugConflictRedoNum, + "redoRate(%)", redoRate, + "txDAG", txDAG != nil) + if metrics.EnabledExpensive { + parallelTxNumMeter.Mark(int64(len(p.commonTxs))) + parallelConflictTxNumMeter.Mark(int64(p.debugConflictRedoNum)) + } + + // 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, p.commonTxs, block.Uncles(), withdrawals) + + var allLogs []*types.Log + for _, receipt := range p.receipts { + allLogs = append(allLogs, receipt.Logs...) + } + return p.receipts, allLogs, *usedGas, nil +} + +func buildMessage(txReq *PEVMTxRequest, signer types.Signer, header *types.Header) error { + if txReq.msg != nil { + return nil + } + msg, err := TransactionToMessage(txReq.tx, signer, header.BaseFee) + if err != nil { + return err + //return fmt.Errorf("could not apply tx %d [%v]: %w", txReq.txIndex, txReq.tx.Hash().Hex(), err) + } + txReq.msg = msg + return nil +} + +func pevmApplyTransactionStageExecution(msg *Message, gp *GasPool, statedb *state.UncommittedDB, 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). + 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 + } + + return evm, result, err +} + +func pevmApplyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg Message, config *params.ChainConfig, statedb *state.UncommittedDB, 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. + 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.PackLogs(block.NumberU64(), block.Hash()) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + receipt.BlockHash = block.Hash() + receipt.BlockNumber = block.Number() + receipt.TransactionIndex = uint(statedb.TxIndex()) + return receipt, nil +} diff --git a/core/pevm_processor_test.go b/core/pevm_processor_test.go new file mode 100644 index 0000000000..4f1c5ff26d --- /dev/null +++ b/core/pevm_processor_test.go @@ -0,0 +1,265 @@ +package core + +import ( + "context" + "crypto/ecdsa" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "math/big" + "os" + "runtime" + "sync" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "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/ethclient" + "github.com/ethereum/go-ethereum/params" +) + +type parallel chan func() + +func (p parallel) do(f func()) { + p <- f +} + +func (p parallel) close() { + close(p) +} + +func (p parallel) start(pnum int) { + for i := 0; i < pnum; i++ { + go func() { + for f := range p { + f() + } + }() + } +} + +type keypair struct { + key *ecdsa.PrivateKey + addr common.Address +} + +func randAddress() (common.Address, *ecdsa.PrivateKey) { + // Generate a new private key using rand.Reader + key, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + if err != nil { + panic(fmt.Sprintf("Failed to generate private key: %v", err)) + } + return crypto.PubkeyToAddress(key.PublicKey), key +} + +func generateAddress(num int) []*keypair { + proc := parallel(make(chan func())) + proc.start(16) + address := make([]*keypair, num) + wait := sync.WaitGroup{} + wait.Add(num) + for i := 0; i < num; i++ { + index := i + proc.do(func() { + addr, key := randAddress() + address[index] = &keypair{key, addr} + wait.Done() + }) + } + wait.Wait() + proc.close() + return address +} + +func genesisAlloc(addresses []*keypair, funds *big.Int) GenesisAlloc { + alloc := GenesisAlloc{} + for _, addr := range addresses { + alloc[addr.addr] = GenesisAccount{Balance: funds} + } + return alloc +} + +func TestPevmInsertChain(t *testing.T) { + //from := 8000 + //to := 8001 + //endpoint := "https://opbnb-qanet-ec-5-seq-pevm-2.bk.nodereal.cc" + //gJson := "/Users/awen/Desktop/Nodereal/aweneagle_projects/chain-infra/qa/gitops/qa-us/opbnb-qanet-ec-5/contracts-info/genesis.json" + //genesis := loadGenesis(gJson) + //blocks := fetchBlocks(uint64(from), uint64(to), endpoint) + ////preapare parent header + //chain := buildBlockChain(genesis, false) + //chain.hc.headerCache.Add(blocks[0].Hash(), blocks[0].Header()) + //if err := InsertChain(chain, blocks[1:]); err != nil { + // t.Fatal(err) + //} +} + +func fetchBlocks(from, to uint64, endpoint string) []*types.Block { + client, err := ethclient.Dial(endpoint) + if err != nil { + panic(err) + } + defer client.Close() + + var blocks []*types.Block + for i := from; i <= to; i++ { + block, err := client.BlockByNumber(context.Background(), big.NewInt(int64(i))) + if err != nil { + panic(err) + } + blocks = append(blocks, block) + } + return blocks +} + +func loadGenesis(jsonfile string) *Genesis { + // Open the JSON file + file, err := os.Open(jsonfile) + if err != nil { + panic("failed to open json file, err=" + err.Error()) + } + defer file.Close() + + // Read the file content + bytes, err := io.ReadAll(file) + if err != nil { + panic(fmt.Sprintf("failed to read genesis file: %v", err)) + } + + // Unmarshal the JSON content into the Genesis struct + var genesis Genesis + if err := json.Unmarshal(bytes, &genesis); err != nil { + panic(fmt.Sprintf("failed to unmarshal genesis JSON: %v", err)) + } + return &genesis +} + +func buildBlockChain(genesis *Genesis, parallel bool) *BlockChain { + archiveDb := rawdb.NewMemoryDatabase() + // Import the chain as an archive node for the comparison baseline + archive, err := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, ethash.NewFaker(), vm.Config{EnableParallelExecV2: parallel}, nil, nil) + if err != nil { + panic(err) + } + return archive +} + +func InsertChain(bc *BlockChain, blocks []*types.Block) error { + _, err := bc.InsertChain(blocks) + return err +} + +func BenchmarkAkaka(b *testing.B) { + addrNum := 10000 + // Configure and generate a sample block chain + funds := big.NewInt(1000000000000000) + addresses := generateAddress(addrNum) + genesisAlloc := genesisAlloc(addresses, funds) + randomAddr := make(chan common.Address, addrNum) + for addr := range genesisAlloc { + randomAddr <- addr + } + var ( + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: genesisAlloc, + BaseFee: big.NewInt(params.InitialBaseFee), + GasLimit: 500000000, + } + signer = types.LatestSigner(gspec.Config) + ) + + _, blocks, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 2, func(i int, block *BlockGen) { + block.SetCoinbase(common.Address{0x00}) + txs := make([]*types.Transaction, len(addresses)) + for i, addr := range addresses { + // borrow an address + to := <-randomAddr + from := addr + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(from.addr), to, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, from.key) + if err != nil { + panic(err) + } + txs[i] = tx + randomAddr <- to + } + for i := 0; i < len(txs); i++ { + block.AddTx(txs[i]) + } + }) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + archiveDb := rawdb.NewMemoryDatabase() + // Import the chain as an archive node for the comparison baseline + archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExecV2: true}, nil, nil) + if n, err := archive.InsertChain(blocks); err != nil { + panic(fmt.Sprintf("failed to process block %d: %v", n, err)) + } + archive.Stop() + } +} + +var cacheLock = sync.RWMutex{} +var cached = make(map[common.Address]uint64) + +func getAddressSafe(addr common.Address) uint64 { + cacheLock.Lock() + defer cacheLock.Unlock() + return cached[addr] +} + +func getAddress(addr common.Address) uint64 { + return cached[addr] +} + +func BenchmarkSequencialRead(b *testing.B) { + // generate a set of addresses + // case 1: read the state sequentially + // case 2: read them in parallel + address := generateAddress(10000) + for i := 0; i < len(address); i++ { + cached[address[i].addr] = uint64(i) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // iterate all the address + for i := 0; i < len(address); i++ { + getAddress(address[i].addr) + } + } +} + +func BenchmarkParallelRead(b *testing.B) { + // generate a set of addresses + // case 1: read the state sequentially + // case 2: read them in parallel + address := generateAddress(10000) + for i := 0; i < len(address); i++ { + cached[address[i].addr] = uint64(i) + } + + proc := parallel(make(chan func())) + proc.start(runtime.NumCPU()) + wait := sync.WaitGroup{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + // iterator all the address in parallel + wait.Add(len(address)) + for i := 0; i < len(address); i++ { + index := i + proc.do(func() { + getAddressSafe(address[index].addr) + wait.Done() + }) + } + wait.Wait() + } + proc.close() +} diff --git a/core/state/pevm_journal.go b/core/state/pevm_journal.go new file mode 100644 index 0000000000..397dc44360 --- /dev/null +++ b/core/state/pevm_journal.go @@ -0,0 +1,327 @@ +package state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +type jBalance struct { + created bool // whether the object is newly created in the uncommitted db + addr common.Address + prev *uint256.Int +} + +func newJBalance(obj *state, addr common.Address) *jBalance { + if obj == nil { + return &jBalance{ + created: true, + addr: addr, + prev: nil, + } + } else { + return &jBalance{ + created: false, + addr: addr, + prev: new(uint256.Int).Set(obj.balance), + } + } +} + +func (j *jBalance) revert(db *UncommittedDB) { + if j.created { + delete(db.cache, j.addr) + return + } + db.cache.setBalance(j.addr, j.prev) +} + +type jNonce struct { + created bool + addr common.Address + nonce uint64 +} + +func newJNonce(obj *state, addr common.Address) *jNonce { + if obj == nil { + return &jNonce{ + created: true, + addr: addr, + nonce: 0, + } + } else { + return &jNonce{ + created: false, + addr: addr, + nonce: obj.nonce, + } + } +} + +func (j *jNonce) revert(db *UncommittedDB) { + if j.created { + delete(db.cache, j.addr) + return + } + db.cache.setNonce(j.addr, j.nonce) +} + +type jStorage struct { + created bool + addr common.Address + keyCreated bool // whether the key is newly created in the object + key, val common.Hash +} + +func newJStorage(obj *state, addr common.Address, key common.Hash) *jStorage { + if obj == nil { + return &jStorage{ + created: true, + addr: addr, + keyCreated: false, + key: key, + val: common.Hash{}, + } + } + val, ok := obj.state[key] + if !ok { + return &jStorage{ + created: false, + addr: addr, + keyCreated: true, + key: key, + val: common.Hash{}, + } + } + return &jStorage{ + created: false, + addr: addr, + keyCreated: false, + key: key, + val: val, + } +} + +func (j *jStorage) revert(db *UncommittedDB) { + if j.created { + delete(db.cache, j.addr) + return + } + if j.keyCreated { + delete(db.cache[j.addr].state, j.key) + } else { + db.cache.setState(j.addr, j.key, j.val) + } +} + +type jCode struct { + created bool + addr common.Address + code []byte +} + +func newJCode(obj *state, addr common.Address) *jCode { + if obj == nil { + return &jCode{ + created: true, + addr: addr, + code: nil, + } + } else { + return &jCode{ + created: false, + addr: addr, + code: append([]byte(nil), obj.code...), + } + } +} + +func (j *jCode) revert(db *UncommittedDB) { + if j.created { + delete(db.cache, j.addr) + return + } + db.cache.setCode(j.addr, j.code) +} + +type jCreateAccount struct { + replaced bool + addr common.Address + obj *state +} + +func newJCreateAccount(obj *state, addr common.Address) *jCreateAccount { + if obj == nil { + return &jCreateAccount{ + addr: addr, + obj: nil, + replaced: false, + } + } else { + return &jCreateAccount{ + addr: addr, + obj: obj.clone(), + replaced: true, + } + } +} + +func (j *jCreateAccount) revert(db *UncommittedDB) { + if !j.replaced { + delete(db.cache, j.addr) + } else { + db.cache[j.addr] = j.obj + } +} + +type jSelfDestruct struct { + addr common.Address + obj *state +} + +func newJSelfDestruct(obj *state) *jSelfDestruct { + if obj == nil { + //@TODO: should we handle this case? + return &jSelfDestruct{ + addr: common.Address{}, + obj: nil, + } + } + return &jSelfDestruct{ + addr: obj.addr, + obj: &state{ + modified: obj.modified, + addr: obj.addr, + balance: new(uint256.Int).Set(obj.balance), + nonce: obj.nonce, + code: append([]byte(nil), obj.code...), + codeHash: append([]byte(nil), obj.codeHash...), + codeSize: obj.codeSize, + created: obj.created, + deleted: obj.deleted, + }, + } +} + +func (j *jSelfDestruct) revert(db *UncommittedDB) { + db.cache[j.addr] = j.obj +} + +type jLogs struct { +} + +func (j *jLogs) revert(db *UncommittedDB) { + if len(db.logs) == 0 { + // it should never happen + return + } + db.logs = db.logs[:len(db.logs)-1] +} + +type jRefund struct { + prev uint64 +} + +func newJRefund(prev uint64) *jRefund { + return &jRefund{ + prev: prev, + } +} + +func (j *jRefund) revert(db *UncommittedDB) { + db.refund = j.prev +} + +type jPreimage struct { + created bool + hash common.Hash +} + +func newJPreimage(hash common.Hash) *jPreimage { + return &jPreimage{ + created: true, + hash: hash, + } +} + +func (j *jPreimage) revert(db *UncommittedDB) { + delete(db.preimages, j.hash) +} + +type jAccessList struct { + addr *common.Address +} + +func (j *jAccessList) revert(db *UncommittedDB) { + db.accessList.DeleteAddress(*j.addr) +} + +type jAccessListSlot struct { + addr *common.Address + slot *common.Hash +} + +func (j *jAccessListSlot) revert(db *UncommittedDB) { + db.accessList.DeleteSlot(*j.addr, *j.slot) +} + +type jLog struct { + i uint +} + +func newJLog(i uint) *jLog { + return &jLog{ + i: i, + } +} + +func (j *jLog) revert(db *UncommittedDB) { + db.logs = db.logs[:j.i] +} + +type jTransientStorage struct { + addr common.Address + key, val common.Hash +} + +func newJTransientStorage(addr common.Address, key, val common.Hash) *jTransientStorage { + return &jTransientStorage{ + addr: addr, + key: key, + val: val, + } +} + +func (j *jTransientStorage) revert(db *UncommittedDB) { + storage, ok := db.transientStorage[j.addr] + if !ok { + return + } + delete(storage, j.key) + if len(storage) == 0 { + delete(db.transientStorage, j.addr) + } +} + +type ujournal []ustate + +func (j *ujournal) append(st ustate) { + *j = append(*j, st) +} + +func (j *ujournal) revertTo(db *UncommittedDB, snapshot int) { + if snapshot < 0 || snapshot >= len(*j) { + return // invalid snapshot index + } + for i := len(*j) - 1; i >= snapshot; i-- { + (*j)[i].revert(db) + } + *j = (*j)[:snapshot] +} + +func (j *ujournal) snapshot() int { + return len(*j) +} + +type ustate interface { + revert(db *UncommittedDB) +} diff --git a/core/state/pevm_journal_test.go b/core/state/pevm_journal_test.go new file mode 100644 index 0000000000..0b1ae01e18 --- /dev/null +++ b/core/state/pevm_journal_test.go @@ -0,0 +1,154 @@ +package state + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +func TestPevmJournalOfState(t *testing.T) { + var snapid int + var ( + Address1 = common.Address{0x01} + Address2 = common.Address{0x02} + Address3 = common.Address{0x03} + Address4 = common.Address{0x04} + ) + prepare := Tx{ + {"SetNonce", Address1, 1}, + {"AddBalance", Address2, big.NewInt(100)}, + {"SetState", Address3, "key", "value"}, + {"SetCode", Address4, []byte{0x04}}, + } + // set states that will be reverted + tx := Tx{ + {"Snapshot", &snapid}, + {"SetNonce", Address1, 2}, + {"AddBalance", Address2, big.NewInt(100)}, + {"SetState", Address3, "key", "value3"}, + {"SetCode", Address4, []byte{0x040}}, + } + revertTx := Tx{ + {"Revert", &snapid}, + } + beforeRevert := Checks{ + {"nonce", Address1, 2}, + {"balance", Address2, big.NewInt(200)}, + {"state", Address3, "key", "value3"}, + {"code", Address4, []byte{0x040}}, + } + afterRevert := Checks{ + {"nonce", Address1, 1}, + {"balance", Address2, big.NewInt(100)}, + {"state", Address3, "key", "value"}, + {"code", Address4, []byte{0x04}}, + } + + statedb := newStateDB() + prepare.Call(statedb) + tx.Call(statedb) + if err := beforeRevert.Verify(statedb); err != nil { + t.Fatalf("[maindb]before revert: %v", err) + } + revertTx.Call(statedb) + if err := afterRevert.Verify(statedb); err != nil { + t.Fatalf("[maindb]after revert: %v", err) + } + + uncommitted := NewUncommittedDB(newStateDB()) + prepare.Call(uncommitted) + tx.Call(uncommitted) + if err := beforeRevert.Verify(uncommitted); err != nil { + t.Fatalf("[uncommitted]before revert: %v", err) + } + revertTx.Call(uncommitted) + if err := afterRevert.Verify(uncommitted); err != nil { + t.Fatalf("[uncommitted]after revert: %v", err) + } +} + +func TestPevmJournal(t *testing.T) { + var snapid int + var tx1 = common.Hash{0x012} + var txIndex = 20 + var ( + Address1 = common.Address{0x01} + Address2 = common.Address{0x02} + Address3 = common.Address{0x03} + Address4 = common.Address{0x04} + Address5 = common.Address{0x05} + Address6 = common.Address{0x06} + ) + tx := Tx{ + {"Snapshot", &snapid}, + {"SetNonce", Address1, 1}, + {"AddBalance", Address2, big.NewInt(100)}, + {"SetState", Address3, "key", "value"}, + {"SetCode", Address4, []byte{0x04}}, + {"Create", Address5}, + {"AddPreimage", common.Hash{0x3}, []byte{0x05}}, + {"SetTransientStorage", Address6, "hash", "tx"}, + {"AddRefund", 102}, + {"AddLog", &types.Log{TxHash: tx1, Data: []byte("hello"), TxIndex: uint(txIndex)}}, + {"AddLog", &types.Log{TxHash: tx1, Data: []byte("world"), TxIndex: uint(txIndex)}}, + {"AddAddress", Address1}, + {"AddSlots", Address2, common.Hash{0x22}}, + } + revertTx := Tx{ + {"Revert", &snapid}, + } + beforeRevert := Checks{ + {"nonce", Address1, 1}, + {"balance", Address2, big.NewInt(100)}, + {"state", Address3, "key", "value"}, + {"code", Address4, []byte{0x04}}, + {"exists", Address5, true}, + {"preimage", common.Hash{0x3}, []byte{0x05}}, + {"tstorage", Address6, "hash", "tx"}, + {"refund", 102}, + {"log", tx1, 0, []byte("hello"), txIndex, 0}, + {"log", tx1, 1, []byte("world"), txIndex, 1}, + {"loglen", 2}, + {"address", Address1, true}, + {"address", Address2, true}, + {"slot", Address2, common.Hash{0x22}, true}, + } + afterRevert := Checks{ + {"exists", Address1, false}, + {"exists", Address2, false}, + {"exists", Address3, false}, + {"exists", Address4, false}, + {"exists", Address5, false}, + {"preimageExists", common.Hash{0x3}, false}, + {"tstorage", Address6, "hash", ""}, + {"refund", 0}, + {"loglen", 0}, + {"address", Address1, false}, + {"address", Address2, false}, + {"slot", Address2, common.Hash{0x22}, false}, + } + + statedb := newStateDB() + statedb.SetTxContext(tx1, txIndex) + tx.Call(statedb) + if err := beforeRevert.Verify(statedb); err != nil { + t.Fatalf("[maindb]before revert: %v", err) + } + revertTx.Call(statedb) + if err := afterRevert.Verify(statedb); err != nil { + t.Fatalf("[maindb]after revert: %v", err) + } + + uncommitted := NewUncommittedDB(newStateDB()) + uncommitted.SetTxContext(tx1, txIndex) + tx.Call(uncommitted) + if err := beforeRevert.Verify(uncommitted); err != nil { + t.Fatalf("[uncommitted]before revert: %v", err) + } + revertTx.Call(uncommitted) + if err := afterRevert.Verify(uncommitted); err != nil { + t.Fatalf("[uncommitted]after revert: %v", err) + } +} diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go new file mode 100644 index 0000000000..7092996e65 --- /dev/null +++ b/core/state/pevm_statedb.go @@ -0,0 +1,920 @@ +package state + +import ( + "bytes" + "errors" + "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/params" + "github.com/holiman/uint256" +) + +// UncommittedDB is a wrapper of StateDB, which records all the writes of the state. +// It is designed for parallel running of the EVM. +// All fields of UncommittedDB survive in the scope of a transaction. +// Here is the original score of each field in the maindb(StateDB): +// |------------------|---------|---------------------|------------------------| +// | field | score | need to be merged | need to be committed | +// |------------------|---------|---------------------|------------------------| +// | accessList | block: before Berlin hardfork ; transaction : after Berlin hardfork | no | no | +// | transientStorage | transaction | no | no | +// | objects | block | yes | yes | +// | refund | block | yes | yes | +// | preimages | block | yes | yes | +// |------------------|---------|-------|-------| +type UncommittedDB struct { + isBerlin bool + discarded bool + + // transaction context + txHash common.Hash + txIndex int + logs []*types.Log + + // accessList + accessList *accessList + + // in the maindb, transientStorage survives only in the scope of a transaction, and no need to + //@todo make transientStorage lazy loaded + transientStorage transientStorage + + // object reads and writes + reads reads + cache writes + + // in the maindb, refund survives in the scope of block; and will be reset to 0 whenever a transaction's modification is Finalized to trie . + prevRefund *uint64 + refund uint64 + + // in the maindb, preimages survives in the scope of block + // it might be too large that we need to lazy load it. + preimages map[common.Hash][]byte + preimagesReads map[common.Hash][]byte + + journal ujournal + + maindb *StateDB +} + +func NewUncommittedDB(maindb *StateDB) *UncommittedDB { + return &UncommittedDB{ + accessList: newAccessList(), + transientStorage: newTransientStorage(), + maindb: maindb, + reads: make(reads), + cache: make(writes), + } +} + +// @TODO drop? +func (pst *UncommittedDB) BeforeTxTransition() { +} + +// @TODO drop? +func (pst *UncommittedDB) FinaliseRWSet() error { + return nil +} + +// @TODO drop? +func (pst *UncommittedDB) TxIndex() int { + return pst.txIndex +} + +func (pst *UncommittedDB) SetTxContext(txHash common.Hash, txIndex int) { + pst.txHash = txHash + pst.txIndex = txIndex +} + +// =============================================== +// Constructor +func (pst *UncommittedDB) CreateAccount(addr common.Address) { + pst.journal.append(newJCreateAccount(pst.cache[addr], addr)) + obj := pst.getDeletedObject(addr, pst.maindb) + // keep the balance + pst.cache.create(addr) + if obj != nil { + pst.cache[addr].balance.Set(obj.balance) + } +} + +// Prepare handles the preparatory steps for executing a state transition with. +// This method must be invoked before state transition. +// +// Berlin fork: +// - Add sender to access list (2929) +// - Add destination to access list (2929) +// - Add precompiles to access list (2929) +// - Add the contents of the optional tx access list (2930) +// +// Potential EIPs: +// - Reset access list (Berlin) +// - Add coinbase to access list (EIP-3651) +// - Reset transient storage (EIP-1153) +func (pst *UncommittedDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + // do nothing, because the work had been done in constructer NewUncommittedDB() + // 0. init accessList + // 1. init transientStorage + pst.isBerlin = rules.IsBerlin + if rules.IsBerlin { + // Clear out any leftover from previous executions + al := newAccessList() + pst.accessList = al + + al.AddAddress(sender) + if dst != nil { + al.AddAddress(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range precompiles { + al.AddAddress(addr) + } + for _, el := range list { + al.AddAddress(el.Address) + for _, key := range el.StorageKeys { + al.AddSlot(el.Address, key) + } + } + if rules.IsShanghai { // EIP-3651: warm coinbase + al.AddAddress(coinbase) + } + } else { + pst.accessList = pst.maindb.accessList.Copy() + } + // Reset transient storage at the beginning of transaction execution + pst.transientStorage = newTransientStorage() +} + +// =============================================== +// Object Methods +// 1. journal +// 2. object + +func (pst *UncommittedDB) SubBalance(addr common.Address, amount *uint256.Int) { + pst.journal.append(newJBalance(pst.cache[addr], addr)) + obj := pst.getOrNewObject(addr) + newb := new(uint256.Int).Sub(obj.balance, amount) + pst.cache.setBalance(addr, newb) +} + +func (pst *UncommittedDB) AddBalance(addr common.Address, amount *uint256.Int) { + pst.journal.append(newJBalance(pst.cache[addr], addr)) + obj := pst.getOrNewObject(addr) + newb := new(uint256.Int).Add(obj.balance, amount) + pst.cache.setBalance(addr, newb) +} + +func (pst *UncommittedDB) GetBalance(addr common.Address) *uint256.Int { + if obj := pst.getObject(addr); obj != nil { + return new(uint256.Int).Set(obj.balance) + } + return uint256.NewInt(0) +} + +func (pst *UncommittedDB) GetNonce(addr common.Address) uint64 { + if obj := pst.getObject(addr); obj != nil { + return obj.nonce + } + return 0 +} + +func (pst *UncommittedDB) SetNonce(addr common.Address, nonce uint64) { + pst.journal.append(newJNonce(pst.cache[addr], addr)) + pst.getOrNewObject(addr) + pst.cache.setNonce(addr, nonce) +} + +func (pst *UncommittedDB) GetCodeHash(addr common.Address) common.Hash { + obj := pst.getDeletedObjectWithCode(addr, pst.maindb) + if obj == nil || obj.deleted { + return common.Hash{} + } + return common.BytesToHash(obj.codeHash) +} + +func (pst *UncommittedDB) GetCode(addr common.Address) []byte { + obj := pst.getDeletedObjectWithCode(addr, pst.maindb) + if obj == nil || obj.deleted { + return nil + } + return obj.code +} + +func (pst *UncommittedDB) GetCodeSize(addr common.Address) int { + obj := pst.getDeletedObjectWithCode(addr, pst.maindb) + if obj == nil || obj.deleted { + return 0 + } + return obj.codeSize +} + +func (pst *UncommittedDB) SetCode(addr common.Address, code []byte) { + pst.journal.append(newJCode(pst.cache[addr], addr)) + if obj := pst.getDeletedObjectWithCode(addr, pst.maindb); obj == nil || obj.deleted { + pst.journal.append(newJCreateAccount(pst.cache[addr], addr)) + pst.cache.create(addr) + } + pst.cache.setCode(addr, code) +} + +func (pst *UncommittedDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + // this is a uncommitted db, so just get it from the maindb + //@TODO GetCommittedState() need to be thread safe + return pst.maindb.GetCommittedState(addr, hash) +} + +func (pst *UncommittedDB) GetState(addr common.Address, hash common.Hash) common.Hash { + obj := pst.getDeletedObjectWithState(addr, pst.maindb, hash) + if obj == nil || obj.deleted { + return common.Hash{} + } + return obj.state[hash] +} + +func (pst *UncommittedDB) SetState(addr common.Address, key, value common.Hash) { + pst.journal.append(newJStorage(pst.cache[addr], addr, key)) + if obj := pst.getDeletedObjectWithState(addr, pst.maindb, key); obj == nil || obj.deleted { + pst.journal.append(newJCreateAccount(pst.cache[addr], addr)) + pst.cache.create(addr) + } + pst.cache.setState(addr, key, value) +} + +func (pst *UncommittedDB) SelfDestruct(addr common.Address) { + pst.journal.append(newJSelfDestruct(pst.cache[addr])) + if obj := pst.getObject(addr); obj == nil { + return + } + pst.cache.selfDestruct(addr) +} + +func (pst *UncommittedDB) HasSelfDestructed(addr common.Address) bool { + if obj := pst.getObject(addr); obj != nil { + return obj.selfDestruct + } + return false +} + +func (pst *UncommittedDB) Selfdestruct6780(addr common.Address) { + obj := pst.getObject(addr) + if obj == nil { + return + } + if obj.created { + pst.cache.selfDestruct(addr) + } +} + +func (pst *UncommittedDB) Exist(addr common.Address) bool { + return pst.getObject(addr) != nil +} + +func (pst *UncommittedDB) Empty(addr common.Address) bool { + obj := pst.getObject(addr) + return obj == nil || obj.empty() +} + +// =============================================== +// Refund Methods +// 1. journal +// 2. refunds + +func (pst *UncommittedDB) AddRefund(gas uint64) { + pst.journal.append(newJRefund(pst.refund)) + pst.recordRefundOnce() + pst.refund += gas +} + +func (pst *UncommittedDB) SubRefund(gas uint64) { + pst.journal.append(newJRefund(pst.refund)) + pst.recordRefundOnce() + if pst.refund < gas { + panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, pst.refund)) + } + pst.refund -= gas +} + +func (pst *UncommittedDB) GetRefund() uint64 { + pst.recordRefundOnce() + return pst.refund +} + +func (pst *UncommittedDB) recordRefundOnce() { + if pst.prevRefund == nil { + refund := pst.maindb.GetRefund() + pst.prevRefund = &refund + pst.refund = refund + } +} + +// =============================================== +// transientStorage Methods (EIP-1153: https://eips.ethereum.org/EIPS/eip-1153) + +func (pst *UncommittedDB) GetTransientState(addr common.Address, key common.Hash) common.Hash { + return pst.transientStorage.Get(addr, key) +} +func (pst *UncommittedDB) SetTransientState(addr common.Address, key, value common.Hash) { + pst.journal.append(newJTransientStorage(addr, key, value)) + pst.transientStorage.Set(addr, key, value) +} + +// =============================================== +// AccessList Methods (EIP-2930: https://eips.ethereum.org/EIPS/eip-2930) +func (pst *UncommittedDB) AddressInAccessList(addr common.Address) bool { + return pst.accessList.ContainsAddress(addr) +} +func (pst *UncommittedDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { + return pst.accessList.Contains(addr, slot) +} +func (pst *UncommittedDB) AddAddressToAccessList(addr common.Address) { + if pst.accessList.AddAddress(addr) { + pst.journal.append(&jAccessList{addr: &addr}) + } +} +func (pst *UncommittedDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + addrChanged, slotChanged := pst.accessList.AddSlot(addr, slot) + if addrChanged { + pst.journal.append(&jAccessList{addr: &addr}) + } + if slotChanged { + pst.journal.append(&jAccessListSlot{addr: &addr, slot: &slot}) + } +} + +// =============================================== +// Snapshot Methods +// (is it necessary to do snapshot and revert ?) +func (pst *UncommittedDB) RevertToSnapshot(id int) { + pst.journal.revertTo(pst, id) +} +func (pst *UncommittedDB) Snapshot() int { + return pst.journal.snapshot() +} + +// =============================================== +// Logs Methods +func (pst *UncommittedDB) AddLog(log *types.Log) { + pst.journal.append(&jLog{i: uint(len(pst.logs))}) + log.TxHash = pst.txHash + log.TxIndex = uint(pst.txIndex) + // we don't need to set Index now, because it will be recalculated when merging into maindb + log.Index = uint(len(pst.logs)) + pst.logs = append(pst.logs, log) +} + +// PackLogs returns the logs matching the specified transaction hash, and annotates +// them with the given blockNumber and blockHash. +func (s *UncommittedDB) PackLogs(blockNumber uint64, blockHash common.Hash) []*types.Log { + for _, l := range s.logs { + l.BlockNumber = blockNumber + l.BlockHash = blockHash + } + return s.logs +} + +// =============================================== +// Preimage Methods (EIP-1352: https://eips.ethereum.org/EIPS/eip-1352) +func (pst *UncommittedDB) AddPreimage(hash common.Hash, preimage []byte) { + pst.journal.append(newJPreimage(hash)) + pst.recordPreimageOnce(hash) + if _, ok := pst.preimages[hash]; !ok { + pi := make([]byte, len(preimage)) + copy(pi, preimage) + pst.preimages[hash] = pi + } +} + +func (pst *UncommittedDB) recordPreimageOnce(hash common.Hash) { + // first time to read the preimage + if pst.preimages == nil { + // load all preimages from maindb + pst.preimages = make(map[common.Hash][]byte) + pst.preimagesReads = make(map[common.Hash][]byte) + for h, pi := range pst.maindb.preimages { + pst.preimagesReads[h], pst.preimages[h] = pi, pi + } + } + if _, ok := pst.preimagesReads[hash]; ok { + return + } + // get and record the preimage state from the maindb + preimageFromMaindb, ok := pst.maindb.preimages[hash] + if ok { + pst.preimagesReads[hash] = preimageFromMaindb + } else { + // not exists in the maindb too + pst.preimagesReads[hash] = nil + } +} + +func (pst *UncommittedDB) Preimages() map[common.Hash][]byte { + return pst.preimages +} + +// check conflict +func (pst *UncommittedDB) conflictsToMaindb() error { + // 1. check preimages reads conflict (@TODO preimage should be lazy loaded) + // 2. check accesslist reads conflict (@TODO confirm: whether the accesslist should be checked or not) + // 3. check logs conflict (check the txIndex) + // 4. check object conflict + if err := pst.hasLogConflict(pst.maindb); err != nil { + return err + } + if err := pst.hasRefundConflict(pst.maindb); err != nil { + return err + } + if err := pst.hasPreimageConflict(pst.maindb); err != nil { + return err + } + return pst.reads.conflictsTo(pst.maindb) +} + +func (pst *UncommittedDB) hasLogConflict(maindb *StateDB) error { + if pst.txIndex == 0 { + // this is the first transaction in the block, + // so the logs should be empty + // and the maindb.txIndex should be -1 + if maindb.logSize != 0 { + return fmt.Errorf("conflict logs, logSize: %d, expected 0", maindb.logSize) + } + if len(maindb.logs) != 0 { + return fmt.Errorf("conflict logs, logsLen: %d, expected 0", len(maindb.logs)) + } + } else { + // this is not the first transaction in the block + if maindb.txIndex != int(pst.txIndex)-1 { + return fmt.Errorf("conflict txIndex, txIndex: %d, expected %d", maindb.txIndex, pst.txIndex-1) + } + } + return nil +} + +func (pst *UncommittedDB) hasPreimageConflict(maindb *StateDB) error { + for hash, preimage := range pst.preimagesReads { + mainstate := maindb.preimages[hash] + if !bytes.Equal(preimage, mainstate) { + return fmt.Errorf("conflict preimage, hash:%s, expected:%s, actual:%s", hash.String(), preimage, mainstate) + } + } + return nil +} + +func (pst *UncommittedDB) hasRefundConflict(maindb *StateDB) error { + // never read + if pst.prevRefund == nil { + return nil + } + if *pst.prevRefund != maindb.GetRefund() { + return fmt.Errorf("conflict refund, expected:%d, actual:%d", *pst.prevRefund, maindb.GetRefund()) + } + return nil +} + +func (pst *UncommittedDB) ConflictsToMaindb() error { + return pst.conflictsToMaindb() +} + +func (pst *UncommittedDB) Merge() error { + if pst.discarded { + // all the writes of this db will be discarded, including: + // 1. accessList + // 2. preimages + // 3. obj states + // 4. refund + // so we don't need to merge anything. + return nil + } + if err := pst.conflictsToMaindb(); err != nil { + return err + } + + // 0. set the TxContext + pst.maindb.SetTxContext(pst.txHash, pst.txIndex) + // 1. merge preimages writes + for hash, preimage := range pst.preimages { + pst.maindb.preimages[hash] = preimage + } + // 2. merge accesslist writes + // we need to merge accessList before Berlin hardfork + if !pst.isBerlin { + for addr, slot := range pst.accessList.addresses { + pst.maindb.accessList.AddAddress(addr) + if slot >= 0 { + slots := pst.accessList.slots[slot] + for hash := range slots { + pst.maindb.accessList.AddSlot(addr, hash) + } + } + } + } else { + // after Berlin hardfork, all the accessList should be reset before a transaction was executed + pst.maindb.accessList = pst.accessList + } + // 3. merge logs writes + for _, st := range pst.cache { + st.merge(pst.maindb) + } + // 4. merge object states + for _, log := range pst.logs { + pst.maindb.AddLog(log) + } + // 5. merge refund + if pst.refund != 0 { + pst.maindb.AddRefund(pst.refund) + } + return nil +} + +func (pst *UncommittedDB) Finalise(deleteEmptyObjects bool) { + pst.maindb.Finalise(deleteEmptyObjects) +} + +// getDeletedObj returns the state object for the given address or nil if not found. +// it first gets from the maindb, if not found, then get from the maindb. +// it never modifies the maindb. the maindb is read-only. +// there are some cases to be handle: +// 1. it exists in the maindb's cache +// a) just return it +// 2. it exists in the maindb, but not in the cache: +// a) record a read state +// b) clone it, and put it into the cache +// c) return it +// 3. it doesn't exist in the maindb, and neighter in the slaveb: +// a) record a read state with create = true +// b) return nil +// +// it is notable that the getObj() will not be called parallelly, but the getStateObject() of maindb will be. +// so we need: +// 1. the uncommittedDB's cache is not thread safe +// 2. the maindb's cache is thread safe +func (pst *UncommittedDB) getDeletedObject(addr common.Address, maindb *StateDB) (o *state) { + if pst.cache[addr] != nil { + return pst.cache[addr] + } + // it reads the cache from the maindb + obj := maindb.getDeletedStateObject(addr) + defer func() { + pst.reads.recordOnce(addr, copyObj(obj)) + }() + if obj == nil { + // the object is not found, do a read record + return nil + } + // write it into the cache and return + pst.cache[addr] = copyObj(obj) + return pst.cache[addr] +} + +// getObject returns the state object for the given address or nil if not found. +// 1. it first gets from the cache. +// 2. if not found, then get from the maindb, +// 3. record its state in maindb for the first read, which is for further conflict check. +func (pst *UncommittedDB) getObject(addr common.Address) *state { + obj := pst.getDeletedObject(addr, pst.maindb) + if obj == nil || obj.deleted { + return nil + } + return obj +} + +// getOrNewObj returns the state object for the given address or create a new one if not found. +// 1. it first gets from the cache. +// 2. if not found, then get from the maindb: +// 2.1) if not found +// 2.1.1) create a new one in the cache, keep maindb unchanged +// 2.1.2) record a read state with create = true +// 2.1.3) record a write cache with create = true, balance = 0 +// 2.2) if found, record its state in maindb for the first read, which is for further conflict check. +// +// 3. return the state object +func (pst *UncommittedDB) getOrNewObject(addr common.Address) *state { + obj := pst.getObject(addr) + if obj != nil { + return obj + } + return pst.cache.create(addr) +} + +func (pst *UncommittedDB) getObjectWithCode(addr common.Address) *state { + obj := pst.getObject(addr) + if obj == nil { + return nil + } + if obj.codeIsLoaded() { + return obj + } + // try to load the code from maindb + deletedObject := pst.maindb.getStateObject(addr) + if deletedObject == nil { + return nil + } + code, codeHash := deletedObject.Code(), common.BytesToHash(deletedObject.CodeHash()) + obj.code = code + obj.codeHash = codeHash[:] + obj.codeSize = len(code) + return obj +} + +// getDeletedObjectWithCode return an object with code, and load the code from maindb if it is not loaded. +func (pst *UncommittedDB) getDeletedObjectWithCode(addr common.Address, maindb *StateDB) (o *state) { + o = pst.getDeletedObject(addr, maindb) + if o == nil { + pst.reads.recordCodeOnce(addr, common.Hash{}, nil) + return nil + } + if o.codeIsLoaded() { + return o + } + // load code from maindb + deletedObj := pst.maindb.getDeletedStateObject(addr) + if deletedObj == nil { + pst.reads.recordCodeOnce(addr, common.Hash{}, nil) + } else { + pst.reads.recordCodeOnce(addr, common.BytesToHash(deletedObj.CodeHash()), deletedObj.Code()) + } + // set code into the cache + code, codeHash := deletedObj.Code(), common.BytesToHash(deletedObj.CodeHash()) + o.code = code + o.codeHash = codeHash[:] + o.codeSize = len(code) + return o +} + +// getDeletedObjectWithState return an object with state, and load the state from maindb if it is not loaded. +func (pst *UncommittedDB) getDeletedObjectWithState(addr common.Address, maindb *StateDB, hash common.Hash) (o *state) { + o = pst.getDeletedObject(addr, maindb) + if o == nil { + pst.reads.recordKVOnce(addr, hash, common.Hash{}) + return nil + } + if _, ok := o.state[hash]; ok { + return o + } + // load code from maindb + deletedObj := pst.maindb.getDeletedStateObject(addr) + var value = common.Hash{} + if deletedObj == nil { + pst.reads.recordKVOnce(addr, hash, common.Hash{}) + } else { + value = deletedObj.GetState(hash) + pst.reads.recordKVOnce(addr, hash, value) + } + // set code into the cache + o.state[hash] = value + return o +} + +type state struct { + // object states + modified int32 //records all the modified fields + addr common.Address + balance *uint256.Int + nonce uint64 + //@TODO code is lazy loaded, be careful to record its state + code []byte + codeHash []byte + codeSize int // when codeSize == -1, it means the code is unloaded + state map[common.Hash]common.Hash + selfDestruct bool + created bool + deleted bool +} + +func (c *state) clone() *state { + newstate := make(map[common.Hash]common.Hash, len(c.state)) + for k, v := range c.state { + newstate[k] = v + } + return &state{ + modified: c.modified, + addr: c.addr, + balance: new(uint256.Int).Set(c.balance), + nonce: c.nonce, + code: append([]byte(nil), c.code...), + codeHash: append([]byte(nil), c.codeHash...), + codeSize: c.codeSize, + state: newstate, + selfDestruct: c.selfDestruct, + created: c.created, + deleted: c.deleted, + } +} + +func (s *state) markAllModified() { + s.modified |= ModifyBalance + s.modified |= ModifyNonce + // @TODO confirm: whether the code should be reset or not? + // @TODO confirm: whether the state should be reset or not? + s.modified |= ModifyCode + s.modified |= ModifyState +} + +// check whether the state of current object is conflicted with the maindb +func (s state) conflicts(maindb *StateDB) error { + addr := s.addr + obj := maindb.getDeletedStateObject(addr) + // created == true means it doen't exist in the maindb + if s.created != (obj == nil) { + return errors.New("conflict: created") + } + // newly created object, no need to compare anything + if obj == nil { + return nil + } + // they are all deleted, no need to compare anything + //if obj.deleted && s.deleted { + // return nil + //} + if s.deleted != obj.deleted { + return fmt.Errorf("conlict: deleted, expected:%t, actual:%t", s.deleted, obj.deleted) + } + if s.selfDestruct != obj.selfDestructed { + return fmt.Errorf("conflict: selfDestruct, expected:%t, actual:%t", s.selfDestruct, obj.selfDestructed) + } + if s.nonce != obj.Nonce() { + return fmt.Errorf("conflict: nonce, expected:%d, actual:%d", s.nonce, obj.Nonce()) + } + if s.balance.Cmp(obj.Balance()) != 0 { + return fmt.Errorf("conflict: balance, expected:%d, actual:%d", s.balance.Uint64(), obj.data.Balance.Uint64()) + } + // code is lazy loaded + if s.codeIsLoaded() && !bytes.Equal(obj.Code(), s.code) { + return fmt.Errorf("conflict: code, expected len:%d, actual len:%d", len(s.code), len(obj.Code())) + } + // state is lazy loaded, and should be checked as less as possible , too. + for key, val := range s.state { + if obj.GetState(key).Cmp(val) != 0 { + return fmt.Errorf("conflict: state, key:%s, expected:%s, actual:%s", key.String(), val.String(), obj.GetState(key).String()) + } + } + return nil +} + +func (s state) merge(maindb *StateDB) { + // 1. merge the balance + // 2. merge the nonce + // 3. merge the code + // 4. merge the state + if s.modified&ModifySelfDestruct != 0 { + maindb.SelfDestruct(s.addr) + return + } + obj := maindb.getOrNewStateObject(s.addr) + if s.modified&ModifyBalance != 0 { + obj.SetBalance(s.balance) + } + if s.modified&ModifyNonce != 0 { + obj.SetNonce(s.nonce) + } + if s.modified&ModifyCode != 0 { + obj.SetCode(common.BytesToHash(s.codeHash), s.code) + } + if s.modified&ModifyState != 0 { + for key, val := range s.state { + obj.SetState(key, val) + } + //TODO: should we reset all kv pairs if the s.state == nil ? + } +} + +func (s *state) empty() bool { + return s.nonce == 0 && s.balance.Sign() == 0 && bytes.Equal(s.codeHash, types.EmptyCodeHash.Bytes()) +} + +func (s *state) codeIsLoaded() bool { + return s.codeSize != -1 +} + +func copyObj(obj *stateObject) *state { + if obj == nil { + return nil + } + // we don't copy the fields of `code` and `state` here, because they are lazy loaded. + // we don't copy the `created` eigher, because it true only when the object is nil, which means "it was created newly by the uncommited db". + // we need to copy the fields `deleted`, because it identifies whether the object is deleted or not. + return &state{ + addr: obj.Address(), + nonce: obj.Nonce(), + balance: new(uint256.Int).Set(obj.Balance()), + selfDestruct: obj.selfDestructed, + deleted: obj.deleted, // deleted is true when a "selfDestruct=true" object is finalized. more details can be found in the method Finalize() of StateDB + codeSize: -1, // mark the code "unloaded" + state: make(map[common.Hash]common.Hash), + } +} + +type reads map[common.Address]*state + +func (sts reads) recordOnce(addr common.Address, st *state) *state { + if _, ok := sts[addr]; !ok { + if st == nil { + // this is a newly created object + sts[addr] = &state{addr: addr, created: true, state: make(map[common.Hash]common.Hash), codeSize: -1} + } else { + sts[addr] = st + } + } + return st +} + +func (sts reads) recordKVOnce(addr common.Address, key, val common.Hash) { + obj := sts[addr] + if _, ok := obj.state[key]; !ok { + obj.state[key] = val + } +} + +func (sts reads) recordCodeOnce(addr common.Address, codeHash common.Hash, code []byte) { + st := sts[addr] + // we can't check whether the code is loaded or not, by checking codeHash == nil or code == nil (maybe the code is empty) + // so we need another fields to record the code previous state. + // and that is the `codeSize`, when codeSize == -1, it means the code is unloaded. + if !st.codeIsLoaded() { + st.codeHash = codeHash[:] + st.code = code + st.codeSize = len(code) + } +} + +func (sts reads) conflictsTo(maindb *StateDB) error { + for addr, st := range sts { + if err := st.conflicts(maindb); err != nil { + return fmt.Errorf("conflict at address %s: %s", addr.String(), err.Error()) + } + } + return nil +} + +// =============================================== +// Writes Methods +// it is used to record all the writes of the uncommitted db. + +type writes map[common.Address]*state + +const ( + ModifyNonce = 1 << iota + ModifyBalance + ModifyCode + ModifyState + ModifySelfDestruct + ModifySelfDestruct6780 +) + +func (wst writes) setBalance(addr common.Address, balance *uint256.Int) { + wst[addr].balance = balance + wst[addr].modified |= ModifyBalance +} + +func (wst writes) setNonce(addr common.Address, nonce uint64) { + wst[addr].nonce = nonce + wst[addr].modified |= ModifyNonce +} + +func (wst writes) setState(addr common.Address, key, val common.Hash) { + wst[addr].state[key] = val + wst[addr].modified |= ModifyState +} + +func (wst writes) setCode(addr common.Address, code []byte) { + codeHash := crypto.Keccak256Hash(code) + wst[addr].code = code + wst[addr].codeHash = codeHash[:] + wst[addr].codeSize = len(code) + wst[addr].modified |= ModifyCode +} + +func (wst writes) create(addr common.Address) *state { + wst[addr] = &state{ + addr: addr, + balance: uint256.NewInt(0), + nonce: 0, + //need to init code & codeHash + code: nil, + codeHash: types.EmptyCodeHash.Bytes(), + // mark the code "unloaded"? + //codeSize: -1, + //need to reset storage + state: make(map[common.Hash]common.Hash), + created: true, + } + wst[addr].markAllModified() + return wst[addr] +} + +func (wst writes) selfDestruct(addr common.Address) { + obj := wst[addr] + if obj == nil { + return + } + obj.modified |= ModifySelfDestruct + obj.selfDestruct = true + obj.balance = uint256.NewInt(0) +} + +func (wst writes) merge(maindb *StateDB) { + for _, st := range wst { + st.merge(maindb) + } +} diff --git a/core/state/pevm_statedb_test.go b/core/state/pevm_statedb_test.go new file mode 100644 index 0000000000..a86ee7fc0f --- /dev/null +++ b/core/state/pevm_statedb_test.go @@ -0,0 +1,1944 @@ +package state + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rand" + "fmt" + "math/big" + "runtime" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "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/params" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + "github.com/holiman/uint256" +) + +var Address1 = common.HexToAddress("0x1") +var Address2 = common.HexToAddress("0x2") + +// TestUncommitedDBCreateAccount tests the creation of an account in an uncommited DB. +func TestPevmUncommitedDBCreateAccount(t *testing.T) { + // case 1. create an account without previous state + txs := Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SetNonce", Address1, 2}, + }, + } + check := CheckState{ + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 2}, + }, + Maindb: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + }, + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 2}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + // case 2. create an account with previous state + txs = Txs{ + // previous state + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(200)}, + {"SetNonce", Address1, 2}, + }, + // create a new account, should have a balance of 100, and nonce = 0 + { + {"Create", Address1}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(200)}, + {"nonce", Address1, 0}, + }, + } + if er := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); er != nil { + t.Fatalf("ut failed, err=%s", er.Error()) + } +} + +func TestPevmAddBalance(t *testing.T) { + // case 1. add balance to an account without previous state + txs := Txs{ + { + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + check := CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 0}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +func TestPevmSubBalance(t *testing.T) { + txs := Txs{ + { + {"AddBalance", Address1, big.NewInt(120)}, + {"SubBalance", Address1, big.NewInt(100)}, + {"SetNonce", Address1, 2}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + Maindb: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"balance", Address1, big.NewInt(20)}, + {"nonce", Address1, 2}, + }, + Maindb: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + }, + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(20)}, + {"nonce", Address1, 2}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +func TestPevmSetCode(t *testing.T) { + // case 1. set code to an account without previous state + txs := Txs{ + { + {"SetCode", Address1, []byte("hello")}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("")}, + }, + Maindb: []Check{ + {"code", Address1, []byte("")}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("hello")}, + }, + Maindb: []Check{ + {"code", Address1, []byte("")}, + }, + }, + AfterMerge: []Check{ + {"code", Address1, []byte("hello")}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 2. + txs = Txs{ + { + {"Create", Address1}, + {"SetCode", Address1, []byte("hello")}, + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"code", Address1, []byte("hello")}, + {"balance", Address1, big.NewInt(100)}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmSetState(t *testing.T) { + // 1. set state to an account without previous state, check getState, getCommittedState + txs := Txs{ + { + {"SetState", Address1, "key1", "value1"}, + {"SetState", Address1, "key2", "value2"}, + {"SetNonce", Address1, 1}, // to avoid the deletion when finalise + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"state", Address1, "key1", ""}, + {"state", Address1, "key2", ""}, + {"obj", Address1, "nil"}, + }, + Maindb: []Check{ + {"state", Address1, "key1", ""}, + {"state", Address1, "key2", ""}, + {"obj", Address1, "nil"}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key2", "value2"}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"state", Address1, "key1", ""}, + {"state", Address1, "key2", ""}, + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key2", "value2"}, + {"cstate", Address1, "key1", "value1"}, + {"cstate", Address1, "key2", "value2"}, + {"obj", Address1, "exists"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // 1. set state to an account without previous state, check getState, getCommittedState + prepare := Txs{ + { + {"Create", Address1}, + {"SetState", Address1, "key0", "value0"}, + {"SetState", Address1, "key1", "valuea"}, + {"AddBalance", Address1, big.NewInt(100)}, // to avoid the deletion when finalise + }, + } + statedb := newStateDB() + prepare.Call(statedb) + sroot := statedb.IntermediateRoot(true) + + maindb := newStateDB() + prepare.Call(maindb) + proot := maindb.IntermediateRoot(true) + if sroot.Cmp(proot) != 0 { + t.Fatalf("ut failed, err=%s", "roots mismatch") + } + uncommitedDB := newUncommittedDB(maindb) + + txs = Txs{ + { + {"SetState", Address1, "key1", "value1"}, + {"SetState", Address1, "key2", "value2"}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"state", Address1, "key1", "valuea"}, + {"state", Address1, "key2", ""}, + {"cstate", Address1, "key1", "valuea"}, + {"cstate", Address1, "key0", "value0"}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"state", Address1, "key1", "valuea"}, + {"state", Address1, "key2", ""}, + {"cstate", Address1, "key1", "valuea"}, + {"cstate", Address1, "key0", "value0"}, + {"obj", Address1, "exists"}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key2", "value2"}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"obj", Address1, "exists"}, + {"state", Address1, "key1", "valuea"}, + {"state", Address1, "key2", ""}, + }, + }, + AfterMerge: []Check{ + {"state", Address1, "key0", "value0"}, + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key2", "value2"}, + {"cstate", Address1, "key0", "value0"}, + {"cstate", Address1, "key1", "value1"}, + {"cstate", Address1, "key2", "value2"}, + {"obj", Address1, "exists"}, + {"balance", Address1, big.NewInt(100)}, + }, + } + if err := runCase(txs, statedb, uncommitedDB, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmSelfDestructStateDB(t *testing.T) { + //1. create address1, set code, set balance, set nonce + //2. self destruct address1 + //3. check the state of address1 before finalize + //4. finalize the stateDB + //5. check the state of address1 after finalize + statedb := newStateDB() + uncommitedState := newUncommittedDB(newStateDB()) + prepare := Txs{ + { + {"Create", Address1}, + {"SetNonce", Address1, 12}, + {"SetCode", Address1, []byte("hello world")}, + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + prepare.Call(statedb) + prepare.Call(uncommitedState) + checks := Checks{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 12}, + } + if err := checks.Verify(statedb); err != nil { + t.Fatalf("unexpected prepared state, err=%s", err.Error()) + } + if err := checks.Verify(uncommitedState); err != nil { + t.Fatalf("unexpected prepared state, err=%s", err.Error()) + } + destruct := Txs{ + { + {"SelfDestruct", Address1}, + }, + } + destruct.Call(statedb) + destruct.Call(uncommitedState) + // check the state of address1 before finalise: they should keep the same except the balance + checks = Checks{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 12}, + } + if err := checks.Verify(statedb); err != nil { + t.Fatalf("[statedb] unexpected selfdestruct state before finalized, err=%s", err.Error()) + } + if err := checks.Verify(uncommitedState); err != nil { + t.Fatalf("[uncommitted] unexpected selfdestruct state before finalized, err=%s", err.Error()) + } + statedb.Finalise(true) + uncommitedState.Merge() + uncommitedState.maindb.Finalise(true) + checks = Checks{ + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + } + // check the state of address1 after finalise: they should be all nil + if err := checks.Verify(statedb); err != nil { + t.Fatalf("[statedb] unexpected selfdestruct state after finalized, err=%s", err.Error()) + } + if err := checks.Verify(uncommitedState.maindb); err != nil { + t.Fatalf("[uncommitted] unexpected selfdestruct state after finalized, err=%s", err.Error()) + } + statedb.IntermediateRoot(true) + uncommitedState.maindb.IntermediateRoot(true) + // check the state of address1 after finalise: they should be all nil + checks = Checks{ + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + } + if err := checks.Verify(statedb); err != nil { + t.Fatalf("[statedb] unexpected selfdestruct state after committed, err=%s", err.Error()) + } + if err := checks.Verify(uncommitedState.maindb); err != nil { + t.Fatalf("[unstate.maindb] unexpected selfdestruct state after committed, err=%s", err.Error()) + } +} + +func TestPevmSelfDestruct(t *testing.T) { + // case 1. self destruct an account without previous state + txs := Txs{ + { + {"SelfDestruct", Address1}, + }, + } + check := CheckState{ + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"obj", Address1, "nil"}, + }, + Maindb: []Check{ + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 2. selfdestruct an account who exists in uncommited db but not maindb + txs = Txs{ + { + {"Create", Address1}, + {"SetNonce", Address1, 12}, + {"SetCode", Address1, []byte("hello world")}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SelfDestruct", Address1}, + }, + } + check = CheckState{ + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"codeHash", Address1, common.Hash{}}, // an empty hash will be returned if the account does not exist + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"codeHash", Address1, common.Hash{}}, + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 3. destruct an account who exist in maindb but not in uncommited db + prepare := Txs{ + { + {"Create", Address1}, + {"SetNonce", Address1, 12}, + {"SetCode", Address1, []byte("hello world")}, + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + statedb := newStateDB() + maindb := newStateDB() + prepare.Call(statedb) + prepare.Call(maindb) + UncommittedDB := newUncommittedDB(maindb) + txs = Txs{ + { + {"SelfDestruct", Address1}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + }, + AfterMerge: []Check{ + {"codeHash", Address1, common.Hash{}}, + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, statedb, UncommittedDB, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 3. create an account after self destruct + txs = Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SelfDestruct", Address1}, + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(50)}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(50)}, + {"nonce", Address1, 0}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmSelfDestruct6780(t *testing.T) { + // case 1. no previous state + txs := Txs{ + { + {"SelfDestruct6780", Address1}, + }, + } + check := CheckState{ + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"obj", Address1, "nil"}, + }, + Maindb: []Check{ + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 2. self destruct an account with previous state + txs = Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SelfDestruct6780", Address1}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 3. create an account after self destruct + txs = Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SetNonce", Address1, 10}, + {"SelfDestruct6780", Address1}, + {"Create", Address1}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 4. self destruct 6780 should not work if the account is not empty + maindb := newStateDB() + statedb := newStateDB() + prepare := Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SetNonce", Address1, 2}, + }, + } + prepare.Call(maindb) + prepare.Call(statedb) + maindb.IntermediateRoot(true) + statedb.IntermediateRoot(true) + txs = Txs{ + { + {"AddBalance", Address1, big.NewInt(100)}, + {"SelfDestruct6780", Address1}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"balance", Address1, big.NewInt(200)}, + {"nonce", Address1, 2}, + }, + Maindb: []Check{ + {"balance", Address1, big.NewInt(200)}, + {"nonce", Address1, 2}, + }, + }, + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(200)}, + {"nonce", Address1, 2}, + }, + } + if err := runCase(txs, statedb, newUncommittedDB(maindb), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +func TestPevmExistsAndEmpty(t *testing.T) { + // case 1. exists an account without previous state + txs := Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + }, + Maindb: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"exists", Address1, true}, + {"empty", Address1, false}, + }, + Maindb: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"empty", Address1, false}, + {"exists", Address1, true}, + {"balance", Address1, big.NewInt(100)}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + txs = Txs{ + { + {"Create", Address1}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + }, + Maindb: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"exists", Address1, true}, //the account is created, but empty + {"empty", Address1, true}, + }, + Maindb: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"empty", Address1, true}, //empty accounts will be deleted after finalise + {"exists", Address1, false}, + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmRefund(t *testing.T) { + // case 1. add refund from 0 + txs := Txs{ + { + {"AddRefund", 100}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"refund", 0}, + }, + Maindb: []Check{ + {"refund", 0}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"refund", 100}, + }, + Maindb: []Check{ + {"refund", 0}, + }, + }, + AfterMerge: []Check{ + {"refund", 0}, //refund will be cleared after finalise + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + // case 2. add refund to a new account + statedb := newStateDB() + unstateMain := newStateDB() + unstateMain.refund, statedb.refund = 100, 100 + txs = Txs{ + { + {"AddRefund", 100}, + {"SubRefund", 20}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"refund", 100}, + }, + Maindb: []Check{ + {"refund", 100}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"refund", 180}, + }, + Maindb: []Check{ + {"refund", 100}, + }, + }, + AfterMerge: []Check{ + {"refund", 0}, + }, + } + if err := runCase(txs, statedb, newUncommittedDB(unstateMain), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmPreimages(t *testing.T) { + txs := Txs{ + { + {"AddPreimage", common.BytesToHash([]byte("hello")), []byte("world")}, + {"AddPreimage", common.BytesToHash([]byte("hello")), []byte("world2")}, // the second one should be ignored + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("")}, + }, + Maindb: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("")}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("world")}, + }, + Maindb: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("")}, + }, + }, + AfterMerge: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("world")}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmLogs(t *testing.T) { + // diff logs of two stateDBs: + // 1. check thash + // 2. check index + // 3. check txIndex + // log.thash, log.index, log.txIndex = s.txHash, s.logSize, s.txIndex + // so we need to test it by running a true transaction, in which we call the log function + txs := Txs{ + { + {"AddLog", &types.Log{Data: []byte("hello")}}, + {"AddLog", &types.Log{Data: []byte("world")}}, + }, + } + thash := common.BytesToHash([]byte("00000000000000000tx")) + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"loglen", 0}, + }, + Maindb: []Check{ + {"loglen", 0}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"loglen", 2}, + {"log", thash, 0, []byte("hello"), 20, 0}, // i, Data, TxIndex, Index + {"log", thash, 1, []byte("world"), 20, 1}, + }, + Maindb: []Check{ + {"loglen", 0}, + }, + }, + AfterMerge: []Check{ + {"loglen", 2}, + {"log", thash, 0, []byte("hello"), 20, 0}, + {"log", thash, 1, []byte("world"), 20, 1}, + }, + } + state := newStateDB() + maindb := newStateDB() + unstate := newUncommittedDB(maindb) + //set TxContext + unstate.txIndex, state.txIndex, maindb.txIndex = 20, 20, 19 + unstate.txHash, state.thash, maindb.thash = thash, thash, common.Hash{} + + if err := runCase(txs, state, unstate, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + // test the maindb with txIndex=-1 + txs = Txs{ + { + {"AddLog", &types.Log{Data: []byte("hello")}}, + {"AddLog", &types.Log{Data: []byte("world")}}, + }, + } + thash = common.BytesToHash([]byte("00000000000000000tx2")) + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"loglen", 0}, + }, + Maindb: []Check{ + {"loglen", 0}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"loglen", 2}, + {"log", thash, 0, []byte("hello"), 0, 0}, // i, Data, TxIndex, Index + {"log", thash, 1, []byte("world"), 0, 1}, + }, + Maindb: []Check{ + {"loglen", 0}, + }, + }, + AfterMerge: []Check{ + {"loglen", 2}, + {"log", thash, 0, []byte("hello"), 0, 0}, + {"log", thash, 1, []byte("world"), 0, 1}, + }, + } + state = newStateDB() + state.SetTxContext(thash, 0) + unstate = newUncommittedDB(newStateDB()) + unstate.SetTxContext(thash, 0) + if err := runCase(txs, state, unstate, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +func TestPevmAccessList(t *testing.T) { + // before Berlin hardfork: + // accesslist will be available in the whole scope of block + // after Berlin hardfork: + // accesslist will be available only in the scope of the transaction, except those predefined. + + // before Berlin hardfork: + // case 1. + // prepare: add address 1, add slot 1 + // tx: add address 2, add slot 2 + // check: address 1, slot 1, address 2, slot 2 are all in the accesslist + coinBase := common.BytesToAddress([]byte("0xcoinbase")) + sender := common.BytesToAddress([]byte("0xsender")) + key1 := common.Hash{0x33} + key2 := common.Hash{0x34} + prepare := Txs{ + { + {"AddAddress", Address1}, + {"AddSlots", Address1, key1}, + }, + } + tx := Txs{ + { + {"AddAddress", Address2}, + {"AddSlots", Address2, key2}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"address", Address1, true}, + {"slot", Address1, key1, true}, + }, + Maindb: []Check{ + {"address", Address1, true}, + {"slot", Address1, key1, true}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"address", Address1, true}, + {"address", Address2, true}, + {"slot", Address1, key1, true}, + {"slot", Address2, key2, true}, + {"address", sender, false}, + {"address", coinBase, false}, + }, + Maindb: []Check{ + {"address", Address1, true}, + {"address", Address2, false}, + {"slot", Address1, key1, true}, + {"slot", Address2, key2, false}, + }, + }, + AfterMerge: []Check{ + {"address", Address1, true}, + {"address", Address2, true}, + {"slot", Address1, key1, true}, + {"slot", Address2, key2, true}, + {"address", sender, false}, + {"address", coinBase, false}, + }, + } + statedb := newStateDB() + maindb := newStateDB() + prepare.Call(statedb) + prepare.Call(maindb) + + statedb.Prepare(params.Rules{IsBerlin: false}, sender, coinBase, nil, nil, nil) + uncommited := newUncommittedDB(maindb) + uncommited.Prepare(params.Rules{IsBerlin: false}, sender, coinBase, nil, nil, nil) + if err := runCase(tx, statedb, uncommited, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + // after Berlin hardfork: + // case 2: + // prepare: add address 1, add slot 1 + // tx: add address 2, add slot 2 + // before merge: + // address 2, slot 2 are in the accesslist; but not address 1, slot 1 + // predefined address n, slot n are in the accesslist + // after merge: + // the same as before merge + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"address", sender, true}, + {"address", coinBase, true}, + {"address", Address1, false}, + {"slot", Address1, key1, false}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"address", sender, true}, + {"address", coinBase, true}, + {"address", Address1, false}, + {"address", Address2, true}, + {"slot", Address1, key1, false}, + {"slot", Address2, key2, true}, + }, + }, + AfterMerge: []Check{ + {"address", sender, true}, + {"address", coinBase, true}, + {"address", Address1, false}, + {"address", Address2, true}, + {"slot", Address1, key1, false}, + {"slot", Address2, key2, true}, + }, + } + statedb, maindb = newStateDB(), newStateDB() + prepare.Call(statedb) + prepare.Call(maindb) + statedb.Prepare(params.Rules{IsBerlin: true, IsShanghai: true}, sender, coinBase, nil, nil, nil) + uncommited = newUncommittedDB(maindb) + uncommited.Prepare(params.Rules{IsBerlin: true, IsShanghai: true}, sender, coinBase, nil, nil, nil) + if err := runCase(tx, statedb, uncommited, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +// ================== conflict test ================== +// case 1. conflict in objects: balance, nonce, code, state +// case 2. conflict in transient storage +// case 3. conflict in accesslist +// case 4. conflict in logs +func TestPevmConflictObject(t *testing.T) { + // case 1. conflict in balance + // case 2. conflict in nonce + // case 3. conflict in code + // case 4. conflict in state + + // case of balance + prepare := Txs{ + { + {"AddBalance", Address1, big.NewInt(10)}, + }, + } + add40 := Txs{ + { + {"AddBalance", Address1, big.NewInt(30)}, + }, + } + add50 := Txs{ + { + {"AddBalance", Address1, big.NewInt(50)}, + }, + } + check := []Check{ + {"balance", Address1, big.NewInt(40)}, + } + check2 := []Check{ + {"balance", Address1, big.NewInt(30)}, + } + if err := runConflictCase(nil, add40, add50, check2); err != nil { + t.Fatalf("ut failed, errr:%s", err.Error()) + } + if err := runConflictCase(prepare, add40, add50, check); err != nil { + t.Fatalf("ut failed, errr:%s", err.Error()) + } + + // case of nonce + prepare = Txs{ + { + {"SetNonce", Address1, 10}, + }, + } + txs := Txs{ + { + {"SetNonce", Address1, 21}, + }, + } + txs2 := Txs{ + { + {"SetNonce", Address1, 22}, + }, + } + check = []Check{ + {"nonce", Address1, 21}, + } + if err := runConflictCase(nil, txs, txs2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + if err := runConflictCase(prepare, txs, txs2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + + // case of code + prepare = Txs{ + { + {"SetCode", Address1, []byte("hello")}, + }, + } + op1 := Txs{ + { + {"SetCode", Address1, []byte("here we go")}, + }, + } + op2 := Txs{ + { + {"SetCode", Address1, []byte("here we go now")}, + }, + } + check = []Check{ + {"code", Address1, []byte("here we go")}, + } + if err := runConflictCase(nil, op1, op2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + if err := runConflictCase(prepare, op1, op2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + + // case of state + prepare = Txs{ + { + {"SetState", Address1, "key1", "value1"}, + {"SetState", Address1, "key2", "value2"}, + }, + } + op1 = Txs{ + { + {"SetState", Address1, "key2", "value2.22"}, + {"SetState", Address1, "key3", "value2.33"}, + }, + } + op2 = Txs{ + { + {"SetState", Address1, "key2", "value3.22"}, + {"SetState", Address1, "key1", "value3.11"}, + }, + } + check = []Check{ + {"state", Address1, "key2", "value3.22"}, + {"state", Address1, "key1", "value3.11"}, + {"state", Address1, "key3", ""}, + } + if err := runConflictCase(prepare, op2, op1, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + if err := runConflictCase(nil, op2, op1, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + +} + +func TestPevmConflictLogs(t *testing.T) { + thash1 := common.BytesToHash([]byte("tx1")) + thash2 := common.BytesToHash([]byte("tx2")) + thash3 := common.BytesToHash([]byte("tx3")) + prepare := Txs{ + { + {"SetTxContext", thash1, 0}, + {"AddLog", &types.Log{Data: []byte("hello")}}, + }, + } + op1 := Txs{ + { + {"SetTxContext", thash2, 1}, + {"AddLog", &types.Log{Data: []byte("world")}}, + }, + } + op2 := Txs{ + { + {"SetTxContext", thash3, 1}, + {"AddLog", &types.Log{Data: []byte("eth")}}, + }, + } + check := []Check{ + {"loglen", 2}, + {"log", thash1, 0, []byte("hello"), 0, 0}, + {"log", thash2, 0, []byte("world"), 1, 1}, + } + if err := runConflictCase(prepare, op1, op2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + +} + +func TestPevmConflictPreimage(t *testing.T) { + op1 := Txs{ + { + {"AddPreimage", common.BytesToHash([]byte("key1")), []byte("value1")}, + }, + } + op2 := Txs{ + { + {"AddPreimage", common.BytesToHash([]byte("key1")), []byte("value2")}, + }, + } + check := []Check{ + {"preimage", common.BytesToHash([]byte("key1")), []byte("value1")}, + } + if err := runConflictCase(nil, op1, op2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } +} + +type uncommitedState struct { + Uncommited []Check + Maindb []Check +} + +type CheckState struct { + BeforeRun uncommitedState + BeforeMerge uncommitedState + AfterMerge []Check +} + +type Check []interface{} + +type Checks []Check + +func (c Checks) Verify(state vm.StateDB) error { + for _, check := range c { + if err := check.Verify(state); err != nil { + return err + } + } + return nil +} + +// +// {"address", Address1, true}, +// {"slot", Address1, "key1", true}, + +func (c Check) Verify(state vm.StateDB) error { + switch c[0].(string) { + + case "address": + addr := c[1].(common.Address) + exists := c[2].(bool) + var accesslist *accessList + if db, ok := state.(*StateDB); ok { + accesslist = db.accessList + } else if db, ok := state.(*UncommittedDB); ok { + accesslist = db.accessList + } else { + panic("unknown stateDB type") + } + if exists == accesslist.ContainsAddress(addr) { + return nil + } else { + return fmt.Errorf("address 'in list' mismatch, addr:%s, expected:%t, actual:%t", addr.String(), exists, accesslist.ContainsAddress(addr)) + } + + case "slot": + addr := c[1].(common.Address) + key := c[2].(common.Hash) + exists := c[3].(bool) + var accesslist *accessList + if db, ok := state.(*StateDB); ok { + accesslist = db.accessList + } else if db, ok := state.(*UncommittedDB); ok { + accesslist = db.accessList + } else { + panic("unknown stateDB type") + } + _, slotExists := accesslist.Contains(addr, key) + if exists == slotExists { + return nil + } else { + return fmt.Errorf("slot 'in list' mismatch, addr:%s, key:%s, expected:%t, actual:%t", addr, key.String(), exists, slotExists) + } + + case "loglen": + loglen := c[1].(int) + var logSize int + if db, ok := state.(*StateDB); ok { + logSize = int(db.logSize) + } else if db, ok := state.(*UncommittedDB); ok { + logSize = len(db.logs) + } else { + panic("unknown stateDB type") + } + if loglen == logSize { + return nil + } else { + return fmt.Errorf("loglen mismatch, expected:%d, actual:%d", loglen, logSize) + } + + //{"log", thash, 0, []byte("hello"), 20, 0}, + case "log": + thash := c[1].(common.Hash) + i := c[2].(int) + data := c[3].([]byte) + txIndex := c[4].(int) + Index := c[5].(int) + var logs []*types.Log + if db, ok := state.(*StateDB); ok { + logs, ok = db.logs[thash] + if !ok { + return fmt.Errorf("log thash not found, thash:%s", thash.String()) + } + } else if db, ok := state.(*UncommittedDB); ok { + if thash.Cmp(db.txHash) != 0 { + return fmt.Errorf("log thash not found, expected:%s, actual:%s", thash.String(), db.txHash.String()) + } + logs = db.logs + } else { + panic("unknown stateDB type") + } + if len(logs) <= i { + return fmt.Errorf("log index out of range, index:%d, len:%d", i, len(logs)) + } + log := logs[i] + if !bytes.Equal(log.Data, data) { + return fmt.Errorf("log mismatch, expected:%s, actual:%s", string(data), string(logs[i].Data)) + } + if log.TxIndex != uint(txIndex) { + return fmt.Errorf("log txIndex mismatch, expected:%d, actual:%d", txIndex, log.TxIndex) + } + if log.Index != uint(Index) { + return fmt.Errorf("log index mismatch, expected:%d, actual:%d", Index, log.Index) + } + return nil + + case "preimage": + hash := c[1].(common.Hash) + data := c[2].([]byte) + var preimages map[common.Hash][]byte + if db, ok := state.(*StateDB); ok { + preimages = db.preimages + } else if db, ok := state.(*UncommittedDB); ok { + preimages = db.preimages + } else { + panic("unknown stateDB type") + } + if bytes.Equal(preimages[hash], data) { + return nil + } else { + return fmt.Errorf("preimage mismatch, expected:%s, actual:%s", string(data), string(preimages[hash])) + } + + case "preimageExists": + hash := c[1].(common.Hash) + exists := c[2].(bool) + var preimages map[common.Hash][]byte + if db, ok := state.(*StateDB); ok { + preimages = db.preimages + } else if db, ok := state.(*UncommittedDB); ok { + preimages = db.preimages + } else { + panic("unknown stateDB type") + } + if _, ok := preimages[hash]; ok == exists { + return nil + } else { + return fmt.Errorf("preimageExists mismatch, expected:%t, actual:%t", exists, ok) + } + + case "tstorage": + addr := c[1].(common.Address) + key := common.BytesToHash([]byte(c[2].(string))) + val := common.BytesToHash([]byte(c[3].(string))) + if state.GetTransientState(addr, key).Cmp(val) == 0 { + return nil + } else { + return fmt.Errorf("tstorage mismatch, key:%s, expected:%s, actual:%s", key.String(), val.String(), state.GetTransientState(addr, key).String()) + } + + case "transientExists": + addr := c[1].(common.Address) + key := common.BytesToHash([]byte(c[2].(string))) + exists := c[3].(bool) + var ts transientStorage + if db, ok := state.(*StateDB); ok { + ts = db.transientStorage + } else if db, ok := state.(*UncommittedDB); ok { + ts = db.transientStorage + } else { + panic("unknown stateDB type") + } + found := false + if _, ok := ts[addr]; ok { + if _, ok := ts[addr][key]; ok { + found = true + } + } + if found == exists { + return nil + } else { + return fmt.Errorf("transientExists mismatch, expected:%t, actual:%t", exists, found) + } + + case "exists": + addr := c[1].(common.Address) + exists := c[2].(bool) + if state.Exist(addr) == exists { + return nil + } else { + return fmt.Errorf("exists mismatch,addr:%s, expected:%t, actual:%t", addr.String(), exists, state.Exist(addr)) + } + + case "empty": + addr := c[1].(common.Address) + empty := c[2].(bool) + if state.Empty(addr) == empty { + return nil + } else { + return fmt.Errorf("empty mismatch, expected:%t, actual:%t", empty, state.Empty(addr)) + } + + case "balance": + addr := c[1].(common.Address) + balance, _ := uint256.FromBig(c[2].(*big.Int)) + if state.GetBalance(addr).Cmp(balance) == 0 { + return nil + } else { + return fmt.Errorf("balance mismatch, expected:%d, actual:%d", balance.Uint64(), state.GetBalance(addr).Uint64()) + } + + case "nonce": + addr := c[1].(common.Address) + nonce := uint64(c[2].(int)) + if state.GetNonce(addr) == nonce { + return nil + } else { + return fmt.Errorf("nonce mismatch, expected:%d, actual:%d", nonce, state.GetNonce(addr)) + } + + case "code": + addr := c[1].(common.Address) + code := c[2].([]byte) + if bytes.Equal(state.GetCode(addr), code) { + return nil + } else { + return fmt.Errorf("code mismatch, expected:%s, actual:%s", string(code), string(state.GetCode(addr))) + } + + case "codeHash": + addr := c[1].(common.Address) + codeHash := c[2].(common.Hash) + if codeHash.Cmp(state.GetCodeHash(addr)) == 0 { + return nil + } else { + return fmt.Errorf("codeHash mismatch, expected:%s, actual:%s", codeHash.String(), state.GetCodeHash(addr).String()) + } + + case "state": + addr := c[1].(common.Address) + key := common.BytesToHash([]byte(c[2].(string))) + val := common.BytesToHash([]byte(c[3].(string))) + if state.GetState(addr, key).Cmp(val) == 0 { + return nil + } else { + return fmt.Errorf("state mismatch, key:%s, expected:%s, actual:%s", key.String(), val.String(), state.GetState(addr, key).String()) + } + + case "cstate": + addr := c[1].(common.Address) + key := common.BytesToHash([]byte(c[2].(string))) + val := common.BytesToHash([]byte(c[3].(string))) + if state.GetCommittedState(addr, key).Cmp(val) == 0 { + return nil + } else { + return fmt.Errorf("committed state mismatch, expected:%s, actual:%s", val.String(), state.GetCommittedState(addr, key).String()) + } + + case "obj": + addr := c[1].(common.Address) + exists := c[2].(string) + if exists == "nil" { + if state.Exist(addr) { + return fmt.Errorf("object was expected not exists, addr:%s", addr.String()) + } else { + return nil + } + } else { + if !state.Exist(addr) { + return fmt.Errorf("object was expected exists, addr:%s", addr.String()) + } else { + return nil + } + } + + case "refund": + refund := uint64(c[1].(int)) + if state.GetRefund() == refund { + return nil + } else { + return fmt.Errorf("refund mismatch, expected:%d, actual:%d", refund, state.GetRefund()) + } + + default: + panic(fmt.Sprintf("unknown check type: %s", c[0].(string))) + } +} + +type Op []interface{} + +type Tx []Op + +func (op Op) Call(db vm.StateDB) error { + switch op[0].(string) { + case "SetTxContext": + state := db + if db, ok := state.(*UncommittedDB); ok { + db.SetTxContext(op[1].(common.Hash), op[2].(int)) + } else if db, ok := state.(*StateDB); ok { + db.SetTxContext(op[1].(common.Hash), op[2].(int)) + } else { + panic("unknown stateDB type") + } + return nil + + case "AddSlots": + addr := op[1].(common.Address) + slot := op[2].(common.Hash) + db.AddSlotToAccessList(addr, slot) + return nil + + case "AddAddress": + addr := op[1].(common.Address) + db.AddAddressToAccessList(addr) + return nil + + case "AddLog": + log := op[1].(*types.Log) + db.AddLog(log) + return nil + + case "SetTransientStorage": + addr := op[1].(common.Address) + key := op[2].(string) + val := op[3].(string) + db.SetTransientState(addr, common.BytesToHash([]byte(key)), common.BytesToHash([]byte(val))) + return nil + + case "Create": + addr := op[1].(common.Address) + db.CreateAccount(addr) + return nil + + case "AddPreimage": + hash := op[1].(common.Hash) + data := op[2].([]byte) + db.AddPreimage(hash, data) + return nil + + case "AddBalance": + addr := op[1].(common.Address) + balance, _ := uint256.FromBig(op[2].(*big.Int)) + db.AddBalance(addr, balance) + return nil + case "SubBalance": + addr := op[1].(common.Address) + balance, _ := uint256.FromBig(op[2].(*big.Int)) + db.SubBalance(addr, balance) + return nil + + case "SetNonce": + addr := op[1].(common.Address) + nonce := uint64(op[2].(int)) + db.SetNonce(addr, nonce) + return nil + + case "SetCode": + addr := op[1].(common.Address) + code := op[2].([]byte) + db.SetCode(addr, code) + return nil + + case "AddRefund": + refund := uint64(op[1].(int)) + db.AddRefund(refund) + return nil + + case "SubRefund": + refund := uint64(op[1].(int)) + db.SubRefund(refund) + return nil + + case "SetState": + addr := op[1].(common.Address) + key := common.BytesToHash([]byte(op[2].(string))) + val := common.BytesToHash([]byte(op[3].(string))) + db.SetState(addr, key, val) + return nil + + case "SelfDestruct": + addr := op[1].(common.Address) + db.SelfDestruct(addr) + return nil + + case "SelfDestruct6780": + addr := op[1].(common.Address) + db.Selfdestruct6780(addr) + return nil + + case "Snapshot": + snapid := op[1].(*int) + *snapid = db.Snapshot() + return nil + + case "Revert": + snapid := op[1].(*int) + db.RevertToSnapshot(*snapid) + return nil + + default: + return fmt.Errorf("unknown op type: %s", op[0].(string)) + } +} + +// Call executes the transaction. +func (tx Tx) Call(db vm.StateDB) error { + for _, op := range tx { + if err := op.Call(db); err != nil { + return err + } + } + return nil +} + +type Txs []Tx + +func (txs Txs) Call(db vm.StateDB) error { + for _, tx := range txs { + if err := tx.Call(db); err != nil { + return err + } + } + return nil +} + +func triedbConfig(StateScheme string) *triedb.Config { + config := &triedb.Config{ + Preimages: true, + NoTries: false, + } + if StateScheme == rawdb.HashScheme { + config.HashDB = &hashdb.Config{ + CleanCacheSize: 1 * 1024 * 1024, + } + } + if StateScheme == rawdb.PathScheme { + config.PathDB = &pathdb.Config{ + //TrieNodeBufferType: c.PathNodeBuffer, + //StateHistory: c.StateHistory, + CleanCacheSize: 1 * 1024 * 1024, + DirtyCacheSize: 1 * 1024 * 1024, + //ProposeBlockInterval: c.ProposeBlockInterval, + //NotifyKeep: keepFunc, + //JournalFilePath: c.JournalFilePath, + //JournalFile: c.JournalFile, + } + } + return config +} + +func newStateDB() *StateDB { + memdb := rawdb.NewMemoryDatabase() + // Open trie database with provided config + triedb := triedb.NewDatabase(memdb, triedbConfig(rawdb.HashScheme)) + stateCache := NewDatabaseWithNodeDB(memdb, triedb) + st, err := New(common.Hash{}, stateCache, nil) + if err != nil { + panic(err) + } + return st +} + +func newUncommittedDB(db *StateDB) *UncommittedDB { + return NewUncommittedDB(db) +} + +func runTxsOnStateDB(txs Txs, db *StateDB, check CheckState) (common.Hash, error) { + // run the transactions + if err := txs.Call(db); err != nil { + return common.Hash{}, fmt.Errorf("state failed to run txs: %v", err) + } + // states before merge should be the same as the uncommitted db + for _, c := range check.BeforeMerge.Uncommited { + if err := c.Verify(db); err != nil { + return common.Hash{}, fmt.Errorf("[before merge][statedbt db] failed to verify : %v", err) + } + } + return db.IntermediateRoot(true), nil +} + +func runTxOnUncommittedDB(txs Txs, db *UncommittedDB, check CheckState) (common.Hash, error) { + // run the transaction + if err := txs.Call(db); err != nil { + return common.Hash{}, fmt.Errorf("unconfirm db failed to run txs: %v", err) + } + for _, check := range check.BeforeMerge.Uncommited { + if err := check.Verify(db); err != nil { + return common.Hash{}, fmt.Errorf("[before merge][uncommited db] failed to verify : %v", err) + } + } + for _, check := range check.BeforeMerge.Maindb { + if err := check.Verify(db.maindb); err != nil { + return common.Hash{}, fmt.Errorf("[before merge][maindb] failed to verify : %v", err) + } + } + if err := db.Merge(); err != nil { + return common.Hash{}, fmt.Errorf("failed to merge: %v", err) + } + return db.maindb.IntermediateRoot(true), nil +} + +func runConflictCase(prepare, txs1, txs2 Txs, checks []Check) error { + maindb := newStateDB() + if prepare != nil { + maindb.CreateAccount(Address1) + for _, op := range prepare { + if err := op.Call(maindb); err != nil { + return fmt.Errorf("failed to call prepare txs, err:%s", err.Error()) + } + } + } + un1, un2 := newUncommittedDB(maindb), newUncommittedDB(maindb) + if err := txs1.Call(un1); err != nil { + return fmt.Errorf("failed to call txs1, err:%s", err.Error()) + } + if err := txs2.Call(un2); err != nil { + return fmt.Errorf("failed to call txs2, err:%s", err.Error()) + } + if err := un1.Merge(); err != nil { + return fmt.Errorf("failed to merge un1, err:%s", err.Error()) + } + if err := un2.Merge(); err == nil { + return fmt.Errorf("un2 merge is expected to be failed") + } + for _, c := range checks { + if err := c.Verify(maindb); err != nil { + return fmt.Errorf("failed to verify maindb, err:%s", err.Error()) + } + } + return nil +} + +func runCase(txs Txs, state *StateDB, unstate *UncommittedDB, check CheckState) error { + stRoot, errSt := runTxsOnStateDB(txs, state, check) + unRoot, errUn := runTxOnUncommittedDB(txs, unstate, check) + if errSt != nil { + return fmt.Errorf("failed to run txs on state db: %v", errSt) + } + if errUn != nil { + return fmt.Errorf("failed to run tx on uncommited db: %v", errUn) + } + for _, check := range check.AfterMerge { + if err := check.Verify(state); err != nil { + return fmt.Errorf("[after merge] failed to verify statedb: %v", err) + } + // an uncommitted is invalid after merge, so we verify its maindb instead + if err := check.Verify(unstate.maindb); err != nil { + return fmt.Errorf("[after merge] failed to verify uncommited db: %v", err) + } + } + if stRoot.Cmp(unRoot) != 0 { + return fmt.Errorf("state root mismatch: %s != %s", stRoot.String(), unRoot.String()) + } + return nil +} + +func Diff(a, b *StateDB) []common.Hash { + // compare the two stateDBs, and return the different objects + return nil +} + +type object struct { + data int +} + +type HashSyncMap struct { + cap int + maps []sync.Map +} + +func newHashSyncMap(cap int) *HashSyncMap { + hsm := &HashSyncMap{cap: cap, maps: make([]sync.Map, cap)} + return hsm +} + +func (hsm *HashSyncMap) Load(key interface{}) (value any, ok bool) { + addr := key.(common.Address) + slot := hash2int(addr) % hsm.cap + return hsm.maps[slot].Load(key) +} + +func (hsm *HashSyncMap) Store(key, val interface{}) { + addr := key.(common.Address) + slot := hash2int(addr) % hsm.cap + hsm.maps[slot].Store(key, val) +} + +type HashMutexMap struct { + cap int + maps []struct { + sync.Mutex + data map[common.Address]*object + } +} + +func newHashMutexMap(cap int) *HashMutexMap { + hmm := &HashMutexMap{cap: cap, maps: make([]struct { + sync.Mutex + data map[common.Address]*object + }, cap)} + for i := 0; i < cap; i++ { + hmm.maps[i].data = make(map[common.Address]*object) + } + return hmm +} + +func (hmm *HashMutexMap) Load(key interface{}) (value any, ok bool) { + addr := key.(common.Address) + slot := hash2int(addr) % hmm.cap + cache := &hmm.maps[slot] + cache.Mutex.Lock() + defer cache.Mutex.Unlock() + value, ok = cache.data[addr] + return value, ok +} + +func (hmm *HashMutexMap) Store(key, val interface{}) { + addr := key.(common.Address) + obj := val.(*object) + slot := hash2int(addr) % hmm.cap + cache := &hmm.maps[slot] + cache.Mutex.Lock() + defer cache.Mutex.Unlock() + cache.data[addr] = obj +} + +type istore interface { + Store(key, val interface{}) + Load(key interface{}) (value any, ok bool) +} + +type DB struct { + cache istore + persist map[common.Address]*object +} + +func newDB(cache istore, addresses []common.Address) *DB { + db := &DB{cache: cache, persist: make(map[common.Address]*object)} + i := 0 + for _, addr := range addresses { + db.persist[addr] = &object{data: i} + i++ + } + return db +} + +func (db *DB) Get(addr common.Address) *object { + if obj, ok := db.cache.Load(addr); ok { + return obj.(*object) + } + obj := db.persist[addr] + db.cache.Store(addr, obj) + return obj +} + +func hash2int(addr common.Address) int { + return int(addr[common.AddressLength/2]) +} + +func randAddresses(num int) []common.Address { + addresses := make([]common.Address, num) + for i := 0; i < len(addresses); i++ { + addr, _ := randAddress() + addresses[i] = addr + } + return addresses +} + +func randAddress() (common.Address, *ecdsa.PrivateKey) { + // Generate a new private key using rand.Reader + key, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + if err != nil { + panic(fmt.Sprintf("Failed to generate private key: %v", err)) + } + return crypto.PubkeyToAddress(key.PublicKey), key +} + +func accessWithMultiProcesses(db *DB, parallelNum int, queryNum int, randAddress []common.Address) { + wait := sync.WaitGroup{} + wait.Add(parallelNum) + for p := 0; p < parallelNum; p++ { + runner <- func() { + for j := 0; j < queryNum; j++ { + addr := randAddress[j%10000] + obj := db.Get(addr) + if obj != nil { + obj.data++ + } + db.cache.Store(addr, obj) + } + wait.Done() + } + } + wait.Wait() +} + +var runner = make(chan func(), runtime.NumCPU()) + +func init() { + for i := 0; i < runtime.NumCPU(); i++ { + go func() { + for f := range runner { + f() + } + }() + } +} + +func BenchmarkSyncMap10000(b *testing.B) { + sm := sync.Map{} + runBench(&sm, b) +} + +func BenchmarkHashSyncMap10000(b *testing.B) { + sm := newHashSyncMap(128) + runBench(sm, b) +} + +func BenchmarkHashMutexMap10000(b *testing.B) { + sm := newHashMutexMap(128) + runBench(sm, b) +} + +func runBench(sm istore, b *testing.B) { + randAddress := randAddresses(10000) + db := newDB(sm, randAddress) + b.ResetTimer() + var duration int64 = 0 + var round int64 = 0 + for i := 0; i < b.N; i++ { + start := time.Now() + accessWithMultiProcesses(db, runtime.NumCPU(), 10000, randAddress) + atomic.AddInt64(&duration, int64(time.Since(start))) + atomic.AddInt64(&round, 1) + } + fmt.Printf("total cost:%s, rounds:%d, avg costs:%s\n", time.Duration(duration), round, time.Duration(duration/round)) +} diff --git a/core/state/statedb.go b/core/state/statedb.go index d71c675055..c86134dcda 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -183,6 +183,7 @@ type StateDB struct { trie Trie noTrie bool hasher crypto.KeccakState + hasherLock sync.Mutex snaps *snapshot.Tree // Nil if snapshot is not available snap snapshot.Snapshot // Nil if snapshot is not available @@ -877,7 +878,11 @@ func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *t // If no live objects are available, attempt to use snapshots if s.snap != nil { start := time.Now() - acc, err := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())) + // the s.hasher is not thread-safe, let's use a mutex to protect it + s.hasherLock.Lock() + hash := crypto.HashData(s.hasher, addr.Bytes()) + s.hasherLock.Unlock() + acc, err := s.snap.Account(hash) if metrics.EnabledExpensive { s.SnapshotAccountReads += time.Since(start) } diff --git a/core/state_processor.go b/core/state_processor.go index df9d788707..2d13af1123 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 && !p.bc.vmConfig.EnableParallelExec { + if p.bc.enableTxDAG && !p.bc.vmConfig.EnableParallelExec && !p.bc.vmConfig.EnableParallelExecV2 { statedb.ResetMVStates(len(block.Transactions())) } // Iterate over and process the individual transactions diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 2278b81710..73741cbc46 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -35,6 +35,7 @@ type Config struct { 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 + EnableParallelExecV2 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 diff --git a/eth/backend.go b/eth/backend.go index 2a82f1e074..3465302778 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -21,12 +21,13 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum/core/txpool/bundlepool" "math/big" "runtime" "sync" "time" + "github.com/ethereum/go-ethereum/core/txpool/bundlepool" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -237,6 +238,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, EnableParallelExec: config.ParallelTxMode, + EnableParallelExecV2: config.ParallelTxMode2, ParallelTxNum: config.ParallelTxNum, EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, } @@ -286,7 +288,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { return nil, err } if config.EnableParallelTxDAG { - eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile, config.ParallelTxMode) + if config.ParallelTxMode2 { + eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile, config.ParallelTxMode2) + } else { + 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/eth/ethconfig/config.go b/eth/ethconfig/config.go index 3f9624dc7c..75cc3d8ed7 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -19,9 +19,10 @@ package ethconfig import ( "errors" - "github.com/ethereum/go-ethereum/core/txpool/bundlepool" "time" + "github.com/ethereum/go-ethereum/core/txpool/bundlepool" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/beacon" @@ -219,6 +220,7 @@ type Config struct { RollupHaltOnIncompatibleProtocolVersion string ParallelTxMode bool // Whether to execute transaction in parallel mode when do full sync + ParallelTxMode2 bool // Whether to execute transaction in parallel mode when do full sync ParallelTxNum int // Number of slot for transaction execution EnableOpcodeOptimizing bool EnableParallelTxDAG bool From 7601e06acaa7276a9df4d54c1e554bc64d78082f Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Sat, 19 Oct 2024 07:57:26 +0800 Subject: [PATCH 073/104] Fix/pevm v0.5.0 invalid gas used (#199) Co-authored-by: andyzhang2023 --- core/pevm_processor.go | 2 +- core/state/pevm_statedb.go | 26 ++- core/state/pevm_statedb_test.go | 288 +++++++++++++++++++++++++++++++- 3 files changed, 304 insertions(+), 12 deletions(-) diff --git a/core/pevm_processor.go b/core/pevm_processor.go index 0dfa15b121..f1fe0adb4e 100644 --- a/core/pevm_processor.go +++ b/core/pevm_processor.go @@ -160,7 +160,7 @@ func (p *PEVMProcessor) confirmTxResult(statedb *state.StateDB, gp *GasPool, res isByzantium := p.config.IsByzantium(header.Number) isEIP158 := p.config.IsEIP158(header.Number) //result.slotDB.FinaliseForParallel(isByzantium || isEIP158, statedb) - if err := result.slotDB.Merge(); err != nil { + if err := result.slotDB.Merge(isByzantium || isEIP158); err != nil { // something very wrong, should not happen log.Error("merge slotDB failed", "err", err) return err diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go index 7092996e65..f511b0c651 100644 --- a/core/state/pevm_statedb.go +++ b/core/state/pevm_statedb.go @@ -476,7 +476,7 @@ func (pst *UncommittedDB) ConflictsToMaindb() error { return pst.conflictsToMaindb() } -func (pst *UncommittedDB) Merge() error { +func (pst *UncommittedDB) Merge(deleteEmptyObjects bool) error { if pst.discarded { // all the writes of this db will be discarded, including: // 1. accessList @@ -524,6 +524,15 @@ func (pst *UncommittedDB) Merge() error { if pst.refund != 0 { pst.maindb.AddRefund(pst.refund) } + // clean empty objects if needed + for _, obj := range pst.cache { + if obj.selfDestruct || (deleteEmptyObjects && obj.empty()) { + obj.deleted = true + } + // we don't need to do obj.finalize() here, it will be done in the maindb.Finalize() + // just mark the object as deleted + obj.created = false + } return nil } @@ -652,16 +661,19 @@ func (pst *UncommittedDB) getDeletedObjectWithState(addr common.Address, maindb if _, ok := o.state[hash]; ok { return o } - // load code from maindb + // first, load code from maindb and record the previous state + // we can't use getStateObject() here , because the state of deletedObj will be used for conflict check. deletedObj := pst.maindb.getDeletedStateObject(addr) + if deletedObj != nil { + // record the previous state for conflict check. + pst.reads.recordKVOnce(addr, hash, deletedObj.GetState(hash)) + } + + // now write the true state into cache var value = common.Hash{} - if deletedObj == nil { - pst.reads.recordKVOnce(addr, hash, common.Hash{}) - } else { + if deletedObj != nil && !deletedObj.deleted { value = deletedObj.GetState(hash) - pst.reads.recordKVOnce(addr, hash, value) } - // set code into the cache o.state[hash] = value return o } diff --git a/core/state/pevm_statedb_test.go b/core/state/pevm_statedb_test.go index a86ee7fc0f..38a917e2de 100644 --- a/core/state/pevm_statedb_test.go +++ b/core/state/pevm_statedb_test.go @@ -27,6 +27,278 @@ import ( var Address1 = common.HexToAddress("0x1") var Address2 = common.HexToAddress("0x2") +func TestInvalidGasUsed(t *testing.T) { + txs := Txs{ + { // prepare for the account + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, // make the object not empty + {"SetState", Address1, "key1", "value1"}, + {"SetCode", Address1, []byte("hello")}, + }, + { + {"SetNonce", Address1, 1}, // make the object not empty + {"SelfDestruct", Address1}, + }, + { + {"GetCodeHash", Address1, common.Hash{0x1}}, + {"GetNonce", Address1, 0}, + {"Create", Address1}, // re-create the object + {"SetNonce", Address1, 1}, // make the object not empty + }, + } + checks := []Checks{ + // after execute tx1, before finalize + { + {"state", Address1, "key1", "value1"}, + {"code", Address1, []byte("hello")}, + }, + //after finalize tx1, + { + {"state", Address1, "key1", ""}, + {"code", Address1, []byte{}}, + }, + // after execute tx1, before && after finalize + { + {"state", Address1, "key1", ""}, + {"code", Address1, []byte{}}, + }, + } + + verifyDBs := func(c Checks, maindb *StateDB, uncommited *UncommittedDB) error { + if c.Verify(maindb) != nil { + return fmt.Errorf("maindb: %s", c.Verify(maindb).Error()) + } + if c.Verify(uncommited) != nil { + return fmt.Errorf("uncommited: %s", c.Verify(uncommited).Error()) + } + return nil + } + + maindb := newStateDB() + shadow := newStateDB() + // firtst prepare for the account, to ensure the selfDestruct happens + txs[0].Call(maindb) + txs[0].Call(shadow) + maindb.Finalise(true) + shadow.Finalise(true) + + uncommitted := newUncommittedDB(maindb) + txs[1].Call(uncommitted) + txs[1].Call(shadow) + if err := verifyDBs(checks[0], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now finalize the data to maindb + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // now check the state after finalize + if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now execute another tx + uncommitted = newUncommittedDB(maindb) + txs[2].Call(uncommitted) + txs[2].Call(shadow) + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again, to ensure the Finalize works + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestStateAfterDestructWithBalance(t *testing.T) { + // tx0: + // 1. create an account -> key1: nil + // 2. set state -> key1: value1 + // 3. self destruct -> key1: nil + // tx1: + // 1. create an account -> key1: nil + // 2. set state -> key1: value2 + // 3. self destruct -> key1: nil + txs := Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, // balance should not be carried over + {"SetNonce", Address1, 1}, // make the object not empty + {"SetState", Address1, "key1", "value1"}, + {"SelfDestruct", Address1}, + }, + { + {"Create", Address1}, + {"SetNonce", Address1, 1}, // make the object not empty + {"SetState", Address1, "key1", "value2"}, + }, + } + checks := []Checks{ + // after execute tx0, before finalize + { + {"state", Address1, "key1", "value1"}, + {"balance", Address1, big.NewInt(0)}, + }, + //after finalize tx0, + { + {"state", Address1, "key1", ""}, + {"balance", Address1, big.NewInt(0)}, + }, + // after execute tx1, before && after finalize + { + {"state", Address1, "key1", "value2"}, + {"balance", Address1, big.NewInt(0)}, + }, + } + + verifyDBs := func(c Checks, maindb *StateDB, uncommited *UncommittedDB) error { + if c.Verify(maindb) != nil { + return fmt.Errorf("maindb: %s", c.Verify(maindb).Error()) + } + if c.Verify(uncommited) != nil { + return fmt.Errorf("uncommited: %s", c.Verify(uncommited).Error()) + } + return nil + } + + maindb := newStateDB() + shadow := newStateDB() + uncommitted := newUncommittedDB(maindb) + txs[0].Call(uncommitted) + txs[0].Call(shadow) + if err := verifyDBs(checks[0], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now finalize the data to maindb + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // now check the state after finalize + if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now execute another tx + uncommitted = newUncommittedDB(maindb) + txs[1].Call(uncommitted) + txs[1].Call(shadow) + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again, to ensure the Finalize works + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestStateAfterDestruct(t *testing.T) { + // tx0: + // 1. create an account -> key1: nil + // 2. set state -> key1: value1 + // 3. self destruct -> key1: nil + // tx1: + // 1. create an account -> key1: nil + // 2. set state -> key1: value2 + // 3. self destruct -> key1: nil + txs := Txs{ + { + {"Create", Address1}, + {"SetNonce", Address1, 1}, // make the object not empty + {"SetState", Address1, "key1", "value1"}, + }, + { + {"SelfDestruct", Address1}, + }, + { + {"Create", Address1}, + {"SetNonce", Address1, 1}, // make the object not empty + {"SetState", Address1, "key1", "value2"}, + }, + { + {"SelfDestruct", Address1}, + }, + } + checks := []Check{ + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key1", ""}, + {"state", Address1, "key1", "value2"}, + {"state", Address1, "key1", ""}, + } + + verifyDBs := func(c Check, maindb *StateDB, uncommited *UncommittedDB) error { + if c.Verify(maindb) != nil { + return fmt.Errorf("maindb: %s", c.Verify(maindb).Error()) + } + if c.Verify(uncommited) != nil { + return fmt.Errorf("uncommited: %s", c.Verify(uncommited).Error()) + } + return nil + } + + maindb := newStateDB() + shadow := newStateDB() + uncommitted := newUncommittedDB(maindb) + txs[0].Call(uncommitted) + txs[0].Call(shadow) + // key1: value1 + if err := verifyDBs(checks[0], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + txs[1].Call(uncommitted) + txs[1].Call(shadow) + beforeMerge := checks[0] + if err := verifyDBs(beforeMerge, shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now finalize the data to maindb + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again + if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now recreate the account + uncommitted = newUncommittedDB(maindb) + // check the state again, to make sure the newly create uncommited have the same state of the shadow db + if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + txs[2].Call(uncommitted) + txs[2].Call(shadow) + beforeMerge = checks[2] + if err := verifyDBs(beforeMerge, shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again, to ensure the Finalize works + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted = newUncommittedDB(maindb) + txs[3].Call(uncommitted) + txs[3].Call(shadow) + // check the state again, to ensure the Finalize works + beforeMerge = checks[2] + if err := verifyDBs(beforeMerge, shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again, to ensure the Finalize works + if err := verifyDBs(checks[3], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + // TestUncommitedDBCreateAccount tests the creation of an account in an uncommited DB. func TestPevmUncommitedDBCreateAccount(t *testing.T) { // case 1. create an account without previous state @@ -356,7 +628,7 @@ func TestPevmSelfDestructStateDB(t *testing.T) { t.Fatalf("[uncommitted] unexpected selfdestruct state before finalized, err=%s", err.Error()) } statedb.Finalise(true) - uncommitedState.Merge() + uncommitedState.Merge(true) uncommitedState.maindb.Finalise(true) checks = Checks{ {"code", Address1, []byte(nil)}, @@ -1499,6 +1771,14 @@ type Tx []Op func (op Op) Call(db vm.StateDB) error { switch op[0].(string) { + case "GetNonce": + addr := op[1].(common.Address) + db.GetNonce(addr) + return nil + case "GetCodeHash": + addr := op[1].(common.Address) + db.GetCodeHash(addr) + return nil case "SetTxContext": state := db if db, ok := state.(*UncommittedDB); ok { @@ -1700,7 +1980,7 @@ func runTxOnUncommittedDB(txs Txs, db *UncommittedDB, check CheckState) (common. return common.Hash{}, fmt.Errorf("[before merge][maindb] failed to verify : %v", err) } } - if err := db.Merge(); err != nil { + if err := db.Merge(true); err != nil { return common.Hash{}, fmt.Errorf("failed to merge: %v", err) } return db.maindb.IntermediateRoot(true), nil @@ -1723,10 +2003,10 @@ func runConflictCase(prepare, txs1, txs2 Txs, checks []Check) error { if err := txs2.Call(un2); err != nil { return fmt.Errorf("failed to call txs2, err:%s", err.Error()) } - if err := un1.Merge(); err != nil { + if err := un1.Merge(true); err != nil { return fmt.Errorf("failed to merge un1, err:%s", err.Error()) } - if err := un2.Merge(); err == nil { + if err := un2.Merge(true); err == nil { return fmt.Errorf("un2 merge is expected to be failed") } for _, c := range checks { From 0a5e3752c6a2ebed681fbb862419e9be8427c685 Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Sat, 19 Oct 2024 07:57:39 +0800 Subject: [PATCH 074/104] rename pevm option (#200) Co-authored-by: andyzhang2023 --- cmd/geth/main.go | 2 +- cmd/utils/flags.go | 18 +++++++++--------- core/blockchain.go | 12 +++++++----- core/parallel_state_scheduler.go | 5 +++-- core/pevm_processor_test.go | 4 ++-- core/state_processor.go | 2 +- core/vm/interpreter.go | 2 +- eth/backend.go | 8 ++++---- eth/ethconfig/config.go | 2 +- 9 files changed, 29 insertions(+), 26 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index f851b18856..03b24458db 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -170,8 +170,8 @@ var ( utils.RollupComputePendingBlock, utils.RollupHaltOnIncompatibleProtocolVersionFlag, utils.RollupSuperchainUpgradesFlag, + utils.ParallelTxLegacyFlag, utils.ParallelTxFlag, - utils.ParallelTx2Flag, utils.ParallelTxNumFlag, utils.ParallelTxDAGFlag, utils.ParallelTxDAGFileFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 278b7772b4..85ef38dfaa 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1100,14 +1100,14 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Category: flags.MetricsCategory, } - ParallelTxFlag = &cli.BoolFlag{ - Name: "parallel", + ParallelTxLegacyFlag = &cli.BoolFlag{ + Name: "parallel-legacy", Usage: "Enable the experimental parallel transaction execution mode, only valid in full sync mode (default = false)", Category: flags.VMCategory, } - ParallelTx2Flag = &cli.BoolFlag{ - Name: "parallel2", + ParallelTxFlag = &cli.BoolFlag{ + Name: "parallel", Usage: "Enable the experimental parallel transaction execution mode, only valid in full sync mode (default = false)", Category: flags.VMCategory, } @@ -2028,9 +2028,9 @@ 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 + if ctx.IsSet(ParallelTxLegacyFlag.Name) { + cfg.ParallelTxLegacyMode = ctx.Bool(ParallelTxLegacyFlag.Name) + // 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) { @@ -2050,8 +2050,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.ParallelTxNum = parallelNum } - if ctx.IsSet(ParallelTx2Flag.Name) { - cfg.ParallelTxMode2 = ctx.Bool(ParallelTx2Flag.Name) + if ctx.IsSet(ParallelTxFlag.Name) { + cfg.ParallelTxMode = ctx.Bool(ParallelTxFlag.Name) } if ctx.IsSet(ParallelTxDAGFlag.Name) { diff --git a/core/blockchain.go b/core/blockchain.go index 289034180d..da03d87125 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -560,11 +560,13 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) } - if bc.vmConfig.EnableParallelExec { + if bc.vmConfig.EnableParallelExecLegacy { bc.CreateParallelProcessor(bc.vmConfig.ParallelTxNum) bc.CreateSerialProcessor(chainConfig, bc, engine) - } else if bc.vmConfig.EnableParallelExecV2 { + log.Info("Parallel V1 enabled", "parallelNum", bc.vmConfig.ParallelTxNum) + } else if bc.vmConfig.EnableParallelExec { bc.processor = newPEVMProcessor(chainConfig, bc, engine) + log.Info("Parallel V2 enabled", "parallelNum", ParallelNum()) } else { bc.processor = NewStateProcessor(chainConfig, bc, engine) } @@ -1972,7 +1974,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) statedb.StartPrefetcher("chain") activeState = statedb - if bc.vmConfig.EnableParallelExec { + if bc.vmConfig.EnableParallelExecLegacy { bc.parseTxDAG(block) txsCount := block.Transactions().Len() threshold := min(bc.vmConfig.ParallelTxNum/2+2, 4) @@ -1986,7 +1988,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } } - if bc.vmConfig.EnableParallelExecV2 { + if bc.vmConfig.EnableParallelExec { bc.parseTxDAG(block) } // If we have a followup block, run that against the current state to pre-cache @@ -2034,7 +2036,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) vtime := time.Since(vstart) proctime := time.Since(start) // processing + validation - if bc.enableTxDAG && !bc.vmConfig.EnableParallelExec && !bc.vmConfig.EnableParallelExecV2 { + if bc.enableTxDAG && !bc.vmConfig.EnableParallelExecLegacy && !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 { diff --git a/core/parallel_state_scheduler.go b/core/parallel_state_scheduler.go index ca53357567..615b56f2d3 100644 --- a/core/parallel_state_scheduler.go +++ b/core/parallel_state_scheduler.go @@ -12,8 +12,9 @@ import ( var runner chan func() func init() { - runner = make(chan func(), runtime.NumCPU()) - for i := 0; i < runtime.NumCPU(); i++ { + cpuNum := runtime.NumCPU() + runner = make(chan func(), cpuNum) + for i := 0; i < cpuNum; i++ { go func() { for f := range runner { f() diff --git a/core/pevm_processor_test.go b/core/pevm_processor_test.go index 4f1c5ff26d..1804243ac8 100644 --- a/core/pevm_processor_test.go +++ b/core/pevm_processor_test.go @@ -142,7 +142,7 @@ func loadGenesis(jsonfile string) *Genesis { func buildBlockChain(genesis *Genesis, parallel bool) *BlockChain { archiveDb := rawdb.NewMemoryDatabase() // Import the chain as an archive node for the comparison baseline - archive, err := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, ethash.NewFaker(), vm.Config{EnableParallelExecV2: parallel}, nil, nil) + archive, err := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: parallel}, nil, nil) if err != nil { panic(err) } @@ -197,7 +197,7 @@ func BenchmarkAkaka(b *testing.B) { for i := 0; i < b.N; i++ { archiveDb := rawdb.NewMemoryDatabase() // Import the chain as an archive node for the comparison baseline - archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExecV2: true}, nil, nil) + archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true}, nil, nil) if n, err := archive.InsertChain(blocks); err != nil { panic(fmt.Sprintf("failed to process block %d: %v", n, err)) } diff --git a/core/state_processor.go b/core/state_processor.go index 2d13af1123..6cc91be944 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 && !p.bc.vmConfig.EnableParallelExec && !p.bc.vmConfig.EnableParallelExecV2 { + if p.bc.enableTxDAG && !p.bc.vmConfig.EnableParallelExecLegacy && !p.bc.vmConfig.EnableParallelExec { statedb.ResetMVStates(len(block.Transactions())) } // Iterate over and process the individual transactions diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 73741cbc46..d8f56ff2a3 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -34,8 +34,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 + EnableParallelExecLegacy bool // Whether to execute transaction in parallel mode when do full sync EnableParallelExec bool // Whether to execute transaction in parallel mode when do full sync - EnableParallelExecV2 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 diff --git a/eth/backend.go b/eth/backend.go index 3465302778..567cf10345 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -237,8 +237,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { var ( vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, + EnableParallelExecLegacy: config.ParallelTxLegacyMode, EnableParallelExec: config.ParallelTxMode, - EnableParallelExecV2: config.ParallelTxMode2, ParallelTxNum: config.ParallelTxNum, EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, } @@ -288,10 +288,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { return nil, err } if config.EnableParallelTxDAG { - if config.ParallelTxMode2 { - eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile, config.ParallelTxMode2) - } else { + if config.ParallelTxMode { eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile, config.ParallelTxMode) + } else { + eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile, config.ParallelTxLegacyMode) } } 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 diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 75cc3d8ed7..1b6f1f345b 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -219,8 +219,8 @@ type Config struct { RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string + ParallelTxLegacyMode bool // Whether to execute transaction in parallel mode when do full sync ParallelTxMode bool // Whether to execute transaction in parallel mode when do full sync - ParallelTxMode2 bool // Whether to execute transaction in parallel mode when do full sync ParallelTxNum int // Number of slot for transaction execution EnableOpcodeOptimizing bool EnableParallelTxDAG bool From 6ffd3e12498ac5fccf436a3a5197bcf65b5198e0 Mon Sep 17 00:00:00 2001 From: welkin22 <136572398+welkin22@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:37:08 +0800 Subject: [PATCH 075/104] pevm: unordered merge mode flag (#202) --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 13 ++++++++++++ core/blockchain.go | 6 +++++- core/parallel_state_scheduler.go | 25 +++++++++++++++++----- core/parallel_state_scheduler_test.go | 16 +++++++------- core/pevm_processor.go | 30 ++++++++++++++++++++++----- core/state/pevm_statedb.go | 6 +++--- core/types/dag.go | 1 - core/types/dag_test.go | 22 ++++++++++++++++++++ core/vm/interpreter.go | 21 ++++++++++--------- eth/backend.go | 11 +++++----- eth/ethconfig/config.go | 13 ++++++------ 12 files changed, 120 insertions(+), 45 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 03b24458db..ee70c29912 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -172,6 +172,7 @@ var ( utils.RollupSuperchainUpgradesFlag, utils.ParallelTxLegacyFlag, utils.ParallelTxFlag, + utils.ParallelTxUnorderedMergeFlag, utils.ParallelTxNumFlag, utils.ParallelTxDAGFlag, utils.ParallelTxDAGFileFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 85ef38dfaa..63bd5988fa 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1112,6 +1112,12 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Category: flags.VMCategory, } + ParallelTxUnorderedMergeFlag = &cli.BoolFlag{ + Name: "parallel.unordered-merge", + Usage: "Enable unordered merge mode, during the parallel confirm phase, merge transaction execution results without following the transaction order.", + 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)", @@ -2054,6 +2060,13 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.ParallelTxMode = ctx.Bool(ParallelTxFlag.Name) } + if ctx.IsSet(ParallelTxUnorderedMergeFlag.Name) { + cfg.ParallelTxUnorderedMerge = ctx.Bool(ParallelTxUnorderedMergeFlag.Name) + if ctx.IsSet(ParallelTxLegacyFlag.Name) && ctx.Bool(ParallelTxLegacyFlag.Name) { + log.Warn("ParallelTxUnorderedMergeFlag does not have any effect in ParallelTxLegacy mode") + } + } + if ctx.IsSet(ParallelTxDAGFlag.Name) { cfg.EnableParallelTxDAG = ctx.Bool(ParallelTxDAGFlag.Name) } diff --git a/core/blockchain.go b/core/blockchain.go index da03d87125..9ba384ea8b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -101,6 +101,10 @@ var ( parallelTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/txs", nil) parallelConflictTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/conflicttxs", nil) + parallelExecutionTimer = metrics.NewRegisteredTimer("chain/parallel/exec", nil) + parallelConfirmTimer = metrics.NewRegisteredTimer("chain/parallel/confirm", nil) + parallelTxLevelsSizeMeter = metrics.NewRegisteredGauge("chain/parallel/txlevel/size", nil) + parallelTxLevelTxSizeMeter = metrics.NewRegisteredGauge("chain/parallel/txlevel/txsize", nil) blockGasUsedGauge = metrics.NewRegisteredGauge("chain/block/gas/used", nil) mgaspsGauge = metrics.NewRegisteredGauge("chain/mgas/ps", nil) @@ -2881,7 +2885,7 @@ func (bc *BlockChain) HeaderChainForceSetHead(headNumber uint64) { } func (bc *BlockChain) TxDAGEnabledWhenMine() bool { - return bc.enableTxDAG && bc.txDAGWriteCh == nil && bc.txDAGReader == nil + return bc.enableTxDAG && bc.txDAGWriteCh == nil && bc.txDAGReader == nil && !bc.vmConfig.EnableParallelExec && !bc.vmConfig.EnableParallelExecLegacy } func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { diff --git a/core/parallel_state_scheduler.go b/core/parallel_state_scheduler.go index 615b56f2d3..60d6070644 100644 --- a/core/parallel_state_scheduler.go +++ b/core/parallel_state_scheduler.go @@ -4,9 +4,11 @@ import ( "fmt" "runtime" "sync" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" ) var runner chan func() @@ -99,7 +101,7 @@ func (cq *confirmQueue) collect(result *PEVMTxResult) error { return nil } -func (cq *confirmQueue) confirmWithTrust(level TxLevel, execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) (error, int) { +func (cq *confirmQueue) confirmWithUnordered(level TxLevel, execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) (error, int) { // find all able-to-confirm transactions, and try to confirm them for _, tx := range level { i := tx.txIndex @@ -187,16 +189,23 @@ func (cq *confirmQueue) rerun(i int, execute func(*PEVMTxRequest) *PEVMTxResult, // run runs the transactions in parallel // execute must return a non-nil result, otherwise it panics. -func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) (error, int) { +func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error, unorderedMerge bool) (error, int) { toConfirm := &confirmQueue{ queue: make([]confirmation, tls.txCount()), confirmed: -1, } - trustDAG := false + totalExecutionTime := int64(0) + totalConfirmTime := int64(0) + maxLevelTxCount := 0 // execute all transactions in parallel for _, txLevel := range tls { + start := time.Now() + log.Debug("txLevel tx count", "tx count", len(txLevel)) + if len(txLevel) > maxLevelTxCount { + maxLevelTxCount = len(txLevel) + } wait := sync.WaitGroup{} trunks := txLevel.Split(runtime.NumCPU()) wait.Add(len(trunks)) @@ -215,9 +224,11 @@ func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func runner <- run } wait.Wait() + totalExecutionTime += time.Since(start).Nanoseconds() + start = time.Now() // all transactions of current level are executed, now try to confirm. - if trustDAG { - if err, txIndex := toConfirm.confirmWithTrust(txLevel, execute, confirm); err != nil { + if unorderedMerge { + if err, txIndex := toConfirm.confirmWithUnordered(txLevel, execute, confirm); err != nil { // something very wrong, stop the process return err, txIndex } @@ -227,7 +238,11 @@ func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func return err, txIndex } } + totalConfirmTime += time.Since(start).Nanoseconds() } + parallelTxLevelTxSizeMeter.Update(int64(maxLevelTxCount)) + parallelExecutionTimer.Update(time.Duration(totalExecutionTime)) + parallelConfirmTimer.Update(time.Duration(totalConfirmTime)) return nil, 0 } diff --git a/core/parallel_state_scheduler_test.go b/core/parallel_state_scheduler_test.go index 9b2496cc0f..6c181e532c 100644 --- a/core/parallel_state_scheduler_test.go +++ b/core/parallel_state_scheduler_test.go @@ -263,9 +263,7 @@ func setTxIndex(allReq []*PEVMTxRequest) { func TestTxLevelRun(t *testing.T) { // case 1: empty txs case1 := func() { - levels([]uint64{}, [][]int{}).Run( - func(*PEVMTxRequest) *PEVMTxResult { return nil }, - func(*PEVMTxResult) error { return nil }) + levels([]uint64{}, [][]int{}).Run(func(*PEVMTxRequest) *PEVMTxResult { return nil }, func(*PEVMTxResult) error { return nil }, false) } // case 2: 4 txs with no dependencies, no conflicts case2 := func() { @@ -287,7 +285,7 @@ func TestTxLevelRun(t *testing.T) { nil, nil, nil, nil, }) caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm) + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, false) ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21, 7: 31, 8: 41}) if err != nil { t.Fatalf("failed, err:%v", err) @@ -323,7 +321,7 @@ func TestTxLevelRun(t *testing.T) { nil, nil, {0}, {1}, }) caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm) + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, false) ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21}) if err != nil { t.Fatalf("failed, err:%v", err) @@ -359,7 +357,7 @@ func TestTxLevelRun(t *testing.T) { {0}, nil, {-1}, {-1}, }) caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm) + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, false) ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21}) if err != nil { t.Fatalf("failed, err:%v", err) @@ -407,7 +405,7 @@ func TestTxLevelRun(t *testing.T) { res[i+2000] = i } caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm) + err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm, false) ok := checkMainDB(res) if err != nil { t.Fatalf("failed, err:%v", err) @@ -433,7 +431,7 @@ func TestTxLevelRun(t *testing.T) { } setTxIndex(allReqs) caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm) + err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm, false) ok := checkMainDB(map[int]int{1: 5, 2: 20, 3: 10}) if err != nil { t.Fatalf("failed, err:%v", err) @@ -462,7 +460,7 @@ func TestTxLevelRun(t *testing.T) { } caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} setTxIndex(allReqs) - err, _ := NewTxLevels(allReqs, dag).Run(caller.execute, caller.confirm) + err, _ := NewTxLevels(allReqs, dag).Run(caller.execute, caller.confirm, false) ok := checkMainDB(map[int]int{1: 5, 2: 20, 3: 10}) if err != nil { t.Fatalf("failed, err:%v", err) diff --git a/core/pevm_processor.go b/core/pevm_processor.go index f1fe0adb4e..339e4d7179 100644 --- a/core/pevm_processor.go +++ b/core/pevm_processor.go @@ -26,14 +26,16 @@ type PEVMProcessor struct { commonTxs []*types.Transaction receipts types.Receipts debugConflictRedoNum uint64 + unorderedMerge bool } func newPEVMProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *PEVMProcessor { processor := &PEVMProcessor{ StateProcessor: *NewStateProcessor(config, bc, engine), + unorderedMerge: bc.vmConfig.EnableParallelUnorderedMerge, } log.Info("Parallel execution mode is enabled", "Parallel Num", ParallelNum(), - "CPUNum", runtime.NumCPU()) + "CPUNum", runtime.NumCPU(), "unorderedMerge", processor.unorderedMerge) return processor } @@ -120,9 +122,14 @@ func (p *PEVMProcessor) executeInSlot(maindb *state.StateDB, txReq *PEVMTxReques // if it is in Stage 2 it is a likely result, not 100% sure func (p *PEVMProcessor) toConfirmTxIndexResult(txResult *PEVMTxResult) error { txReq := txResult.txReq - if err := p.hasConflict(txResult); err != nil { - log.Info(fmt.Sprintf("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex)) - return err + if !p.unorderedMerge || !txReq.useDAG { + // If we do not use a DAG, then we need to check for conflicts to ensure correct execution. + // When we perform an unordered merge, we cannot conduct conflict checks + // and can only choose to trust that the DAG is correct and that conflicts do not exist. + if err := p.hasConflict(txResult); err != nil { + log.Info(fmt.Sprintf("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex)) + return err + } } // goroutine unsafe operation will be handled from here for safety @@ -253,6 +260,8 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // parallel execution start := time.Now() txLevels := NewTxLevels(p.allTxReqs, txDAG) + log.Debug("txLevels size", "txLevels size", len(txLevels)) + parallelTxLevelsSizeMeter.Update(int64(len(txLevels))) buildLevelsDuration := time.Since(start) var executeDurations, confirmDurations int64 = 0, 0 err, txIndex := txLevels.Run(func(pr *PEVMTxRequest) (res *PEVMTxResult) { @@ -274,8 +283,9 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg atomic.AddUint64(&p.debugConflictRedoNum, 1) } }(time.Now()) + log.Debug("pevm confirm", "txIndex", pr.txReq.txIndex) return p.confirmTxResult(statedb, gp, pr) - }) + }, p.unorderedMerge && txDAG != nil) parallelRunDuration := time.Since(start) - buildLevelsDuration if err != nil { tx := allTxs[txIndex] @@ -320,7 +330,17 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg p.engine.Finalize(p.bc, header, statedb, p.commonTxs, block.Uncles(), withdrawals) var allLogs []*types.Log + var lindex = 0 + var cumulativeGasUsed uint64 for _, receipt := range p.receipts { + // reset the log index + for _, log := range receipt.Logs { + log.Index = uint(lindex) + lindex++ + } + // re-calculate the cumulativeGasUsed + cumulativeGasUsed += receipt.GasUsed + receipt.CumulativeGasUsed = cumulativeGasUsed allLogs = append(allLogs, receipt.Logs...) } return p.receipts, allLogs, *usedGas, nil diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go index f511b0c651..38cde4b53f 100644 --- a/core/state/pevm_statedb.go +++ b/core/state/pevm_statedb.go @@ -486,9 +486,9 @@ func (pst *UncommittedDB) Merge(deleteEmptyObjects bool) error { // so we don't need to merge anything. return nil } - if err := pst.conflictsToMaindb(); err != nil { - return err - } + //if err := pst.conflictsToMaindb(); err != nil { + // return err + //} // 0. set the TxContext pst.maindb.SetTxContext(pst.txHash, pst.txIndex) diff --git a/core/types/dag.go b/core/types/dag.go index b3d5f0f081..7dc0d89e9b 100644 --- a/core/types/dag.go +++ b/core/types/dag.go @@ -176,7 +176,6 @@ func ValidatePlainTxDAG(d TxDAG, txCnt int) error { return fmt.Errorf("PlainTxDAG contains unordered dependency, tx: %v", i) } } - } return nil } diff --git a/core/types/dag_test.go b/core/types/dag_test.go index cc50f2e5db..f59ca51cc1 100644 --- a/core/types/dag_test.go +++ b/core/types/dag_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/snappy" "github.com/cometbft/cometbft/libs/rand" @@ -21,16 +22,37 @@ var ( func TestEncodeTxDAGCalldata(t *testing.T) { tg := mockSimpleDAG() + originTg := tg data, err := EncodeTxDAGCalldata(tg) assert.Equal(t, nil, err) tg, err = DecodeTxDAGCalldata(data) assert.Equal(t, nil, err) assert.Equal(t, true, tg.TxCount() > 0) + assert.Equal(t, originTg, tg) _, err = DecodeTxDAGCalldata(nil) assert.NotEqual(t, nil, err) } +func TestDecodeCalldata(t *testing.T) { + calldata := "0x5517ed8c000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b201f901aef901abc2c002c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c152c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c0010000000000000000000000000000" + decode, err := hexutil.Decode(calldata) + if err != nil { + return + } + dagCalldata, err := DecodeTxDAGCalldata(decode) + if err != nil { + t.Errorf("Error decoding calldata: %s", err) + return + } + //for i := 0; i < dagCalldata.TxCount(); i++ { + // dep := dagCalldata.TxDep(i) + // log.Printf("idx:%d,dep:%v", i, dep.TxIndexes) + //} + assert.Equal(t, true, dagCalldata.TxDep(186).Exist(82)) + assert.Equal(t, 0, dagCalldata.TxDep(187).Count()) +} + func TestTxDAG_SetTxDep(t *testing.T) { dag := mockSimpleDAG() require.NoError(t, dag.SetTxDep(9, NewTxDep(nil, NonDependentRelFlag))) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index d8f56ff2a3..2678f7460f 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -30,16 +30,17 @@ type PrecompileOverrides func(params.Rules, PrecompiledContract, common.Address) // Config are the configuration options for the Interpreter type Config struct { - Tracer EVMLogger // Opcode logger - 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 - EnableParallelExecLegacy bool // Whether to execute transaction in parallel mode when do full sync - 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 - TxDAG types.TxDAG + Tracer EVMLogger // Opcode logger + 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 + EnableParallelExecLegacy bool // Whether to execute transaction in parallel mode when do full sync + 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 + TxDAG types.TxDAG + EnableParallelUnorderedMerge bool // Whether to enable unordered merge in parallel mode } // ScopeContext contains the things that are per-call, such as stack and memory, diff --git a/eth/backend.go b/eth/backend.go index 567cf10345..b0271c76c4 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -236,11 +236,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } var ( vmConfig = vm.Config{ - EnablePreimageRecording: config.EnablePreimageRecording, - EnableParallelExecLegacy: config.ParallelTxLegacyMode, - EnableParallelExec: config.ParallelTxMode, - ParallelTxNum: config.ParallelTxNum, - EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, + EnablePreimageRecording: config.EnablePreimageRecording, + EnableParallelExecLegacy: config.ParallelTxLegacyMode, + EnableParallelExec: config.ParallelTxMode, + EnableParallelUnorderedMerge: config.ParallelTxUnorderedMerge, + ParallelTxNum: config.ParallelTxNum, + EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 1b6f1f345b..39738a8824 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -219,12 +219,13 @@ type Config struct { RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string - ParallelTxLegacyMode bool // Whether to execute transaction in parallel mode when do full sync - 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 + ParallelTxLegacyMode bool // Whether to execute transaction in parallel mode when do full sync + 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 + ParallelTxUnorderedMerge bool // Whether to enable unordered merge in parallel mode } // CreateConsensusEngine creates a consensus engine for the given chain config. From 3c2bedf2a501a592531327573c590ca0b25ffa24 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:57:54 +0800 Subject: [PATCH 076/104] pevm: remove the ParallelLegacy (#201) --- cmd/geth/main.go | 1 - cmd/utils/flags.go | 32 - core/blockchain.go | 55 +- core/parallel_state_processor.go | 1236 ------------------- core/state/parallel_statedb.go | 1901 ------------------------------ core/state/state_object.go | 33 - core/state/statedb.go | 420 +------ core/state/statedb_test.go | 376 ------ core/state_processor.go | 2 +- core/vm/interpreter.go | 1 - eth/backend.go | 3 - eth/ethconfig/config.go | 2 +- 12 files changed, 6 insertions(+), 4056 deletions(-) delete mode 100644 core/parallel_state_processor.go delete mode 100644 core/state/parallel_statedb.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index ee70c29912..1cba78f04d 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -170,7 +170,6 @@ var ( utils.RollupComputePendingBlock, utils.RollupHaltOnIncompatibleProtocolVersionFlag, utils.RollupSuperchainUpgradesFlag, - utils.ParallelTxLegacyFlag, utils.ParallelTxFlag, utils.ParallelTxUnorderedMergeFlag, utils.ParallelTxNumFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 63bd5988fa..cd8cadd115 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -29,7 +29,6 @@ import ( "net/http" "os" "path/filepath" - "runtime" godebug "runtime/debug" "strconv" "strings" @@ -1100,12 +1099,6 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Category: flags.MetricsCategory, } - ParallelTxLegacyFlag = &cli.BoolFlag{ - Name: "parallel-legacy", - Usage: "Enable the experimental parallel transaction execution mode, only valid in full sync mode (default = false)", - Category: flags.VMCategory, - } - ParallelTxFlag = &cli.BoolFlag{ Name: "parallel", Usage: "Enable the experimental parallel transaction execution mode, only valid in full sync mode (default = false)", @@ -2034,37 +2027,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) } - if ctx.IsSet(ParallelTxLegacyFlag.Name) { - cfg.ParallelTxLegacyMode = ctx.Bool(ParallelTxLegacyFlag.Name) - // 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 - parallelNum = ctx.Int(ParallelTxNumFlag.Name) - if parallelNum < 1 { - parallelNum = 1 - } - } else if numCpu == 1 { - parallelNum = 1 // single CPU core - } else { - // 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 - } - if ctx.IsSet(ParallelTxFlag.Name) { cfg.ParallelTxMode = ctx.Bool(ParallelTxFlag.Name) } if ctx.IsSet(ParallelTxUnorderedMergeFlag.Name) { cfg.ParallelTxUnorderedMerge = ctx.Bool(ParallelTxUnorderedMergeFlag.Name) - if ctx.IsSet(ParallelTxLegacyFlag.Name) && ctx.Bool(ParallelTxLegacyFlag.Name) { - log.Warn("ParallelTxUnorderedMergeFlag does not have any effect in ParallelTxLegacy mode") - } } if ctx.IsSet(ParallelTxDAGFlag.Name) { diff --git a/core/blockchain.go b/core/blockchain.go index 9ba384ea8b..e79a5761d6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -564,11 +564,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) } - if bc.vmConfig.EnableParallelExecLegacy { - bc.CreateParallelProcessor(bc.vmConfig.ParallelTxNum) - bc.CreateSerialProcessor(chainConfig, bc, engine) - log.Info("Parallel V1 enabled", "parallelNum", bc.vmConfig.ParallelTxNum) - } else if bc.vmConfig.EnableParallelExec { + if bc.vmConfig.EnableParallelExec { bc.processor = newPEVMProcessor(chainConfig, bc, engine) log.Info("Parallel V2 enabled", "parallelNum", ParallelNum()) } else { @@ -1978,20 +1974,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) statedb.StartPrefetcher("chain") activeState = statedb - if bc.vmConfig.EnableParallelExecLegacy { - bc.parseTxDAG(block) - txsCount := block.Transactions().Len() - threshold := min(bc.vmConfig.ParallelTxNum/2+2, 4) - 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 { - bc.UseParallelProcessor() - log.Debug("Enable Parallel Tx execution", "block", block.NumberU64(), "transactions", txsCount, "parallelTxNum", bc.vmConfig.ParallelTxNum) - - } - } - if bc.vmConfig.EnableParallelExec { bc.parseTxDAG(block) } @@ -2018,10 +2000,6 @@ 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) @@ -2040,7 +2018,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) vtime := time.Since(vstart) proctime := time.Since(start) // processing + validation - if bc.enableTxDAG && !bc.vmConfig.EnableParallelExecLegacy && !bc.vmConfig.EnableParallelExec { + 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 { @@ -2845,14 +2823,6 @@ func (bc *BlockChain) GetTrieFlushInterval() time.Duration { return time.Duration(bc.flushInterval.Load()) } -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 { return bc.stateCache.NoTries() } @@ -2885,7 +2855,7 @@ func (bc *BlockChain) HeaderChainForceSetHead(headNumber uint64) { } func (bc *BlockChain) TxDAGEnabledWhenMine() bool { - return bc.enableTxDAG && bc.txDAGWriteCh == nil && bc.txDAGReader == nil && !bc.vmConfig.EnableParallelExec && !bc.vmConfig.EnableParallelExecLegacy + return bc.enableTxDAG && bc.txDAGWriteCh == nil && bc.txDAGReader == nil && !bc.vmConfig.EnableParallelExec } func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { @@ -2933,25 +2903,6 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { }() } -func (bc *BlockChain) UseParallelProcessor() { - if bc.parallelProcessor != nil { - bc.parallelExecution = true - bc.processor = bc.parallelProcessor - } else { - log.Error("bc.ParallelProcessor is nil! fallback to serial processor!") - bc.UseSerialProcessor() - } -} - -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 deleted file mode 100644 index e61ffe0cb9..0000000000 --- a/core/parallel_state_processor.go +++ /dev/null @@ -1,1236 +0,0 @@ -package core - -import ( - "context" - "errors" - "fmt" - "github.com/ethereum/go-ethereum/metrics" - "runtime" - "sync" - "sync/atomic" - - "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" -) - -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 -) - -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 *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 *sync.Map - stopSlotChan chan struct{} - stopConfirmChan chan struct{} - debugConflictRedoNum int - - confirmStage2Chan chan int - stopConfirmStage2Chan chan struct{} - txReqExecuteRecord map[int]int - txReqExecuteCount int - inConfirmStage2 bool - targetStage2Count int - nextStage2TxIndex int - delayGasFee bool - - commonTxs []*types.Transaction - receipts types.Receipts - error error - 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 { - processor := &ParallelStateProcessor{ - StateProcessor: *NewStateProcessor(config, bc, engine), - parallelNum: parallelNum, - } - processor.init() - return processor -} - -type MergedTxInfo struct { - slotDB *state.StateDB - 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 // record the current execute number of the tx - slotIndex int - txReq *ParallelTxRequest - receipt *types.Receipt - slotDB *state.ParallelStateDB - gpSlot *GasPool - evm *vm.EVM - result *ExecutionResult - originalNonce *uint64 - err error -} - -type ParallelTxRequest struct { - txIndex int - baseStateDB *state.StateDB - staticSlotIndex int - tx *types.Transaction - gasLimit uint64 - msg *Message - block *types.Block - vmConfig vm.Config - usedGas *uint64 - curTxChan chan int - runnable int32 // 0: not runnable 1: runnable - can be scheduled - executedNum atomic.Int32 - conflictIndex atomic.Int32 // the conflicted mainDB index, the txs will not be executed before this number - useDAG bool -} - -// 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()) - 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.resultProcessChan = make(chan *ResultHandleEnv, 1) - 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{ - 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) - }(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) - go func() { - p.runConfirmStage2Loop() - }() - - go func() { - p.handlePendingResultLoop() - }() - -} - -// resetState clear slot state for each block. -func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { - if txNum == 0 { - return - } - p.mergedTxIndex.Store(-1) - p.debugConflictRedoNum = 0 - p.inConfirmStage2 = false - - statedb.PrepareForParallel() - p.allTxReqs = make([]*ParallelTxRequest, txNum) - - for _, slot := range p.slotState { - slot.pendingTxReqList = make([]*ParallelTxRequest, 0) - slot.activatedType = parallelPrimarySlot - } - 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 - 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 goes to 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 { - slotIndex = p.mostHungrySlot() - } - // 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) - } -} - -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 -} - -// hasConflict conducts conflict check -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 { - // check whether the slot db reads during execution are 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{}{} - } - } else if atomic.CompareAndSwapInt32(&slot.activatedType, parallelShadowSlot, parallelPrimarySlot) { - // switch from shadow to normal slot - if len(slot.primaryWakeUpChan) == 0 { - 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. - // 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(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) - - 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 = slotDB.GetNonce(txReq.msg.From) - } - - slotDB.SetTxContext(txReq.tx.Hash(), txReq.txIndex) - evm, result, err := applyTransactionStageExecution(txReq.msg, gpSlot, slotDB, vmenv, p.delayGasFee) - txResult := ParallelTxResult{ - executedIndex: execNum, - slotIndex: slotIndex, - txReq: txReq, - receipt: nil, - 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. - // 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 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 - // during execution. - conflictIndex = txReq.conflictIndex.Load() - if conflictIndex < mIndex { - if txReq.conflictIndex.CompareAndSwap(conflictIndex, mIndex) { - log.Debug(fmt.Sprintf("Update conflictIndex in execution because of error: %s, new conflictIndex: %d", err.Error(), 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 - // 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 -} - -// 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 { - // `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 < targetResult.txReq.executedNum.Load() { - // skip the intermediate result that is not the latest. - return nil - } - } else { - // pop one result as target result. - result, ok := p.pendingConfirmResults.LoadAndDelete(targetTxIndex) - if !ok { - return nil - } - targetResult = result.(*ParallelTxResult) - } - - valid := p.toConfirmTxIndexResult(targetResult, isStage2) - if !valid { - 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)) { - log.Debug("Update conflict index", "conflictIndex", conflictIndex, "conflictBase", conflictBase) - } - } - 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 _, 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. - if targetResult.txReq.txIndex == int(p.mergedTxIndex.Load())+1 && - targetResult.slotDB.BaseTxIndex() == int(p.mergedTxIndex.Load()) { - 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++ - // interrupt its current routine, and switch to the other routine - p.switchSlot(staticSlotIndex) - // reclaim the result. - p.slotDBsToRelease.Store(targetResult.slotDB, targetResult.slotDB) - 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.Info(fmt.Sprintf("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 - txResult.receipt, txResult.err = applyTransactionStageFinalization(txResult.evm, txResult.result, - *txReq.msg, p.config, txResult.slotDB, txReq.block, - 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 - } - - lastStartPos := 0 - for { - select { - case <-stopChan: - p.stopSlotChan <- struct{}{} - continue - case <-wakeupChan: - } - - interrupted := false - - 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 nextMergeReq.runnable == 1 { - if atomic.CompareAndSwapInt32(&nextMergeReq.runnable, 1, 0) { - // execute. - res := p.executeInSlot(slotIndex, nextMergeReq) - if res != nil { - p.txResultChan <- res - } - } - } - } - - 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 - } - } - // 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 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 nextMergeReq.runnable == 1 { - if atomic.CompareAndSwapInt32(&nextMergeReq.runnable, 1, 0) { - // execute. - res := p.executeInSlot(slotIndex, nextMergeReq) - if res != nil { - p.txResultChan <- res - } - } - } - } - - if stealTxReq.runnable == 1 { - if !atomic.CompareAndSwapInt32(&stealTxReq.runnable, 1, 0) { - continue - } - res := p.executeInSlot(slotIndex, stealTxReq) - if res == nil { - continue - } - p.txResultChan <- res - } - } - } -} - -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 - - 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 txReq.runnable == 1 { - if !atomic.CompareAndSwapInt32(&txReq.runnable, 1, 0) { - continue - } - res := p.executeInSlot(slotIndex, txReq) - if res != nil { - executed-- - p.txResultChan <- res - } - } - } - } -} - -func (p *ParallelStateProcessor) runConfirmStage2Loop() { - for { - 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 - 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) { - endTxIndex = txSize - 1 - } - log.Debug("runConfirmStage2Loop", "startTxIndex", startTxIndex, "endTxIndex", endTxIndex) - 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(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 && int(p.mergedTxIndex.Load()) >= p.nextStage2TxIndex { - p.nextStage2TxIndex = int(p.mergedTxIndex.Load()) + stage2CheckNumber - p.confirmStage2Chan <- int(p.mergedTxIndex.Load()) - } - 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() - - 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 { - statedb.AddBalance(delayGasFee.Coinbase, delayGasFee.TipFee) - } - if delayGasFee.BaseFee != nil { - statedb.AddBalance(params.OptimismBaseFeeRecipient, delayGasFee.BaseFee) - } - if delayGasFee.L1Fee != nil { - statedb.AddBalance(params.OptimismL1FeeRecipient, delayGasFee.L1Fee) - } - } - - // 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()) - } - 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: - } - } - // 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]++ - } - // after merge, the slotDB will not accessible, reclaim the resource - p.slotDBsToRelease.Store(result.slotDB, result.slotDB) - 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 { - <-p.txResultChan - continue - } - break - } - // 3.make sure the confirmation routine is stopped - 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(p.parallelDBManager) - return true - }) - }() -} - -// 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 ( - 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) - } - - misc.EnsureCreate2Deployer(p.config, block.Time(), statedb) - - allTxs := block.Transactions() - p.resetState(len(allTxs), statedb) - - 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) - } - statedb.MarkFullProcessed() - txDAG := cfg.TxDAG - - txNum := len(allTxs) - latestExcludedTx := -1 - // Iterate over and process the individual transactions - p.commonTxs = make([]*types.Transaction, 0, txNum) - p.receipts = make([]*types.Receipt, 0, txNum) - - parallelNum := p.parallelNum - - if txNum > parallelNum*2 && txNum >= 4 { - var wg sync.WaitGroup - errChan := make(chan error) - - 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 - } - } - - // 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) - } - - // 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 - } - } - - // Wait for all workers to finish and handle errors - go func() { - wg.Wait() - close(errChan) - }() - - for err := range errChan { - return nil, nil, 0, err - } - // - } 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) - // set up stage2 enter criteria - 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 - p.targetStage2Count = p.targetStage2Count - stage2AheadNum - } - - p.delayGasFee = false - p.doStaticDispatch(p.allTxReqs) - if txDAG != nil && txDAG.DelayGasFeeDistribution() { - p.delayGasFee = true - } - - // after static dispatch, we notify the slot to work. - for _, slot := range p.slotState { - slot.primaryWakeUpChan <- struct{}{} - } - - // kick off the result handler. - p.resultProcessChan <- &ResultHandleEnv{statedb: statedb, gp: gp, txCount: allTxCount} - for { - 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 { - // 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 - } - 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 - // 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(p.commonTxs), - "conflictNum", p.debugConflictRedoNum, - "redoRate(%)", 100*(p.debugConflictRedoNum)/len(p.commonTxs), - "txDAG", txDAG != nil) - } - if metrics.EnabledExpensive { - parallelTxNumMeter.Mark(int64(len(p.commonTxs))) - parallelConflictTxNumMeter.Mark(int64(p.debugConflictRedoNum)) - } - - // 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, p.commonTxs, block.Uncles(), withdrawals) - - var allLogs []*types.Log - for _, receipt := range p.receipts { - allLogs = append(allLogs, receipt.Logs...) - } - 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 (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) - evm.Reset(txContext, statedb) - - // Apply the transaction to the current state (included in the env). - 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 - } - - return evm, result, err -} - -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. - 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(), block.NumberU64(), block.Hash()) - receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) - receipt.BlockHash = block.Hash() - receipt.BlockNumber = block.Number() - receipt.TransactionIndex = uint(statedb.TxIndex()) - return receipt, nil -} diff --git a/core/state/parallel_statedb.go b/core/state/parallel_statedb.go deleted file mode 100644 index 6321dfb9d1..0000000000 --- a/core/state/parallel_statedb.go +++ /dev/null @@ -1,1901 +0,0 @@ -package state - -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/holiman/uint256" - "runtime" - "sort" - "sync" -) - -const defaultNumOfSlots = 5 - -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 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, - "valSlot", val, "valUnconfirm", valUnconfirm, - "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - return true - } - } - } - valMain := slotDB.getStateFromMainNoUpdate(addr, key) // mainDB.GetStateNoUpdate(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, - "mainDB.TxIndex", mainDB.TxIndex()) - 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, manager *ParallelDBManager, unconfirmedDBs *sync.Map, useDAG bool) *ParallelStateDB { - slotDB := db.CopyForSlot(manager) - 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 -} - -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) - } - 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) - - s.reset() - parallelDBManager.reclaim(s) -} - -// getStateDBBasePtr get the pointer of parallelStateDB. -func (s *ParallelStateDB) getStateDBBasePtr() *StateDB { - return &s.StateDB -} - -func (s *ParallelStateDB) SetSlotIndex(index int) { - s.parallel.SlotIndex = index -} - -// 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 { - if obj.deleted { - return nil - } - object = obj - } else { - object = s.getStateObjectNoSlot(addr) - } - return object -} - -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.locatStateObjects[addr] = stateObject -} - -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) { - 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" - - if prev != nil { - // check slot - _, prevdestruct = s.getStateObjectsDestruct(prev.address) - - if !prevdestruct { - // 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() - } - } - } - - 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 (if not DAG) -> 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 - 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(), types.EmptyCodeHash.Bytes()) // 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 read 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 - } - if _, ok := s.parallel.balanceReadsInSlot[addr]; !ok { - 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 - } - if _, ok := s.parallel.nonceReadsInSlot[addr]; !ok { - 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 - } else { - dirtyObj = nil - } - - // 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() - } - } - - if _, ok := s.parallel.codeReadsInSlot[addr]; !ok { - s.parallel.codeReadsInSlot[addr] = code - } - // fixup dirties - if dirtyObj != nil { - if dirtyObj.code == nil { - dirtyObj.code = code - } - if !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) - 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 - } - if _, ok := s.parallel.codeReadsInSlot[addr]; !ok { - 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()) - } - } - if _, ok := s.parallel.codeHashReadsInSlot[addr]; !ok { - s.parallel.codeHashReadsInSlot[addr] = codeHash - } - - // 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) - // 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(), types.EmptyCodeHash.Bytes()) { - 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 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. - // 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 - - 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 - } - } - // 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) - } - - value := common.Hash{} - // 2.3 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) - } - value = val - } - 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 -} - -// 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 { - - // 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. - // 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{} - // 1.2 Try to get from unconfirmed DB if exist - if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { - value = val - } else { - // 2. 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) - } - if _, ok := s.parallel.kvReadsInSlot[addr].GetValue(hash); !ok { - s.parallel.kvReadsInSlot[addr].StoreValue(hash, value) // update cache - } - 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) - // 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) - // 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 { - 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) - } - - 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 { - newStateObject := object.lightCopy(s) - newStateObject.markSelfdestructed() - newStateObject.setBalance(new(uint256.Int)) - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - s.parallel.addrStateChangesInSlot[addr] = false - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.codeChangesInSlot[addr] = struct{}{} - 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.setBalance(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) - 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) { - 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 { - 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 { - 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 { - 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) { - 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 { - 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 { - 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) { - 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 { - 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 { - 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) { - 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 { - 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 { - 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) { - 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) - if !ok { - continue - } - db := db_.(*ParallelStateDB) - if exist, ok := db.parallel.addrStateChangesInSlot[addr]; ok { - if obj, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - 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) { - 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) - 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 - } - // 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 - } - } - } - 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) { - 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) - if !ok { - continue - } - db := db_.(*ParallelStateDB) - if obj, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; ok { - return obj, true - } - } - 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 { - parallelKvOnce.Do(func() { - StartKvCheckLoop() - }) - - mainDB := slotDB.parallel.baseStateDB - // conservatively use kvRead size as the initial size. - 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 - if slotDB.parallel.useDAG { - // DAG never reads from unconfirmedDB, skip check. - return true - } - 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 - } - } - } - var nonceMain uint64 = 0 - mainObj := slotDB.getStateObjectFromMainDBNoUpdate(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, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, - "mainIndex", mainDB.txIndex) - - return false - } - } - // 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 true - } - if balanceUnconfirm := slotDB.getBalanceFromUnconfirmedDB(addr); balanceUnconfirm != nil { - if balanceSlot.Cmp(balanceUnconfirm) == 0 { - continue - } - return false - } - } - - balanceMain := common.U2560 - mainObj := slotDB.getStateObjectFromMainDBNoUpdate(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, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, - "mainIndex", mainDB.txIndex) - 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) - if readLen < 8 || 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 { - var codeMain []byte = nil - object := slotDB.getStateObjectFromMainDBNoUpdate(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, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, - "mainIndex", mainDB.txIndex) - return false - } - } - // check codeHash - for addr, codeHashSlot := range slotDB.parallel.codeHashReadsInSlot { - codeHashMain := common.Hash{} - object := slotDB.getStateObjectFromMainDBNoUpdate(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, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, "mainIndex", mainDB.txIndex) - return false - } - } - // addr state check - for addr, stateSlot := range slotDB.parallel.addrStateReadsInSlot { - stateMain := false // addr not exist - if slotDB.getStateObjectFromMainDBNoUpdate(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, "mainIndex", mainDB.txIndex) - return false - } - } - // snapshot destructs check - for addr, destructRead := range slotDB.parallel.addrSnapDestructsReadsInSlot { - mainObj := mainDB.getDeletedStateObjectNoUpdate(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 && addr.Hex() != "0x0000000000000000000000000000000000000001" { - log.Debug("IsSlotDBReadsValid snapshot destructs read invalid", - "addr", addr, "destructRead", destructRead, "destructMain", destructMain, - "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex, - "mainIndex", mainDB.txIndex) - 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 handles 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 { - 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 - obj, exist = mainDB.getStateObjectFromStateObjects(addr) - if !exist { - 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. - 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. - mainDB.AccountMux.Lock() - 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) - mainDB.StorageMux.Unlock() - } 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 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 - } - 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 { - 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. - // 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 - } - - // 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.AccountMux.Lock() - 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) - mainDB.StorageMux.Unlock() - } 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 { - // don't do finalise() here as to keep dirtyObjects unchanged in dirtyStorages, which avoid contention issue. - obj.fixUpOriginAndResetPendingStorage() - } - } - - if obj.created { - s.parallel.createdObjectRecord[addr] = struct{}{} - } - obj.created = false - - s.stateObjectsPending[addr] = struct{}{} - s.stateObjectsDirty[addr] = struct{}{} - - // 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 - } - - 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() -} - -func (s *ParallelStateDB) reset() { - - 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 = nil - s.parallel.locatStateObjects = nil - 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]*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) - 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/state_object.go b/core/state/state_object.go index 561ee0fbef..a7a0aafb67 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -744,39 +744,6 @@ func (s *stateObject) setBalance(amount *uint256.Int) { // ReturnGas Return the gas back to the origin. Used by the Virtual machine or Closures 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 - - 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.storageRecordsLock.RLock() - object.originStorage = s.originStorage.Copy() - object.pendingStorage = s.pendingStorage.Copy() - s.storageRecordsLock.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. diff --git a/core/state/statedb.go b/core/state/statedb.go index c86134dcda..efecb52e58 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,7 +18,6 @@ package state import ( - "container/list" "errors" "fmt" "runtime" @@ -308,7 +307,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) 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), + stateObjectsDestructDirty: make(map[common.Address]*types.StateAccount), logs: make(map[common.Hash][]*types.Log), preimages: make(map[common.Hash][]byte), journal: newJournal(), @@ -1214,146 +1213,6 @@ func (s *StateDB) copyInternal(doPrefetch bool) *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 logsPool = sync.Pool{ - New: func() interface{} { return make(map[common.Hash][]*types.Log, defaultNumOfSlots) }, -} - -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. - // 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. - - 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{}), - 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), - createdObjectRecord: addressToStructPool.Get().(map[common.Address]struct{}), - } - state := &ParallelStateDB{ - 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, - }, - } - state.snapDestructs = addressToStructPool.Get().(map[common.Address]struct{}) - return state -} - -// CopyForSlot copy all the basic fields, initialize the memory ones -func (s *StateDB) CopyForSlot(parallelDBManager *ParallelDBManager) *ParallelStateDB { - state := 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 - } - s.snapParallelLock.RUnlock() - - if s.snaps != nil { - state.snaps = s.snaps - state.snap = s.snap - } - - // 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.accountsOrigin = copySet(state.accountsOrigin) - s.AccountMux.Unlock() - s.StorageMux.Lock() - state.storages = copy2DSet(s.storages) - state.storagesOrigin = copy2DSet(state.storagesOrigin) - s.StorageMux.Unlock() - - return state -} - // Snapshot returns an identifier for the current revision of the state. func (s *StateDB) Snapshot() int { id := s.nextRevisionId @@ -2435,280 +2294,3 @@ func (s *StateDB) PrepareForParallel() { } } } - -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 - } - obj.storageRecordsLock.RLock() - // 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 - }) - 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 { - s.trieParallelLock.Lock() - s.prefetcher.prefetch(common.Hash{}, s.originalRoot, emptyAddr, addressesToPrefetch) - s.trieParallelLock.Unlock() - } -} - -// 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, fees *DelayedGasFee) *StateDB { - - 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 { - // addr not exist on main DB, the object is created in the merging tx. - mainObj = dirtyObj.deepCopy(s) - 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 - 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) - 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 { - // 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 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 - // 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 - } - } else { - // The object is deleted in the TX. - newMainObj = dirtyObj.deepCopy(s) - } - - // 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 - // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts - 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) - 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.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 - } - 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.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 { - if _, exist := s.stateObjectsPending[addr]; !exist { - s.stateObjectsPending[addr] = struct{}{} - } - } - - 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 - } - - if s.accessList != nil && slotDb.accessList != nil { - s.accessList.Append(slotDb.accessList) - } - - for k := range slotDb.snapDestructs { - s.snapParallelLock.Lock() - s.snapDestructs[k] = struct{}{} - s.snapParallelLock.Unlock() - } - - s.SetTxContext(slotDb.thash, slotDb.txIndex) - return s -} - -// 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 -} - -// 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 { - m.mutex.Lock() - defer m.mutex.Unlock() - - if m.pool.Len() == 0 { - return m.newFunc() - } - - elem := m.pool.Front() - m.pool.Remove(elem) - ret := elem.Value.(*ParallelStateDB) - return ret -} - -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 1a85f8f2b3..1ee27444c1 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -19,7 +19,6 @@ package state import ( "bytes" "encoding/binary" - "encoding/hex" "errors" "fmt" "math" @@ -1198,378 +1197,3 @@ 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() - manager := NewParallelDBManager(1, NewEmptySlotDB) - slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - - addr := common.BytesToAddress([]byte("so")) - slotDb.SetBalance(addr, uint256.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, uint256.NewInt(1)) - unconfirmedDBs := new(sync.Map) - state.PrepareForParallel() - 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 { - 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, uint256.NewInt(1)) - state.PrepareForParallel() - - unconfirmedDBs := new(sync.Map) - 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") - } - - 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, uint256.NewInt(1)) - state.PrepareForParallel() - - unconfirmedDBs := new(sync.Map) - manager := NewParallelDBManager(1, NewEmptySlotDB) - slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - 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, uint256.NewInt(1)) - state.PrepareForParallel() - unconfirmedDBs := new(sync.Map) - manager := NewParallelDBManager(1, NewEmptySlotDB) - slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - - 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, uint256.NewInt(1)) - state.SetNonce(addr, 1) - state.PrepareForParallel() - - unconfirmedDBs := new(sync.Map) - manager := NewParallelDBManager(1, NewEmptySlotDB) - slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - 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 := testAddress - 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, uint256.NewInt(2)) - - oldBalance := state.GetBalance(addr) - if oldBalance.Uint64() != 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.Uint64() != 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 := testAddress - 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, uint256.NewInt(1)) - - oldBalance := state.GetBalance(addr) - if oldBalance.Uint64() != 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.Uint64() != 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 := testAddress - 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, uint256.NewInt(1)) - - oldBalance := state.GetBalance(addr) - if oldBalance.Uint64() != 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.Uint64() != 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 := testAddress - state.SetBalance(addr, uint256.NewInt(2)) - state.PrepareForParallel() - - unconfirmedDBs := new(sync.Map) - manager := NewParallelDBManager(1, NewEmptySlotDB) - slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - - 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 := testAddress - state.SetBalance(addr, uint256.NewInt(2)) - state.PrepareForParallel() - unconfirmedDBs := new(sync.Map) - manager := NewParallelDBManager(1, NewEmptySlotDB) - slotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - - 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) - manager := NewParallelDBManager(2, NewEmptySlotDB) - oldSlotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - - newSlotDb := NewSlotDB(state, 0, 0, manager, unconfirmedDBs, false) - - addr := testAddress - 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) - newSlotDb.Finalise(true) - - changeList := oldSlotDb.MergeSlotDB(newSlotDb, &types.Receipt{}, 0, nil) - - 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.U2560 { - 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_processor.go b/core/state_processor.go index 6cc91be944..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 && !p.bc.vmConfig.EnableParallelExecLegacy && !p.bc.vmConfig.EnableParallelExec { + if p.bc.enableTxDAG && !p.bc.vmConfig.EnableParallelExec { statedb.ResetMVStates(len(block.Transactions())) } // Iterate over and process the individual transactions diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 2678f7460f..e204c36b0a 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -34,7 +34,6 @@ 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 - EnableParallelExecLegacy bool // Whether to execute transaction in parallel mode when do full sync 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 diff --git a/eth/backend.go b/eth/backend.go index b0271c76c4..8bb408482b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -237,7 +237,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { var ( vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, - EnableParallelExecLegacy: config.ParallelTxLegacyMode, EnableParallelExec: config.ParallelTxMode, EnableParallelUnorderedMerge: config.ParallelTxUnorderedMerge, ParallelTxNum: config.ParallelTxNum, @@ -291,8 +290,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.EnableParallelTxDAG { if config.ParallelTxMode { eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile, config.ParallelTxMode) - } else { - eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile, config.ParallelTxLegacyMode) } } 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 diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 39738a8824..181a3ac383 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -219,13 +219,13 @@ type Config struct { RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string - ParallelTxLegacyMode bool // Whether to execute transaction in parallel mode when do full sync 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 ParallelTxUnorderedMerge bool // Whether to enable unordered merge in parallel mode + } // CreateConsensusEngine creates a consensus engine for the given chain config. From d00d951a513c2a7c4780d7a542cc516b7e3fcdc6 Mon Sep 17 00:00:00 2001 From: welkin22 Date: Tue, 22 Oct 2024 16:17:04 +0800 Subject: [PATCH 077/104] If TxDAG is nil, then use the serial processor to handle it. --- core/blockchain.go | 8 ++++++-- core/pevm_processor.go | 12 ++++-------- core/state/pevm_statedb.go | 3 --- core/state_processor.go | 8 -------- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index e79a5761d6..fc50019ca4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -321,7 +321,6 @@ type BlockChain struct { txDAGWriteCh chan TxDAGOutputItem txDAGReader *TxDAGFileReader serialProcessor Processor - parallelProcessor Processor } // NewBlockChain returns a fully initialised block chain using information @@ -566,6 +565,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if bc.vmConfig.EnableParallelExec { bc.processor = newPEVMProcessor(chainConfig, bc, engine) + bc.serialProcessor = NewStateProcessor(chainConfig, bc, engine) log.Info("Parallel V2 enabled", "parallelNum", ParallelNum()) } else { bc.processor = NewStateProcessor(chainConfig, bc, engine) @@ -1999,7 +1999,11 @@ 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 bc.vmConfig.TxDAG == nil && bc.vmConfig.EnableParallelUnorderedMerge { + receipts, logs, usedGas, err = bc.serialProcessor.Process(block, statedb, bc.vmConfig) + } else { + 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/pevm_processor.go b/core/pevm_processor.go index 339e4d7179..3acfbcae86 100644 --- a/core/pevm_processor.go +++ b/core/pevm_processor.go @@ -122,7 +122,7 @@ func (p *PEVMProcessor) executeInSlot(maindb *state.StateDB, txReq *PEVMTxReques // if it is in Stage 2 it is a likely result, not 100% sure func (p *PEVMProcessor) toConfirmTxIndexResult(txResult *PEVMTxResult) error { txReq := txResult.txReq - if !p.unorderedMerge || !txReq.useDAG { + if !p.unorderedMerge { // If we do not use a DAG, then we need to check for conflicts to ensure correct execution. // When we perform an unordered merge, we cannot conduct conflict checks // and can only choose to trust that the DAG is correct and that conflicts do not exist. @@ -166,7 +166,6 @@ func (p *PEVMProcessor) confirmTxResult(statedb *state.StateDB, gp *GasPool, res isByzantium := p.config.IsByzantium(header.Number) isEIP158 := p.config.IsEIP158(header.Number) - //result.slotDB.FinaliseForParallel(isByzantium || isEIP158, statedb) if err := result.slotDB.Merge(isByzantium || isEIP158); err != nil { // something very wrong, should not happen log.Error("merge slotDB failed", "err", err) @@ -285,7 +284,7 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg }(time.Now()) log.Debug("pevm confirm", "txIndex", pr.txReq.txIndex) return p.confirmTxResult(statedb, gp, pr) - }, p.unorderedMerge && txDAG != nil) + }, p.unorderedMerge) parallelRunDuration := time.Since(start) - buildLevelsDuration if err != nil { tx := allTxs[txIndex] @@ -293,9 +292,6 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", txIndex, tx.Hash().Hex(), err) } - //fmt.Printf("ProcessParallel tx all done, parallelNum:%d, txNum: %d, conflictNum: %d, executeDuration:%s, confirmDurations:%s, buildLevelsDuration:%s, runDuration:%s\n", - // ParallelNum(), txNum, p.debugConflictRedoNum, time.Duration(executeDurations), time.Duration(confirmDurations), buildLevelsDuration, parallelRunDuration) - // len(commonTxs) could be 0, such as: https://bscscan.com/block/14580486 var redoRate int = 0 if len(p.commonTxs) == 0 { @@ -334,8 +330,8 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg var cumulativeGasUsed uint64 for _, receipt := range p.receipts { // reset the log index - for _, log := range receipt.Logs { - log.Index = uint(lindex) + for _, oneLog := range receipt.Logs { + oneLog.Index = uint(lindex) lindex++ } // re-calculate the cumulativeGasUsed diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go index 38cde4b53f..80bdb712dd 100644 --- a/core/state/pevm_statedb.go +++ b/core/state/pevm_statedb.go @@ -486,9 +486,6 @@ func (pst *UncommittedDB) Merge(deleteEmptyObjects bool) error { // so we don't need to merge anything. return nil } - //if err := pst.conflictsToMaindb(); err != nil { - // return err - //} // 0. set the TxContext pst.maindb.SetTxContext(pst.txHash, pst.txIndex) diff --git a/core/state_processor.go b/core/state_processor.go index df9d788707..d5308af37e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -54,14 +54,6 @@ 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 9729844d20e66436df4b3ced4575552205f96163 Mon Sep 17 00:00:00 2001 From: welkin22 <136572398+welkin22@users.noreply.github.com> Date: Wed, 23 Oct 2024 20:20:44 +0800 Subject: [PATCH 078/104] fix: use ParallelTxNum config and uber automaxprocs (#204) --- cmd/utils/flags.go | 4 ++++ core/parallel_state_scheduler.go | 14 +++++++++----- core/pevm_processor.go | 3 ++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cd8cadd115..a42f6d23f3 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2035,6 +2035,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.ParallelTxUnorderedMerge = ctx.Bool(ParallelTxUnorderedMergeFlag.Name) } + if ctx.IsSet(ParallelTxNumFlag.Name) { + cfg.ParallelTxNum = ctx.Int(ParallelTxNumFlag.Name) + } + if ctx.IsSet(ParallelTxDAGFlag.Name) { cfg.EnableParallelTxDAG = ctx.Bool(ParallelTxDAGFlag.Name) } diff --git a/core/parallel_state_scheduler.go b/core/parallel_state_scheduler.go index 60d6070644..963a39ba88 100644 --- a/core/parallel_state_scheduler.go +++ b/core/parallel_state_scheduler.go @@ -13,10 +13,12 @@ import ( var runner chan func() -func init() { - cpuNum := runtime.NumCPU() - runner = make(chan func(), cpuNum) - for i := 0; i < cpuNum; i++ { +func initParallelRunner(targetNum int) { + if targetNum == 0 { + targetNum = runtime.GOMAXPROCS(0) + } + runner = make(chan func(), targetNum) + for i := 0; i < targetNum; i++ { go func() { for f := range runner { f() @@ -187,6 +189,8 @@ func (cq *confirmQueue) rerun(i int, execute func(*PEVMTxRequest) *PEVMTxResult, return nil } +var goMaxProcs = runtime.GOMAXPROCS(0) + // run runs the transactions in parallel // execute must return a non-nil result, otherwise it panics. func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error, unorderedMerge bool) (error, int) { @@ -207,7 +211,7 @@ func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func maxLevelTxCount = len(txLevel) } wait := sync.WaitGroup{} - trunks := txLevel.Split(runtime.NumCPU()) + trunks := txLevel.Split(goMaxProcs) wait.Add(len(trunks)) // split tx into chunks, to save the cost of channel communication for _, txs := range trunks { diff --git a/core/pevm_processor.go b/core/pevm_processor.go index 3acfbcae86..83edcb56e7 100644 --- a/core/pevm_processor.go +++ b/core/pevm_processor.go @@ -34,8 +34,9 @@ func newPEVMProcessor(config *params.ChainConfig, bc *BlockChain, engine consens StateProcessor: *NewStateProcessor(config, bc, engine), unorderedMerge: bc.vmConfig.EnableParallelUnorderedMerge, } + initParallelRunner(bc.vmConfig.ParallelTxNum) log.Info("Parallel execution mode is enabled", "Parallel Num", ParallelNum(), - "CPUNum", runtime.NumCPU(), "unorderedMerge", processor.unorderedMerge) + "CPUNum", runtime.GOMAXPROCS(0), "unorderedMerge", processor.unorderedMerge) return processor } From 2ac990cec14c7e6eaa5762a16a9421866da1d98d Mon Sep 17 00:00:00 2001 From: andyzhang2023 Date: Wed, 23 Oct 2024 10:27:00 +0800 Subject: [PATCH 079/104] fix: Transient Storage should set its prev value but not delete it when reverting --- core/state/pevm_journal.go | 9 +-------- core/state/pevm_statedb.go | 6 +++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/core/state/pevm_journal.go b/core/state/pevm_journal.go index 397dc44360..21487449a2 100644 --- a/core/state/pevm_journal.go +++ b/core/state/pevm_journal.go @@ -292,14 +292,7 @@ func newJTransientStorage(addr common.Address, key, val common.Hash) *jTransient } func (j *jTransientStorage) revert(db *UncommittedDB) { - storage, ok := db.transientStorage[j.addr] - if !ok { - return - } - delete(storage, j.key) - if len(storage) == 0 { - delete(db.transientStorage, j.addr) - } + db.transientStorage.Set(j.addr, j.key, j.val) } type ujournal []ustate diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go index 80bdb712dd..b4a1d8b01a 100644 --- a/core/state/pevm_statedb.go +++ b/core/state/pevm_statedb.go @@ -316,7 +316,11 @@ func (pst *UncommittedDB) GetTransientState(addr common.Address, key common.Hash return pst.transientStorage.Get(addr, key) } func (pst *UncommittedDB) SetTransientState(addr common.Address, key, value common.Hash) { - pst.journal.append(newJTransientStorage(addr, key, value)) + prev := pst.transientStorage.Get(addr, key) + if prev == value { + return // no need to record the same value + } + pst.journal.append(newJTransientStorage(addr, key, prev)) pst.transientStorage.Set(addr, key, value) } From 7cf3d350f8bf7e3e4940580e76c95df1d09257cf Mon Sep 17 00:00:00 2001 From: andyzhang2023 Date: Fri, 25 Oct 2024 09:12:12 +0800 Subject: [PATCH 080/104] bugfix: it panic when accessing storage from a reverted object which had been destructed --- core/state/pevm_journal.go | 12 +--- core/state/pevm_statedb_test.go | 107 ++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 11 deletions(-) diff --git a/core/state/pevm_journal.go b/core/state/pevm_journal.go index 21487449a2..a84349b94e 100644 --- a/core/state/pevm_journal.go +++ b/core/state/pevm_journal.go @@ -188,17 +188,7 @@ func newJSelfDestruct(obj *state) *jSelfDestruct { } return &jSelfDestruct{ addr: obj.addr, - obj: &state{ - modified: obj.modified, - addr: obj.addr, - balance: new(uint256.Int).Set(obj.balance), - nonce: obj.nonce, - code: append([]byte(nil), obj.code...), - codeHash: append([]byte(nil), obj.codeHash...), - codeSize: obj.codeSize, - created: obj.created, - deleted: obj.deleted, - }, + obj: obj.clone(), } } diff --git a/core/state/pevm_statedb_test.go b/core/state/pevm_statedb_test.go index 38a917e2de..ebddc22422 100644 --- a/core/state/pevm_statedb_test.go +++ b/core/state/pevm_statedb_test.go @@ -799,6 +799,113 @@ func TestPevmSelfDestruct(t *testing.T) { } } +func TestPevmSelfDestructAndRevert(t *testing.T) { + shadow := newStateDB() + uncommitted := newUncommittedDB(newStateDB()) + //prepare: create an account, set state, set balance + // A1{key1: val1, balance: 100, accesslist: 0x33} + prepare := Tx{ + {"Create", Address1}, + {"SetState", Address1, "key1", "val1"}, + {"AddBalance", Address1, big.NewInt(100)}, + {"AddSlots", Address1, common.Hash{0x33}}, + } + prepareCheck := Checks{ + {"balance", Address1, big.NewInt(100)}, + {"state", Address1, "key1", "val1"}, + {"slot", Address1, common.Hash{0x33}, true}, + } + check := func(verify Checks, shadow, uncommitted vm.StateDB) { + if err := verify.Verify(shadow); err != nil { + t.Fatalf("maindb verify failed, err=%s", err.Error()) + } + if err := verify.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted verify failed, err=%s", err.Error()) + } + } + prepare.Call(shadow) + prepare.Call(uncommitted) + shadow.Finalise(true) + uncommitted.Finalise(true) + check(prepareCheck, shadow, uncommitted) + + // do the following operations, and then selfdestruct, and then revert + // 1. add balance 200, + // 2. set state key1: val2 + // 3. add slot 0x34 + // 4. selfdestruct + // 5. revert + case1 := Tx{ + {"AddBalance", Address1, big.NewInt(200)}, + {"SetState", Address1, "key1", "val2"}, + {"SetState", Address1, "key2", "val0"}, + {"AddSlots", Address1, common.Hash{0x34}}, + } + case1SelfDestruct := Tx{ + {"SelfDestruct", Address1}, + } + beforeSelfDestruct := Checks{ + {"balance", Address1, big.NewInt(300)}, + {"state", Address1, "key1", "val2"}, + {"state", Address1, "key2", "val0"}, + {"slot", Address1, common.Hash{0x34}, true}, + {"slot", Address1, common.Hash{0x33}, true}, + } + beforeRevert := Checks{ + {"balance", Address1, big.NewInt(0)}, + {"state", Address1, "key1", "val2"}, + {"state", Address1, "key2", "val0"}, + {"slot", Address1, common.Hash{0x34}, true}, + {"slot", Address1, common.Hash{0x33}, true}, + } + afterRevert := Checks{ + {"balance", Address1, big.NewInt(100)}, + {"state", Address1, "key1", "val1"}, + {"state", Address1, "key2", ""}, + {"slot", Address1, common.Hash{0x34}, false}, + {"slot", Address1, common.Hash{0x33}, true}, + } + // run the case1 on shadow + snapshot := shadow.Snapshot() + case1.Call(shadow) + if err := beforeSelfDestruct.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + case1SelfDestruct.Call(shadow) + if err := beforeRevert.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + shadow.RevertToSnapshot(snapshot) + if err := afterRevert.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + + // now on uncommitted + snapshot = uncommitted.Snapshot() + case1.Call(uncommitted) + if err := beforeSelfDestruct.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + case1SelfDestruct.Call(uncommitted) + if err := beforeRevert.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + uncommitted.RevertToSnapshot(snapshot) + if err := afterRevert.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + + // now compare the mercle root of shadow and uncommitted + shadow.Finalise(true) + if err := uncommitted.Merge(true); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + uncommitted.Finalise(true) + if shadow.IntermediateRoot(true) != uncommitted.maindb.IntermediateRoot(true) { + t.Fatalf("ut failed, err: the mercle root of shadow and uncommitted are different") + } +} + func TestPevmSelfDestruct6780(t *testing.T) { // case 1. no previous state txs := Txs{ From 3586be6db563a22ca18f4d84b63cfdda023a867a Mon Sep 17 00:00:00 2001 From: andyzhang2023 Date: Fri, 25 Oct 2024 09:16:18 +0800 Subject: [PATCH 081/104] bugfix: 'invalid mercle root' after reverting an object which had been selfdestruct6780 --- core/state/pevm_statedb.go | 2 +- core/state/pevm_statedb_test.go | 105 ++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go index b4a1d8b01a..3930ffc646 100644 --- a/core/state/pevm_statedb.go +++ b/core/state/pevm_statedb.go @@ -263,7 +263,7 @@ func (pst *UncommittedDB) Selfdestruct6780(addr common.Address) { return } if obj.created { - pst.cache.selfDestruct(addr) + pst.SelfDestruct(addr) } } diff --git a/core/state/pevm_statedb_test.go b/core/state/pevm_statedb_test.go index ebddc22422..18559e4440 100644 --- a/core/state/pevm_statedb_test.go +++ b/core/state/pevm_statedb_test.go @@ -799,6 +799,111 @@ func TestPevmSelfDestruct(t *testing.T) { } } +func TestPevmSelfDestruct6780AndRevert(t *testing.T) { + shadow := newStateDB() + uncommitted := newUncommittedDB(newStateDB()) + //prepare: create an account, set state, set balance + // A1{key1: val1, balance: 100, accesslist: 0x33} + prepare := Tx{ + {"Create", Address1}, + {"SetState", Address1, "key1", "val1"}, + {"AddBalance", Address1, big.NewInt(100)}, + {"AddSlots", Address1, common.Hash{0x33}}, + } + prepareCheck := Checks{ + {"balance", Address1, big.NewInt(100)}, + {"state", Address1, "key1", "val1"}, + {"slot", Address1, common.Hash{0x33}, true}, + } + check := func(verify Checks, shadow, uncommitted vm.StateDB) { + if err := verify.Verify(shadow); err != nil { + t.Fatalf("maindb verify failed, err=%s", err.Error()) + } + if err := verify.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted verify failed, err=%s", err.Error()) + } + } + prepare.Call(shadow) + prepare.Call(uncommitted) + check(prepareCheck, shadow, uncommitted) + + // do the following operations, and then selfdestruct, and then revert + // 1. add balance 200, + // 2. set state key1: val2 + // 3. add slot 0x34 + // 4. selfdestruct + // 5. revert + case1 := Tx{ + {"AddBalance", Address1, big.NewInt(200)}, + {"SetState", Address1, "key1", "val2"}, + {"SetState", Address1, "key2", "val0"}, + {"AddSlots", Address1, common.Hash{0x34}}, + } + case1SelfDestruct := Tx{ + {"SelfDestruct6780", Address1}, + } + beforeSelfDestruct := Checks{ + {"balance", Address1, big.NewInt(300)}, + {"state", Address1, "key1", "val2"}, + {"state", Address1, "key2", "val0"}, + {"slot", Address1, common.Hash{0x34}, true}, + {"slot", Address1, common.Hash{0x33}, true}, + } + beforeRevert := Checks{ + {"balance", Address1, big.NewInt(0)}, + {"state", Address1, "key1", "val2"}, + {"state", Address1, "key2", "val0"}, + {"slot", Address1, common.Hash{0x34}, true}, + {"slot", Address1, common.Hash{0x33}, true}, + } + afterRevert := Checks{ + {"balance", Address1, big.NewInt(100)}, + {"state", Address1, "key1", "val1"}, + {"state", Address1, "key2", ""}, + {"slot", Address1, common.Hash{0x34}, false}, + {"slot", Address1, common.Hash{0x33}, true}, + } + // run the case1 on shadow + snapshot := shadow.Snapshot() + case1.Call(shadow) + if err := beforeSelfDestruct.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + case1SelfDestruct.Call(shadow) + if err := beforeRevert.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + shadow.RevertToSnapshot(snapshot) + if err := afterRevert.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + + // now on uncommitted + snapshot = uncommitted.Snapshot() + case1.Call(uncommitted) + if err := beforeSelfDestruct.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + case1SelfDestruct.Call(uncommitted) + if err := beforeRevert.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + uncommitted.RevertToSnapshot(snapshot) + if err := afterRevert.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + + // now compare the mercle root of shadow and uncommitted + shadow.Finalise(true) + if err := uncommitted.Merge(true); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + uncommitted.Finalise(true) + if shadow.IntermediateRoot(true) != uncommitted.maindb.IntermediateRoot(true) { + t.Fatalf("ut failed, err: the mercle root of shadow and uncommitted are different") + } +} + func TestPevmSelfDestructAndRevert(t *testing.T) { shadow := newStateDB() uncommitted := newUncommittedDB(newStateDB()) From 5ac07706ce2ae46e83d609c14dcaacbad243f52a Mon Sep 17 00:00:00 2001 From: andyzhang2023 Date: Fri, 25 Oct 2024 09:18:29 +0800 Subject: [PATCH 082/104] UncommittedDB of PEVM shutdown when an invalid snapshot id found (just like the StateDB do) --- core/state/pevm_journal.go | 3 --- core/state/pevm_statedb.go | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/state/pevm_journal.go b/core/state/pevm_journal.go index a84349b94e..b9df4e1eca 100644 --- a/core/state/pevm_journal.go +++ b/core/state/pevm_journal.go @@ -292,9 +292,6 @@ func (j *ujournal) append(st ustate) { } func (j *ujournal) revertTo(db *UncommittedDB, snapshot int) { - if snapshot < 0 || snapshot >= len(*j) { - return // invalid snapshot index - } for i := len(*j) - 1; i >= snapshot; i-- { (*j)[i].revert(db) } diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go index 3930ffc646..14f5224753 100644 --- a/core/state/pevm_statedb.go +++ b/core/state/pevm_statedb.go @@ -349,8 +349,10 @@ func (pst *UncommittedDB) AddSlotToAccessList(addr common.Address, slot common.H // =============================================== // Snapshot Methods -// (is it necessary to do snapshot and revert ?) func (pst *UncommittedDB) RevertToSnapshot(id int) { + if id < 0 || id > len(pst.journal) { + panic(fmt.Sprintf("invalid snapshot index, out of range, snapshot:%d, len:%d", id, len(pst.journal))) + } pst.journal.revertTo(pst, id) } func (pst *UncommittedDB) Snapshot() int { From 4df7b8d09697cbd235adff10ed09f2146890526a Mon Sep 17 00:00:00 2001 From: andyzhang2023 Date: Fri, 25 Oct 2024 15:21:42 +0800 Subject: [PATCH 083/104] fix: do initParallelRunner() only once no matter how many chains are created by NewBlockChain() --- core/parallel_state_scheduler.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/core/parallel_state_scheduler.go b/core/parallel_state_scheduler.go index 963a39ba88..4ad229c399 100644 --- a/core/parallel_state_scheduler.go +++ b/core/parallel_state_scheduler.go @@ -12,19 +12,22 @@ import ( ) var runner chan func() +var runnerOnce sync.Once func initParallelRunner(targetNum int) { - if targetNum == 0 { - targetNum = runtime.GOMAXPROCS(0) - } - runner = make(chan func(), targetNum) - for i := 0; i < targetNum; i++ { - go func() { - for f := range runner { - f() - } - }() - } + runnerOnce.Do(func() { + if targetNum == 0 { + targetNum = runtime.GOMAXPROCS(0) + } + runner = make(chan func(), targetNum) + for i := 0; i < targetNum; i++ { + go func() { + for f := range runner { + f() + } + }() + } + }) } func ParallelNum() int { From bc5d72db26fa95ed97abc5767f57ebb3aac9c113 Mon Sep 17 00:00:00 2001 From: andyzhang2023 Date: Fri, 25 Oct 2024 16:42:13 +0800 Subject: [PATCH 084/104] fixut: init the runner before running the cases of PevmProcessor, otherwise it will stuck forever because of a nil runner --- core/parallel_state_scheduler_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/parallel_state_scheduler_test.go b/core/parallel_state_scheduler_test.go index 6c181e532c..6908e00fa8 100644 --- a/core/parallel_state_scheduler_test.go +++ b/core/parallel_state_scheduler_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "math/big" "os" + "runtime" "sync" "sync/atomic" "testing" @@ -81,6 +82,10 @@ func (mt *mockTx) To() int { return int(mt.req.value) } +func init() { + initParallelRunner(runtime.GOMAXPROCS(0)) +} + func (mt *mockTx) execute(req *PEVMTxRequest) *PEVMTxResult { result := &PEVMTxResult{ txReq: req, From 0dea5ceaafd0f582f6551b28744f3d8bcde58231 Mon Sep 17 00:00:00 2001 From: andyzhang2023 Date: Fri, 25 Oct 2024 16:52:52 +0800 Subject: [PATCH 085/104] fixut: err should be returned by a seprated method ConflictsToMaindb() when running conflict check cases --- core/state/pevm_statedb_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/state/pevm_statedb_test.go b/core/state/pevm_statedb_test.go index 18559e4440..d4443c8581 100644 --- a/core/state/pevm_statedb_test.go +++ b/core/state/pevm_statedb_test.go @@ -2215,10 +2215,13 @@ func runConflictCase(prepare, txs1, txs2 Txs, checks []Check) error { if err := txs2.Call(un2); err != nil { return fmt.Errorf("failed to call txs2, err:%s", err.Error()) } + if err := un1.ConflictsToMaindb(); err != nil { + return fmt.Errorf("failed to check conflicts of un1, err:%s", err.Error()) + } if err := un1.Merge(true); err != nil { return fmt.Errorf("failed to merge un1, err:%s", err.Error()) } - if err := un2.Merge(true); err == nil { + if err := un2.ConflictsToMaindb(); err == nil { return fmt.Errorf("un2 merge is expected to be failed") } for _, c := range checks { From b3c941c477096169ca557b1d5bed31af32f32e99 Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Fri, 25 Oct 2024 19:49:05 +0800 Subject: [PATCH 086/104] =?UTF-8?q?increase=20the=20conflict=20counter=20o?= =?UTF-8?q?nly=20if=20the=20state=20of=20uncommitted=20db=20con=E2=80=A6?= =?UTF-8?q?=20(#207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: andyzhang2023 --- core/pevm_processor.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/pevm_processor.go b/core/pevm_processor.go index 83edcb56e7..83c5ef65cb 100644 --- a/core/pevm_processor.go +++ b/core/pevm_processor.go @@ -79,6 +79,7 @@ func (p *PEVMProcessor) hasConflict(txResult *PEVMTxResult) error { } // check whether the slot db reads during execution are correct. if err := slotDB.ConflictsToMaindb(); err != nil { + atomic.AddUint64(&p.debugConflictRedoNum, 1) log.Debug("executed result conflicts", "err", err) return err } @@ -267,9 +268,6 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg err, txIndex := txLevels.Run(func(pr *PEVMTxRequest) (res *PEVMTxResult) { defer func(t0 time.Time) { atomic.AddInt64(&executeDurations, time.Since(t0).Nanoseconds()) - if res.err != nil { - atomic.AddUint64(&p.debugConflictRedoNum, 1) - } }(time.Now()) if err := buildMessage(pr, signer, header); err != nil { @@ -279,9 +277,6 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg }, func(pr *PEVMTxResult) (err error) { defer func(t0 time.Time) { atomic.AddInt64(&confirmDurations, time.Since(t0).Nanoseconds()) - if err != nil { - atomic.AddUint64(&p.debugConflictRedoNum, 1) - } }(time.Now()) log.Debug("pevm confirm", "txIndex", pr.txReq.txIndex) return p.confirmTxResult(statedb, gp, pr) From 7e7e8079436bebfeccd17cf30c94116ef2a851c2 Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:15:47 +0800 Subject: [PATCH 087/104] fix ut case of TestBlockchainWithTxDAGDebug (#213) Co-authored-by: andyzhang2023 --- core/parallel_state_scheduler.go | 4 ++++ core/state/pevm_statedb.go | 27 +++++++++++++++++++++------ tests/block_test.go | 7 +++++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/core/parallel_state_scheduler.go b/core/parallel_state_scheduler.go index 4ad229c399..5cbf48ed1a 100644 --- a/core/parallel_state_scheduler.go +++ b/core/parallel_state_scheduler.go @@ -30,6 +30,10 @@ func initParallelRunner(targetNum int) { }) } +func InitPevmRunner(targetNum int) { + initParallelRunner(targetNum) +} + func ParallelNum() int { return cap(runner) } diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go index 14f5224753..aca12b1f2d 100644 --- a/core/state/pevm_statedb.go +++ b/core/state/pevm_statedb.go @@ -273,7 +273,7 @@ func (pst *UncommittedDB) Exist(addr common.Address) bool { func (pst *UncommittedDB) Empty(addr common.Address) bool { obj := pst.getObject(addr) - return obj == nil || obj.empty() + return obj == nil || obj.empty(pst) } // =============================================== @@ -529,7 +529,7 @@ func (pst *UncommittedDB) Merge(deleteEmptyObjects bool) error { } // clean empty objects if needed for _, obj := range pst.cache { - if obj.selfDestruct || (deleteEmptyObjects && obj.empty()) { + if obj.selfDestruct || (deleteEmptyObjects && obj.empty(pst)) { obj.deleted = true } // we don't need to do obj.finalize() here, it will be done in the maindb.Finalize() @@ -641,13 +641,15 @@ func (pst *UncommittedDB) getDeletedObjectWithCode(addr common.Address, maindb * } // load code from maindb deletedObj := pst.maindb.getDeletedStateObject(addr) + var code = []byte{} + var codeHash = common.Hash{} if deletedObj == nil { pst.reads.recordCodeOnce(addr, common.Hash{}, nil) } else { pst.reads.recordCodeOnce(addr, common.BytesToHash(deletedObj.CodeHash()), deletedObj.Code()) + // set code into the cache + code, codeHash = deletedObj.Code(), common.BytesToHash(deletedObj.CodeHash()) } - // set code into the cache - code, codeHash := deletedObj.Code(), common.BytesToHash(deletedObj.CodeHash()) o.code = code o.codeHash = codeHash[:] o.codeSize = len(code) @@ -794,8 +796,21 @@ func (s state) merge(maindb *StateDB) { } } -func (s *state) empty() bool { - return s.nonce == 0 && s.balance.Sign() == 0 && bytes.Equal(s.codeHash, types.EmptyCodeHash.Bytes()) +func (s *state) empty(pst *UncommittedDB) bool { + return s.nonce == 0 && s.balance.Sign() == 0 && s.codeIsEmpty(pst) +} + +func (s *state) codeIsEmpty(pst *UncommittedDB) bool { + if !s.codeIsLoaded() { + codeState := pst.getDeletedObjectWithCode(s.addr, pst.maindb) + if codeState == nil { + return true + } + s.code = codeState.code + s.codeHash = codeState.codeHash + s.codeSize = codeState.codeSize + } + return bytes.Equal(s.codeHash, types.EmptyCodeHash.Bytes()) || bytes.Equal(s.code, common.Hash{}.Bytes()) } func (s *state) codeIsLoaded() bool { diff --git a/tests/block_test.go b/tests/block_test.go index ae0290862a..f18cfdf1df 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -18,14 +18,16 @@ 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/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" ) func TestBlockchainWithTxDAG(t *testing.T) { @@ -130,6 +132,7 @@ func execBlockTestWithTxDAG(t *testing.T, bt *testMatcher, test *BlockTest) { } // run again with dagFile + core.InitPevmRunner(1) 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 From db297bb477ce51f32ef0eea37b2b9478a1bf13f7 Mon Sep 17 00:00:00 2001 From: welkin22 <136572398+welkin22@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:16:05 +0800 Subject: [PATCH 088/104] pevm: refactor txdag reader (#212) --- core/blockchain.go | 190 ++++++------------------------ core/blockchain_test.go | 46 +++++--- core/txdag_reader.go | 242 ++++++++++++++++++++++++++++++++++++++ core/txdag_reader_test.go | 126 ++++++++++++++++++++ 4 files changed, 428 insertions(+), 176 deletions(-) create mode 100644 core/txdag_reader.go create mode 100644 core/txdag_reader_test.go diff --git a/core/blockchain.go b/core/blockchain.go index fc50019ca4..8c276d0777 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -18,7 +18,6 @@ package core import ( - "bufio" "bytes" "encoding/hex" "errors" @@ -97,7 +96,8 @@ var ( triedbCommitExternalTimer = metrics.NewRegisteredTimer("chain/triedb/commit/external", nil) innerExecutionTimer = metrics.NewRegisteredTimer("chain/inner/execution", nil) - txDAGGenerateTimer = metrics.NewRegisteredTimer("chain/block/txdag/gen", nil) + txDAGGenerateTimer = metrics.NewRegisteredTimer("chain/block/txdag/gen", nil) + txDAGReaderChanGauge = metrics.NewRegisteredGauge("chain/block/txdag/reader/chan", nil) parallelTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/txs", nil) parallelConflictTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/conflicttxs", nil) @@ -920,7 +920,10 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha bc.futureBlocks.Purge() if bc.txDAGReader != nil { - bc.txDAGReader.Reset(head) + err := bc.txDAGReader.Reset(head) + if err != nil { + log.Error("reset txDAG reader fail", "err", err) + } } // Clear safe block, finalized block if needed @@ -2878,33 +2881,38 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { // startup with latest block curHeader := bc.CurrentHeader() 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()) + err := bc.txDAGReader.InitAndStartReadingLock(curHeader.Number.Uint64()) + if err != nil { + log.Error("load TxDAG from file err", "err", err, "output", output, "block", curHeader.Number, "latest", bc.txDAGReader.Latest()) + } else { + log.Info("load TxDAG from file", "output", output, "block", curHeader.Number, "latest", bc.txDAGReader.Latest()) + } } return - } - - // write handler - go func() { - 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, "err", err) - return - } - bc.txDAGWriteCh = make(chan TxDAGOutputItem, 10000) - defer writeHandle.Close() - for { - select { - case <-bc.quit: + } else { + // write handler + go func() { + 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, "err", err) return - case item := <-bc.txDAGWriteCh: - if err := writeTxDAGToFile(writeHandle, item); err != nil { - log.Error("encode TxDAG err in OutputHandler", "err", err) - continue + } + bc.txDAGWriteCh = make(chan TxDAGOutputItem, 10000) + 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 { @@ -2925,135 +2933,3 @@ func writeTxDAGToFile(writeHandle *os.File, item TxDAGOutputItem) error { _, err = writeHandle.Write(buf.Bytes()) return err } - -var TxDAGCacheSize = uint64(10000) - -type TxDAGFileReader struct { - output string - file *os.File - scanner *bufio.Scanner - cache map[uint64]types.TxDAG - latest uint64 - lock sync.RWMutex -} - -func NewTxDAGFileReader(output string) (*TxDAGFileReader, error) { - reader := &TxDAGFileReader{output: output} - err := reader.openFile(output) - if err != nil { - return nil, err - } - return reader, nil -} - -func (t *TxDAGFileReader) Close() { - t.lock.Lock() - defer t.lock.Unlock() - 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 - } - 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] - } - if t.scanner == nil { - return nil - } - - logTime := time.Now() - t.cache = make(map[uint64]types.TxDAG, TxDAGCacheSize) - for t.scanner.Scan() { - num, dag, err := readTxDAGItemFromLine(t.scanner.Text()) - if err != nil { - 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 { - continue - } - t.cache[num] = dag - t.latest = num - if uint64(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] -} - -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 { - 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 uint64(num), txDAG, nil -} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 2309f0b21c..f00002f7b2 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4339,39 +4339,33 @@ func TestTxDAGFile_ReadWrite(t *testing.T) { 0: types.NewEmptyTxDAG(), 1: makeEmptyPlainTxDAG(1), 2: makeEmptyPlainTxDAG(2, types.NonDependentRelFlag), - } - 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})) - } - writeFile.Close() - - 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) + 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 + for i := uint64(0); i < 6; i++ { + if i == 3 { + writeFile.WriteString("num,tag\n") } - require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: dag})) + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: i, txDAG: except[i]})) } + writeFile.Sync() writeFile.Close() reader, err := NewTxDAGFileReader(path) require.NoError(t, err) - for i := 0; i < 5; i++ { + err = reader.InitAndStartReadingLock(0) + require.NoError(t, err) + for reader.latest < 5 { + time.Sleep(10 * time.Millisecond) + } + for i := 0; i < 6; i++ { num := uint64(i) if except[num] != nil { - require.Equal(t, except[num], reader.TxDAG(num)) - continue + require.Equal(t, except[num], reader.TxDAG(num), "num:%d", num) } - require.Equal(t, except2[num], reader.TxDAG(num)) } } @@ -4392,18 +4386,29 @@ func TestTxDAGFile_LargeRead(t *testing.T) { for num := uint64(0); num < totalSize; num++ { require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: except[num]})) } + writeFile.Sync() writeFile.Close() reader, err := NewTxDAGFileReader(path) require.NoError(t, err) + err = reader.InitAndStartReadingLock(0) + require.NoError(t, err) + for i := uint64(0); i < totalSize; i++ { + for reader.latest < i || reader.latest == 0 { + time.Sleep(10 * time.Millisecond) + } 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++ { + for reader.latest < i || reader.latest == 0 { + time.Sleep(10 * time.Millisecond) + } require.Equal(t, except[i], reader.TxDAG(i), i) } @@ -4411,6 +4416,9 @@ func TestTxDAGFile_LargeRead(t *testing.T) { err = reader.Reset(totalSize - TxDAGCacheSize) require.NoError(t, err) for i := totalSize - TxDAGCacheSize; i < totalSize; i++ { + for reader.latest < i || reader.latest == 0 { + time.Sleep(10 * time.Millisecond) + } require.Equal(t, except[i], reader.TxDAG(i), i) } } diff --git a/core/txdag_reader.go b/core/txdag_reader.go new file mode 100644 index 0000000000..957f9d60b6 --- /dev/null +++ b/core/txdag_reader.go @@ -0,0 +1,242 @@ +package core + +import ( + "bufio" + "encoding/hex" + "errors" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +var TxDAGCacheSize = uint64(300) + +type TxDAGFileReader struct { + output string + file *os.File + scanner *bufio.Scanner + dagChan chan *TxDAGOutputItem + chanFirstBlockNumber int64 + latest uint64 + lock sync.RWMutex + isInit bool + closeChan chan struct{} +} + +func NewTxDAGFileReader(output string) (*TxDAGFileReader, error) { + reader := &TxDAGFileReader{ + output: output, + dagChan: make(chan *TxDAGOutputItem, TxDAGCacheSize), + closeChan: make(chan struct{}, 1), + chanFirstBlockNumber: -1, + } + err := reader.openFile(output) + if err != nil { + return nil, err + } + return reader, nil +} + +func (t *TxDAGFileReader) Close() { + t.lock.Lock() + defer t.lock.Unlock() + t.closeChan <- struct{}{} + 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 + } + 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) InitAndStartReadingLock(startBlockNum uint64) error { + t.lock.Lock() + defer t.lock.Unlock() + return t.initAndStartReading(startBlockNum) +} +func (t *TxDAGFileReader) initAndStartReading(startBlockNum uint64) error { + if t.isInit { + return nil + } + if t.scanner == nil { + return errors.New("TxDAG reader init fail,missing scanner") + } + if startBlockNum > 0 { + //We move the scanner to the position of startBlockNum-1 so that we can start reading data from startBlockNum next. + startBlockNum = startBlockNum - 1 + for t.scanner != nil && t.scanner.Scan() { + text := t.scanner.Text() + blockNum, err := readTxDAGBlockNumFromLine(text) + if err != nil { + log.Error("TxDAG reader init fail at readTxDAGBlockNumFromLine", "err", err, "text", text) + return err + } + if startBlockNum > blockNum { + continue + } + t.latest = blockNum + break + } + if t.scanner != nil && t.scanner.Err() != nil { + log.Error("TxDAG reader init, scan TxDAG file got err", "err", t.scanner.Err(), "startBlockNum", startBlockNum, "latest", t.latest) + return t.scanner.Err() + } + } + log.Info("TxDAG reader init done", "startBlockNum", startBlockNum, "latest", t.latest) + go t.loopReadDAGIntoChan() + t.isInit = true + return nil +} + +func (t *TxDAGFileReader) loopReadDAGIntoChan() { + start := time.Now() + + for t.scanner != nil && t.scanner.Scan() { + select { + case <-t.closeChan: + close(t.dagChan) + log.Info("TxDAG reader is closed. Exiting...", "latest", t.latest) + return + default: + text := t.scanner.Text() + num, dag, err := readTxDAGItemFromLine(text) + if err != nil { + log.Error("query TxDAG error", "latest", t.latest, "err", err) + continue + } + t.dagChan <- &TxDAGOutputItem{blockNumber: num, txDAG: dag} + t.latest = num + if t.chanFirstBlockNumber == -1 { + t.chanFirstBlockNumber = int64(num) + } + if time.Since(start) > 1*time.Minute { + log.Debug("TxDAG reader dagChan report", "dagChanSize", len(t.dagChan), "latest", t.latest, "chanFirstBlockNumber", t.chanFirstBlockNumber) + txDAGReaderChanGauge.Update(int64(len(t.dagChan))) + start = time.Now() + } + } + } + if t.scanner != nil && t.scanner.Err() != nil { + log.Error("scan TxDAG file got err", "latest", t.latest, "err", t.scanner.Err()) + } else { + log.Info("TxDAG reader done. Exiting...", "latest", t.latest) + } +} + +func (t *TxDAGFileReader) TxDAG(expect uint64) types.TxDAG { + t.lock.Lock() + defer t.lock.Unlock() + + if !t.isInit { + log.Error("TxDAG reader not init yet") + return nil + } + + if t.scanner == nil { + return nil + } + + if t.chanFirstBlockNumber > int64(expect) { + log.Debug("expect less than chanFirstBlockNumber,skip", "expect", expect, "chanFirstBlockNumber", t.chanFirstBlockNumber) + return nil + } + + for { + select { + case dag := <-t.dagChan: + if dag == nil { + return nil + } + t.chanFirstBlockNumber = int64(dag.blockNumber + 1) + if dag.blockNumber < expect { + continue + } else if dag.blockNumber > expect { + log.Warn("dag.blockNumber > expect", "dag.blockNumber", dag.blockNumber, "expect", expect, "chanFirstBlockNumber", t.chanFirstBlockNumber) + return nil + } else { + return dag.txDAG + } + default: + return nil + } + } +} + +func (t *TxDAGFileReader) Reset(number uint64) error { + t.lock.Lock() + defer t.lock.Unlock() + t.closeChan <- struct{}{} + t.closeFile() + if err := t.openFile(t.output); err != nil { + return err + } + t.latest = 0 + t.chanFirstBlockNumber = -1 + t.isInit = false + t.closeChan = make(chan struct{}, 1) + t.dagChan = make(chan *TxDAGOutputItem, TxDAGCacheSize) + err := t.initAndStartReading(number) + if err != nil { + return err + } + return nil +} + +func readTxDAGBlockNumFromLine(line string) (uint64, error) { + tokens := strings.Split(line, ",") + if len(tokens) != 2 { + return 0, errors.New("txDAG output contain wrong size") + } + num, err := strconv.Atoi(tokens[0]) + if err != nil { + return 0, err + } + return uint64(num), nil +} + +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 uint64(num), txDAG, nil +} diff --git a/core/txdag_reader_test.go b/core/txdag_reader_test.go new file mode 100644 index 0000000000..66e66872ed --- /dev/null +++ b/core/txdag_reader_test.go @@ -0,0 +1,126 @@ +package core + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewTxDAGFileReader(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, types.NonDependentRelFlag), + 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 i := uint64(0); i < 6; i++ { + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: i, txDAG: except[i]})) + } + writeFile.Sync() + writeFile.Close() + + reader, err := NewTxDAGFileReader(path) + if err != nil { + t.Error("newReaderErr", "err", err) + return + } + err = reader.InitAndStartReadingLock(2) + if err != nil { + t.Error("newReaderErr", "err", err) + return + } + //Check the initialization status + assert.Equal(t, true, reader.isInit) + assert.NotNil(t, reader.scanner) + //Waiting for the first data to enter the channel + for reader.chanFirstBlockNumber == -1 { + time.Sleep(10 * time.Millisecond) + } + //The starting point is 2, so 1 should not exist, 2 should exist, and txCount==2 + dag1 := reader.TxDAG(1) + assert.Nil(t, dag1) + dag2 := reader.TxDAG(2) + assert.NotNil(t, dag2) + assert.Equal(t, 2, dag2.TxCount()) + //Waiting to process to 5 + for reader.latest < 5 { + time.Sleep(10 * time.Millisecond) + } + //There are 9 transactions in 20 + dag5 := reader.TxDAG(5) + assert.NotNil(t, dag5) + assert.Equal(t, 5, dag5.TxCount()) + //Already read 5, data less than 5 cannot be read. + dag3 := reader.TxDAG(3) + assert.Nil(t, dag3) + err = reader.Reset(2) + if err != nil { + t.Error("resetErr", "err", err) + return + } + //Check the initialization status again after reset + assert.Equal(t, true, reader.isInit) + assert.NotNil(t, reader.scanner) + //Waiting for 3 to be read + for reader.latest < 3 { + time.Sleep(10 * time.Millisecond) + } + //11 should no longer be nil, because after reset, we haven't read it yet. + dag3 = reader.TxDAG(3) + assert.NotNil(t, dag3) + assert.Equal(t, 0, dag3.TxCount()) + //1000 should not exist + dag1000 := reader.TxDAG(1000) + assert.Nil(t, dag1000) +} + +func TestTxDAGFileReader_Close(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, types.NonDependentRelFlag), + 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 i := uint64(0); i < 6; i++ { + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: i, txDAG: except[i]})) + } + writeFile.Sync() + writeFile.Close() + + reader, err := NewTxDAGFileReader(path) + if err != nil { + t.Error("newReaderErr", "err", err) + return + } + err = reader.InitAndStartReadingLock(2) + if err != nil { + t.Error("newReaderErr", "err", err) + return + } + assert.Equal(t, true, reader.isInit) + assert.NotNil(t, reader.scanner) + reader.Close() + assert.Nil(t, reader.scanner) + assert.Nil(t, reader.file) +} From c43afd336a52ceee8827133969f35ef869540ece Mon Sep 17 00:00:00 2001 From: welkin22 Date: Thu, 31 Oct 2024 19:17:29 +0800 Subject: [PATCH 089/104] use currentBlock for init --- core/blockchain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 8c276d0777..2d8fcb9338 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2879,7 +2879,7 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { log.Error("read TxDAG err", "err", err) } // startup with latest block - curHeader := bc.CurrentHeader() + curHeader := bc.CurrentBlock() if curHeader != nil && bc.txDAGReader != nil { err := bc.txDAGReader.InitAndStartReadingLock(curHeader.Number.Uint64()) if err != nil { From a759fdfb1c0f41e5d59440d3b979b00fe71411f2 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:49:13 +0800 Subject: [PATCH 090/104] doc: add release note for TxDAG-PEVM alpha release (#215) --- ...d parallel transaction execution (PEVM).md | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 Release Note - TxDAG based parallel transaction execution (PEVM).md diff --git a/Release Note - TxDAG based parallel transaction execution (PEVM).md b/Release Note - TxDAG based parallel transaction execution (PEVM).md new file mode 100644 index 0000000000..2850ddc664 --- /dev/null +++ b/Release Note - TxDAG based parallel transaction execution (PEVM).md @@ -0,0 +1,145 @@ +# Release Note \- TxDAG based parallel transaction execution (PEVM) \- Alpha + +## Version + +**Alpha - Not production ready** + +**Based on op-geth v0.5.1** + +## Description + +This is the first release of the op-geth that implements the TxDAG based parallel transaction execution for block synchronization. The goal is to speed up the execution of transactions in blocks by exploiting parallelism in EVM, and improve the performance further with the TxDAG data guidance by reducing the re-execution caused by transaction dependencies. + +### Release goals + +* Provide the parallel transaction execution implementation that is 100% compatible with the original EVM. +* Provide the raw TxDAG data in file format as a POC for TxDAG guided PEVM execution +* Provide the op-geth binary release that is verified to successfully sync the block from genesis to block \#15000000 with TxDAG and PEVM enabled, and with performance improved in some scenarios and no obvious performance drop overall. +* Provide the methodology to enable and use the TxDAG base PEVM for block syncing of opBNB. +* Provide the preliminary performance data and analysis for further improvement work + +### Options Added + +* --parallel (default: false) ($GETH\_PARALLEL) + + Enable the experimental parallel transaction execution mode, only valid in full + sync mode (default = false) + + +* --parallel.num value (default: 0\) ($GETH\_PARALLEL\_NUM) + + Number of slot for transaction execution, only valid in parallel mode (runtime + calculated, no fixed default value) + + +* --parallel.txdag (default: false) ($GETH\_PARALLEL\_TXDAG) + + Enable the experimental parallel TxDAG generation, only valid in full sync mode + (default = false) + + +* --parallel.txdagfile value (default: "./parallel-txdag-output.csv") ($GETH\_PARALLEL\_TXDAGFILE) + + It indicates the TxDAG file path + + +* --parallel.unordered-merge (default: false) ($GETH\_PARALLEL\_UNORDERED\_MERGE) + + Enable unordered merge mode, during the parallel confirm phase, merge + transaction execution results without following the transaction order. + + +### Usage + +* Get the TxDAG data: + * User could download the TxDAG data from [https://pub-c0627345c16f47ab858c9469133073a8.r2.dev/opbnb-txdag/parallel-txdag-output\_compare\_15000000.csv](https://pub-c0627345c16f47ab858c9469133073a8.r2.dev/opbnb-txdag/parallel-txdag-output_compare_15000000.csv) +* Use the following options of the geth to enable TxDAG based parallel execution + * '--parallel' + * '--parallel.txtag' + + Enable the TxDAG functionality of geth, this will tell EVM to check the TxDAG data existence before block execution + + * '--parallel.txdagfile=./parallel-txdag-output\_compare\_15000000.csv' + + Specify the path of the TxDAG data file + + * '--parallel.unordered-merge' + + Trust the DAG data, enable the advanced unordered-merge optimization and skip conflict check. + * '--parallel.num=4' + + Optional, Specify the parallel execution number, if not assigned, use the core number for the running platform + + +### Example +``` +op-geth \ + --datadir="./datadir" \ + --verbosity=4 \ + --http \ + --http.corsdomain="*" \ + --http.vhosts="*" \ + --http.addr=0.0.0.0 \ + --http.port=8545 \ + --http.api=net,eth,engine,debug \ + --pprof \ + --pprof.port=6070 \ + --ws \ + --ws.addr=0.0.0.0 \ + --ws.port=8545 \ + --ws.origins="*" \ + --ws.api=eth,engine \ + --syncmode=full \ + --networkid=$CHAIN_ID \ + --txpool.globalslots=20000 \ + --txpool.globalqueue=5000 \ + --txpool.accountqueue=200 \ + --txpool.accountslots=200 \ + --txpool.nolocals=true \ + --txpool.pricelimit=1 \ + --cache.preimages \ + --allow-insecure-unlock \ + --authrpc.addr="0.0.0.0" \ + --authrpc.port="8551" \ + --authrpc.vhosts="*" \ + --authrpc.jwtsecret=./jwt.txt \ + --rpc.allow-unprotected-txs \ + --parallel \ + --parallel.txdag \ + --parallel.txdagfile=./parallel-txdag-output.csv \ + --parallel.unordered-merge \ + --parallel.num=4 \ + --gcmode=full \ + --metrics \ + --metrics.port 6068 \ + --metrics.addr 0.0.0.0 \ + --rollup.sequencerhttp=$L2_RPC \ + --rollup.disabletxpoolgossip=false \ + --bootnodes=$P2P_BOOTNODES +``` + +### + +### Performance + +| Scenario | Description | mgasps
(Original) | mgasps
(PEVM) | Improvement | Comments | +|-------------|-------------------------------------------------------------------------------------------------------------|----------------------|--------------|-------------|----------------------------------------------------------| +| **A** | **Internal test chain blocks with 250k accounts transfer to another 250k account** | **107** | **202** | **88%** | **conflict rate(avg): ~0%
txs in block(avg): 3103** | +| **B** | **Internal test chain blocks with 250k accounts transfer to 1 fixed account** | **201** | **216** | **7%** | **conflict rate(avg): ~95%
txs in block(avg): 3644** | +| **C** | **Internal test chain blocks contain random selected txs with a mix of smart contract and native transfer** | **155** | **228** | **47%** | **conflict rate(avg): ~81%
txs in block(avg): 1356** | +| **Mainnet** | **opBNB mainnet block range from #9m-9.3m** | **9.3** | **11.2** | **20%** | **conflict rate(avg): ~12%
txs in block(avg): 25** | +| **Mainnet** | **opBNB mainnet block range from #11.9m-12.1m (mostly inscription txs)** | **57** | **61.1** | **7%** | **conflict rate(avg): ~50%
txs in block(avg): 2195** | + +### Additional Info + +Conclusions for alpha release + +* The performance of PEVM is highly depend on scenarios and dependencies between txs in block +* Generally no obvious degradation of performance observed on mainnet sync (from block#1 to #15000000) + +#### TxDAG data solution + +The current file-based TxDAG data solution will be optimized with an alternative gasless transaction based +TxDAG transfer in the future. It will guarantee that the TxDAG data will be available with the block transactions +as soon as the block is generated. +More details in [BEP-396: Accelerate Block Execution by TxDAG](https://forum.bnbchain.org/t/bep-396-accelerate-block-execution-by-txdag/2869) \ No newline at end of file From ca71a5cb6799da2c0a555423de76913ddf3dcf8e Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:24:06 +0800 Subject: [PATCH 091/104] PEVM-fix: clear useless legacy pevm code (#216) --- core/state/journal.go | 27 +---- core/state/state_object.go | 83 --------------- core/state/statedb.go | 212 +++++++------------------------------ 3 files changed, 43 insertions(+), 279 deletions(-) diff --git a/core/state/journal.go b/core/state/journal.go index d436dbd5ac..ddd2446175 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -153,16 +153,7 @@ type ( 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) - } + s.deleteStateObj(*ch.account) delete(s.stateObjectsDirty, *ch.account) } @@ -172,25 +163,13 @@ func (ch createObjectChange) dirtied() *common.Address { func (ch resetObjectChange) revert(dber StateDBer) { s := dber.getBaseStateDB() - if s.parallel.isSlotDB { - // 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.setStateObject(ch.prev) - } + // ch.prev was got from main DB, put it back to main DB. + s.setStateObject(ch.prev) if !ch.prevdestruct { 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/state_object.go b/core/state/state_object.go index a7a0aafb67..e18214c2a4 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -325,15 +325,6 @@ func (s *stateObject) GetState(key common.Hash) common.Hash { // Otherwise return the entry's original value 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) - } - if _, ok := s.db.parallel.kvReadsInSlot[addr].GetValue(key); !ok { - s.db.parallel.kvReadsInSlot[addr].StoreValue(key, result) - } - } return result } @@ -348,32 +339,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { return value } - if s.db.isParallel && s.db.parallel.isSlotDB { - // 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 - 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) - 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: @@ -451,10 +416,6 @@ func (s *stateObject) SetState(key, value common.Hash) { key: key, prevalue: prev, }) - - if s.db.isParallel && s.db.parallel.isSlotDB { - s.db.parallel.kvChangesInSlot[s.address][key] = struct{}{} - } s.setState(key, value) } @@ -529,14 +490,6 @@ func (s *stateObject) finaliseRWSet() { // 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 problematic since the origin - // is wrong. - maindb = s.db.parallel.baseStateDB - // For dirty/pending/origin Storage access and update. - s.storageRecordsLock.Lock() - defer s.storageRecordsLock.Unlock() - } // Make sure all dirty slots are finalized into the pending storage area s.finalise(false) @@ -973,39 +926,3 @@ func (s *stateObject) GetCommittedStateNoUpdate(key common.Hash) common.Hash { } 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 -// lightCopy() -func (s *stateObject) fixUpOriginAndResetPendingStorage() { - if s.db.isParallel && s.db.parallel.isSlotDB { - mainDB := s.db.parallel.baseStateDB - origObj := mainDB.getStateObject(s.address) - 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 - 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) - } - s.storageRecordsLock.Unlock() - } -} diff --git a/core/state/statedb.go b/core/state/statedb.go index efecb52e58..d9b9340a9d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -82,14 +82,6 @@ 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 { - if s.parallel.isSlotDB { - if ret, ok := s.parallel.locatStateObjects[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 } @@ -101,11 +93,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 { - if s.parallel.isSlotDB { - s.parallel.locatStateObjects[addr] = stateObject - } else { - s.parallel.stateObjects.StoreStateObject(addr, stateObject) - } + s.parallel.stateObjects.StoreStateObject(addr, stateObject) } else { s.stateObjects[addr] = stateObject } @@ -114,9 +102,6 @@ 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) @@ -125,44 +110,8 @@ 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 // stateObjects holds the state objects in the base slot db - 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. - dirtiedStateObjectsInSlot map[common.Address]*stateObject - unconfirmedDBs *sync.Map // do unconfirmed reference in same slot. - - // 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 - 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 - conflictCheckStateObjectCache *sync.Map - conflictCheckKVReadCache *sync.Map + stateObjects *StateObjectSyncMap } // StateDB structs within the ethereum protocol are used to store anything @@ -315,10 +264,8 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) transientStorage: newTransientStorage(), hasher: crypto.NewKeccakState(), - parallel: ParallelState{ - SlotIndex: -1, - }, - txIndex: -1, + parallel: ParallelState{}, + txIndex: -1, } if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) @@ -527,11 +474,6 @@ 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 { defer func() { s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), s.GetCodeHash(addr)) @@ -910,25 +852,7 @@ func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *t s.trieParallelLock.Lock() defer s.trieParallelLock.Unlock() var trie Trie - if s.isParallel { - // hold lock for parallel - 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 - } + trie = s.trie start := time.Now() var err error @@ -972,13 +896,9 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { func (s *StateDB) setStateObject(object *stateObject) { if s.isParallel { - 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) - } + // 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 } @@ -1255,19 +1175,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { } s.stateObjectsDestructDirty = make(map[common.Address]*types.StateAccount) 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) - } + 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 @@ -1294,12 +1202,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { 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) } 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.finalise(true) // Prefetch slots in the background } obj.created = false @@ -1353,37 +1256,19 @@ 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 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() - } + 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() } } } @@ -1429,15 +1314,7 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) for addr := range s.stateObjectsPending { - 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 { + if obj, _ := s.getStateObjectFromStateObjects(addr); obj.deleted { s.deleteStateObject(obj) s.AccountDeleted += 1 } else { @@ -2059,7 +1936,7 @@ func (s *StateDB) GetSnap() snapshot.Snapshot { } func (s *StateDB) BeforeTxTransition() { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return } log.Debug("BeforeTxTransition", "mvStates", s.mvStates == nil, "rwSet", s.rwSet == nil) @@ -2072,7 +1949,7 @@ func (s *StateDB) BeforeTxTransition() { } func (s *StateDB) BeginTxStat(index int) { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return } if s.mvStates == nil { @@ -2084,7 +1961,7 @@ func (s *StateDB) BeginTxStat(index int) { } func (s *StateDB) StopTxStat(usedGas uint64) { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return } if s.mvStates == nil { @@ -2101,7 +1978,7 @@ func (s *StateDB) StopTxStat(usedGas uint64) { } func (s *StateDB) RecordRead(key types.RWKey, val interface{}) { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return } if s.rwSet == nil { @@ -2113,7 +1990,7 @@ func (s *StateDB) RecordRead(key types.RWKey, val interface{}) { } func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return } if s.rwSet == nil { @@ -2123,7 +2000,7 @@ func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) { } func (s *StateDB) ResetMVStates(txCount int) { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return } if s.mvStates != nil { @@ -2134,7 +2011,7 @@ func (s *StateDB) ResetMVStates(txCount int) { } func (s *StateDB) FinaliseRWSet() error { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return nil } if s.rwSet == nil { @@ -2187,34 +2064,25 @@ func (s *StateDB) FinaliseRWSet() error { } 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 - } + if acc, ok := s.stateObjectsDestructDirty[addr]; ok { + return acc, ok } acc, ok := s.stateObjectsDestruct[addr] return acc, ok } func (s *StateDB) setStateObjectsDestruct(addr common.Address, acc *types.StateAccount) { - if !(s.isParallel && s.parallel.isSlotDB) { - s.stateObjectsDestructDirty[addr] = acc - return - } - s.stateObjectsDestruct[addr] = acc + s.stateObjectsDestructDirty[addr] = acc return } func (s *StateDB) removeStateObjectsDestruct(addr common.Address) { - if !(s.isParallel && s.parallel.isSlotDB) { - delete(s.stateObjectsDestructDirty, addr) - return - } - delete(s.stateObjectsDestruct, addr) + delete(s.stateObjectsDestructDirty, addr) + return } func (s *StateDB) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (types.TxDAG, error) { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return nil, nil } if s.mvStates == nil { @@ -2230,7 +2098,7 @@ func (s *StateDB) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (typ } func (s *StateDB) ResolveStats() map[int]*types.ExeStat { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return nil } if s.mvStates == nil { @@ -2241,14 +2109,14 @@ func (s *StateDB) ResolveStats() map[int]*types.ExeStat { } func (s *StateDB) MVStates() *types.MVStates { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return nil } return s.mvStates } func (s *StateDB) RecordSystemTxRWSet(index int) { - if s.isParallel && s.parallel.isSlotDB { + if s.isParallel { return } if s.mvStates == nil { From 8fa277237c3aaa100a0deefc875e2ecc763a0f8e Mon Sep 17 00:00:00 2001 From: welkin22 Date: Wed, 6 Nov 2024 15:54:38 +0800 Subject: [PATCH 092/104] fix unit test --- core/pevm_processor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pevm_processor.go b/core/pevm_processor.go index 83c5ef65cb..990af4e0eb 100644 --- a/core/pevm_processor.go +++ b/core/pevm_processor.go @@ -173,7 +173,6 @@ func (p *PEVMProcessor) confirmTxResult(statedb *state.StateDB, gp *GasPool, res log.Error("merge slotDB failed", "err", err) return err } - result.slotDB.Finalise(isByzantium || isEIP158) delayGasFee := result.result.delayFees // add delayed gas fee @@ -188,6 +187,7 @@ func (p *PEVMProcessor) confirmTxResult(statedb *state.StateDB, gp *GasPool, res statedb.AddBalance(params.OptimismL1FeeRecipient, delayGasFee.L1Fee) } } + result.slotDB.Finalise(isByzantium || isEIP158) // Do IntermediateRoot after mergeSlotDB. if !isByzantium { From b230aabbce300badaddb5c6e157fbd132eac5bf2 Mon Sep 17 00:00:00 2001 From: welkin22 Date: Wed, 6 Nov 2024 16:01:35 +0800 Subject: [PATCH 093/104] use patch to fix ut instead of changing code --- .github/workflows/unit-test.yaml | 1 + tests/fix_unit_test.patch | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 tests/fix_unit_test.patch diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml index feffce65e1..cc1106b4ed 100644 --- a/.github/workflows/unit-test.yaml +++ b/.github/workflows/unit-test.yaml @@ -19,4 +19,5 @@ jobs: go-version-file: go.mod - run: | git apply tests/0001-diff-go-ethereum.patch + git apply tests/fix_unit_test.patch make test diff --git a/tests/fix_unit_test.patch b/tests/fix_unit_test.patch new file mode 100644 index 0000000000..7567c4ba4c --- /dev/null +++ b/tests/fix_unit_test.patch @@ -0,0 +1,19 @@ +Subject: [PATCH] fix unit test +--- +Index: core/txpool/legacypool/legacypool.go +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go +--- a/core/txpool/legacypool/legacypool.go (revision 1233f69823ec26df160575add475f1e949cde211) ++++ b/core/txpool/legacypool/legacypool.go (date 1730875125704) +@@ -391,7 +391,7 @@ + } + + func (pool *LegacyPool) loopOfSync() { +- ticker := time.NewTicker(200 * time.Second) ++ ticker := time.NewTicker(200 * time.Millisecond) + for { + select { + case <-pool.reorgShutdownCh: From 5768dd53df9cc942750bc9d83cbd410dbfc28c74 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:12:36 +0800 Subject: [PATCH 094/104] PEVM-fix: Disable prefetch when PEVM enabled (#218) --- core/blockchain.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 2d8fcb9338..214b7b4d86 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -316,11 +316,10 @@ type BlockChain struct { forker *ForkChoice vmConfig vm.Config - parallelExecution bool - enableTxDAG bool - txDAGWriteCh chan TxDAGOutputItem - txDAGReader *TxDAGFileReader - serialProcessor Processor + enableTxDAG bool + txDAGWriteCh chan TxDAGOutputItem + txDAGReader *TxDAGFileReader + serialProcessor Processor } // NewBlockChain returns a fully initialised block chain using information @@ -1983,7 +1982,7 @@ 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. // 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 !bc.cacheConfig.TrieCleanNoPrefetch && !bc.vmConfig.EnableParallelExec { if followup, err := it.peek(); followup != nil && err == nil { throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) From 617cc39103b59c3107248d44ba25a465d8e6ac05 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:17:56 +0800 Subject: [PATCH 095/104] feat: disable parallel when txs count is low (#226) --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 10 ++++++++++ core/blockchain.go | 9 ++++++++- core/pevm_processor.go | 6 +++++- core/vm/interpreter.go | 1 + eth/backend.go | 1 + eth/ethconfig/config.go | 2 +- 7 files changed, 27 insertions(+), 3 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 1cba78f04d..d2a43a6961 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -173,6 +173,7 @@ var ( utils.ParallelTxFlag, utils.ParallelTxUnorderedMergeFlag, utils.ParallelTxNumFlag, + utils.ParallelThresholdFlag, utils.ParallelTxDAGFlag, utils.ParallelTxDAGFileFlag, utils.ParallelTxDAGSenderPrivFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a42f6d23f3..30eee8ee0e 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1117,6 +1117,12 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Category: flags.VMCategory, } + ParallelThresholdFlag = &cli.IntFlag{ + Name: "parallel.threshold", + Usage: "Threshold of transaction count to trigger parallel execution, only valid in parallel mode (runtime calculated, no fixed default value)", + Category: flags.VMCategory, + } + ParallelTxDAGFlag = &cli.BoolFlag{ Name: "parallel.txdag", Usage: "Enable the experimental parallel TxDAG generation, only valid in full sync mode (default = false)", @@ -2039,6 +2045,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.ParallelTxNum = ctx.Int(ParallelTxNumFlag.Name) } + if ctx.IsSet(ParallelThresholdFlag.Name) { + cfg.ParallelThreshold = ctx.Int(ParallelThresholdFlag.Name) + } + if ctx.IsSet(ParallelTxDAGFlag.Name) { cfg.EnableParallelTxDAG = ctx.Bool(ParallelTxDAGFlag.Name) } diff --git a/core/blockchain.go b/core/blockchain.go index 214b7b4d86..8b8f7bfe4f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -568,6 +568,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis log.Info("Parallel V2 enabled", "parallelNum", ParallelNum()) } else { bc.processor = NewStateProcessor(chainConfig, bc, engine) + bc.serialProcessor = bc.processor } // Start future block processor. bc.wg.Add(1) @@ -1999,9 +2000,15 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) statedb.SetExpectedStateRoot(block.Root()) + // Decide the enabling of parallelExec + invalidParallelConfig := bc.vmConfig.TxDAG == nil && bc.vmConfig.EnableParallelUnorderedMerge + lowTxsNum := bc.vmConfig.ParallelThreshold >= block.Transactions().Len() + useSerialProcessor := invalidParallelConfig || lowTxsNum || !bc.vmConfig.EnableParallelExec + // Process block using the parent state as reference point pstart = time.Now() - if bc.vmConfig.TxDAG == nil && bc.vmConfig.EnableParallelUnorderedMerge { + + if useSerialProcessor { receipts, logs, usedGas, err = bc.serialProcessor.Process(block, statedb, bc.vmConfig) } else { receipts, logs, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig) diff --git a/core/pevm_processor.go b/core/pevm_processor.go index 990af4e0eb..1b3f7a4116 100644 --- a/core/pevm_processor.go +++ b/core/pevm_processor.go @@ -35,8 +35,12 @@ func newPEVMProcessor(config *params.ChainConfig, bc *BlockChain, engine consens unorderedMerge: bc.vmConfig.EnableParallelUnorderedMerge, } initParallelRunner(bc.vmConfig.ParallelTxNum) + if bc.vmConfig.ParallelThreshold == 0 { + bc.vmConfig.ParallelThreshold = ParallelNum() + } log.Info("Parallel execution mode is enabled", "Parallel Num", ParallelNum(), - "CPUNum", runtime.GOMAXPROCS(0), "unorderedMerge", processor.unorderedMerge) + "CPUNum", runtime.GOMAXPROCS(0), "unorderedMerge", processor.unorderedMerge, + "parallel threshold", bc.vmConfig.ParallelThreshold) return processor } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index e204c36b0a..4fa380428b 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -36,6 +36,7 @@ type Config struct { 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 + ParallelThreshold int // Threshold of transactions number to trigger parallel process OptimismPrecompileOverrides PrecompileOverrides // Precompile overrides for Optimism EnableOpcodeOptimizations bool // Enable opcode optimization TxDAG types.TxDAG diff --git a/eth/backend.go b/eth/backend.go index 8bb408482b..a03448853c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -240,6 +240,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { EnableParallelExec: config.ParallelTxMode, EnableParallelUnorderedMerge: config.ParallelTxUnorderedMerge, ParallelTxNum: config.ParallelTxNum, + ParallelThreshold: config.ParallelThreshold, EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, } cacheConfig = &core.CacheConfig{ diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 181a3ac383..164f32c8ed 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -221,11 +221,11 @@ type Config struct { ParallelTxMode bool // Whether to execute transaction in parallel mode when do full sync ParallelTxNum int // Number of slot for transaction execution + ParallelThreshold int // threshold to trigger parallel execution EnableOpcodeOptimizing bool EnableParallelTxDAG bool ParallelTxDAGFile string ParallelTxUnorderedMerge bool // Whether to enable unordered merge in parallel mode - } // CreateConsensusEngine creates a consensus engine for the given chain config. From 38f08460ab2ea744e1368bbe7e9dde7a4b9890c7 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:48:31 +0800 Subject: [PATCH 096/104] feat: PEVM fallback to serial process (#225) --- core/blockchain.go | 75 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 8b8f7bfe4f..ad3c904573 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1896,6 +1896,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) defer func() { DebugInnerExecutionDuration = 0 }() + + if bc.serialProcessor == nil { + bc.serialProcessor = bc.processor + } + for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() { DebugInnerExecutionDuration = 0 // If the chain is terminating, stop processing blocks @@ -1961,6 +1966,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) err error ) + blockProcessedInParallel := false // skip block process if we already have the state, receipts and logs from mining work if !(receiptExist && logExist && stateExist) { // Retrieve the parent block and it's state to execute on top @@ -2010,8 +2016,26 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if useSerialProcessor { receipts, logs, usedGas, err = bc.serialProcessor.Process(block, statedb, bc.vmConfig) + blockProcessedInParallel = false } else { receipts, logs, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig) + blockProcessedInParallel = true + if err != nil { + // parallel processing fail , fallback to serial with new statDB. + log.Warn("ParallelEVM fallback to serial process", "error", err.Error()) + execErr := err + statedb, err = bc.reGenerateStateForFallBack(parent.Root, block.Root(), statedb) + if err != nil { + // Can not get new statedb for serial run, report the process error. + bc.reportBlock(block, receipts, execErr) + followupInterrupt.Store(true) + return it.index, err + } + statedb.StartPrefetcher("chain") + activeState = statedb + receipts, logs, usedGas, err = bc.serialProcessor.Process(block, statedb, bc.vmConfig) + blockProcessedInParallel = false + } } if err != nil { bc.reportBlock(block, receipts, err) @@ -2023,9 +2047,43 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) vstart := time.Now() if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { - bc.reportBlock(block, receipts, err) - followupInterrupt.Store(true) - return it.index, err + if blockProcessedInParallel { + // invalid parallel execution, try serial + log.Warn("ParallelEVM fallback to serial process after ValidateState", "error", err.Error()) + parent := it.previous() + if parent == nil { + parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) + } + + validateErr := err + statedb, err = bc.reGenerateStateForFallBack(parent.Root, block.Root(), statedb) + if err != nil { + // can not get new statedb for serial run, report the validate error. + bc.reportBlock(block, receipts, validateErr) + followupInterrupt.Store(true) + return it.index, err + } + statedb.StartPrefetcher("chain") + activeState = statedb + receipts, logs, usedGas, err = bc.serialProcessor.Process(block, statedb, bc.vmConfig) + if err != nil { + // serial process with process error. + bc.reportBlock(block, receipts, err) + followupInterrupt.Store(true) + return it.index, err + } + blockProcessedInParallel = false + if err := bc.validator.ValidateState(block, statedb, receipts, usedGas); err != nil { + // serial process with validation error. + bc.reportBlock(block, receipts, err) + followupInterrupt.Store(true) + return it.index, err + } + } else { + bc.reportBlock(block, receipts, err) + followupInterrupt.Store(true) + return it.index, err + } } vtime := time.Since(vstart) @@ -2921,6 +2979,17 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { } +func (bc *BlockChain) reGenerateStateForFallBack(parentRoot common.Hash, blockRoot common.Hash, oldDB *state.StateDB) (*state.StateDB, error) { + oldDB.StopPrefetcher() + statedb, err := state.New(parentRoot, bc.stateCache, bc.snaps) + if err != nil { + return nil, err + } + + statedb.SetExpectedStateRoot(blockRoot) + return statedb, nil +} + type TxDAGOutputItem struct { blockNumber uint64 txDAG types.TxDAG From 9e730b335748a1a02a85db1f7033c6cffa7ecfcf Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:30:28 +0800 Subject: [PATCH 097/104] fix: Add test for PEVM fall back (#231) --- core/blockchain_test.go | 246 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index f00002f7b2..b34344f882 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4430,3 +4430,249 @@ func makeEmptyPlainTxDAG(cnt int, flags ...uint8) *types.PlainTxDAG { } return dag } + +func TestPEVMFallBackToSerialProcess(t *testing.T) { + testPEVMFallBackToSerialProcess(t, rawdb.HashScheme) + testPEVMFallBackToSerialProcess(t, rawdb.PathScheme) +} + +/* +testPEVMFallBackToSerialProcess deploys 4 contracts that all modify same kv state, +and provided incorrect TxDAG that intentionally make PEVM create error at runtime with unorderedMerge. +The test should pass with all TXs run successfully as PEVM would fall back to serial process. +*/ +func testPEVMFallBackToSerialProcess(t *testing.T, scheme string) { + var ( + engine = ethash.NewFaker() + + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + aa = common.HexToAddress("0x00000000000000000000000000000000000000aa") + bb = common.HexToAddress("0x00000000000000000000000000000000000000bb") + cc = common.HexToAddress("0x00000000000000000000000000000000000000cc") + dd = common.HexToAddress("0x00000000000000000000000000000000000000dd") + callc = common.HexToAddress("0x0000000000000000000000000000000000000abc") + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + ) + + // Populate two slots + aaStorage[common.HexToHash("01")] = common.HexToHash("00") + aaStorage[common.HexToHash("02")] = common.HexToHash("00") + + aaCode := []byte{ + byte(vm.PUSH1), 0x0, // value + byte(vm.PUSH1), 0x1, // location + byte(vm.SSTORE), // Set slot[1] = 1 + byte(vm.PUSH1), 0x0, // value + byte(vm.PUSH1), 0x2, // location + byte(vm.SSTORE), + } + + bbCode := []byte{ + byte(vm.PUSH1), 0x3, // value + byte(vm.PUSH1), 0x1, // location + byte(vm.SSTORE), // Set slot[1] = 3 + byte(vm.PUSH1), 0x4, // value + byte(vm.PUSH1), 0x2, // location + byte(vm.SSTORE), + } + + ccCode := []byte{ + byte(vm.PUSH1), 0x5, // value + byte(vm.PUSH1), 0x1, // location + byte(vm.SSTORE), // Set slot[1] = 5 + byte(vm.PUSH1), 0x6, // value + byte(vm.PUSH1), 0x2, // location + byte(vm.SSTORE), + } + + ddCode := []byte{ + byte(vm.PUSH1), 0x7, // value + byte(vm.PUSH1), 0x1, // location + byte(vm.SSTORE), // Set slot[1] = 7 + byte(vm.PUSH1), 0x8, // value + byte(vm.PUSH1), 0x2, // location + byte(vm.SSTORE), + } + + callcode := []byte{ + /* ByteCode , stack */ + // Get call value + byte(vm.CALLVALUE), /* value */ + byte(vm.DUP1), /* value, value */ + byte(vm.DUP1), /* value, value, value */ + byte(vm.DUP1), /* value, value, value, value */ + // value == 1 jump to call aa + byte(vm.PUSH1), 0x1, /* value, value, value, value, 0x1 */ + byte(vm.EQ), /* value, value, value, 1 or 0 */ + byte(vm.PUSH1), 0x1d, /* value, value, value, 1/0, 0xa */ + byte(vm.JUMPI), /*value, value, value, */ + // value = 2 jump to call bb + byte(vm.PUSH1), 0x2, /* value, value, value, 0x2 */ + byte(vm.EQ), /* value, value, 1 or 0 */ + byte(vm.PUSH1), 0x2e, /* value, value, 1/0, 0xa */ + byte(vm.JUMPI), /* */ + // value = 3 jump to call cc + byte(vm.PUSH1), 0x3, /* value, value, 0x3 */ + byte(vm.EQ), /* value, 1 or 0 */ + byte(vm.PUSH1), 0x3f, /* value, 1/0, 0xa */ + byte(vm.JUMPI), /* */ + // value = 4 jump to call dd + byte(vm.PUSH1), 0x4, /* value, 0x2 */ + byte(vm.EQ), /* 1 or 0 */ + byte(vm.PUSH1), 0x50, /* 1/0, 0xff */ + byte(vm.JUMPI), /* */ + // if not match, stop + byte(vm.STOP), + + // call 0xaa + // outsize, outoffset, insize, inoffset + // offset 0x1d (29) + byte(vm.JUMPDEST), + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xaa, //address + byte(vm.GAS), // gas + byte(vm.CALLCODE), + byte(vm.POP), + byte(vm.STOP), + + // call 0xbb + // outsize, outoffset, insize, inoffset + // offset 0x2e (46) + byte(vm.JUMPDEST), + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xbb, //address + byte(vm.GAS), // gas + byte(vm.CALLCODE), + byte(vm.POP), + byte(vm.STOP), + + // call 0xcc + // offset 0x3f(63) + // outsize, outoffset, insize, inoffset + byte(vm.JUMPDEST), + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xcc, //address + byte(vm.GAS), // gas + byte(vm.CALLCODE), + byte(vm.POP), + byte(vm.STOP), + + // call 0xdd + // offset (0x50)80 + // outsize, outoffset, insize, inoffset + byte(vm.JUMPDEST), + byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, + byte(vm.PUSH1), 0, // value + byte(vm.PUSH1), 0xdd, //address + byte(vm.GAS), // gas + byte(vm.CALLCODE), + byte(vm.POP), + byte(vm.STOP), + } + + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + aa: { + Code: aaCode, + Nonce: 1, + Balance: big.NewInt(0), + }, + bb: { + Code: bbCode, + Balance: big.NewInt(1), + }, + cc: { + Code: ccCode, + Balance: big.NewInt(1), + }, + dd: { + Code: ddCode, + Balance: big.NewInt(1), + }, + + callc: { + Code: callcode, + Nonce: 1, + Balance: big.NewInt(1000), + Storage: aaStorage, + }, + }, + } + + dag := types.NewPlainTxDAG(4) + // make incorrect DAG, which will cause nonce too high error at runtime + dag.TxDeps[0].TxIndexes = []uint64{} + dag.TxDeps[1].TxIndexes = []uint64{0} + dag.TxDeps[2].TxIndexes = []uint64{} + dag.TxDeps[3].TxIndexes = []uint64{} + + txDAGFile := filepath.Join(os.TempDir(), fmt.Sprintf("test_txdag_%v.csv", "testPEVMFallBackWithDeleteCreateAccount")) + writeFile, err := os.OpenFile(txDAGFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + require.NoError(t, err) + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: 1, txDAG: dag})) + writeFile.Sync() + writeFile.Close() + + defer os.Remove(txDAGFile) + + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to AA + tx, _ := types.SignTx(types.NewTransaction(0, callc, + big.NewInt(1), 50000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + // One transaction to BB + tx, _ = types.SignTx(types.NewTransaction(1, callc, + big.NewInt(2), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + + tx, _ = types.SignTx(types.NewTransaction(2, callc, + big.NewInt(3), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + + tx, _ = types.SignTx(types.NewTransaction(3, callc, + big.NewInt(4), 100000, b.header.BaseFee, nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + }) + // Import the canonical chain with PEVM and trust DAG with UnorderedMerge + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ + EnableParallelExec: true, + ParallelTxNum: 4, + ParallelThreshold: 1, + EnableParallelUnorderedMerge: true, + }, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + defer chain.Stop() + + chain.SetupTxDAGGeneration(txDAGFile, true) + + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + statedb, _ := chain.State() + + // If all is correct, then slot 1 and 2 are zero + if got, exp := statedb.GetState(callc, common.HexToHash("03")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } + if got, exp := statedb.GetState(callc, common.HexToHash("04")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } + // Also, 3 and 4 should be set + if got, exp := statedb.GetState(callc, common.HexToHash("01")), common.HexToHash("07"); got != exp { + t.Fatalf("got %x exp %x", got, exp) + } + if got, exp := statedb.GetState(callc, common.HexToHash("02")), common.HexToHash("08"); got != exp { + t.Fatalf("got %x exp %x", got, exp) + } +} From 16bbb4476b51c5bebe69cefa55a2da501b28fba7 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:46:47 +0800 Subject: [PATCH 098/104] Revert "use patch to fix ut instead of changing code" (#235) --- .github/workflows/unit-test.yaml | 1 - tests/fix_unit_test.patch | 19 ------------------- 2 files changed, 20 deletions(-) delete mode 100644 tests/fix_unit_test.patch diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml index cc1106b4ed..feffce65e1 100644 --- a/.github/workflows/unit-test.yaml +++ b/.github/workflows/unit-test.yaml @@ -19,5 +19,4 @@ jobs: go-version-file: go.mod - run: | git apply tests/0001-diff-go-ethereum.patch - git apply tests/fix_unit_test.patch make test diff --git a/tests/fix_unit_test.patch b/tests/fix_unit_test.patch deleted file mode 100644 index 7567c4ba4c..0000000000 --- a/tests/fix_unit_test.patch +++ /dev/null @@ -1,19 +0,0 @@ -Subject: [PATCH] fix unit test ---- -Index: core/txpool/legacypool/legacypool.go -IDEA additional info: -Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP -<+>UTF-8 -=================================================================== -diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go ---- a/core/txpool/legacypool/legacypool.go (revision 1233f69823ec26df160575add475f1e949cde211) -+++ b/core/txpool/legacypool/legacypool.go (date 1730875125704) -@@ -391,7 +391,7 @@ - } - - func (pool *LegacyPool) loopOfSync() { -- ticker := time.NewTicker(200 * time.Second) -+ ticker := time.NewTicker(200 * time.Millisecond) - for { - select { - case <-pool.reorgShutdownCh: From 62093b3d3afcb7572642b8597ca61669637182b0 Mon Sep 17 00:00:00 2001 From: welkin22 Date: Tue, 12 Nov 2024 20:29:16 +0800 Subject: [PATCH 099/104] pevm: support parallel merge mode Merge pull request #238 from welkin22/feature/TxDAG-PEVM-parallel-merge --- .gitmodules | 4 + cmd/evm/blockrunner.go | 2 +- cmd/geth/main.go | 2 +- cmd/utils/flags.go | 20 +- consensus/beacon/consensus.go | 2 +- consensus/beacon/oplegacy.go | 2 +- consensus/clique/clique.go | 4 +- consensus/consensus.go | 2 +- consensus/ethash/consensus.go | 4 +- consensus/misc/dao.go | 13 + consensus/misc/precontract.go | 7 + core/block_validator.go | 2 +- core/blockchain.go | 107 +- core/blockchain_test.go | 1 - core/gaspool.go | 41 + core/parallel_state_scheduler.go | 63 +- core/parallel_state_scheduler_test.go | 19 +- core/pevm_processor.go | 178 ++- core/state/access_list.go | 95 ++ core/state/interface.go | 51 +- core/state/pevm_statedb.go | 1557 ++++++++++++++++++++++++- core/state/pevm_statedb_test.go | 56 +- core/state/state_object.go | 213 ++-- core/state/statedb.go | 149 ++- core/state_processor.go | 23 +- core/types.go | 4 +- core/vm/interpreter.go | 2 +- eth/backend.go | 2 +- eth/ethconfig/config.go | 3 +- eth/ethconfig/gen_config.go | 36 + tests-dag | 1 + tests/block_test.go | 36 +- tests/block_test_util.go | 12 +- 33 files changed, 2363 insertions(+), 350 deletions(-) create mode 160000 tests-dag diff --git a/.gitmodules b/.gitmodules index 241c169c47..1f21c51bf4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,3 +6,7 @@ path = tests/evm-benchmarks url = https://github.com/ipsilon/evm-benchmarks shallow = true +[submodule "tests-dag"] + path = tests-dag + url = https://github.com/welkin22/txdag-test-file.git + shallow = true diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index 196e86d591..3549805365 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))) } } - }, "", true); err != nil { + }, "", true, false, false); err != nil { return fmt.Errorf("test %v: %w", name, err) } } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index d2a43a6961..b4c79e26c6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -172,8 +172,8 @@ var ( utils.RollupSuperchainUpgradesFlag, utils.ParallelTxFlag, utils.ParallelTxUnorderedMergeFlag, + utils.ParallelTxParallelMergeFlag, utils.ParallelTxNumFlag, - utils.ParallelThresholdFlag, utils.ParallelTxDAGFlag, utils.ParallelTxDAGFileFlag, utils.ParallelTxDAGSenderPrivFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 30eee8ee0e..726f980ad4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1111,15 +1111,15 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. 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)", + ParallelTxParallelMergeFlag = &cli.BoolFlag{ + Name: "parallel.parallel-merge", + Usage: "Enable concurrent merge mode, during the parallel confirm phase, multiple goroutines will be used to perform concurrent merging of execution results. This option will override parallel.unordered-merge", Category: flags.VMCategory, } - ParallelThresholdFlag = &cli.IntFlag{ - Name: "parallel.threshold", - Usage: "Threshold of transaction count to trigger parallel execution, only valid in parallel mode (runtime calculated, no fixed default value)", + 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, } @@ -2041,12 +2041,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.ParallelTxUnorderedMerge = ctx.Bool(ParallelTxUnorderedMergeFlag.Name) } - if ctx.IsSet(ParallelTxNumFlag.Name) { - cfg.ParallelTxNum = ctx.Int(ParallelTxNumFlag.Name) + if ctx.IsSet(ParallelTxParallelMergeFlag.Name) { + cfg.ParallelTxParallelMerge = ctx.Bool(ParallelTxParallelMergeFlag.Name) } - if ctx.IsSet(ParallelThresholdFlag.Name) { - cfg.ParallelThreshold = ctx.Int(ParallelThresholdFlag.Name) + if ctx.IsSet(ParallelTxNumFlag.Name) { + cfg.ParallelTxNum = ctx.Int(ParallelTxNumFlag.Name) } if ctx.IsSet(ParallelTxDAGFlag.Name) { diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index ba4980b8d4..80df07a685 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -364,7 +364,7 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H } // Finalize implements consensus.Engine and processes withdrawals on top. -func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { +func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state state.StateDBer, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { if !beacon.IsPoSHeader(header) { beacon.ethone.Finalize(chain, header, state, txs, uncles, nil) return diff --git a/consensus/beacon/oplegacy.go b/consensus/beacon/oplegacy.go index 62810cb03f..3051535102 100644 --- a/consensus/beacon/oplegacy.go +++ b/consensus/beacon/oplegacy.go @@ -60,7 +60,7 @@ func (o *OpLegacy) Prepare(chain consensus.ChainHeaderReader, header *types.Head return fmt.Errorf("cannot prepare for legacy block header: %s (num %d)", header.Hash(), header.Number) } -func (o *OpLegacy) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { +func (o *OpLegacy) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state state.StateDBer, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { panic(fmt.Errorf("cannot finalize legacy block header: %s (num %d)", header.Hash(), header.Number)) } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index c693189ea5..3cb5a56e2b 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - lru "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" @@ -580,7 +580,7 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header // Finalize implements consensus.Engine. There is no post-transaction // consensus rules in clique, do nothing here. -func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { +func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state state.StateDBer, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { // No block rewards in PoA, so the state remains as is } diff --git a/consensus/consensus.go b/consensus/consensus.go index 3a2c2d2229..2b17b3751e 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -88,7 +88,7 @@ type Engine interface { // // Note: The state database might be updated to reflect any consensus rules // that happen at finalization (e.g. block rewards). - Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, + Finalize(chain ChainHeaderReader, header *types.Header, state state.StateDBer, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) // FinalizeAndAssemble runs any post-transaction state modifications (e.g. block diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index c2936fd4b3..2434779ea1 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -501,7 +501,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H } // Finalize implements consensus.Engine, accumulating the block and uncle rewards. -func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { +func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state state.StateDBer, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) { // Accumulate any block and uncle rewards accumulateRewards(chain.Config(), state, header, uncles) } @@ -570,7 +570,7 @@ var ( // AccumulateRewards credits the coinbase of the given block with the mining // reward. The total reward consists of the static block reward and rewards for // included uncles. The coinbase of each uncle block is also rewarded. -func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { +func accumulateRewards(config *params.ChainConfig, state state.StateDBer, header *types.Header, uncles []*types.Header) { // Select the correct block reward based on chain progression blockReward := FrontierBlockReward if config.IsByzantium(header.Number) { diff --git a/consensus/misc/dao.go b/consensus/misc/dao.go index e21a44f63d..b55e0eb9fb 100644 --- a/consensus/misc/dao.go +++ b/consensus/misc/dao.go @@ -85,3 +85,16 @@ func ApplyDAOHardFork(statedb *state.StateDB) { statedb.SetBalance(addr, new(uint256.Int)) } } + +func ApplyDAOHardFork2(statedb state.StateDBer) { + // Retrieve the contract to refund balances into + if !statedb.Exist(params.DAORefundContract) { + statedb.CreateAccount(params.DAORefundContract) + } + + // Move every DAO account and extra-balance account funds into the refund contract + for _, addr := range params.DAODrainList() { + statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr)) + statedb.SetBalance(addr, new(uint256.Int)) + } +} diff --git a/consensus/misc/precontract.go b/consensus/misc/precontract.go index b426eb815a..f19390ab17 100644 --- a/consensus/misc/precontract.go +++ b/consensus/misc/precontract.go @@ -40,3 +40,10 @@ func ApplyPreContractHardFork(statedb *state.StateDB) { statedb.SetState(WBNBContract, symbolSlot, symbolValue) statedb.SelfDestruct(governanceToken) } + +// ApplyPreContractHardFork modifies the state database according to the hard-fork rules +func ApplyPreContractHardFork2(statedb state.StateDBer) { + statedb.SetState(WBNBContract, nameSlot, nameValue) + statedb.SetState(WBNBContract, symbolSlot, symbolValue) + statedb.SelfDestruct(governanceToken) +} diff --git a/core/block_validator.go b/core/block_validator.go index 79839d7176..3413ee081c 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -162,7 +162,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // ValidateState validates the various changes that happen after a state transition, // such as amount of used gas, the receipt roots and the state root itself. -func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64) error { +func (v *BlockValidator) ValidateState(block *types.Block, statedb state.StateDBer, receipts types.Receipts, usedGas uint64) error { header := block.Header() if block.GasUsed() != usedGas { return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) diff --git a/core/blockchain.go b/core/blockchain.go index ad3c904573..4695b270b5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -99,12 +99,16 @@ var ( txDAGGenerateTimer = metrics.NewRegisteredTimer("chain/block/txdag/gen", nil) txDAGReaderChanGauge = metrics.NewRegisteredGauge("chain/block/txdag/reader/chan", nil) - parallelTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/txs", nil) - parallelConflictTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/conflicttxs", nil) - parallelExecutionTimer = metrics.NewRegisteredTimer("chain/parallel/exec", nil) - parallelConfirmTimer = metrics.NewRegisteredTimer("chain/parallel/confirm", nil) - parallelTxLevelsSizeMeter = metrics.NewRegisteredGauge("chain/parallel/txlevel/size", nil) - parallelTxLevelTxSizeMeter = metrics.NewRegisteredGauge("chain/parallel/txlevel/txsize", nil) + parallelTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/txs", nil) + parallelEnableMeter = metrics.NewRegisteredMeter("chain/parallel/enable", nil) + parallelFallbackMeter = metrics.NewRegisteredMeter("chain/parallel/fallback", nil) + parallelConflictTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/conflicttxs", nil) + parallelExecutionTimer = metrics.NewRegisteredTimer("chain/parallel/exec", nil) + parallelConfirmTimer = metrics.NewRegisteredTimer("chain/parallel/confirm", nil) + parallelConfirmConcurrentTimer = metrics.NewRegisteredTimer("chain/parallel/confirm/concurrent", nil) + parallelConfirmAfterTimer = metrics.NewRegisteredTimer("chain/parallel/confirm/after", nil) + parallelTxLevelsSizeMeter = metrics.NewRegisteredGauge("chain/parallel/txlevel/size", nil) + parallelTxLevelTxSizeMeter = metrics.NewRegisteredGauge("chain/parallel/txlevel/txsize", nil) blockGasUsedGauge = metrics.NewRegisteredGauge("chain/block/gas/used", nil) mgaspsGauge = metrics.NewRegisteredGauge("chain/mgas/ps", nil) @@ -298,7 +302,7 @@ type BlockChain struct { miningReceiptsCache *lru.Cache[common.Hash, []*types.Receipt] miningTxLogsCache *lru.Cache[common.Hash, []*types.Log] - miningStateCache *lru.Cache[common.Hash, *state.StateDB] + miningStateCache *lru.Cache[common.Hash, state.StateDBer] // future blocks are blocks added for later processing futureBlocks *lru.Cache[common.Hash, *types.Block] @@ -374,7 +378,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit), miningReceiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit), miningTxLogsCache: lru.NewCache[common.Hash, []*types.Log](txLogsCacheLimit), - miningStateCache: lru.NewCache[common.Hash, *state.StateDB](miningStateCacheLimit), + miningStateCache: lru.NewCache[common.Hash, state.StateDBer](miningStateCacheLimit), futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks), engine: engine, vmConfig: vmConfig, @@ -1531,7 +1535,7 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error { // writeBlockWithState writes block, metadata and corresponding state data to the // database. -func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error { +func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state state.StateDBer) error { // Calculate the total difficulty of the block ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1) if ptd == nil { @@ -1657,7 +1661,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) { +func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state state.StateDBer, emitHeadEvent bool) (status WriteStatus, err error) { if err := bc.writeBlockWithState(block, receipts, state); err != nil { return NonStatTy, err } @@ -1882,7 +1886,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } // No validation errors for the first block (or chain prefix skipped) - var activeState *state.StateDB + var activeState state.StateDBer defer func() { // The chain importer is starting and stopping trie prefetchers. If a bad // block or other error is hit however, an early return may not properly @@ -1974,22 +1978,28 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if parent == nil { parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } - statedb, err = state.New(parent.Root, bc.stateCache, bc.snaps) + + if bc.vmConfig.EnableParallelExec { + bc.parseTxDAG(block) + } + isByzantium := bc.chainConfig.IsByzantium(block.Number()) + + if bc.vmConfig.EnableParallelExec && bc.vmConfig.TxDAG != nil && bc.vmConfig.EnableTxParallelMerge && isByzantium { + statedb, err = state.NewParallel(parent.Root, bc.stateCache, bc.snaps) + } else { + statedb, err = state.New(parent.Root, bc.stateCache, bc.snaps) + } if err != nil { return it.index, err } - // Enable prefetching to pull in trie node paths while processing transactions statedb.StartPrefetcher("chain") activeState = statedb - if bc.vmConfig.EnableParallelExec { - bc.parseTxDAG(block) - } // 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 - if !bc.cacheConfig.TrieCleanNoPrefetch && !bc.vmConfig.EnableParallelExec { + if !bc.cacheConfig.TrieCleanNoPrefetch { if followup, err := it.peek(); followup != nil && err == nil { throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) @@ -2006,23 +2016,21 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) statedb.SetExpectedStateRoot(block.Root()) - // Decide the enabling of parallelExec - invalidParallelConfig := bc.vmConfig.TxDAG == nil && bc.vmConfig.EnableParallelUnorderedMerge - lowTxsNum := bc.vmConfig.ParallelThreshold >= block.Transactions().Len() - useSerialProcessor := invalidParallelConfig || lowTxsNum || !bc.vmConfig.EnableParallelExec - // Process block using the parent state as reference point pstart = time.Now() - + txDAGMissButNecessary := bc.vmConfig.TxDAG == nil && (bc.vmConfig.EnableParallelUnorderedMerge || bc.vmConfig.EnableTxParallelMerge) + useSerialProcessor := !bc.vmConfig.EnableParallelExec || txDAGMissButNecessary if useSerialProcessor { receipts, logs, usedGas, err = bc.serialProcessor.Process(block, statedb, bc.vmConfig) blockProcessedInParallel = false } else { + parallelEnableMeter.Mark(1) receipts, logs, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig) blockProcessedInParallel = true if err != nil { // parallel processing fail , fallback to serial with new statDB. log.Warn("ParallelEVM fallback to serial process", "error", err.Error()) + parallelFallbackMeter.Mark(1) execErr := err statedb, err = bc.reGenerateStateForFallBack(parent.Root, block.Root(), statedb) if err != nil { @@ -2050,6 +2058,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if blockProcessedInParallel { // invalid parallel execution, try serial log.Warn("ParallelEVM fallback to serial process after ValidateState", "error", err.Error()) + parallelFallbackMeter.Mark(1) parent := it.previous() if parent == nil { parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) @@ -2111,21 +2120,30 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } // Update the metrics touched during block processing and validation - accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) - storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) - snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete(in processing) - snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete(in processing) - accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation) - storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) - accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) - storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) - txDAGGenerateTimer.Update(statedb.TxDAGGenerate) + timers := statedb.Timers() + accountReadTimer.Update(timers.AccountReads) // Account reads are complete(in processing) + storageReadTimer.Update(timers.StorageReads) // Storage reads are complete(in processing) + snapshotAccountReadTimer.Update(timers.SnapshotAccountReads) // Account reads are complete(in processing) + snapshotStorageReadTimer.Update(timers.SnapshotStorageReads) // Storage reads are complete(in processing) + accountUpdateTimer.Update(timers.AccountUpdates) // Account updates are complete(in validation) + storageUpdateTimer.Update(timers.StorageUpdates) // Storage updates are complete(in validation) + accountHashTimer.Update(timers.AccountHashes) // Account hashes are complete(in validation) + storageHashTimer.Update(timers.StorageHashes) // Storage hashes are complete(in validation) + txDAGGenerateTimer.Update(timers.TxDAGGenerate) blockExecutionTimer.Update(ptime) // The time spent on block execution blockValidationTimer.Update(vtime) // The time spent on block validation innerExecutionTimer.Update(DebugInnerExecutionDuration) - log.Debug("New payload execution and validation metrics", "hash", block.Hash(), "execution", common.PrettyDuration(ptime), "validation", common.PrettyDuration(vtime), "accountReads", common.PrettyDuration(statedb.AccountReads), "storageReads", common.PrettyDuration(statedb.StorageReads), "snapshotAccountReads", common.PrettyDuration(statedb.SnapshotAccountReads), "snapshotStorageReads", common.PrettyDuration(statedb.SnapshotStorageReads), "accountUpdates", common.PrettyDuration(statedb.AccountUpdates), "storageUpdates", common.PrettyDuration(statedb.StorageUpdates), "accountHashes", common.PrettyDuration(statedb.AccountHashes), "storageHashes", common.PrettyDuration(statedb.StorageHashes)) + log.Debug("New payload execution and validation metrics", "hash", block.Hash(), "execution", + common.PrettyDuration(ptime), "validation", common.PrettyDuration(vtime), "accountReads", + common.PrettyDuration(timers.AccountReads), "storageReads", common.PrettyDuration(timers.StorageReads), + "snapshotAccountReads", common.PrettyDuration(timers.SnapshotAccountReads), + "snapshotStorageReads", common.PrettyDuration(timers.SnapshotStorageReads), + "accountUpdates", common.PrettyDuration(timers.AccountUpdates), + "storageUpdates", common.PrettyDuration(timers.StorageUpdates), + "accountHashes", common.PrettyDuration(timers.AccountHashes), + "storageHashes", common.PrettyDuration(timers.StorageHashes)) // Write the block to the chain and get the status. var ( @@ -2142,18 +2160,25 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if err != nil { return it.index, err } + timers = statedb.Timers() // Update the metrics touched during block commit - accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them - storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them - snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them - triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them - trieCommitTimer.Update(statedb.TrieCommits) - codeCommitTimer.Update(statedb.CodeCommits) + accountCommitTimer.Update(timers.AccountCommits) // Account commits are complete, we can mark them + storageCommitTimer.Update(timers.StorageCommits) // Storage commits are complete, we can mark them + snapshotCommitTimer.Update(timers.SnapshotCommits) // Snapshot commits are complete, we can mark them + triedbCommitTimer.Update(timers.TrieDBCommits) // Trie database commits are complete, we can mark them + trieCommitTimer.Update(timers.TrieCommits) + codeCommitTimer.Update(timers.CodeCommits) blockWriteTimer.UpdateSince(wstart) blockInsertTimer.UpdateSince(start) - log.Debug("New payload db write metrics", "hash", block.Hash(), "insert", common.PrettyDuration(time.Since(start)), "writeDB", common.PrettyDuration(time.Since(wstart)), "writeBlock", common.PrettyDuration(time.Since(wstart)), "accountCommit", common.PrettyDuration(statedb.AccountCommits), "storageCommit", common.PrettyDuration(statedb.StorageCommits), "snapshotCommits", common.PrettyDuration(statedb.SnapshotCommits), "triedbCommit", common.PrettyDuration(statedb.TrieDBCommits)) + log.Debug("New payload db write metrics", "hash", block.Hash(), + "insert", common.PrettyDuration(time.Since(start)), "writeDB", common.PrettyDuration(time.Since(wstart)), + "writeBlock", common.PrettyDuration(time.Since(wstart)), + "accountCommit", common.PrettyDuration(timers.AccountCommits), + "storageCommit", common.PrettyDuration(timers.StorageCommits), + "snapshotCommits", common.PrettyDuration(timers.SnapshotCommits), + "triedbCommit", common.PrettyDuration(timers.TrieDBCommits)) // Report the import stats before returning the various results stats.processed++ @@ -2979,7 +3004,7 @@ func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { } -func (bc *BlockChain) reGenerateStateForFallBack(parentRoot common.Hash, blockRoot common.Hash, oldDB *state.StateDB) (*state.StateDB, error) { +func (bc *BlockChain) reGenerateStateForFallBack(parentRoot common.Hash, blockRoot common.Hash, oldDB state.StateDBer) (state.StateDBer, error) { oldDB.StopPrefetcher() statedb, err := state.New(parentRoot, bc.stateCache, bc.snaps) if err != nil { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index b34344f882..abc3e9df55 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4646,7 +4646,6 @@ func testPEVMFallBackToSerialProcess(t *testing.T, scheme string) { chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{ EnableParallelExec: true, ParallelTxNum: 4, - ParallelThreshold: 1, EnableParallelUnorderedMerge: true, }, nil, nil) if err != nil { diff --git a/core/gaspool.go b/core/gaspool.go index 767222674f..e6699d7558 100644 --- a/core/gaspool.go +++ b/core/gaspool.go @@ -19,6 +19,7 @@ package core import ( "fmt" "math" + "sync/atomic" ) // GasPool tracks the amount of gas available during execution of the transactions @@ -57,3 +58,43 @@ func (gp *GasPool) SetGas(gas uint64) { func (gp *GasPool) String() string { return fmt.Sprintf("%d", *gp) } + +type ParallelGasPool atomic.Uint64 + +func (gp *ParallelGasPool) AddGas(amount uint64) *ParallelGasPool { + for { + current := (*atomic.Uint64)(gp).Load() + if current > math.MaxUint64-amount { + panic("gas pool pushed above uint64") + } + if (*atomic.Uint64)(gp).CompareAndSwap(current, current+amount) { + break + } + } + return gp +} + +func (gp *ParallelGasPool) SubGas(amount uint64) error { + for { + current := (*atomic.Uint64)(gp).Load() + if current < amount { + return ErrGasLimitReached + } + if (*atomic.Uint64)(gp).CompareAndSwap(current, current-amount) { + break + } + } + return nil +} + +func (gp *ParallelGasPool) Gas() uint64 { + return (*atomic.Uint64)(gp).Load() +} + +func (gp *ParallelGasPool) SetGas(gas uint64) { + (*atomic.Uint64)(gp).Store(gas) +} + +func (gp *ParallelGasPool) String() string { + return fmt.Sprintf("%d", (*atomic.Uint64)(gp).Load()) +} diff --git a/core/parallel_state_scheduler.go b/core/parallel_state_scheduler.go index 5cbf48ed1a..25271bf0dc 100644 --- a/core/parallel_state_scheduler.go +++ b/core/parallel_state_scheduler.go @@ -89,8 +89,10 @@ func (tl TxLevel) Split(chunks int) []TxLevel { type TxLevels []TxLevel type confirmQueue struct { - queue []confirmation - confirmed int // need to be set to -1 originally + queue []confirmation + confirmed int // need to be set to -1 originally + parallelMergeTime int64 + parallelMergeAfterTime int64 } type confirmation struct { @@ -144,6 +146,53 @@ func (cq *confirmQueue) confirmWithUnordered(level TxLevel, execute func(*PEVMTx return nil, 0 } +func (cq *confirmQueue) confirmParallel(levels []TxLevel, confirm func(*PEVMTxResult) error, afterParallelConfirm func(levels TxLevels, cq *confirmQueue) (err error)) (error, int) { + var wg sync.WaitGroup + wg.Add(len(levels)) + errs := make(chan []interface{}, len(levels)) + start := time.Now() + for _, txs := range levels { + temp := txs + run := func() { + defer wg.Done() + for _, tx := range temp { + toConfirm := cq.queue[tx.txIndex] + if toConfirm.result == nil { + log.Warn("transaction should be executed, not result in queue", "index", tx.txIndex) + errs <- []interface{}{fmt.Errorf("transaction should be executed, not result in queue, index %d", tx.txIndex), tx.txIndex} + return + } + if toConfirm.executed != nil { + log.Error("transaction execute fail! we can not do parallel merge here", "err", toConfirm.executed, "index", tx.txIndex) + errs <- []interface{}{toConfirm.executed, tx.txIndex} + return + } + if err := confirm(toConfirm.result); err != nil { + log.Error("parallel merge fail!", "err", err, "index", tx.txIndex) + errs <- []interface{}{err, tx.txIndex} + return + } + } + } + runner <- run + } + go func() { + wg.Wait() + close(errs) + }() + for err := range errs { + return err[0].(error), err[1].(int) + } + cq.parallelMergeTime += time.Since(start).Nanoseconds() + start = time.Now() + if err := afterParallelConfirm(levels, cq); err != nil { + log.Error("confirm after parallel merge fail", "err", err) + return err, 0 + } + cq.parallelMergeAfterTime += time.Since(start).Nanoseconds() + return nil, 0 +} + // try to confirm txs as much as possible, they will be confirmed in a sequencial order. func (cq *confirmQueue) confirm(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) (error, int) { // find all able-to-confirm transactions, and try to confirm them @@ -200,7 +249,7 @@ var goMaxProcs = runtime.GOMAXPROCS(0) // run runs the transactions in parallel // execute must return a non-nil result, otherwise it panics. -func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error, unorderedMerge bool) (error, int) { +func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error, afterParallelConfirm func(levels TxLevels, cq *confirmQueue) (err error), unorderedMerge bool, parallelMerge bool) (error, int) { toConfirm := &confirmQueue{ queue: make([]confirmation, tls.txCount()), confirmed: -1, @@ -238,7 +287,11 @@ func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func totalExecutionTime += time.Since(start).Nanoseconds() start = time.Now() // all transactions of current level are executed, now try to confirm. - if unorderedMerge { + if parallelMerge { + if err, txIndex := toConfirm.confirmParallel(trunks, confirm, afterParallelConfirm); err != nil { + return err, txIndex + } + } else if unorderedMerge { if err, txIndex := toConfirm.confirmWithUnordered(txLevel, execute, confirm); err != nil { // something very wrong, stop the process return err, txIndex @@ -254,6 +307,8 @@ func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func parallelTxLevelTxSizeMeter.Update(int64(maxLevelTxCount)) parallelExecutionTimer.Update(time.Duration(totalExecutionTime)) parallelConfirmTimer.Update(time.Duration(totalConfirmTime)) + parallelConfirmConcurrentTimer.Update(time.Duration(toConfirm.parallelMergeTime)) + parallelConfirmAfterTimer.Update(time.Duration(toConfirm.parallelMergeAfterTime)) return nil, 0 } diff --git a/core/parallel_state_scheduler_test.go b/core/parallel_state_scheduler_test.go index 6908e00fa8..fd78f37f7f 100644 --- a/core/parallel_state_scheduler_test.go +++ b/core/parallel_state_scheduler_test.go @@ -62,7 +62,8 @@ func checkMainDB(data map[int]int) bool { } func newTxReq(from, to, value int) *PEVMTxRequest { - usedGas := uint64(value) + usedGas := atomic.Uint64{} + usedGas.Add(uint64(value)) return &PEVMTxRequest{ usedGas: &usedGas, gasLimit: uint64(from), @@ -71,7 +72,7 @@ func newTxReq(from, to, value int) *PEVMTxRequest { } func (mt *mockTx) Value() int { - return int(*mt.req.usedGas) + return int(mt.req.usedGas.Load()) } func (mt *mockTx) From() int { @@ -268,7 +269,7 @@ func setTxIndex(allReq []*PEVMTxRequest) { func TestTxLevelRun(t *testing.T) { // case 1: empty txs case1 := func() { - levels([]uint64{}, [][]int{}).Run(func(*PEVMTxRequest) *PEVMTxResult { return nil }, func(*PEVMTxResult) error { return nil }, false) + levels([]uint64{}, [][]int{}).Run(func(*PEVMTxRequest) *PEVMTxResult { return nil }, func(*PEVMTxResult) error { return nil }, nil, false, false) } // case 2: 4 txs with no dependencies, no conflicts case2 := func() { @@ -290,7 +291,7 @@ func TestTxLevelRun(t *testing.T) { nil, nil, nil, nil, }) caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, false) + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, nil, false, false) ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21, 7: 31, 8: 41}) if err != nil { t.Fatalf("failed, err:%v", err) @@ -326,7 +327,7 @@ func TestTxLevelRun(t *testing.T) { nil, nil, {0}, {1}, }) caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, false) + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, nil, false, false) ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21}) if err != nil { t.Fatalf("failed, err:%v", err) @@ -362,7 +363,7 @@ func TestTxLevelRun(t *testing.T) { {0}, nil, {-1}, {-1}, }) caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, false) + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, nil, false, false) ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21}) if err != nil { t.Fatalf("failed, err:%v", err) @@ -410,7 +411,7 @@ func TestTxLevelRun(t *testing.T) { res[i+2000] = i } caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm, false) + err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm, nil, false, false) ok := checkMainDB(res) if err != nil { t.Fatalf("failed, err:%v", err) @@ -436,7 +437,7 @@ func TestTxLevelRun(t *testing.T) { } setTxIndex(allReqs) caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} - err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm, false) + err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm, nil, false, false) ok := checkMainDB(map[int]int{1: 5, 2: 20, 3: 10}) if err != nil { t.Fatalf("failed, err:%v", err) @@ -465,7 +466,7 @@ func TestTxLevelRun(t *testing.T) { } caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} setTxIndex(allReqs) - err, _ := NewTxLevels(allReqs, dag).Run(caller.execute, caller.confirm, false) + err, _ := NewTxLevels(allReqs, dag).Run(caller.execute, caller.confirm, nil, false, false) ok := checkMainDB(map[int]int{1: 5, 2: 20, 3: 10}) if err != nil { t.Fatalf("failed, err:%v", err) diff --git a/core/pevm_processor.go b/core/pevm_processor.go index 1b3f7a4116..1c52ade7eb 100644 --- a/core/pevm_processor.go +++ b/core/pevm_processor.go @@ -4,11 +4,10 @@ import ( "errors" "fmt" "runtime" + "sync" "sync/atomic" "time" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/state" @@ -16,6 +15,7 @@ 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" ) @@ -27,20 +27,18 @@ type PEVMProcessor struct { receipts types.Receipts debugConflictRedoNum uint64 unorderedMerge bool + parallelMerge bool } func newPEVMProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *PEVMProcessor { processor := &PEVMProcessor{ StateProcessor: *NewStateProcessor(config, bc, engine), unorderedMerge: bc.vmConfig.EnableParallelUnorderedMerge, + parallelMerge: bc.vmConfig.EnableTxParallelMerge, } initParallelRunner(bc.vmConfig.ParallelTxNum) - if bc.vmConfig.ParallelThreshold == 0 { - bc.vmConfig.ParallelThreshold = ParallelNum() - } log.Info("Parallel execution mode is enabled", "Parallel Num", ParallelNum(), - "CPUNum", runtime.GOMAXPROCS(0), "unorderedMerge", processor.unorderedMerge, - "parallel threshold", bc.vmConfig.ParallelThreshold) + "CPUNum", runtime.GOMAXPROCS(0), "unorderedMerge", processor.unorderedMerge) return processor } @@ -57,19 +55,19 @@ type PEVMTxResult struct { type PEVMTxRequest struct { txIndex int - baseStateDB *state.StateDB + baseStateDB state.StateDBer tx *types.Transaction gasLimit uint64 msg *Message block *types.Block vmConfig vm.Config - usedGas *uint64 + usedGas *atomic.Uint64 useDAG bool value int // only for unit test } // resetState clear slot state for each block. -func (p *PEVMProcessor) resetState(txNum int, statedb *state.StateDB) { +func (p *PEVMProcessor) resetState(txNum int, statedb state.StateDBer) { statedb.PrepareForParallel() p.allTxReqs = make([]*PEVMTxRequest, txNum) } @@ -91,7 +89,7 @@ func (p *PEVMProcessor) hasConflict(txResult *PEVMTxResult) error { } // executeInSlot do tx execution with thread local slot. -func (p *PEVMProcessor) executeInSlot(maindb *state.StateDB, txReq *PEVMTxRequest) *PEVMTxResult { +func (p *PEVMProcessor) executeInSlot(maindb state.StateDBer, txReq *PEVMTxRequest) *PEVMTxResult { slotDB := state.NewUncommittedDB(maindb) blockContext := NewEVMBlockContext(txReq.block.Header(), p.bc, nil, p.config, slotDB) // can share blockContext within a block for efficiency txContext := NewEVMTxContext(txReq.msg) @@ -126,9 +124,9 @@ func (p *PEVMProcessor) executeInSlot(maindb *state.StateDB, txReq *PEVMTxReques // 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 *PEVMProcessor) toConfirmTxIndexResult(txResult *PEVMTxResult) error { +func (p *PEVMProcessor) toConfirmTxIndexResult(txResult *PEVMTxResult, enableParallelMerge bool) error { txReq := txResult.txReq - if !p.unorderedMerge { + if !txReq.useDAG || (!p.unorderedMerge && !enableParallelMerge) { // If we do not use a DAG, then we need to check for conflicts to ensure correct execution. // When we perform an unordered merge, we cannot conduct conflict checks // and can only choose to trust that the DAG is correct and that conflicts do not exist. @@ -154,8 +152,8 @@ func (p *PEVMProcessor) toConfirmTxIndexResult(txResult *PEVMTxResult) error { } // wait until the next Tx is executed and its result is merged to the main stateDB -func (p *PEVMProcessor) confirmTxResult(statedb *state.StateDB, gp *GasPool, result *PEVMTxResult) error { - checkErr := p.toConfirmTxIndexResult(result) +func (p *PEVMProcessor) confirmTxResult(statedb state.StateDBer, gp *ParallelGasPool, result *PEVMTxResult, enableParallelMerge bool) error { + checkErr := p.toConfirmTxIndexResult(result, enableParallelMerge) // ok, the tx result is valid and can be merged if checkErr != nil { return checkErr @@ -167,7 +165,6 @@ func (p *PEVMProcessor) confirmTxResult(statedb *state.StateDB, gp *GasPool, res return fmt.Errorf("gas limit reached") } - var root []byte header := result.txReq.block.Header() isByzantium := p.config.IsByzantium(header.Number) @@ -178,48 +175,51 @@ func (p *PEVMProcessor) confirmTxResult(statedb *state.StateDB, gp *GasPool, res return err } - delayGasFee := result.result.delayFees - // add delayed gas fee - if delayGasFee != nil { - if delayGasFee.TipFee != nil { - statedb.AddBalance(delayGasFee.Coinbase, delayGasFee.TipFee) + if !enableParallelMerge { + delayGasFee := result.result.delayFees + // add delayed gas fee + if delayGasFee != nil { + if delayGasFee.TipFee != nil { + statedb.AddBalance(delayGasFee.Coinbase, delayGasFee.TipFee) + } + if delayGasFee.BaseFee != nil { + statedb.AddBalance(params.OptimismBaseFeeRecipient, delayGasFee.BaseFee) + } + if delayGasFee.L1Fee != nil { + statedb.AddBalance(params.OptimismL1FeeRecipient, delayGasFee.L1Fee) + } } - if delayGasFee.BaseFee != nil { - statedb.AddBalance(params.OptimismBaseFeeRecipient, delayGasFee.BaseFee) - } - if delayGasFee.L1Fee != nil { - statedb.AddBalance(params.OptimismL1FeeRecipient, delayGasFee.L1Fee) - } - } - result.slotDB.Finalise(isByzantium || isEIP158) + var root []byte + result.slotDB.Finalise(isByzantium || isEIP158) - // Do IntermediateRoot after mergeSlotDB. - if !isByzantium { - root = statedb.IntermediateRoot(isEIP158).Bytes() + // Do IntermediateRoot after mergeSlotDB. + if !isByzantium { + root = statedb.IntermediateRoot(isEIP158).Bytes() + } + result.receipt.PostState = root } - result.receipt.PostState = root p.receipts[result.txReq.txIndex] = result.receipt p.commonTxs[result.txReq.txIndex] = result.txReq.tx return nil } // Process implements BEP-130 Parallel Transaction Execution -func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { +func (p *PEVMProcessor) Process(block *types.Block, statedb state.StateDBer, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { var ( - usedGas = new(uint64) + usedGas = atomic.Uint64{} header = block.Header() - gp = new(GasPool).AddGas(block.GasLimit()) + gp = new(ParallelGasPool).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) + misc.ApplyDAOHardFork2(statedb) } if p.config.PreContractForkBlock != nil && p.config.PreContractForkBlock.Cmp(block.Number()) == 0 { - misc.ApplyPreContractHardFork(statedb) + misc.ApplyPreContractHardFork2(statedb) } - misc.EnsureCreate2Deployer(p.config, block.Time(), statedb) + misc.EnsureCreate2Deployer(p.config, block.Time(), statedb.(vm.StateDB)) allTxs := block.Transactions() p.resetState(len(allTxs), statedb) @@ -227,12 +227,12 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg 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) + vmenv = vm.NewEVM(blockContext, vm.TxContext{}, statedb.(vm.StateDB), p.config, cfg) signer = types.MakeSigner(p.bc.chainConfig, block.Number(), block.Time()) ) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { - ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + ProcessBeaconBlockRoot2(*beaconRoot, vmenv, statedb) } statedb.MarkFullProcessed() txDAG := cfg.TxDAG @@ -252,7 +252,7 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg gasLimit: block.GasLimit(), // gp.Gas(). block: block, vmConfig: cfg, - usedGas: usedGas, + usedGas: &usedGas, useDAG: txDAG != nil, } p.allTxReqs[i] = txReq @@ -268,6 +268,9 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg log.Debug("txLevels size", "txLevels size", len(txLevels)) parallelTxLevelsSizeMeter.Update(int64(len(txLevels))) buildLevelsDuration := time.Since(start) + + _, ok := statedb.(*state.ParallelStateDB) + enableParallelMerge := p.parallelMerge && ok && txDAG != nil var executeDurations, confirmDurations int64 = 0, 0 err, txIndex := txLevels.Run(func(pr *PEVMTxRequest) (res *PEVMTxResult) { defer func(t0 time.Time) { @@ -283,8 +286,14 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg atomic.AddInt64(&confirmDurations, time.Since(t0).Nanoseconds()) }(time.Now()) log.Debug("pevm confirm", "txIndex", pr.txReq.txIndex) - return p.confirmTxResult(statedb, gp, pr) - }, p.unorderedMerge) + return p.confirmTxResult(statedb, gp, pr, enableParallelMerge) + }, func(levels TxLevels, cq *confirmQueue) (err error) { + defer func(t0 time.Time) { + atomic.AddInt64(&confirmDurations, time.Since(t0).Nanoseconds()) + }(time.Now()) + log.Debug("after parallel confirm") + return p.afterParallelConfirm(statedb, block.Header(), levels, cq) + }, p.unorderedMerge, enableParallelMerge) parallelRunDuration := time.Since(start) - buildLevelsDuration if err != nil { tx := allTxs[txIndex] @@ -301,7 +310,7 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } pevmBuildLevelsTimer.Update(buildLevelsDuration) pevmRunTimer.Update(parallelRunDuration) - log.Info("ProcessParallel tx all done", "block", header.Number, "usedGas", *usedGas, + log.Info("ProcessParallel tx all done", "block", header.Number, "usedGas", usedGas.Load(), "parallelNum", ParallelNum(), "buildLevelsDuration", buildLevelsDuration, "parallelRunDuration", parallelRunDuration, @@ -339,7 +348,78 @@ func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg receipt.CumulativeGasUsed = cumulativeGasUsed allLogs = append(allLogs, receipt.Logs...) } - return p.receipts, allLogs, *usedGas, nil + return p.receipts, allLogs, usedGas.Load(), nil +} + +func (p *PEVMProcessor) afterParallelConfirm(statedb state.StateDBer, header *types.Header, levels TxLevels, cq *confirmQueue) error { + txCount := levels.txCount() + tipChan := make(chan *state.DelayedGasFee, txCount) + baseChan := make(chan *state.DelayedGasFee, txCount) + l1Chan := make(chan *state.DelayedGasFee, txCount) + var wg sync.WaitGroup + wg.Add(3) + go func() { + defer wg.Done() + for { + gasFee, ok := <-tipChan + if !ok { + return + } + statedb.AddBalance(gasFee.Coinbase, gasFee.TipFee) + } + }() + go func() { + defer wg.Done() + for { + gasFee, ok := <-baseChan + if !ok { + return + } + statedb.AddBalance(params.OptimismBaseFeeRecipient, gasFee.BaseFee) + } + }() + go func() { + defer wg.Done() + for { + gasFee, ok := <-l1Chan + if !ok { + return + } + statedb.AddBalance(params.OptimismL1FeeRecipient, gasFee.L1Fee) + } + }() + for _, txs := range levels { + for _, tx := range txs { + toConfirm := cq.queue[tx.txIndex] + result := toConfirm.result + delayGasFee := result.result.delayFees + if delayGasFee != nil { + if delayGasFee.TipFee != nil { + tipChan <- delayGasFee + } + if delayGasFee.BaseFee != nil { + baseChan <- delayGasFee + } + if delayGasFee.L1Fee != nil { + l1Chan <- delayGasFee + } + } + } + } + close(tipChan) + close(baseChan) + close(l1Chan) + wg.Wait() + + isByzantium := p.config.IsByzantium(header.Number) + if !isByzantium { + panic("afterParallelConfirm not support before Byzantium block") + } + isEIP158 := p.config.IsEIP158(header.Number) + + statedb.Finalise(isByzantium || isEIP158) + + return nil } func buildMessage(txReq *PEVMTxRequest, signer types.Signer, header *types.Header) error { @@ -378,10 +458,10 @@ func pevmApplyTransactionStageExecution(msg *Message, gp *GasPool, statedb *stat return evm, result, err } -func pevmApplyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg Message, config *params.ChainConfig, statedb *state.UncommittedDB, block *types.Block, tx *types.Transaction, usedGas *uint64, nonce *uint64) (*types.Receipt, error) { - *usedGas += result.UsedGas +func pevmApplyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg Message, config *params.ChainConfig, statedb *state.UncommittedDB, block *types.Block, tx *types.Transaction, usedGas *atomic.Uint64, nonce *uint64) (*types.Receipt, error) { + usedGas.Add(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} + receipt := &types.Receipt{Type: tx.Type(), PostState: nil, CumulativeGasUsed: usedGas.Load()} if result.Failed() { receipt.Status = types.ReceiptStatusFailed } else { diff --git a/core/state/access_list.go b/core/state/access_list.go index 942829787a..dcdc13d631 100644 --- a/core/state/access_list.go +++ b/core/state/access_list.go @@ -17,6 +17,8 @@ package state import ( + "sync" + "github.com/ethereum/go-ethereum/common" ) @@ -167,3 +169,96 @@ func (dest *accessList) Append(src *accessList) *accessList { } return dest } + +type parallelAccessList struct { + addresses *sync.Map + slots []*sync.Map +} + +func (p *parallelAccessList) ContainsAddress(address common.Address) bool { + _, ok := p.addresses.Load(address) + return ok +} + +func (p *parallelAccessList) Contains(address common.Address, slot common.Hash) (bool, bool) { + idx, ok := p.addresses.Load(address) + if !ok { + // no such address (and hence zero slots) + return false, false + } + if idx == -1 { + // address yes, but no slots + return true, false + } + _, slotPresent := p.slots[idx.(int)].Load(slot) + return true, slotPresent +} + +func (p *parallelAccessList) AddAddress(address common.Address) bool { + if _, present := p.addresses.Load(address); present { + return false + } + p.addresses.Store(address, -1) + return true +} + +func (p *parallelAccessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { + idx, addrPresent := p.addresses.Load(address) + if !addrPresent || idx == -1 { + // Address not present, or addr present but no slots there + p.addresses.Store(address, len(p.slots)) + slotmap := sync.Map{} + slotmap.Store(slot, struct{}{}) + p.slots = append(p.slots, &slotmap) + return !addrPresent, true + } + // There is already an (address,slot) mapping + slotmap := p.slots[idx.(int)] + if _, ok := slotmap.Load(slot); !ok { + slotmap.Store(slot, struct{}{}) + // Journal add slot change + return false, true + } + // No changes required + return false, false +} + +func (p *parallelAccessList) Copy() *accessList { + cp := newAccessList() + p.addresses.Range(func(key, value interface{}) bool { + cp.addresses[key.(common.Address)] = value.(int) + return true + }) + cp.slots = make([]map[common.Hash]struct{}, len(p.slots)) + for i, slotMap := range p.slots { + newSlotmap := make(map[common.Hash]struct{}) + slotMap.Range(func(key, value interface{}) bool { + newSlotmap[key.(common.Hash)] = struct{}{} + return true + }) + cp.slots[i] = newSlotmap + } + return cp +} + +func (p *parallelAccessList) setByAccessList(list *accessList) { + p.addresses = &sync.Map{} + for key, value := range list.addresses { + p.addresses.Store(key, value) + } + p.slots = make([]*sync.Map, len(list.slots)) + for idx, slot := range list.slots { + slotmap := sync.Map{} + for key := range slot { + slotmap.Store(key, struct{}{}) + } + p.slots[idx] = &slotmap + } +} + +// newAccessList creates a new accessList. +func newParallelAccessList() *parallelAccessList { + return ¶llelAccessList{ + addresses: &sync.Map{}, + } +} diff --git a/core/state/interface.go b/core/state/interface.go index 3e808aa82e..2239aababf 100644 --- a/core/state/interface.go +++ b/core/state/interface.go @@ -17,7 +17,11 @@ package state import ( + "sync" + "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/holiman/uint256" ) @@ -76,6 +80,49 @@ type StateDBer interface { AddLog(*types.Log) AddPreimage(common.Hash, []byte) - - GetStateObjectFromUnconfirmedDB(addr common.Address) (*stateObject, bool) + GetPreimage(common.Hash) ([]byte, bool) + StopPrefetcher() + StartPrefetcher(namespace string) + SetExpectedStateRoot(root common.Hash) + ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (types.TxDAG, error) + ResolveStats() map[int]*types.ExeStat + IntermediateRoot(deleteEmptyObjects bool) common.Hash + Error() error + Timers() *Timers + Preimages() map[common.Hash][]byte + Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) + SetBalance(addr common.Address, amount *uint256.Int) + PrepareForParallel() + Finalise(deleteEmptyObjects bool) + MarkFullProcessed() + AccessListCopy() *accessList + SetTxContext(hash common.Hash, index int) + SetAccessList(list *accessList) + getDeletedStateObject(addr common.Address) *stateObject + appendJournal(journalEntry journalEntry) + addJournalDirty(address common.Address) + getPrefetcher() *triePrefetcher + getDB() Database + getOriginalRoot() common.Hash + getTrie() Trie + getStateObjectDestructLock() *sync.RWMutex + getStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) + getSnap() snapshot.Snapshot + timeAddSnapshotStorageReads(du time.Duration) + setError(err error) + getTrieParallelLock() *sync.Mutex + timeAddStorageReads(du time.Duration) + RecordWrite(key types.RWKey, value interface{}) + timeAddStorageUpdates(du time.Duration) + countAddStorageDeleted(diff int) + countAddStorageUpdated(diff int) + getStorageMux() *sync.Mutex + getStorages(hash common.Hash) map[common.Hash][]byte + setStorages(hash common.Hash, storage map[common.Hash][]byte) + getStoragesOrigin(address common.Address) map[common.Hash][]byte + setStoragesOrigin(address common.Address, origin map[common.Hash][]byte) + timeAddStorageHashes(du time.Duration) + timeAddStorageCommits(du time.Duration) + getOrNewStateObject(addr common.Address) *stateObject + prefetchAccount(address common.Address) } diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go index aca12b1f2d..bff3eadad6 100644 --- a/core/state/pevm_statedb.go +++ b/core/state/pevm_statedb.go @@ -4,11 +4,24 @@ import ( "bytes" "errors" "fmt" + "runtime" + "sync" + "sync/atomic" + "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/gopool" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" "github.com/holiman/uint256" ) @@ -56,16 +69,19 @@ type UncommittedDB struct { journal ujournal - maindb *StateDB + maindb StateDBer + isMainDBIsParallelDB bool } -func NewUncommittedDB(maindb *StateDB) *UncommittedDB { +func NewUncommittedDB(maindb StateDBer) *UncommittedDB { + _, ok := maindb.(*ParallelStateDB) return &UncommittedDB{ - accessList: newAccessList(), - transientStorage: newTransientStorage(), - maindb: maindb, - reads: make(reads), - cache: make(writes), + accessList: newAccessList(), + transientStorage: newTransientStorage(), + maindb: maindb, + reads: make(reads), + cache: make(writes), + isMainDBIsParallelDB: ok, } } @@ -141,7 +157,7 @@ func (pst *UncommittedDB) Prepare(rules params.Rules, sender, coinbase common.Ad al.AddAddress(coinbase) } } else { - pst.accessList = pst.maindb.accessList.Copy() + pst.accessList = pst.maindb.AccessListCopy() } // Reset transient storage at the beginning of transaction execution pst.transientStorage = newTransientStorage() @@ -398,7 +414,7 @@ func (pst *UncommittedDB) recordPreimageOnce(hash common.Hash) { // load all preimages from maindb pst.preimages = make(map[common.Hash][]byte) pst.preimagesReads = make(map[common.Hash][]byte) - for h, pi := range pst.maindb.preimages { + for h, pi := range pst.maindb.Preimages() { pst.preimagesReads[h], pst.preimages[h] = pi, pi } } @@ -406,7 +422,7 @@ func (pst *UncommittedDB) recordPreimageOnce(hash common.Hash) { return } // get and record the preimage state from the maindb - preimageFromMaindb, ok := pst.maindb.preimages[hash] + preimageFromMaindb, ok := pst.maindb.GetPreimage(hash) if ok { pst.preimagesReads[hash] = preimageFromMaindb } else { @@ -425,16 +441,20 @@ func (pst *UncommittedDB) conflictsToMaindb() error { // 2. check accesslist reads conflict (@TODO confirm: whether the accesslist should be checked or not) // 3. check logs conflict (check the txIndex) // 4. check object conflict - if err := pst.hasLogConflict(pst.maindb); err != nil { + stateDB, ok := pst.maindb.(*StateDB) + if !ok { + return errors.New("should not to check conflicts when the Maindb is a StateDB") + } + if err := pst.hasLogConflict(stateDB); err != nil { return err } - if err := pst.hasRefundConflict(pst.maindb); err != nil { + if err := pst.hasRefundConflict(stateDB); err != nil { return err } - if err := pst.hasPreimageConflict(pst.maindb); err != nil { + if err := pst.hasPreimageConflict(stateDB); err != nil { return err } - return pst.reads.conflictsTo(pst.maindb) + return pst.reads.conflictsTo(stateDB) } func (pst *UncommittedDB) hasLogConflict(maindb *StateDB) error { @@ -497,45 +517,49 @@ func (pst *UncommittedDB) Merge(deleteEmptyObjects bool) error { pst.maindb.SetTxContext(pst.txHash, pst.txIndex) // 1. merge preimages writes for hash, preimage := range pst.preimages { - pst.maindb.preimages[hash] = preimage + pst.maindb.AddPreimage(hash, preimage) } // 2. merge accesslist writes // we need to merge accessList before Berlin hardfork if !pst.isBerlin { for addr, slot := range pst.accessList.addresses { - pst.maindb.accessList.AddAddress(addr) + pst.maindb.AddAddressToAccessList(addr) if slot >= 0 { slots := pst.accessList.slots[slot] for hash := range slots { - pst.maindb.accessList.AddSlot(addr, hash) + pst.maindb.AddSlotToAccessList(addr, hash) } } } } else { // after Berlin hardfork, all the accessList should be reset before a transaction was executed - pst.maindb.accessList = pst.accessList + pst.maindb.SetAccessList(pst.accessList) } // 3. merge logs writes for _, st := range pst.cache { - st.merge(pst.maindb) + st.merge(pst.maindb, pst.isMainDBIsParallelDB) } // 4. merge object states for _, log := range pst.logs { - pst.maindb.AddLog(log) + if pst.isMainDBIsParallelDB { + pst.maindb.(*ParallelStateDB).AddLogWithTx(log, pst.txHash, uint(pst.txIndex)) + } else { + pst.maindb.AddLog(log) + } } // 5. merge refund if pst.refund != 0 { pst.maindb.AddRefund(pst.refund) } - // clean empty objects if needed - for _, obj := range pst.cache { - if obj.selfDestruct || (deleteEmptyObjects && obj.empty(pst)) { - obj.deleted = true - } - // we don't need to do obj.finalize() here, it will be done in the maindb.Finalize() - // just mark the object as deleted - obj.created = false - } + //// clean empty objects if needed + //for _, obj := range pst.cache { + // if obj.selfDestruct || (deleteEmptyObjects && obj.empty(pst)) { + // obj.deleted = true + // } + // // we don't need to do obj.finalize() here, it will be done in the maindb.Finalize() + // // just mark the object as deleted + // obj.created = false + //} return nil } @@ -561,7 +585,7 @@ func (pst *UncommittedDB) Finalise(deleteEmptyObjects bool) { // so we need: // 1. the uncommittedDB's cache is not thread safe // 2. the maindb's cache is thread safe -func (pst *UncommittedDB) getDeletedObject(addr common.Address, maindb *StateDB) (o *state) { +func (pst *UncommittedDB) getDeletedObject(addr common.Address, maindb StateDBer) (o *state) { if pst.cache[addr] != nil { return pst.cache[addr] } @@ -630,7 +654,7 @@ func (pst *UncommittedDB) getObjectWithCode(addr common.Address) *state { } // getDeletedObjectWithCode return an object with code, and load the code from maindb if it is not loaded. -func (pst *UncommittedDB) getDeletedObjectWithCode(addr common.Address, maindb *StateDB) (o *state) { +func (pst *UncommittedDB) getDeletedObjectWithCode(addr common.Address, maindb StateDBer) (o *state) { o = pst.getDeletedObject(addr, maindb) if o == nil { pst.reads.recordCodeOnce(addr, common.Hash{}, nil) @@ -657,7 +681,7 @@ func (pst *UncommittedDB) getDeletedObjectWithCode(addr common.Address, maindb * } // getDeletedObjectWithState return an object with state, and load the state from maindb if it is not loaded. -func (pst *UncommittedDB) getDeletedObjectWithState(addr common.Address, maindb *StateDB, hash common.Hash) (o *state) { +func (pst *UncommittedDB) getDeletedObjectWithState(addr common.Address, maindb StateDBer, hash common.Hash) (o *state) { o = pst.getDeletedObject(addr, maindb) if o == nil { pst.reads.recordKVOnce(addr, hash, common.Hash{}) @@ -769,31 +793,45 @@ func (s state) conflicts(maindb *StateDB) error { return nil } -func (s state) merge(maindb *StateDB) { +func (s state) merge(maindb StateDBer, prefetch bool) { // 1. merge the balance // 2. merge the nonce // 3. merge the code // 4. merge the state if s.modified&ModifySelfDestruct != 0 { maindb.SelfDestruct(s.addr) + if prefetch { + maindb.prefetchAccount(s.addr) + } return } + hasModified := false obj := maindb.getOrNewStateObject(s.addr) if s.modified&ModifyBalance != 0 { obj.SetBalance(s.balance) + hasModified = true } if s.modified&ModifyNonce != 0 { obj.SetNonce(s.nonce) + hasModified = true } if s.modified&ModifyCode != 0 { obj.SetCode(common.BytesToHash(s.codeHash), s.code) + hasModified = true } if s.modified&ModifyState != 0 { for key, val := range s.state { obj.SetState(key, val) + if prefetch { + obj.prefetchStorage(key, val) + } } + hasModified = true //TODO: should we reset all kv pairs if the s.state == nil ? } + if hasModified && prefetch { + maindb.prefetchAccount(obj.address) + } } func (s *state) empty(pst *UncommittedDB) bool { @@ -945,6 +983,1457 @@ func (wst writes) selfDestruct(addr common.Address) { func (wst writes) merge(maindb *StateDB) { for _, st := range wst { - st.merge(maindb) + st.merge(maindb, false) + } +} + +type ParallelStateDB struct { + db Database + trie Trie + prefetcher *triePrefetcher + noTrie bool + hasher crypto.KeccakState + hasherLock sync.Mutex + snaps *snapshot.Tree // Nil if snapshot is not available + snap snapshot.Snapshot // Nil if snapshot is not available + + 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. + pendingDirtyLock sync.Mutex + + // originalRoot is the pre-state root, before any changes were made. + // It will be updated when the Commit is called. + originalRoot common.Hash + expectedRoot common.Hash // The state root in the block header + stateRoot common.Hash // The calculation result of IntermediateRoot + + fullProcessed bool + + // These maps hold the state changes (including the corresponding + // original value) that occurred in this **block**. + AccountMux sync.Mutex // Mutex for accounts access + StorageMux sync.Mutex // Mutex for storages access + accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding + storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format + accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding + storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format + + // This map holds 'live' objects, which will get modified while processing + // a state transition. + stateObjects *StateObjectSyncMap + 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 + journalDirty sync.Map // State objects modified in the current execution + stateObjectsDestruct sync.Map // State objects destructed in the block along with its previous value + stateObjectsDestructDirty sync.Map + + // DB error. + // State objects are used by the consensus core and VM which are + // unable to deal with database-level errors. Any error that occurs + // during a database read is memoized here and will eventually be + // returned by StateDB.Commit. Notably, this error is also shared + // by all cached state objects in case the database failure occurs + // when accessing state of accounts. + dbErr error + + // The refund counter, also used by state transitioning. + refund atomic.Uint64 + + logSize atomic.Int32 + + // Preimages occurred seen by VM in the scope of block. + preimages sync.Map + + // Per-transaction access list + accessList *parallelAccessList + + // Measurements gathered during execution for debugging purposes + AccountReads time.Duration + AccountHashes time.Duration + AccountUpdates time.Duration + AccountCommits time.Duration + StorageReads time.Duration + StorageHashes time.Duration + StorageUpdates time.Duration + StorageCommits time.Duration + SnapshotAccountReads time.Duration + SnapshotStorageReads time.Duration + SnapshotCommits time.Duration + TrieDBCommits time.Duration + TrieCommits time.Duration + CodeCommits time.Duration + TxDAGGenerate time.Duration + + AccountUpdated int + StorageUpdated int + AccountDeleted int + StorageDeleted int + + // Testing hooks + onCommit func(states *triestate.Set) // Hook invoked when commit is performed +} + +// NewParallel creates a new parallel statedb +func NewParallel(root common.Hash, db Database, snaps *snapshot.Tree) (*ParallelStateDB, error) { + tr, err := db.OpenTrie(root) + if err != nil { + return nil, err + } + sdb := &ParallelStateDB{ + db: db, + trie: tr, + originalRoot: root, + snaps: snaps, + accessList: newParallelAccessList(), + hasher: crypto.NewKeccakState(), + 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), + stateObjectsPending: make(map[common.Address]struct{}), + stateObjectsDirty: make(map[common.Address]struct{}), + stateObjects: &StateObjectSyncMap{}, + } + if sdb.snaps != nil { + sdb.snap = sdb.snaps.Snapshot(root) + } + _, sdb.noTrie = tr.(*trie.EmptyTrie) + return sdb, nil +} + +func (p *ParallelStateDB) getBaseStateDB() *StateDB { + panic("ParallelStateDB not support get base stateDB") +} + +func (p *ParallelStateDB) getStateObject(address common.Address) *stateObject { + if obj := p.getDeletedStateObject(address); obj != nil && !obj.deleted { + return obj + } + return nil +} + +func (p *ParallelStateDB) storeStateObj(address common.Address, object *stateObject) { + p.stateObjects.StoreStateObject(address, object) +} + +func (p *ParallelStateDB) CreateAccount(address 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(), + newObj, prev := p.createObject(address) + if prev != nil { + newObj.setBalance(prev.Balance()) + } +} + +func (p *ParallelStateDB) SubBalance(addr common.Address, amount *uint256.Int) { + stateObject := p.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SubBalance(amount) + return + } +} + +func (p *ParallelStateDB) AddBalance(addr common.Address, amount *uint256.Int) { + stateObject := p.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.AddBalance(amount) + return + } +} + +func (p *ParallelStateDB) GetBalance(addr common.Address) *uint256.Int { + stateObject := p.getStateObject(addr) + if stateObject != nil { + return stateObject.Balance() + } + return common.U2560 +} + +func (p *ParallelStateDB) GetNonce(addr common.Address) uint64 { + stateObject := p.getStateObject(addr) + if stateObject != nil { + return stateObject.Nonce() + } + return 0 +} + +func (p *ParallelStateDB) SetNonce(addr common.Address, nonce uint64) { + stateObject := p.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetNonce(nonce) + } +} + +func (p *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { + stateObject := p.getStateObject(addr) + if stateObject != nil { + return common.BytesToHash(stateObject.CodeHash()) + } + return common.Hash{} +} + +func (p *ParallelStateDB) GetCode(addr common.Address) []byte { + stateObject := p.getStateObject(addr) + if stateObject != nil { + return stateObject.Code() + } + return nil +} + +func (p *ParallelStateDB) SetCode(addr common.Address, code []byte) { + stateObject := p.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetCode(crypto.Keccak256Hash(code), code) + } +} + +func (p *ParallelStateDB) GetCodeSize(addr common.Address) int { + stateObject := p.getStateObject(addr) + if stateObject != nil { + return stateObject.CodeSize() + } + return 0 +} + +func (p *ParallelStateDB) AddRefund(gas uint64) { + p.refund.Add(gas) +} + +func (p *ParallelStateDB) SubRefund(gas uint64) { + for { + refund := p.refund.Load() + if gas > refund { + panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, p.refund)) + } + swapped := p.refund.CompareAndSwap(refund, refund-gas) + if swapped { + return + } + } +} + +func (p *ParallelStateDB) GetRefund() uint64 { + return p.refund.Load() +} + +func (p *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + stateObject := p.getStateObject(addr) + if stateObject != nil { + return stateObject.GetCommittedState(hash) + } + return common.Hash{} +} + +func (p *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + stateObject := p.getStateObject(addr) + if stateObject != nil { + return stateObject.GetState(hash) + } + return common.Hash{} +} + +func (p *ParallelStateDB) SetState(addr common.Address, key common.Hash, value common.Hash) { + stateObject := p.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetState(key, value) + } +} + +func (p *ParallelStateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash { + panic("ParallelStateDB not support get TransientState") +} + +func (p *ParallelStateDB) SetTransientState(addr common.Address, key, value common.Hash) { + panic("ParallelStateDB not support set TransientState") +} + +func (p *ParallelStateDB) SelfDestruct(addr common.Address) { + stateObject := p.getStateObject(addr) + if stateObject == nil { + return + } + p.journalDirty.Store(addr, struct{}{}) + stateObject.markSelfdestructed() + stateObject.setBalance(new(uint256.Int)) +} + +func (p *ParallelStateDB) HasSelfDestructed(addr common.Address) bool { + stateObject := p.getStateObject(addr) + if stateObject != nil { + return stateObject.selfDestructed + } + return false +} + +func (p *ParallelStateDB) Selfdestruct6780(addr common.Address) { + stateObject := p.getStateObject(addr) + if stateObject == nil { + return + } + if stateObject.created { + p.SelfDestruct(addr) + } +} + +func (p *ParallelStateDB) Exist(addr common.Address) bool { + return p.getStateObject(addr) != nil +} + +func (p *ParallelStateDB) Empty(addr common.Address) bool { + so := p.getStateObject(addr) + return so == nil || so.empty() +} + +func (p *ParallelStateDB) AddressInAccessList(addr common.Address) bool { + return p.accessList.ContainsAddress(addr) +} + +func (p *ParallelStateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { + return p.accessList.Contains(addr, slot) +} + +func (p *ParallelStateDB) AddAddressToAccessList(addr common.Address) { + p.accessList.AddAddress(addr) +} + +func (p *ParallelStateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + p.accessList.AddSlot(addr, slot) +} + +func (p *ParallelStateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) { + //do nothing because not support access list and transientStorage +} + +func (p *ParallelStateDB) RevertToSnapshot(revid int) { + panic("ParallelStateDB not support revert to snapshot") +} + +func (p *ParallelStateDB) Snapshot() int { + return 0 +} + +func (p *ParallelStateDB) AddLog(log *types.Log) { + panic("ParallelStateDB not support add log,use AddLogWithTx") +} + +func (p *ParallelStateDB) AddLogWithTx(log *types.Log, txhash common.Hash, txIndex uint) { + log.TxHash = txhash + log.TxIndex = txIndex + log.Index = uint(p.logSize.Load()) + //logs, loaded := p.logs.LoadOrStore(txhash, []*types.Log{log}) + //if loaded { + // logs = append(logs.([]*types.Log), log) + // p.logs.Store(txhash, logs) + //} + p.logSize.Add(1) +} + +func (p *ParallelStateDB) AddPreimage(hash common.Hash, preimage []byte) { + if _, ok := p.preimages.Load(hash); !ok { + pi := make([]byte, len(preimage)) + copy(pi, preimage) + p.preimages.Store(hash, pi) + } +} + +func (p *ParallelStateDB) TxIndex() int { + // because in parallel env, txIndex is always 0 + return 0 +} + +func (p *ParallelStateDB) BeforeTxTransition() { + //do nothing +} + +func (p *ParallelStateDB) FinaliseRWSet() error { + //do nothing + return nil +} + +func (p *ParallelStateDB) getDeletedStateObject(addr common.Address) *stateObject { + for { + // Prefer live objects if any is available + if obj, _ := p.getStateObjectFromStateObjects(addr); obj != nil { + return obj + } + data, ok := p.getStateObjectFromSnapshotOrTrie(addr) + if !ok { + return nil + } + // Insert into the live set + obj := newObject(p, true, addr, data) + setSuccess := p.setStateObjectIfEmpty(obj) + if setSuccess { + return obj + } else { + continue + } + } +} + +func (p *ParallelStateDB) getStateObjectFromStateObjects(addr common.Address) (*stateObject, bool) { + return p.loadStateObj(addr) +} + +func (p *ParallelStateDB) loadStateObj(addr common.Address) (*stateObject, bool) { + ret, ok := p.stateObjects.LoadStateObject(addr) + return ret, ok +} + +func (p *ParallelStateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *types.StateAccount, ok bool) { + // If no live objects are available, attempt to use snapshots + if p.snap != nil { + start := time.Now() + // the s.hasher is not thread-safe, let's use a mutex to protect it + p.hasherLock.Lock() + hash := crypto.HashData(p.hasher, addr.Bytes()) + p.hasherLock.Unlock() + acc, err := p.snap.Account(hash) + if metrics.EnabledExpensive { + p.SnapshotAccountReads += time.Since(start) + } + if err == nil { + if acc == nil { + return nil, false + } + + data = &types.StateAccount{ + Nonce: acc.Nonce, + Balance: acc.Balance, + CodeHash: acc.CodeHash, + Root: common.BytesToHash(acc.Root), + } + if len(data.CodeHash) == 0 { + data.CodeHash = types.EmptyCodeHash.Bytes() + } + if data.Root == (common.Hash{}) { + data.Root = types.EmptyRootHash + } + } + } + + // If snapshot unavailable or reading from it failed, load from the database + if data == nil { + p.trieParallelLock.Lock() + defer p.trieParallelLock.Unlock() + var trie Trie + trie = p.trie + + start := time.Now() + var err error + data, err = trie.GetAccount(addr) + if metrics.EnabledExpensive { + p.AccountReads += time.Since(start) + } + if err != nil { + p.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err)) + return nil, false + } + if data == nil { + return nil, false + } } + + return data, true +} + +func (p *ParallelStateDB) setError(err error) { + if p.dbErr == nil { + p.dbErr = err + } +} + +func (p *ParallelStateDB) setStateObject(object *stateObject) { + p.stateObjects.Store(object.address, object) +} + +func (p *ParallelStateDB) prefetchAccount(address common.Address) { + if p.prefetcher == nil { + return + } + p.trieParallelLock.Lock() + defer p.trieParallelLock.Unlock() + p.prefetcher.prefetch(common.Hash{}, p.originalRoot, common.Address{}, [][]byte{common.CopyBytes(address[:])}) +} + +func (p *ParallelStateDB) createObject(addr common.Address) (newobj *stateObject, prev *stateObject) { + prev = p.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! + newobj = newObject(p, true, addr, nil) + if prev == nil { + //p.journal.append(createObjectChange{account: &addr}) + p.journalDirty.Store(addr, struct{}{}) + } else { + // The original account should be marked as destructed and all cached + // 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. + p.stateObjectDestructLock.Lock() + _, prevdestruct := p.getStateObjectsDestruct(prev.address) + if !prevdestruct { + p.setStateObjectsDestruct(prev.address, prev.origin) + } + p.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. + //prevAccount, ok := p.accountsOrigin.Load(prev.address) + //p.journal.append(resetObjectChange{ + // account: &addr, + // prev: prev, + // prevdestruct: prevdestruct, + // prevAccount: s.accounts[prev.addrHash], + // prevStorage: s.storages[prev.addrHash], + // prevAccountOriginExist: ok, + // prevAccountOrigin: prevAccount, + // prevStorageOrigin: s.storagesOrigin[prev.address], + //}) + p.journalDirty.Store(addr, struct{}{}) + p.AccountMux.Lock() + delete(p.accounts, prev.addrHash) + delete(p.accountsOrigin, prev.address) + p.AccountMux.Unlock() + p.StorageMux.Lock() + delete(p.storages, prev.addrHash) + delete(p.storagesOrigin, prev.address) + p.StorageMux.Unlock() + } + + newobj.created = true + p.setStateObject(newobj) + if prev != nil && !prev.deleted { + return newobj, prev + } + return newobj, nil +} + +func (p *ParallelStateDB) getStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) { + if acc, ok := p.stateObjectsDestructDirty.Load(addr); ok { + return acc.(*types.StateAccount), ok + } + acc, ok := p.stateObjectsDestruct.Load(addr) + if ok { + return acc.(*types.StateAccount), ok + } + return nil, ok +} + +func (p *ParallelStateDB) SetExpectedStateRoot(root common.Hash) { + p.expectedRoot = root +} + +func (p *ParallelStateDB) setStateObjectsDestruct(address common.Address, acc *types.StateAccount) { + p.stateObjectsDestructDirty.Store(address, acc) +} + +func (p *ParallelStateDB) getOrNewStateObject(addr common.Address) *stateObject { + stateObject := p.getStateObject(addr) + if stateObject == nil { + stateObject, _ = p.createObject(addr) + } + return stateObject +} + +func (p *ParallelStateDB) StopPrefetcher() { + if p.noTrie { + return + } + + if p.prefetcher != nil { + p.prefetcher.close() + p.prefetcher = nil + } +} + +func (p *ParallelStateDB) StartPrefetcher(namespace string) { + if p.noTrie { + return + } + + if p.prefetcher != nil { + p.prefetcher.close() + p.prefetcher = nil + } + if p.snap != nil { + p.prefetcher = newTriePrefetcher(p.db, p.originalRoot, namespace) + } +} + +func (p *ParallelStateDB) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (types.TxDAG, error) { + //do nothing + return nil, nil +} + +func (p *ParallelStateDB) ResolveStats() map[int]*types.ExeStat { + return nil +} + +func (p *ParallelStateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { + // Finalise all the dirty storage states and write them into the tries + p.Finalise(deleteEmptyObjects) + p.AccountsIntermediateRoot() + return p.StateIntermediateRoot() +} + +func (p *ParallelStateDB) Error() error { + return p.dbErr +} + +func (p *ParallelStateDB) Timers() *Timers { + return &Timers{ + AccountReads: p.AccountReads, + AccountHashes: p.AccountHashes, + AccountUpdates: p.AccountUpdates, + AccountCommits: p.AccountCommits, + StorageReads: p.StorageReads, + StorageHashes: p.StorageHashes, + StorageUpdates: p.StorageUpdates, + StorageCommits: p.StorageCommits, + SnapshotAccountReads: p.SnapshotAccountReads, + SnapshotStorageReads: p.SnapshotStorageReads, + SnapshotCommits: p.SnapshotCommits, + TrieDBCommits: p.TrieDBCommits, + TrieCommits: p.TrieCommits, + CodeCommits: p.CodeCommits, + TxDAGGenerate: p.TxDAGGenerate, + } +} + +func (p *ParallelStateDB) Preimages() map[common.Hash][]byte { + result := make(map[common.Hash][]byte) + p.preimages.Range(func(key, value interface{}) bool { + result[key.(common.Hash)] = value.([]byte) + return true + }) + return result +} + +func (p *ParallelStateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) { + // Short circuit in case any database failure occurred earlier. + if p.dbErr != nil { + p.StopPrefetcher() + return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", p.dbErr) + } + + var ( + accountTrieNodesUpdated int + accountTrieNodesDeleted int + // Storage Tree is updated concurrently, and statistics require mutex, + // which will affect performance, so it is discarded + //storageTrieNodesUpdated int + //storageTrieNodesDeleted int + + nodes = trienode.NewMergedNodeSet() + incomplete map[common.Address]struct{} + ) + + if !p.fullProcessed { + p.stateRoot = p.IntermediateRoot(deleteEmptyObjects) + } + + if metrics.EnabledExpensive { + defer func(start time.Time) { + p.AccountCommits += time.Since(start) + accountUpdatedMeter.Mark(int64(p.AccountUpdated)) + storageUpdatedMeter.Mark(int64(p.StorageUpdated)) + accountDeletedMeter.Mark(int64(p.AccountDeleted)) + storageDeletedMeter.Mark(int64(p.StorageDeleted)) + accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated)) + accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted)) + //storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated)) + //storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted)) + p.AccountUpdated, p.AccountDeleted = 0, 0 + p.StorageUpdated, p.StorageDeleted = 0, 0 + }(time.Now()) + } + + commitFuncs := []func() error{ + func() error { + if metrics.EnabledExpensive { + defer func(start time.Time) { p.TrieCommits += time.Since(start) }(time.Now()) + } + if p.fullProcessed { + if p.stateRoot = p.StateIntermediateRoot(); p.expectedRoot != p.stateRoot { + log.Error("Invalid merkle root", "remote", p.expectedRoot, "local", p.stateRoot) + return fmt.Errorf("invalid merkle root (remote: %x local: %x)", p.expectedRoot, p.stateRoot) + } + } + var err error + // Handle all state deletions first + incomplete, err = p.handleDestruction(nodes) + if err != nil { + return err + } + + tasks := make(chan func()) + type taskResult struct { + err error + nodeSet *trienode.NodeSet + } + taskResults := make(chan taskResult, len(p.stateObjectsDirty)) + tasksNum := 0 + finishCh := make(chan struct{}) + + threads := gopool.Threads(len(p.stateObjectsDirty)) + wg := sync.WaitGroup{} + for i := 0; i < threads; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case task := <-tasks: + task() + case <-finishCh: + return + } + } + }() + } + for addr := range p.stateObjectsDirty { + if obj, _ := p.getStateObjectFromStateObjects(addr); !obj.deleted { + tasks <- func() { + // Write any storage changes in the state object to its storage trie + if !p.noTrie { + if set, err := obj.commit(); err != nil { + taskResults <- taskResult{err, nil} + return + } else { + taskResults <- taskResult{nil, set} + } + } else { + taskResults <- taskResult{nil, nil} + } + } + tasksNum++ + } + } + + for i := 0; i < tasksNum; i++ { + res := <-taskResults + if res.err != nil { + close(finishCh) + return res.err + } + // Merge the dirty nodes of storage trie into global set. It is possible + // that the account was destructed and then resurrected in the same block. + // In this case, the node set is shared by both accounts. + if res.nodeSet != nil { + if err := nodes.Merge(res.nodeSet); err != nil { + return err + } + } + } + close(finishCh) + + if !p.noTrie { + var start time.Time + if metrics.EnabledExpensive { + start = time.Now() + } + root, set, err := p.trie.Commit(true) + if err != nil { + return err + } + // Merge the dirty nodes of account trie into global set + if set != nil { + if err := nodes.Merge(set); err != nil { + return err + } + accountTrieNodesUpdated, accountTrieNodesDeleted = set.Size() + } + if metrics.EnabledExpensive { + p.AccountCommits += time.Since(start) + } + + origin := p.originalRoot + if origin == (common.Hash{}) { + origin = types.EmptyRootHash + } + + if root != origin { + start := time.Now() + set := triestate.New(p.accountsOrigin, p.storagesOrigin, incomplete) + if err := p.db.TrieDB().Update(root, origin, block, nodes, set); err != nil { + return err + } + p.originalRoot = root + if metrics.EnabledExpensive { + p.TrieDBCommits += time.Since(start) + } + if p.onCommit != nil { + p.onCommit(set) + } + } + } + wg.Wait() + return nil + }, + func() error { + if metrics.EnabledExpensive { + defer func(start time.Time) { p.CodeCommits += time.Since(start) }(time.Now()) + } + codeWriter := p.db.DiskDB().NewBatch() + for addr := range p.stateObjectsDirty { + if obj, _ := p.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) + obj.dirtyCode = false + if codeWriter.ValueSize() > ethdb.IdealBatchSize { + if err := codeWriter.Write(); err != nil { + return err + } + codeWriter.Reset() + } + } + } + } + if codeWriter.ValueSize() > 0 { + if err := codeWriter.Write(); err != nil { + log.Crit("Failed to commit dirty codes", "error", err) + return err + } + } + return nil + }, + func() error { + // If snapshotting is enabled, update the snapshot tree with this new version + if p.snap != nil { + if metrics.EnabledExpensive { + defer func(start time.Time) { p.SnapshotCommits += time.Since(start) }(time.Now()) + } + // Only update if there's a state transition (skip empty Clique blocks) + if parent := p.snap.Root(); parent != p.expectedRoot { + err := p.snaps.Update(p.expectedRoot, parent, p.convertAccountSet(&p.stateObjectsDestruct), p.accounts, p.storages) + + if err != nil { + log.Warn("Failed to update snapshot tree", "from", parent, "to", p.expectedRoot, "err", err) + } + + // Keep n diff layers in the memory + // - head layer is paired with HEAD state + // - head-1 layer is paired with HEAD-1 state + // - head-(n-1) layer(bottom-most diff layer) is paired with HEAD-(n-1)state + go func() { + if err := p.snaps.Cap(p.expectedRoot, 128); err != nil { + log.Warn("Failed to cap snapshot tree", "root", p.expectedRoot, "layers", 128, "err", err) + } + }() + } + } + return nil + }, + } + defer p.StopPrefetcher() + commitRes := make(chan error, len(commitFuncs)) + for _, f := range commitFuncs { + // commitFuncs[0] and commitFuncs[1] both read map `stateObjects`, but no conflicts + tmpFunc := f + go func() { + commitRes <- tmpFunc() + }() + } + for i := 0; i < len(commitFuncs); i++ { + r := <-commitRes + if r != nil { + return common.Hash{}, r + } + } + + root := p.stateRoot + p.snap = nil + if root == (common.Hash{}) { + root = types.EmptyRootHash + } + // Clear all internal flags at the end of commit operation. + p.accounts = make(map[common.Hash][]byte) + p.storages = make(map[common.Hash]map[common.Hash][]byte) + p.accountsOrigin = make(map[common.Address][]byte) + p.storagesOrigin = make(map[common.Address]map[common.Hash][]byte) + p.stateObjectsDirty = make(map[common.Address]struct{}) + p.stateObjectsDestruct = sync.Map{} + return root, nil +} + +func (p *ParallelStateDB) SetBalance(addr common.Address, amount *uint256.Int) { + stateObject := p.getOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetBalance(amount) + } +} + +func (p *ParallelStateDB) PrepareForParallel() { + //do nothing +} + +var goMaxProcs = runtime.GOMAXPROCS(0) + +func (p *ParallelStateDB) Finalise(deleteEmptyObjects bool) { + + // finalise stateObjectsDestruct + // The finalise of stateDB is called at verify & commit phase, which is global, no need to acquire the lock. + p.stateObjectsDestructDirty.Range(func(key, value interface{}) bool { + p.stateObjectsDestruct.Store(key, value) + return true + }) + p.stateObjectsDestructDirty = sync.Map{} + + runnerCount := goMaxProcs * 3 / 4 + dirtyChan := make(chan common.Address, runnerCount) + addressesToPrefetch := make([][]byte, 0, 16) + go func() { + p.journalDirty.Range(func(key, value interface{}) bool { + address := key.(common.Address) + dirtyChan <- address + addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(address[:])) + return true + }) + close(dirtyChan) + }() + var wg sync.WaitGroup + wg.Add(runnerCount) + for i := 0; i < runnerCount; i++ { + err := gopool.Submit(func() { + defer wg.Done() + for { + addr, isOpen := <-dirtyChan + if !isOpen { + return + } + obj, exist := p.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 := p.stateObjectsDestruct.Load(obj.address); !ok { + p.stateObjectsDestruct.Store(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. + p.AccountMux.Lock() + delete(p.accounts, obj.addrHash) + delete(p.accountsOrigin, obj.address) + p.AccountMux.Unlock() + p.StorageMux.Lock() + delete(p.storages, obj.addrHash) + delete(p.storagesOrigin, obj.address) + p.StorageMux.Unlock() + } else { + obj.finalise(true) // Prefetch slots in the background + } + + obj.created = false + p.pendingDirtyLock.Lock() + p.stateObjectsPending[addr] = struct{}{} + p.stateObjectsDirty[addr] = struct{}{} + p.pendingDirtyLock.Unlock() + } + }) + if err != nil { + panic(fmt.Errorf("parallel stateDB Finalise submit err:%w", err)) + } + } + wg.Wait() + if p.prefetcher != nil && len(addressesToPrefetch) > 0 { + p.trieParallelLock.Lock() + p.prefetcher.prefetch(common.Hash{}, p.originalRoot, common.Address{}, addressesToPrefetch) + p.trieParallelLock.Unlock() + } + // Invalidate journal because reverting across transactions is not allowed. + p.clearJournalAndRefund() +} + +func (p *ParallelStateDB) MarkFullProcessed() { + p.fullProcessed = true +} + +func (p *ParallelStateDB) AccessListCopy() *accessList { + return p.accessList.Copy() +} + +func (p *ParallelStateDB) SetTxContext(hash common.Hash, index int) { + //do nothing +} + +func (p *ParallelStateDB) SetAccessList(list *accessList) { + //Do nothing, because after the Berlin hardfork, + //the accessList will be cleared before the next transaction execution, + //so there is no need for us to set the accessList of the underlying statedb. +} + +func (p *ParallelStateDB) AccountsIntermediateRoot() { + tasks := make(chan func()) + finishCh := make(chan struct{}) + defer close(finishCh) + wg := sync.WaitGroup{} + for i := 0; i < runtime.NumCPU(); i++ { + go func() { + for { + select { + case task := <-tasks: + task() + case <-finishCh: + return + } + } + }() + } + + // 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 p.stateObjectsPending { + if obj, _ := p.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. + p.AccountMux.Lock() + p.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data) + p.AccountMux.Unlock() + } + } + } + wg.Wait() +} + +func (p *ParallelStateDB) StateIntermediateRoot() common.Hash { + // 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. + prefetcher := p.prefetcher + r := p.originalRoot + if p.prefetcher != nil { + defer func() { + p.prefetcher.close() + p.prefetcher = nil + }() + } + + if prefetcher != nil { + if prefetchTrie := prefetcher.trie(common.Hash{}, r); prefetchTrie != nil { + p.trie = prefetchTrie + } + } + + if p.trie == nil { + tr, err := p.db.OpenTrie(p.originalRoot) + if err != nil { + panic(fmt.Sprintf("failed to open trie tree %s", p.originalRoot)) + } + p.trie = tr + } + + usedAddrs := make([][]byte, 0, len(p.stateObjectsPending)) + + for addr := range p.stateObjectsPending { + if obj, _ := p.getStateObjectFromStateObjects(addr); obj.deleted { + p.deleteStateObject(obj) + p.AccountDeleted += 1 + } else { + p.updateStateObject(obj) + p.AccountUpdated += 1 + } + usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure + } + + if prefetcher != nil { + prefetcher.used(common.Hash{}, p.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(p.stateObjectsPending) > 0 { + p.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) { p.AccountHashes += time.Since(start) }(time.Now()) + } + + if p.noTrie { + return p.expectedRoot + } else { + return p.trie.Hash() + } +} + +func (p *ParallelStateDB) GetPreimage(hash common.Hash) ([]byte, bool) { + value, ok := p.preimages.Load(hash) + if ok { + return value.([]byte), ok + } + return nil, ok +} + +func (p *ParallelStateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.Address]struct{}, error) { + // Short circuit if geth is running with hash mode. This procedure can consume + // considerable time and storage deletion isn't supported in hash mode, thus + // preemptively avoiding unnecessary expenses. + incomplete := make(map[common.Address]struct{}) + if p.db.TrieDB().Scheme() == rawdb.HashScheme { + return incomplete, nil + } + var resultErr error + + p.stateObjectsDestruct.Range(func(key, value interface{}) bool { + addr := key.(common.Address) + prev := value.(*types.StateAccount) + // The original account was non-existing, and it's marked as destructed + // in the scope of block. It can be case (a) or (b). + // - for (a), skip it without doing anything. + // - for (b), track account's original value as nil. It may overwrite + // the data cached in s.accountsOrigin set by 'updateStateObject'. + addrHash := crypto.Keccak256Hash(addr[:]) + if prev == nil { + if _, ok := p.accounts[addrHash]; ok { + p.accountsOrigin[addr] = nil // case (b) + } + return true + } + // It can overwrite the data in s.accountsOrigin set by 'updateStateObject'. + p.accountsOrigin[addr] = types.SlimAccountRLP(*prev) // case (c) or (d) + + // Short circuit if the storage was empty. + if prev.Root == types.EmptyRootHash { + return true + } + + // Remove storage slots belong to the account. + aborted, slots, set, err := p.deleteStorage(addr, addrHash, prev.Root) + if err != nil { + resultErr = fmt.Errorf("failed to delete storage, err: %w", err) + return false + } + // The storage is too huge to handle, skip it but mark as incomplete. + // For case (d), the account is resurrected might with a few slots + // created. In this case, wipe the entire storage state diff because + // of aborted deletion. + if aborted { + incomplete[addr] = struct{}{} + delete(p.storagesOrigin, addr) + return true + } + if storages, ok := p.storagesOrigin[addr]; !ok || storages == nil { + p.storagesOrigin[addr] = slots + } else { + // It can overwrite the data in s.storagesOrigin[addrHash] set by + // 'object.updateTrie'. + for key, val := range slots { + p.storagesOrigin[addr][key] = val + } + } + if err := nodes.Merge(set); err != nil { + resultErr = err + return false + } + return true + }) + if resultErr != nil { + return nil, resultErr + } + return incomplete, nil +} + +func (p *ParallelStateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (bool, map[common.Hash][]byte, *trienode.NodeSet, error) { + var ( + start = time.Now() + err error + aborted bool + size common.StorageSize + slots map[common.Hash][]byte + nodes *trienode.NodeSet + ) + // The fast approach can be failed if the snapshot is not fully + // generated, or it's internally corrupted. Fallback to the slow + // one just in case. + if p.snap != nil { + aborted, size, slots, nodes, err = p.fastDeleteStorage(addrHash, root) + } + if p.snap == nil || err != nil { + aborted, size, slots, nodes, err = p.slowDeleteStorage(addr, addrHash, root) + } + if err != nil { + return false, nil, nil, err + } + if metrics.EnabledExpensive { + if aborted { + slotDeletionSkip.Inc(1) + } + n := int64(len(slots)) + + slotDeletionMaxCount.UpdateIfGt(int64(len(slots))) + slotDeletionMaxSize.UpdateIfGt(int64(size)) + + slotDeletionTimer.UpdateSince(start) + slotDeletionCount.Mark(n) + slotDeletionSize.Mark(int64(size)) + } + return aborted, slots, nodes, nil +} + +func (p *ParallelStateDB) fastDeleteStorage(addrHash common.Hash, root common.Hash) (bool, common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) { + iter, err := p.snaps.StorageIterator(p.originalRoot, addrHash, common.Hash{}) + if err != nil { + return false, 0, nil, nil, err + } + defer iter.Release() + + var ( + size common.StorageSize + nodes = trienode.NewNodeSet(addrHash) + slots = make(map[common.Hash][]byte) + ) + stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + nodes.AddNode(path, trienode.NewDeleted()) + size += common.StorageSize(len(path)) + }) + for iter.Next() { + if size > storageDeleteLimit { + return true, size, nil, nil, nil + } + slot := common.CopyBytes(iter.Slot()) + if err := iter.Error(); err != nil { // error might occur after Slot function + return false, 0, nil, nil, err + } + size += common.StorageSize(common.HashLength + len(slot)) + slots[iter.Hash()] = slot + + if err := stack.Update(iter.Hash().Bytes(), slot); err != nil { + return false, 0, nil, nil, err + } + } + if err := iter.Error(); err != nil { // error might occur during iteration + return false, 0, nil, nil, err + } + if stack.Hash() != root { + return false, 0, nil, nil, fmt.Errorf("snapshot is not matched, exp %x, got %x", root, stack.Hash()) + } + return false, size, slots, nodes, nil +} + +func (p *ParallelStateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (bool, common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) { + tr, err := p.db.OpenStorageTrie(p.originalRoot, addr, root, p.trie) + if err != nil { + return false, 0, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err) + } + it, err := tr.NodeIterator(nil) + if err != nil { + return false, 0, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err) + } + var ( + size common.StorageSize + nodes = trienode.NewNodeSet(addrHash) + slots = make(map[common.Hash][]byte) + ) + for it.Next(true) { + if size > storageDeleteLimit { + return true, size, nil, nil, nil + } + if it.Leaf() { + slots[common.BytesToHash(it.LeafKey())] = common.CopyBytes(it.LeafBlob()) + size += common.StorageSize(common.HashLength + len(it.LeafBlob())) + continue + } + if it.Hash() == (common.Hash{}) { + continue + } + size += common.StorageSize(len(it.Path())) + nodes.AddNode(it.Path(), trienode.NewDeleted()) + } + if err := it.Error(); err != nil { + return false, 0, nil, nil, err + } + return false, size, slots, nodes, nil +} + +func (p *ParallelStateDB) convertAccountSet(set *sync.Map) map[common.Hash]struct{} { + ret := make(map[common.Hash]struct{}) + set.Range(func(k, v interface{}) bool { + addr := k.(common.Address) + obj, exist := p.getStateObjectFromStateObjects(addr) + if !exist { + ret[crypto.Keccak256Hash(addr[:])] = struct{}{} + } else { + ret[obj.addrHash] = struct{}{} + } + return true + }) + return ret +} + +func (p *ParallelStateDB) clearJournalAndRefund() { + p.journalDirty = sync.Map{} + p.refund.Store(0) +} + +func (p *ParallelStateDB) deleteStateObject(obj *stateObject) { + if p.noTrie { + return + } + + // Track the amount of time wasted on deleting the account from the trie + if metrics.EnabledExpensive { + defer func(start time.Time) { p.AccountUpdates += time.Since(start) }(time.Now()) + } + // Delete the account from the trie + addr := obj.Address() + if err := p.trie.DeleteAccount(addr); err != nil { + p.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err)) + } +} + +func (p *ParallelStateDB) updateStateObject(obj *stateObject) { + if !p.noTrie { + // Track the amount of time wasted on updating the account from the trie + if metrics.EnabledExpensive { + defer func(start time.Time) { p.AccountUpdates += time.Since(start) }(time.Now()) + } + // Encode the account and update the account trie + addr := obj.Address() + p.trieParallelLock.Lock() + if err := p.trie.UpdateAccount(addr, &obj.data); err != nil { + p.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err)) + } + if obj.dirtyCode { + p.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) + } + p.trieParallelLock.Unlock() + } + + p.AccountMux.Lock() + defer p.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 + // ensure we capture state clearing. + p.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data) + + // Track the original value of mutated account, nil means it was not present. + // Skip if it has been tracked (because updateStateObject may be called + // multiple times in a block). + if _, ok := p.accountsOrigin[obj.address]; !ok { + if obj.origin == nil { + p.accountsOrigin[obj.address] = nil + } else { + p.accountsOrigin[obj.address] = types.SlimAccountRLP(*obj.origin) + } + } +} + +func (p *ParallelStateDB) appendJournal(journalEntry journalEntry) { + if journalEntry.dirtied() != nil { + p.journalDirty.Store(*journalEntry.dirtied(), struct{}{}) + } +} + +func (p *ParallelStateDB) addJournalDirty(address common.Address) { + p.journalDirty.Store(address, struct{}{}) +} + +func (p *ParallelStateDB) getPrefetcher() *triePrefetcher { + return p.prefetcher +} + +func (p *ParallelStateDB) getDB() Database { + return p.db +} + +func (p *ParallelStateDB) getOriginalRoot() common.Hash { + return p.originalRoot +} + +func (p *ParallelStateDB) getTrie() Trie { + return p.trie +} + +func (p *ParallelStateDB) getStateObjectDestructLock() *sync.RWMutex { + return &p.stateObjectDestructLock +} + +func (p *ParallelStateDB) getSnap() snapshot.Snapshot { + return p.snap +} + +func (p *ParallelStateDB) timeAddSnapshotStorageReads(du time.Duration) { + p.SnapshotStorageReads += du +} + +func (p *ParallelStateDB) getTrieParallelLock() *sync.Mutex { + return &p.trieParallelLock +} + +func (p *ParallelStateDB) timeAddStorageReads(du time.Duration) { + p.StorageReads += du +} + +func (p *ParallelStateDB) RecordWrite(key types.RWKey, value interface{}) { + //do nothing +} + +func (p *ParallelStateDB) timeAddStorageUpdates(du time.Duration) { + p.StorageUpdates += du +} + +func (p *ParallelStateDB) countAddStorageDeleted(diff int) { + p.StorageDeleted += diff +} + +func (p *ParallelStateDB) countAddStorageUpdated(diff int) { + p.StorageUpdated += diff +} + +func (p *ParallelStateDB) getStorageMux() *sync.Mutex { + return &p.StorageMux +} + +func (p *ParallelStateDB) getStorages(hash common.Hash) map[common.Hash][]byte { + return p.storages[hash] +} + +func (p *ParallelStateDB) setStorages(hash common.Hash, storage map[common.Hash][]byte) { + p.storages[hash] = storage +} + +func (p *ParallelStateDB) getStoragesOrigin(address common.Address) map[common.Hash][]byte { + return p.storagesOrigin[address] +} + +func (p *ParallelStateDB) setStoragesOrigin(address common.Address, origin map[common.Hash][]byte) { + p.storagesOrigin[address] = origin +} + +func (p *ParallelStateDB) timeAddStorageHashes(du time.Duration) { + p.StorageHashes += du +} + +func (p *ParallelStateDB) timeAddStorageCommits(du time.Duration) { + p.StorageCommits += du +} + +func (p *ParallelStateDB) setStateObjectIfEmpty(obj *stateObject) bool { + _, loaded := p.stateObjects.LoadOrStore(obj.address, obj) + return !loaded } diff --git a/core/state/pevm_statedb_test.go b/core/state/pevm_statedb_test.go index d4443c8581..93937c7596 100644 --- a/core/state/pevm_statedb_test.go +++ b/core/state/pevm_statedb_test.go @@ -74,6 +74,16 @@ func TestInvalidGasUsed(t *testing.T) { return nil } + verifyMainDBs := func(c Checks, maindb *StateDB, shallow *StateDB) error { + if c.Verify(maindb) != nil { + return fmt.Errorf("maindb: %s", c.Verify(maindb).Error()) + } + if c.Verify(shallow) != nil { + return fmt.Errorf("uncommited: %s", c.Verify(shallow).Error()) + } + return nil + } + maindb := newStateDB() shadow := newStateDB() // firtst prepare for the account, to ensure the selfDestruct happens @@ -93,7 +103,8 @@ func TestInvalidGasUsed(t *testing.T) { maindb.Finalise(true) shadow.Finalise(true) // now check the state after finalize - if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + // No need to check the uncommitted database because its data will be useless after the merge. + if err := verifyMainDBs(checks[1], maindb, shadow); err != nil { t.Fatalf("ut failed, err=%s", err.Error()) } // now execute another tx @@ -163,6 +174,16 @@ func TestStateAfterDestructWithBalance(t *testing.T) { return nil } + verifyMainDBs := func(c Checks, maindb *StateDB, shallow *StateDB) error { + if c.Verify(maindb) != nil { + return fmt.Errorf("maindb: %s", c.Verify(maindb).Error()) + } + if c.Verify(shallow) != nil { + return fmt.Errorf("uncommited: %s", c.Verify(shallow).Error()) + } + return nil + } + maindb := newStateDB() shadow := newStateDB() uncommitted := newUncommittedDB(maindb) @@ -176,7 +197,8 @@ func TestStateAfterDestructWithBalance(t *testing.T) { maindb.Finalise(true) shadow.Finalise(true) // now check the state after finalize - if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + // No need to check the uncommitted database because its data will be useless after the merge. + if err := verifyMainDBs(checks[1], maindb, shadow); err != nil { t.Fatalf("ut failed, err=%s", err.Error()) } // now execute another tx @@ -190,7 +212,8 @@ func TestStateAfterDestructWithBalance(t *testing.T) { maindb.Finalise(true) shadow.Finalise(true) // check the state again, to ensure the Finalize works - if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + // No need to check the uncommitted database because its data will be useless after the merge. + if err := verifyMainDBs(checks[2], maindb, shadow); err != nil { t.Fatalf("ut failed, err=%s", err.Error()) } } @@ -239,6 +262,16 @@ func TestStateAfterDestruct(t *testing.T) { return nil } + verifyMainDBs := func(c Check, maindb *StateDB, shallow *StateDB) error { + if c.Verify(maindb) != nil { + return fmt.Errorf("maindb: %s", c.Verify(maindb).Error()) + } + if c.Verify(shallow) != nil { + return fmt.Errorf("uncommited: %s", c.Verify(shallow).Error()) + } + return nil + } + maindb := newStateDB() shadow := newStateDB() uncommitted := newUncommittedDB(maindb) @@ -259,7 +292,8 @@ func TestStateAfterDestruct(t *testing.T) { maindb.Finalise(true) shadow.Finalise(true) // check the state again - if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + // No need to check the uncommitted database because its data will be useless after the merge. + if err := verifyMainDBs(checks[1], maindb, shadow); err != nil { t.Fatalf("ut failed, err=%s", err.Error()) } // now recreate the account @@ -278,7 +312,8 @@ func TestStateAfterDestruct(t *testing.T) { maindb.Finalise(true) shadow.Finalise(true) // check the state again, to ensure the Finalize works - if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + // No need to check the uncommitted database because its data will be useless after the merge. + if err := verifyMainDBs(checks[2], maindb, shadow); err != nil { t.Fatalf("ut failed, err=%s", err.Error()) } uncommitted = newUncommittedDB(maindb) @@ -293,7 +328,8 @@ func TestStateAfterDestruct(t *testing.T) { maindb.Finalise(true) shadow.Finalise(true) // check the state again, to ensure the Finalize works - if err := verifyDBs(checks[3], shadow, uncommitted); err != nil { + // No need to check the uncommitted database because its data will be useless after the merge. + if err := verifyMainDBs(checks[3], maindb, shadow); err != nil { t.Fatalf("ut failed, err=%s", err.Error()) } @@ -639,7 +675,7 @@ func TestPevmSelfDestructStateDB(t *testing.T) { if err := checks.Verify(statedb); err != nil { t.Fatalf("[statedb] unexpected selfdestruct state after finalized, err=%s", err.Error()) } - if err := checks.Verify(uncommitedState.maindb); err != nil { + if err := checks.Verify(uncommitedState.maindb.(vm.StateDB)); err != nil { t.Fatalf("[uncommitted] unexpected selfdestruct state after finalized, err=%s", err.Error()) } statedb.IntermediateRoot(true) @@ -654,7 +690,7 @@ func TestPevmSelfDestructStateDB(t *testing.T) { if err := checks.Verify(statedb); err != nil { t.Fatalf("[statedb] unexpected selfdestruct state after committed, err=%s", err.Error()) } - if err := checks.Verify(uncommitedState.maindb); err != nil { + if err := checks.Verify(uncommitedState.maindb.(vm.StateDB)); err != nil { t.Fatalf("[unstate.maindb] unexpected selfdestruct state after committed, err=%s", err.Error()) } } @@ -2188,7 +2224,7 @@ func runTxOnUncommittedDB(txs Txs, db *UncommittedDB, check CheckState) (common. } } for _, check := range check.BeforeMerge.Maindb { - if err := check.Verify(db.maindb); err != nil { + if err := check.Verify(db.maindb.(vm.StateDB)); err != nil { return common.Hash{}, fmt.Errorf("[before merge][maindb] failed to verify : %v", err) } } @@ -2246,7 +2282,7 @@ func runCase(txs Txs, state *StateDB, unstate *UncommittedDB, check CheckState) return fmt.Errorf("[after merge] failed to verify statedb: %v", err) } // an uncommitted is invalid after merge, so we verify its maindb instead - if err := check.Verify(unstate.maindb); err != nil { + if err := check.Verify(unstate.maindb.(vm.StateDB)); err != nil { return fmt.Errorf("[after merge] failed to verify uncommited db: %v", err) } } diff --git a/core/state/state_object.go b/core/state/state_object.go index e18214c2a4..d5bbce89cd 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -150,7 +150,6 @@ func newStorage(isParallel bool) Storage { // 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. address common.Address // address of ethereum account addrHash common.Hash // hash of ethereum address of the account @@ -168,8 +167,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 - storageRecordsLock sync.RWMutex // for pending/dirty/origin storage read (lightCopy) and write (Intermediate/FixupOrigin) + isParallel bool + dirtyStorageLock 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 @@ -241,7 +240,6 @@ func (s *stateObject) empty() bool { // newObject creates a state object. 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 @@ -250,7 +248,6 @@ func newObject(dbItf StateDBer, isParallel bool, address common.Address, acct *t acct = types.NewEmptyStateAccount() } s := &stateObject{ - db: db, dbItf: dbItf, address: address, addrHash: crypto.Keccak256Hash(address[:]), @@ -284,13 +281,13 @@ func (s *stateObject) markSelfdestructed() { } func (s *stateObject) touch() { - s.db.journal.append(touchChange{ + s.dbItf.appendJournal(touchChange{ account: &s.address, }) if s.address == ripemd { // Explicitly put it in the dirty-cache, which is otherwise generated from // flattened journals. - s.db.journal.dirty(s.address) + s.dbItf.addJournalDirty(s.address) } } @@ -300,12 +297,12 @@ func (s *stateObject) touch() { func (s *stateObject) getTrie() (Trie, error) { if s.trie == nil { // Try fetching from prefetcher first - if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil { + if s.data.Root != types.EmptyRootHash && s.dbItf.getPrefetcher() != nil { // When the miner is creating the pending state, there is no prefetcher - s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root) + s.trie = s.dbItf.getPrefetcher().trie(s.addrHash, s.data.Root) } if s.trie == nil { - tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie) + tr, err := s.dbItf.getDB().OpenStorageTrie(s.dbItf.getOriginalRoot(), s.address, s.data.Root, s.dbItf.getTrie()) if err != nil { return nil, err } @@ -345,12 +342,12 @@ 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 - s.db.stateObjectDestructLock.RLock() - if _, destructed := s.db.getStateObjectsDestruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? - s.db.stateObjectDestructLock.RUnlock() + s.dbItf.getStateObjectDestructLock().RLock() + if _, destructed := s.dbItf.getStateObjectsDestruct(s.address); destructed { + s.dbItf.getStateObjectDestructLock().RUnlock() return common.Hash{} } - s.db.stateObjectDestructLock.RUnlock() + s.dbItf.getStateObjectDestructLock().RUnlock() // If no live objects are available, attempt to use snapshots var ( @@ -358,36 +355,36 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { err error value common.Hash ) - if s.db.snap != nil { + if s.dbItf.getSnap() != nil { start := time.Now() - enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes())) + enc, err = s.dbItf.getSnap().Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes())) if metrics.EnabledExpensive { - s.db.SnapshotStorageReads += time.Since(start) + s.dbItf.timeAddSnapshotStorageReads(time.Since(start)) } if len(enc) > 0 { _, content, _, err := rlp.Split(enc) if err != nil { - s.db.setError(err) + s.dbItf.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 { + if s.dbItf.getSnap() == nil || err != nil { start := time.Now() - s.db.trieParallelLock.Lock() - defer s.db.trieParallelLock.Unlock() + s.dbItf.getTrieParallelLock().Lock() + defer s.dbItf.getTrieParallelLock().Unlock() tr, err := s.getTrie() if err != nil { - s.db.setError(err) + s.dbItf.setError(err) return common.Hash{} } val, err := tr.GetStorage(s.address, key.Bytes()) if metrics.EnabledExpensive { - s.db.StorageReads += time.Since(start) + s.dbItf.timeAddStorageReads(time.Since(start)) } if err != nil { - s.db.setError(err) + s.dbItf.setError(err) return common.Hash{} } value.SetBytes(val) @@ -411,7 +408,7 @@ func (s *stateObject) SetState(key, value common.Hash) { return } // New value is different, update and journal the change - s.db.journal.append(storageChange{ + s.dbItf.appendJournal(storageChange{ account: &s.address, key: key, prevalue: prev, @@ -426,13 +423,13 @@ 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() + s.dirtyStorageLock.Lock() + defer s.dirtyStorageLock.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)) originalValue, _ := s.originStorage.GetValue(key.(common.Hash)) - if value.(common.Hash) != originalValue { + if value.(common.Hash) != originalValue && prefetch { originalKey := key.(common.Hash) slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(originalKey[:])) // Copy needed for closure } @@ -450,10 +447,10 @@ func (s *stateObject) finalise(prefetch bool) { s.data.CodeHash = s.dirtyCodeHash 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.dbItf.getPrefetcher() != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { + s.dbItf.getTrieParallelLock().Lock() + s.dbItf.getPrefetcher().prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch) + s.dbItf.getTrieParallelLock().Unlock() } if s.dirtyStorage.Length() > 0 { s.dirtyStorage = newStorage(s.isParallel) @@ -467,18 +464,18 @@ func (s *stateObject) finaliseRWSet() { if value == s.GetCommittedState(key.(common.Hash)) { return true } - s.db.RecordWrite(types.StorageStateKey(s.address, key.(common.Hash)), value.(common.Hash)) + s.dbItf.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) + s.dbItf.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)) + s.dbItf.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) + s.dbItf.RecordWrite(types.AccountStateKey(s.address, types.AccountCodeHash), s.dirtyCodeHash) } } @@ -489,7 +486,6 @@ 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 // Make sure all dirty slots are finalized into the pending storage area s.finalise(false) @@ -499,7 +495,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) { maindb.StorageUpdates += time.Since(start) }(time.Now()) + defer func(start time.Time) { s.dbItf.timeAddStorageUpdates(time.Since(start)) }(time.Now()) } // The snapshot storage map for the object var ( @@ -509,7 +505,7 @@ func (s *stateObject) updateTrie() (Trie, error) { ) tr, err := s.getTrie() if err != nil { - maindb.setError(err) + s.dbItf.setError(err) return nil, err } @@ -540,14 +536,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 { - maindb.setError(err) + s.dbItf.setError(err) } - maindb.StorageDeleted += 1 + s.dbItf.countAddStorageDeleted(1) } else { if err := tr.UpdateStorage(s.address, key[:], value); err != nil { - maindb.setError(err) + s.dbItf.setError(err) } - maindb.StorageUpdated += 1 + s.dbItf.countAddStorageUpdated(1) } // Cache the items for preloading usedStorage = append(usedStorage, common.CopyBytes(key[:])) @@ -557,21 +553,21 @@ func (s *stateObject) updateTrie() (Trie, error) { wg.Add(1) go func() { defer wg.Done() - maindb.StorageMux.Lock() + s.dbItf.getStorageMux().Lock() // The snapshot storage map for the object - storage = maindb.storages[s.addrHash] + storage = s.dbItf.getStorages(s.addrHash) if storage == nil { storage = make(map[common.Hash][]byte, len(dirtyStorage)) - maindb.storages[s.addrHash] = storage + s.dbItf.setStorages(s.addrHash, storage) } // Cache the original value of mutated storage slots - origin = maindb.storagesOrigin[s.address] + origin = s.dbItf.getStoragesOrigin(s.address) if origin == nil { origin = make(map[common.Hash][]byte) - maindb.storagesOrigin[s.address] = origin + s.dbItf.setStoragesOrigin(s.address, origin) } - maindb.StorageMux.Unlock() + s.dbItf.getStorageMux().Unlock() for key, value := range dirtyStorage { khash := crypto.HashData(hasher, key[:]) @@ -598,8 +594,8 @@ func (s *stateObject) updateTrie() (Trie, error) { }() wg.Wait() - if maindb.prefetcher != nil { - maindb.prefetcher.used(s.addrHash, s.data.Root, usedStorage) + if s.dbItf.getPrefetcher() != nil { + s.dbItf.getPrefetcher().used(s.addrHash, s.data.Root, usedStorage) } s.pendingStorage = newStorage(s.isParallel) // reset pending map @@ -612,15 +608,15 @@ func (s *stateObject) updateRoot() { // If node runs in no trie mode, set root to empty defer func() { - if s.db.db.NoTries() { + if s.dbItf.getDB().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() - defer s.db.trieParallelLock.Unlock() + s.dbItf.getTrieParallelLock().Lock() + defer s.dbItf.getTrieParallelLock().Unlock() tr, err := s.updateTrie() if err != nil || tr == nil { @@ -628,7 +624,7 @@ func (s *stateObject) updateRoot() { } // Track the amount of time wasted on hashing the storage trie if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now()) + defer func(start time.Time) { s.dbItf.timeAddStorageHashes(time.Since(start)) }(time.Now()) } s.data.Root = tr.Hash() } @@ -644,7 +640,7 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) { } // Track the amount of time wasted on committing the storage trie if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now()) + defer func(start time.Time) { s.dbItf.timeAddStorageCommits(time.Since(start)) }(time.Now()) } // The trie is currently in an open state and could potentially contain // cached mutations. Call commit to acquire a set of nodes that have been @@ -683,7 +679,7 @@ func (s *stateObject) SubBalance(amount *uint256.Int) { } func (s *stateObject) SetBalance(amount *uint256.Int) { - s.db.journal.append(balanceChange{ + s.dbItf.appendJournal(balanceChange{ account: &s.address, prev: new(uint256.Int).Set(s.Balance()), }) @@ -702,7 +698,6 @@ func (s *stateObject) ReturnGas(gas *uint256.Int) {} // otherwise the origin/dirty/pending storages may cause incorrect issue. func (s *stateObject) deepCopy(db *StateDB) *stateObject { object := &stateObject{ - db: db.getBaseStateDB(), dbItf: db, address: s.address, addrHash: s.addrHash, @@ -711,9 +706,9 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { isParallel: s.isParallel, } if s.trie != nil { - s.db.trieParallelLock.Lock() + s.dbItf.getTrieParallelLock().Lock() object.trie = db.db.CopyTrie(s.trie) - s.db.trieParallelLock.Unlock() + s.dbItf.getTrieParallelLock().Unlock() } object.code = s.code @@ -766,9 +761,9 @@ func (s *stateObject) Code() []byte { if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { return nil } - code, err := s.db.db.ContractCode(s.address, common.BytesToHash(s.CodeHash())) + code, err := s.dbItf.getDB().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.dbItf.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err)) } s.code = code return code @@ -784,16 +779,16 @@ func (s *stateObject) CodeSize() int { if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { return 0 } - size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash())) + size, err := s.dbItf.getDB().ContractCodeSize(s.address, common.BytesToHash(s.CodeHash())) if err != nil { - s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err)) + s.dbItf.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err)) } return size } func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { prevcode := s.dbItf.GetCode(s.address) - s.db.journal.append(codeChange{ + s.dbItf.appendJournal(codeChange{ account: &s.address, prevhash: s.CodeHash(), prevcode: prevcode, @@ -810,7 +805,7 @@ 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{ + s.dbItf.appendJournal(nonceChange{ account: &s.address, prev: prevNonce, }) @@ -846,83 +841,15 @@ 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.getStateObjectsDestruct(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) - } +func (s *stateObject) prefetchStorage(key common.Hash, val common.Hash) { + prefetcher := s.dbItf.getPrefetcher() + if prefetcher == nil { + return } - // 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{} - } - val, err := tr.GetStorage(s.address, key.Bytes()) - if metrics.EnabledExpensive { - s.db.StorageReads += time.Since(start) - } - if err != nil { - s.db.setError(err) - return common.Hash{} - } - value.SetBytes(val) + originalValue, _ := s.originStorage.GetValue(key) + if val != originalValue && s.data.Root != types.EmptyRootHash { + s.dbItf.getTrieParallelLock().Lock() + s.dbItf.getPrefetcher().prefetch(s.addrHash, s.data.Root, s.address, [][]byte{common.CopyBytes(key[:])}) + s.dbItf.getTrieParallelLock().Unlock() } - return value } diff --git a/core/state/statedb.go b/core/state/statedb.go index d9b9340a9d..c5c930007d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -231,8 +231,23 @@ type StateDB struct { onCommit func(states *triestate.Set) // Hook invoked when commit is performed } -func (s *StateDB) GetStateObjectFromUnconfirmedDB(addr common.Address) (*stateObject, bool) { - return nil, false +type Timers struct { + // Measurements gathered during execution for debugging purposes + AccountReads time.Duration + AccountHashes time.Duration + AccountUpdates time.Duration + AccountCommits time.Duration + StorageReads time.Duration + StorageHashes time.Duration + StorageUpdates time.Duration + StorageCommits time.Duration + SnapshotAccountReads time.Duration + SnapshotStorageReads time.Duration + SnapshotCommits time.Duration + TrieDBCommits time.Duration + TrieCommits time.Duration + CodeCommits time.Duration + TxDAGGenerate time.Duration } // New creates a new state from a given trie. @@ -548,6 +563,10 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool { return false } +func (s *StateDB) prefetchAccount(address common.Address) { + panic("stateDB not support prefetchAccount") +} + /* * SETTERS */ @@ -750,15 +769,6 @@ 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 { @@ -2162,3 +2172,120 @@ func (s *StateDB) PrepareForParallel() { } } } + +func (s *StateDB) Timers() *Timers { + return &Timers{ + AccountReads: s.AccountReads, + AccountHashes: s.AccountHashes, + AccountUpdates: s.AccountUpdates, + AccountCommits: s.AccountCommits, + StorageReads: s.StorageReads, + StorageHashes: s.StorageHashes, + StorageUpdates: s.StorageUpdates, + StorageCommits: s.StorageCommits, + SnapshotAccountReads: s.SnapshotAccountReads, + SnapshotStorageReads: s.SnapshotStorageReads, + SnapshotCommits: s.SnapshotCommits, + TrieDBCommits: s.TrieDBCommits, + TrieCommits: s.TrieCommits, + CodeCommits: s.CodeCommits, + TxDAGGenerate: s.TxDAGGenerate, + } +} + +func (s *StateDB) AccessListCopy() *accessList { + return s.accessList.Copy() +} + +func (s *StateDB) SetAccessList(list *accessList) { + s.accessList = list +} + +func (s *StateDB) GetPreimage(hash common.Hash) ([]byte, bool) { + bytes, ok := s.preimages[hash] + return bytes, ok +} + +func (s *StateDB) appendJournal(journalEntry journalEntry) { + s.journal.append(journalEntry) +} + +func (s *StateDB) addJournalDirty(address common.Address) { + s.journal.dirty(address) +} + +func (s *StateDB) getPrefetcher() *triePrefetcher { + return s.prefetcher +} + +func (s *StateDB) getDB() Database { + return s.db +} + +func (s *StateDB) getOriginalRoot() common.Hash { + return s.originalRoot +} + +func (s *StateDB) getTrie() Trie { + return s.trie +} + +func (s *StateDB) getStateObjectDestructLock() *sync.RWMutex { + return &s.stateObjectDestructLock +} + +func (s *StateDB) getSnap() snapshot.Snapshot { + return s.snap +} + +func (s *StateDB) timeAddSnapshotStorageReads(du time.Duration) { + s.SnapshotStorageReads += du +} + +func (s *StateDB) getTrieParallelLock() *sync.Mutex { + return &s.trieParallelLock +} + +func (s *StateDB) timeAddStorageReads(du time.Duration) { + s.StorageReads += du +} + +func (s *StateDB) timeAddStorageUpdates(du time.Duration) { + s.StorageUpdates += du +} + +func (s *StateDB) countAddStorageDeleted(diff int) { + s.StorageDeleted += diff +} + +func (s *StateDB) countAddStorageUpdated(diff int) { + s.StorageUpdated += diff +} + +func (s *StateDB) getStorageMux() *sync.Mutex { + return &s.StorageMux +} + +func (s *StateDB) getStorages(hash common.Hash) map[common.Hash][]byte { + return s.storages[hash] +} + +func (s *StateDB) setStorages(hash common.Hash, storage map[common.Hash][]byte) { + s.storages[hash] = storage +} + +func (s *StateDB) getStoragesOrigin(address common.Address) map[common.Hash][]byte { + return s.storagesOrigin[address] +} + +func (s *StateDB) setStoragesOrigin(address common.Address, origin map[common.Hash][]byte) { + s.storagesOrigin[address] = origin +} + +func (s *StateDB) timeAddStorageHashes(du time.Duration) { + s.StorageHashes += du +} + +func (s *StateDB) timeAddStorageCommits(du time.Duration) { + s.StorageCommits += du +} diff --git a/core/state_processor.go b/core/state_processor.go index d5308af37e..cdac9f2494 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -61,7 +61,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { +func (p *StateProcessor) Process(block *types.Block, statedber state.StateDBer, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { var ( receipts types.Receipts usedGas = new(uint64) @@ -70,6 +70,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg blockNumber = block.Number() allLogs []*types.Log gp = new(GasPool).AddGas(block.GasLimit()) + statedb = statedber.(*state.StateDB) ) // 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 { @@ -229,3 +230,23 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *stat _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) statedb.Finalise(true) } + +// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root +// contract. This method is exported to be used in tests. +func ProcessBeaconBlockRoot2(beaconRoot common.Hash, vmenv *vm.EVM, statedb state.StateDBer) { + // If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with + // the new root + msg := &Message{ + From: params.SystemAddress, + GasLimit: 30_000_000, + GasPrice: common.Big0, + GasFeeCap: common.Big0, + GasTipCap: common.Big0, + To: ¶ms.BeaconRootsStorageAddress, + Data: beaconRoot[:], + } + vmenv.Reset(NewEVMTxContext(msg), statedb.(vm.StateDB)) + statedb.AddAddressToAccessList(params.BeaconRootsStorageAddress) + _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.U2560) + statedb.Finalise(true) +} diff --git a/core/types.go b/core/types.go index 36eb0d1ded..55d4e63f7d 100644 --- a/core/types.go +++ b/core/types.go @@ -33,7 +33,7 @@ type Validator interface { // ValidateState validates the given statedb and optionally the receipts and // gas used. - ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64) error + ValidateState(block *types.Block, state state.StateDBer, receipts types.Receipts, usedGas uint64) error } // Prefetcher is an interface for pre-caching transaction signatures and state. @@ -49,5 +49,5 @@ type Processor interface { // 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. - Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) + Process(block *types.Block, statedb state.StateDBer, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 4fa380428b..a0d2ebb820 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -36,11 +36,11 @@ type Config struct { 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 - ParallelThreshold int // Threshold of transactions number to trigger parallel process OptimismPrecompileOverrides PrecompileOverrides // Precompile overrides for Optimism EnableOpcodeOptimizations bool // Enable opcode optimization TxDAG types.TxDAG EnableParallelUnorderedMerge bool // Whether to enable unordered merge in parallel mode + EnableTxParallelMerge bool // Whether to enable parallel merge in parallel mode } // ScopeContext contains the things that are per-call, such as stack and memory, diff --git a/eth/backend.go b/eth/backend.go index a03448853c..df71708182 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -239,8 +239,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { EnablePreimageRecording: config.EnablePreimageRecording, EnableParallelExec: config.ParallelTxMode, EnableParallelUnorderedMerge: config.ParallelTxUnorderedMerge, + EnableTxParallelMerge: config.ParallelTxParallelMerge, ParallelTxNum: config.ParallelTxNum, - ParallelThreshold: config.ParallelThreshold, EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, } cacheConfig = &core.CacheConfig{ diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 164f32c8ed..1d43484abc 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -221,11 +221,12 @@ type Config struct { ParallelTxMode bool // Whether to execute transaction in parallel mode when do full sync ParallelTxNum int // Number of slot for transaction execution - ParallelThreshold int // threshold to trigger parallel execution EnableOpcodeOptimizing bool EnableParallelTxDAG bool ParallelTxDAGFile string ParallelTxUnorderedMerge bool // Whether to enable unordered merge in parallel mode + ParallelTxParallelMerge bool + } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index e6f4e1528f..f98fce6748 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -77,7 +77,13 @@ func (c Config) MarshalTOML() (interface{}, error) { RollupDisableTxPoolGossip bool RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string + ParallelTxMode bool + ParallelTxNum int EnableOpcodeOptimizing bool + EnableParallelTxDAG bool + ParallelTxDAGFile string + ParallelTxUnorderedMerge bool + ParallelTxParallelMerge bool } var enc Config enc.Genesis = c.Genesis @@ -138,7 +144,13 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.RollupDisableTxPoolGossip = c.RollupDisableTxPoolGossip enc.RollupDisableTxPoolAdmission = c.RollupDisableTxPoolAdmission enc.RollupHaltOnIncompatibleProtocolVersion = c.RollupHaltOnIncompatibleProtocolVersion + enc.ParallelTxMode = c.ParallelTxMode + enc.ParallelTxNum = c.ParallelTxNum enc.EnableOpcodeOptimizing = c.EnableOpcodeOptimizing + enc.EnableParallelTxDAG = c.EnableParallelTxDAG + enc.ParallelTxDAGFile = c.ParallelTxDAGFile + enc.ParallelTxUnorderedMerge = c.ParallelTxUnorderedMerge + enc.ParallelTxParallelMerge = c.ParallelTxParallelMerge return &enc, nil } @@ -203,7 +215,13 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { RollupDisableTxPoolGossip *bool RollupDisableTxPoolAdmission *bool RollupHaltOnIncompatibleProtocolVersion *string + ParallelTxMode *bool + ParallelTxNum *int EnableOpcodeOptimizing *bool + EnableParallelTxDAG *bool + ParallelTxDAGFile *string + ParallelTxUnorderedMerge *bool + ParallelTxParallelMerge *bool } var dec Config if err := unmarshal(&dec); err != nil { @@ -383,8 +401,26 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.RollupHaltOnIncompatibleProtocolVersion != nil { c.RollupHaltOnIncompatibleProtocolVersion = *dec.RollupHaltOnIncompatibleProtocolVersion } + if dec.ParallelTxMode != nil { + c.ParallelTxMode = *dec.ParallelTxMode + } + if dec.ParallelTxNum != nil { + c.ParallelTxNum = *dec.ParallelTxNum + } if dec.EnableOpcodeOptimizing != nil { c.EnableOpcodeOptimizing = *dec.EnableOpcodeOptimizing } + if dec.EnableParallelTxDAG != nil { + c.EnableParallelTxDAG = *dec.EnableParallelTxDAG + } + if dec.ParallelTxDAGFile != nil { + c.ParallelTxDAGFile = *dec.ParallelTxDAGFile + } + if dec.ParallelTxUnorderedMerge != nil { + c.ParallelTxUnorderedMerge = *dec.ParallelTxUnorderedMerge + } + if dec.ParallelTxParallelMerge != nil { + c.ParallelTxParallelMerge = *dec.ParallelTxParallelMerge + } return nil } diff --git a/tests-dag b/tests-dag new file mode 160000 index 0000000000..0cee86f38a --- /dev/null +++ b/tests-dag @@ -0,0 +1 @@ +Subproject commit 0cee86f38a2e6586e72ddd008d65c32ba46bba94 diff --git a/tests/block_test.go b/tests/block_test.go index f18cfdf1df..5525d53024 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -17,16 +17,12 @@ package tests import ( - "fmt" "math/rand" - "os" - "path/filepath" "runtime" "sync/atomic" "testing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" ) @@ -60,7 +56,7 @@ func TestBlockchainWithTxDAG(t *testing.T) { if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { t.Skip("test (randomly) skipped on 32-bit windows") } - execBlockTestWithTxDAG(t, bt, test) + execBlockTestWithTxDAG(t, bt, test, name) }) //bt := new(testMatcher) //path := filepath.Join(blockTestDir, "ValidBlocks", "bcEIP1559", "intrinsic.json") @@ -124,38 +120,46 @@ 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_%v.csv", txDAGFileCounter.Add(1))) - if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, false)); err != nil { +func execBlockTestWithTxDAG(t *testing.T, bt *testMatcher, test *BlockTest, name string) { + txDAGFile := "../tests-dag/dag/" + name + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, "", false, false, false)); err != nil { t.Errorf("test in path mode with snapshotter failed: %v", err) return } // run again with dagFile - core.InitPevmRunner(1) - if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, true)); err != nil { + //core.InitPevmRunner(2) + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, true, false, false)); err != nil { + t.Errorf("test in path mode with snapshotter failed: %v", err) + return + } + + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, true, true, false)); err != nil { + t.Errorf("test in path mode with snapshotter failed: %v", err) + return + } + + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, true, false, 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, "", true)); err != nil { + if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil, "", true, false, false)); 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, "", true)); err != nil { + if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil, "", true, false, false)); 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, "", true)); err != nil { + if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil, "", true, false, false)); 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, "", true)); err != nil { + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, "", true, false, false)); 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 6cca172f96..6aa0c16675 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -25,6 +25,7 @@ import ( "math/big" "os" "reflect" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -109,7 +110,7 @@ type btHeaderMarshaling struct { ExcessBlobGas *math.HexOrDecimal64 } -func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain), dagFile string, enableParallel bool) (result error) { +func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain), dagFile string, enableParallel bool, enableUnorderedMerge bool, enableParallelMerge bool) (result error) { config, ok := Forks[t.json.Network] if !ok { return UnsupportedForkError{t.json.Network} @@ -153,9 +154,11 @@ 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: enableParallel, - ParallelTxNum: 4, - Tracer: tracer, + EnableParallelExec: enableParallel, + ParallelTxNum: 4, + EnableParallelUnorderedMerge: enableUnorderedMerge, + EnableTxParallelMerge: enableParallelMerge, + Tracer: tracer, }, nil, nil) if err != nil { return err @@ -163,6 +166,7 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po defer chain.Stop() if len(dagFile) > 0 { chain.SetupTxDAGGeneration(dagFile, enableParallel) + time.Sleep(100 * time.Millisecond) } validBlocks, err := t.insertBlocks(chain) if err != nil { From e9f99d6f14cc968746d0abac8de269ddd61fa839 Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:52:54 +0800 Subject: [PATCH 100/104] pevm-fix: fix journal record in add/sub balance with 0 (#249) --- core/state/pevm_statedb.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go index bff3eadad6..a9605373f4 100644 --- a/core/state/pevm_statedb.go +++ b/core/state/pevm_statedb.go @@ -169,6 +169,12 @@ func (pst *UncommittedDB) Prepare(rules params.Rules, sender, coinbase common.Ad // 2. object func (pst *UncommittedDB) SubBalance(addr common.Address, amount *uint256.Int) { + if amount.IsZero() { + obj := pst.getOrNewObject(addr) + if !obj.empty(pst) { + return + } + } pst.journal.append(newJBalance(pst.cache[addr], addr)) obj := pst.getOrNewObject(addr) newb := new(uint256.Int).Sub(obj.balance, amount) @@ -176,6 +182,12 @@ func (pst *UncommittedDB) SubBalance(addr common.Address, amount *uint256.Int) { } func (pst *UncommittedDB) AddBalance(addr common.Address, amount *uint256.Int) { + if amount.IsZero() { + obj := pst.getOrNewObject(addr) + if !obj.empty(pst) { + return + } + } pst.journal.append(newJBalance(pst.cache[addr], addr)) obj := pst.getOrNewObject(addr) newb := new(uint256.Int).Add(obj.balance, amount) From f8d6a95820c355286e6b0d62ba73c2659a1a18dd Mon Sep 17 00:00:00 2001 From: sunny2022da <124866865+sunny2022da@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:46:33 +0800 Subject: [PATCH 101/104] PEVM-fix: try load TxDAG from block if fail from file (#253) --- core/blockchain.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index 4695b270b5..3801f4e138 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2257,7 +2257,8 @@ func (bc *BlockChain) parseTxDAG(block *types.Block) { if bc.txDAGReader != nil { // load cache txDAG from file first txDAG = bc.txDAGReader.TxDAG(block.NumberU64()) - } else { + } + if txDAG == nil { // load TxDAG from block txDAG, err = types.GetTxDAG(block) if err != nil { From 3fd6e45dabfe2a0fa209b6a4d480ce9e4da03793 Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:43:52 +0800 Subject: [PATCH 102/104] pevm: fallback to sequencial processor when the TxDAG is too deep (#251) Co-authored-by: andyzhang2023 --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 11 +++++ core/blockchain.go | 27 +++++++++--- core/parallel_state_scheduler.go | 62 +++++++++++++++++++-------- core/parallel_state_scheduler_test.go | 47 +++++++++++++++++++- core/vm/interpreter.go | 1 + eth/backend.go | 1 + eth/ethconfig/config.go | 16 +++---- 8 files changed, 133 insertions(+), 33 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b4c79e26c6..cc3ae5805a 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -177,6 +177,7 @@ var ( utils.ParallelTxDAGFlag, utils.ParallelTxDAGFileFlag, utils.ParallelTxDAGSenderPrivFlag, + utils.ParallelTxDATMaxDepthRatioFlag, configFileFlag, utils.LogDebugFlag, utils.LogBacktraceAtFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 726f980ad4..703fcaf2b0 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1136,6 +1136,13 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Category: flags.VMCategory, } + ParallelTxDATMaxDepthRatioFlag = &cli.Float64Flag{ + Name: "parallel.txdag-max-depth-ratio", + Usage: "A ratio to decide whether or not to execute transactions in parallel, it will fallback to sequencial processor if the depth is larger than this value (default = 0.9)", + Value: 0.9, + Category: flags.VMCategory, + } + VMOpcodeOptimizeFlag = &cli.BoolFlag{ Name: "vm.opcode.optimize", Usage: "enable opcode optimization", @@ -2057,6 +2064,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.ParallelTxDAGFile = ctx.String(ParallelTxDAGFileFlag.Name) } + if ctx.IsSet(ParallelTxDATMaxDepthRatioFlag.Name) { + cfg.ParallelTxDAGMaxDepthRatio = ctx.Float64(ParallelTxDATMaxDepthRatioFlag.Name) + } + if ctx.IsSet(ParallelTxDAGSenderPrivFlag.Name) { priHex := ctx.String(ParallelTxDAGSenderPrivFlag.Name) if cfg.Miner.ParallelTxDAGSenderPriv, err = crypto.HexToECDSA(priHex); err != nil { diff --git a/core/blockchain.go b/core/blockchain.go index 3801f4e138..efb5101223 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1760,6 +1760,19 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { return bc.insertChain(chain, true) } +func (bc *BlockChain) useSerialProcessor(block *types.Block) (bool, bool) { + // findout whether or not the dependencies of the block are too deep to be processed + // if the dependencies are too deep, we will fallback to serial processing + txCount := len(block.Transactions()) + _, depth := BuildTxLevels(txCount, bc.vmConfig.TxDAG) + tooDeep := float64(depth)/float64(txCount) > bc.vmConfig.TxDAGMaxDepthRatio + isByzantium := bc.chainConfig.IsByzantium(block.Number()) + + txDAGMissButNecessary := bc.vmConfig.TxDAG == nil && (bc.vmConfig.EnableParallelUnorderedMerge || bc.vmConfig.EnableTxParallelMerge) + useSerialProcessor := !bc.vmConfig.EnableParallelExec || txDAGMissButNecessary || tooDeep || !isByzantium + return useSerialProcessor, tooDeep +} + // insertChain is the internal implementation of InsertChain, which assumes that // 1) chains are contiguous, and 2) The chain mutex is held. // @@ -1971,6 +1984,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) ) blockProcessedInParallel := false + var ( + tooDeep, useSerialProcessor bool + depth int + ) // skip block process if we already have the state, receipts and logs from mining work if !(receiptExist && logExist && stateExist) { // Retrieve the parent block and it's state to execute on top @@ -1982,9 +1999,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) if bc.vmConfig.EnableParallelExec { bc.parseTxDAG(block) } - isByzantium := bc.chainConfig.IsByzantium(block.Number()) - if bc.vmConfig.EnableParallelExec && bc.vmConfig.TxDAG != nil && bc.vmConfig.EnableTxParallelMerge && isByzantium { + useSerialProcessor, tooDeep = bc.useSerialProcessor(block) + if !useSerialProcessor { statedb, err = state.NewParallel(parent.Root, bc.stateCache, bc.snaps) } else { statedb, err = state.New(parent.Root, bc.stateCache, bc.snaps) @@ -2018,8 +2035,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) // Process block using the parent state as reference point pstart = time.Now() - txDAGMissButNecessary := bc.vmConfig.TxDAG == nil && (bc.vmConfig.EnableParallelUnorderedMerge || bc.vmConfig.EnableTxParallelMerge) - useSerialProcessor := !bc.vmConfig.EnableParallelExec || txDAGMissButNecessary if useSerialProcessor { receipts, logs, usedGas, err = bc.serialProcessor.Process(block, statedb, bc.vmConfig) blockProcessedInParallel = false @@ -2143,7 +2158,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) "accountUpdates", common.PrettyDuration(timers.AccountUpdates), "storageUpdates", common.PrettyDuration(timers.StorageUpdates), "accountHashes", common.PrettyDuration(timers.AccountHashes), - "storageHashes", common.PrettyDuration(timers.StorageHashes)) + "storageHashes", common.PrettyDuration(timers.StorageHashes), + "tooDeep", tooDeep, "depth", depth, + ) // Write the block to the chain and get the status. var ( diff --git a/core/parallel_state_scheduler.go b/core/parallel_state_scheduler.go index 25271bf0dc..d9a580df69 100644 --- a/core/parallel_state_scheduler.go +++ b/core/parallel_state_scheduler.go @@ -350,8 +350,6 @@ func (tl TxLevel) predictTxDAG(dag types.TxDAG) { func NewTxLevels(all []*PEVMTxRequest, dag types.TxDAG) TxLevels { var levels TxLevels = make(TxLevels, 0, 8) - var currLevel int = 0 - var enlargeLevelsIfNeeded = func(currLevel int, levels *TxLevels) { if len(*levels) <= currLevel { for i := len(*levels); i <= currLevel; i++ { @@ -367,22 +365,47 @@ func NewTxLevels(all []*PEVMTxRequest, dag types.TxDAG) TxLevels { return TxLevels{all} } - marked := make(map[int]int, len(all)) - for _, tx := range all { - dep := dag.TxDep(tx.txIndex) + // build the levels from the DAG + marked, _ := BuildTxLevels(len(all), dag) + // put the transactions into the levels + for txIndex, tx := range all { + level := marked[txIndex] + enlargeLevelsIfNeeded(level, &levels) + levels[level] = append(levels[level], tx) + } + return levels +} + +func BuildTxLevels(txCount int, dag types.TxDAG) (marked map[int]int, depth int) { + if dag == nil { + return make(map[int]int), 0 + } + // marked is used to record which level that each transaction should be put + marked = make(map[int]int, txCount) + var ( + // currLevelHasTx marks if the current level has any transaction + currLevelHasTx bool + ) + + depth, currLevelHasTx = 0, false + for txIndex := 0; txIndex < txCount; txIndex++ { + dep := dag.TxDep(txIndex) switch true { case dep != nil && dep.CheckFlag(types.ExcludedTxFlag), dep != nil && dep.CheckFlag(types.NonDependentRelFlag): // excluted tx, occupies the whole level // or dependent-to-all tx, occupies the whole level, too - levels = append(levels, TxLevel{tx}) - marked[tx.txIndex], currLevel = len(levels)-1, len(levels) + if currLevelHasTx { + // shift to next level if there are transactions in the current level + depth++ + } + marked[txIndex] = depth + // occupy the current level + depth, currLevelHasTx = depth+1, false case dep == nil || len(dep.TxIndexes) == 0: - // dependent on none - enlargeLevelsIfNeeded(currLevel, &levels) - levels[currLevel] = append(levels[currLevel], tx) - marked[tx.txIndex] = currLevel + // dependent on none, just put it in the current level + marked[txIndex], currLevelHasTx = depth, true case dep != nil && len(dep.TxIndexes) > 0: // dependent on others @@ -395,19 +418,22 @@ func NewTxLevels(all []*PEVMTxRequest, dag types.TxDAG) TxLevels { } if prevLevel < 0 { // broken DAG, just ignored it - enlargeLevelsIfNeeded(currLevel, &levels) - levels[currLevel] = append(levels[currLevel], tx) - marked[tx.txIndex] = currLevel + marked[txIndex], currLevelHasTx = depth, true continue } - enlargeLevelsIfNeeded(prevLevel+1, &levels) - levels[prevLevel+1] = append(levels[prevLevel+1], tx) // record the level of this tx - marked[tx.txIndex] = prevLevel + 1 + marked[txIndex] = prevLevel + 1 + if marked[txIndex] > depth { + depth, currLevelHasTx = marked[txIndex], true + } default: panic("unexpected case") } } - return levels + // check if the last level has any transaction, to avoid the empty level + if !currLevelHasTx { + depth-- + } + return marked, depth + 1 } diff --git a/core/parallel_state_scheduler_test.go b/core/parallel_state_scheduler_test.go index fd78f37f7f..9f0c553f50 100644 --- a/core/parallel_state_scheduler_test.go +++ b/core/parallel_state_scheduler_test.go @@ -601,12 +601,44 @@ func TestNewTxLevels(t *testing.T) { assertEqual(levels([]uint64{1, 2, 3, 4, 5}, [][]int{nil, nil, nil, {-2}, {-2}}), [][]uint64{{1, 2, 3}, {4}, {5}}, t) // case 9: loop-back txdag - assertEqual(levels([]uint64{1, 2, 3, 4}, [][]int{{1}, nil, {0}, nil}), [][]uint64{{1, 2, 4}, {3}}, t) + assertEqual(levels([]uint64{1, 2, 3, 4}, [][]int{{1}, nil, {0}, nil}), [][]uint64{{1, 2}, {3, 4}}, t) + + // case 10: nonedependent txs + execlude txs + nonedependent txs + assertEqual(levels([]uint64{1, 2, 3, 4, 5}, [][]int{nil, nil, {-2}, nil, nil}), [][]uint64{{1, 2}, {3}, {4, 5}}, t) +} + +func TestBuildLevels(t *testing.T) { + var ( + marks map[int]int + depth int + ) + // case 1: 1 excluded tx + n no dependencies txs + n dependencies txs + 1 all-dependencies tx + marks, depth = levelMarks([]uint64{1, 2, 3, 4, 5, 6, 7}, [][]int{{-1}, nil, nil, nil, {0, 1}, {2}, {-2}}) + assertEqualMarks(marks, map[int]int{0: 0, 1: 1, 2: 1, 3: 1, 4: 2, 5: 2, 6: 3}, t) + if depth != 4 { + t.Fatalf("expected depth: 4, got depth: %d", depth) + } + // case 2: nonedependent txs + execlude txs + nonedependent txs + marks, depth = levelMarks([]uint64{1, 2, 3, 4, 5}, [][]int{nil, nil, {-2}, nil, nil}) + assertEqualMarks(marks, map[int]int{0: 0, 1: 0, 2: 1, 3: 2, 4: 2}, t) + if depth != 3 { + t.Fatalf("expected depth: 3, got depth: %d", depth) + } + // case 3: (broken TxDAG) n dependent txs + 1 execlude tx + none dependent txs + marks, depth = levelMarks([]uint64{1, 2, 3, 4, 5}, [][]int{{1}, {2}, {-1}, nil, nil}) + assertEqualMarks(marks, map[int]int{0: 0, 1: 0, 2: 1, 3: 2, 4: 2}, t) + if depth != 3 { + t.Fatalf("expected depth: 3, got depth: %d", depth) + } } func TestMultiLevel(t *testing.T) { // case 7: 1 excluded tx + n no dependencies txs + n dependencies txs + 1 all-dependencies tx - assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6, 7, 8}, [][]int{nil, nil, nil, {0}, nil, {1}, nil, {2}}), [][]uint64{{1, 2, 3, 5, 7}, {4, 6, 8}}, t) + assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6, 7, 8}, [][]int{nil, nil, nil, {0}, nil, {1}, nil, {2}}), [][]uint64{{1, 2, 3}, {4, 5, 6, 7, 8}}, t) +} + +func levelMarks(nonces []uint64, txdag [][]int) (map[int]int, int) { + return BuildTxLevels(len(nonces), int2txdag(txdag)) } func levels(nonces []uint64, txdag [][]int) TxLevels { @@ -650,6 +682,17 @@ func int2txdag(txdag [][]int) types.TxDAG { return &dag } +func assertEqualMarks(actual map[int]int, expected map[int]int, t *testing.T) { + if len(actual) != len(expected) { + t.Fatalf("expected %d marks, got %d marks", len(expected), len(actual)) + } + for i, mark := range actual { + if expected[i] != mark { + t.Fatalf("expected mark[%d]: %d, got mark[%d]: %d", i, expected[i], i, mark) + } + } +} + func assertEqual(actual TxLevels, expected [][]uint64, t *testing.T) { if len(actual) != len(expected) { t.Fatalf("expected %d levels, got %d levels", len(expected), len(actual)) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index a0d2ebb820..7b4a2cc645 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -41,6 +41,7 @@ type Config struct { TxDAG types.TxDAG EnableParallelUnorderedMerge bool // Whether to enable unordered merge in parallel mode EnableTxParallelMerge bool // Whether to enable parallel merge in parallel mode + TxDAGMaxDepthRatio float64 } // ScopeContext contains the things that are per-call, such as stack and memory, diff --git a/eth/backend.go b/eth/backend.go index df71708182..0c83e36bf2 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -242,6 +242,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { EnableTxParallelMerge: config.ParallelTxParallelMerge, ParallelTxNum: config.ParallelTxNum, EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, + TxDAGMaxDepthRatio: config.ParallelTxDAGMaxDepthRatio, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 1d43484abc..27f56bf3cb 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -219,14 +219,14 @@ 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 - EnableParallelTxDAG bool - ParallelTxDAGFile string - ParallelTxUnorderedMerge bool // Whether to enable unordered merge in parallel mode - ParallelTxParallelMerge bool - + 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 + ParallelTxUnorderedMerge bool // Whether to enable unordered merge in parallel mode + ParallelTxParallelMerge bool + ParallelTxDAGMaxDepthRatio float64 } // CreateConsensusEngine creates a consensus engine for the given chain config. From 921afc545f40dfdbd837db2d99f04aa74e08ccad Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:22:35 +0800 Subject: [PATCH 103/104] add metrics for parallel processor (#254) Co-authored-by: andyzhang2023 --- core/blockchain.go | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/core/blockchain.go b/core/blockchain.go index f18e0a3f26..a7fc5cd20c 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -99,6 +99,18 @@ var ( txDAGGenerateTimer = metrics.NewRegisteredTimer("chain/block/txdag/gen", nil) txDAGReaderChanGauge = metrics.NewRegisteredGauge("chain/block/txdag/reader/chan", nil) + // expensive metrics + // metrics of reasons why a block is processed in a parallel EVM or serial EVM + parallelConditionTooDeep = metrics.NewRegisteredMeter("chain/parallel/condition/toodeep", nil) + parallelConditionTxDAGMiss = metrics.NewRegisteredMeter("chain/parallel/condition/txdagmiss", nil) + parallelConditionByzantium = metrics.NewRegisteredMeter("chain/parallel/condition/byzantium", nil) + + // metrics to identify whether a block is processed in parallel EVM or serial EVM + parallelInSequencial = metrics.NewRegisteredMeter("chain/parallel/sequencial", nil) + parallelTxDepth = metrics.NewRegisteredMeter("chain/parallel/txdepth", nil) + // TxDepthRatio = TxDepth / TxNum * 100 + parallelTxDepthRatio = metrics.NewRegisteredGauge("chain/parallel/txdepth/ratio", nil) + parallelTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/txs", nil) parallelEnableMeter = metrics.NewRegisteredMeter("chain/parallel/enable", nil) parallelFallbackMeter = metrics.NewRegisteredMeter("chain/parallel/fallback", nil) @@ -1766,11 +1778,36 @@ func (bc *BlockChain) useSerialProcessor(block *types.Block) (bool, bool) { // if the dependencies are too deep, we will fallback to serial processing txCount := len(block.Transactions()) _, depth := BuildTxLevels(txCount, bc.vmConfig.TxDAG) - tooDeep := float64(depth)/float64(txCount) > bc.vmConfig.TxDAGMaxDepthRatio + var depthRatio float64 + + if txCount > 0 { + depthRatio = float64(depth) / float64(txCount) + } else { + depthRatio = 0 + } + tooDeep := depthRatio > bc.vmConfig.TxDAGMaxDepthRatio isByzantium := bc.chainConfig.IsByzantium(block.Number()) txDAGMissButNecessary := bc.vmConfig.TxDAG == nil && (bc.vmConfig.EnableParallelUnorderedMerge || bc.vmConfig.EnableTxParallelMerge) useSerialProcessor := !bc.vmConfig.EnableParallelExec || txDAGMissButNecessary || tooDeep || !isByzantium + + // mark the metrics + defer func() { + parallelTxDepth.Mark(int64(depth)) + parallelTxDepthRatio.Update(int64(depthRatio * 100)) + // put reasons in expensive metrics + if metrics.EnabledExpensive { + if tooDeep { + parallelConditionTooDeep.Mark(1) + } + if bc.vmConfig.TxDAG == nil { + parallelConditionTxDAGMiss.Mark(1) + } + if isByzantium { + parallelConditionByzantium.Mark(1) + } + } + }() return useSerialProcessor, tooDeep } @@ -2037,6 +2074,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) // Process block using the parent state as reference point pstart = time.Now() if useSerialProcessor { + parallelInSequencial.Mark(1) receipts, logs, usedGas, err = bc.serialProcessor.Process(block, statedb, bc.vmConfig) blockProcessedInParallel = false } else { From 48aaff629d86d4854c3a2f8ba517ef53e1f72cdb Mon Sep 17 00:00:00 2001 From: andyzhang2023 <147463846+andyzhang2023@users.noreply.github.com> Date: Thu, 16 Jan 2025 19:47:41 +0800 Subject: [PATCH 104/104] Fix pevm 20per performance issue (#256) Co-authored-by: andyzhang2023 --- cmd/utils/flags.go | 2 ++ core/blockchain.go | 82 +++++++++++++++++++++++++++++++--------------- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2e47491eb9..a75076edcc 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2065,6 +2065,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(ParallelTxDATMaxDepthRatioFlag.Name) { cfg.ParallelTxDAGMaxDepthRatio = ctx.Float64(ParallelTxDATMaxDepthRatioFlag.Name) + } else { + cfg.ParallelTxDAGMaxDepthRatio = ParallelTxDATMaxDepthRatioFlag.Value } if ctx.IsSet(ParallelTxDAGSenderPrivFlag.Name) { diff --git a/core/blockchain.go b/core/blockchain.go index a7fc5cd20c..8cf937dcdf 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -107,7 +107,7 @@ var ( // metrics to identify whether a block is processed in parallel EVM or serial EVM parallelInSequencial = metrics.NewRegisteredMeter("chain/parallel/sequencial", nil) - parallelTxDepth = metrics.NewRegisteredMeter("chain/parallel/txdepth", nil) + parallelTxDepth = metrics.NewRegisteredGauge("chain/parallel/txdepth", nil) // TxDepthRatio = TxDepth / TxNum * 100 parallelTxDepthRatio = metrics.NewRegisteredGauge("chain/parallel/txdepth/ratio", nil) @@ -582,7 +582,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if bc.vmConfig.EnableParallelExec { bc.processor = newPEVMProcessor(chainConfig, bc, engine) bc.serialProcessor = NewStateProcessor(chainConfig, bc, engine) - log.Info("Parallel V2 enabled", "parallelNum", ParallelNum()) + log.Info("Parallel V2 enabled", "parallelNum", ParallelNum(), "TxDepthRation", bc.vmConfig.TxDAGMaxDepthRatio) } else { bc.processor = NewStateProcessor(chainConfig, bc, engine) bc.serialProcessor = bc.processor @@ -1773,27 +1773,26 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) { return bc.insertChain(chain, true) } -func (bc *BlockChain) useSerialProcessor(block *types.Block) (bool, bool) { - // findout whether or not the dependencies of the block are too deep to be processed - // if the dependencies are too deep, we will fallback to serial processing - txCount := len(block.Transactions()) - _, depth := BuildTxLevels(txCount, bc.vmConfig.TxDAG) - var depthRatio float64 - - if txCount > 0 { - depthRatio = float64(depth) / float64(txCount) - } else { - depthRatio = 0 - } - tooDeep := depthRatio > bc.vmConfig.TxDAGMaxDepthRatio - isByzantium := bc.chainConfig.IsByzantium(block.Number()) - - txDAGMissButNecessary := bc.vmConfig.TxDAG == nil && (bc.vmConfig.EnableParallelUnorderedMerge || bc.vmConfig.EnableTxParallelMerge) - useSerialProcessor := !bc.vmConfig.EnableParallelExec || txDAGMissButNecessary || tooDeep || !isByzantium - +// userSerialProcessor decides whether or not to use serial processor for the block +// these are the reasons to use serial processor: +// 0. the parallel flag is not enabled +// 1. the block is empty +// 2. the block is not byzantium +// 3. the block is too deep in the dependency graph +// 4. the TxDAG is nil and parallel merge is enabled +func (bc *BlockChain) useSerialProcessor(block *types.Block) (useSerialProcessor bool) { + var ( + enableParallel bool + txCount int + depth int + depthRatio float64 + tooDeep bool + isByzantium bool + txDAGMissButNecessary bool + ) // mark the metrics defer func() { - parallelTxDepth.Mark(int64(depth)) + parallelTxDepth.Update(int64(depth)) parallelTxDepthRatio.Update(int64(depthRatio * 100)) // put reasons in expensive metrics if metrics.EnabledExpensive { @@ -1807,8 +1806,38 @@ func (bc *BlockChain) useSerialProcessor(block *types.Block) (bool, bool) { parallelConditionByzantium.Mark(1) } } + log.Info("run in parallel or sequencial", "block", block.Number(), "useSerialProcessor", useSerialProcessor, "depthRatio", depthRatio, "depth", depth, "txCount", txCount, "txDAG", bc.vmConfig.TxDAG != nil, "byz", isByzantium, "tooDeep", tooDeep, + "enableParallel", enableParallel, "enableParallelExec", bc.vmConfig.EnableParallelExec) }() - return useSerialProcessor, tooDeep + + txCount = len(block.Transactions()) + _, depth = BuildTxLevels(txCount, bc.vmConfig.TxDAG) + if txCount > 0 { + depthRatio = float64(depth) / float64(txCount) + } else { + depthRatio = 0 + } + // switch to serial processor if the block is empty + if txCount <= 1 { + return true + } + // switch to serial processor if parallel flag not enabled + if enableParallel = bc.vmConfig.EnableParallelExec; !enableParallel { + return true + } + // switch to serial processor if the block is not byzantium + if isByzantium = bc.chainConfig.IsByzantium(block.Number()); !isByzantium { + return true + } + // switch to serial processor if the block is too deep in the dependency graph + if tooDeep = (depthRatio > bc.vmConfig.TxDAGMaxDepthRatio); tooDeep { + return true + } + // switch to serial processor if the TxDAG is nil and parallel merge is enabled + if txDAGMissButNecessary = (bc.vmConfig.TxDAG == nil && (bc.vmConfig.EnableParallelUnorderedMerge || bc.vmConfig.EnableTxParallelMerge)); txDAGMissButNecessary { + return true + } + return false } // insertChain is the internal implementation of InsertChain, which assumes that @@ -2023,8 +2052,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) blockProcessedInParallel := false var ( - tooDeep, useSerialProcessor bool - depth int + useSerialProcessor bool ) // skip block process if we already have the state, receipts and logs from mining work if !(receiptExist && logExist && stateExist) { @@ -2038,8 +2066,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) bc.parseTxDAG(block) } - useSerialProcessor, tooDeep = bc.useSerialProcessor(block) - if !useSerialProcessor { + useSerialProcessor = bc.useSerialProcessor(block) + if !useSerialProcessor && bc.vmConfig.TxDAG != nil && bc.vmConfig.EnableTxParallelMerge { + //ParallelStateDB is used for parallel merge, and it works only when the block's TxDAG is not nil statedb, err = state.NewParallel(parent.Root, bc.stateCache, bc.snaps) } else { statedb, err = state.New(parent.Root, bc.stateCache, bc.snaps) @@ -2177,7 +2206,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) "storageUpdates", common.PrettyDuration(timers.StorageUpdates), "accountHashes", common.PrettyDuration(timers.AccountHashes), "storageHashes", common.PrettyDuration(timers.StorageHashes), - "tooDeep", tooDeep, "depth", depth, ) // Write the block to the chain and get the status.