diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 8a54a0e6ef04..bbc1c7becb4a 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -77,6 +77,21 @@ func isKeyWord(arg string) bool { return true } +func duplicates(methods map[string]abi.Method) map[string]bool { + var ( + identifiers = make(map[string]bool) + dups = make(map[string]bool) + ) + for _, method := range methods { + identifiers, dups := identifiers, dups + if identifiers[method.RawName] { + dups[method.RawName] = true + } + identifiers[method.RawName] = true + } + return dups +} + // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant // to be used as is in client code, but rather as an intermediate struct which // enforces compile time type safety and naming convention opposed to having to @@ -121,6 +136,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] callIdentifiers = make(map[string]bool) transactIdentifiers = make(map[string]bool) eventIdentifiers = make(map[string]bool) + dups = duplicates(evmABI.Methods) ) for _, input := range evmABI.Constructor.Inputs { @@ -132,12 +148,16 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] for _, original := range evmABI.Methods { // Normalize the method for capital cases and non-anonymous inputs/outputs normalized := original - normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) // Ensure there is no duplicated identifier var identifiers = callIdentifiers if !original.IsConstant() { identifiers = transactIdentifiers } + name := original.RawName + if dups[original.RawName] { + name = fmt.Sprintf("%s%x", original.RawName, original.ID) + } + normalizedName := methodNormalizer[lang](alias(aliases, name)) // Name shouldn't start with a digit. It will make the generated code invalid. if len(normalizedName) > 0 && unicode.IsDigit(rune(normalizedName[0])) { normalizedName = fmt.Sprintf("M%s", normalizedName) diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 1069f3d396d4..3b08bdf8900e 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1486,7 +1486,7 @@ var bindTests = []struct { } } }() - contract.Foo(auth, big.NewInt(1), big.NewInt(2)) + contract.Foo04bc52f8(auth, big.NewInt(1), big.NewInt(2)) sim.Commit() select { case n := <-resCh: @@ -1497,7 +1497,7 @@ var bindTests = []struct { t.Fatalf("Wait bar0 event timeout") } - contract.Foo0(auth, big.NewInt(1)) + contract.Foo2fbebd38(auth, big.NewInt(1)) sim.Commit() select { case n := <-resCh: diff --git a/arbitrum/apibackend.go b/arbitrum/apibackend.go index 8033063402b0..77af9239f37d 100644 --- a/arbitrum/apibackend.go +++ b/arbitrum/apibackend.go @@ -14,6 +14,8 @@ import ( "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" @@ -22,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/filters" @@ -32,6 +35,13 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +var ( + liveStatesReferencedCounter = metrics.NewRegisteredCounter("arb/apibackend/states/live/referenced", nil) + liveStatesDereferencedCounter = metrics.NewRegisteredCounter("arb/apibackend/states/live/dereferenced", nil) + recreatedStatesReferencedCounter = metrics.NewRegisteredCounter("arb/apibackend/states/recreated/referenced", nil) + recreatedStatesDereferencedCounter = metrics.NewRegisteredCounter("arb/apibackend/states/recreated/dereferenced", nil) +) + type APIBackend struct { b *Backend @@ -145,7 +155,7 @@ func (a *APIBackend) GetAPIs(filterSystem *filters.FilterSystem) []rpc.API { } func (a *APIBackend) BlockChain() *core.BlockChain { - return a.b.arb.BlockChain() + return a.b.BlockChain() } func (a *APIBackend) GetArbitrumNode() interface{} { @@ -444,21 +454,75 @@ func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types return nil, header, types.ErrUseFallback } bc := a.BlockChain() - stateFor := func(header *types.Header) (*state.StateDB, error) { - return bc.StateAt(header.Root) + stateFor := func(db state.Database, snapshots *snapshot.Tree) func(header *types.Header) (*state.StateDB, StateReleaseFunc, error) { + return func(header *types.Header) (*state.StateDB, StateReleaseFunc, error) { + if header.Root != (common.Hash{}) { + // Try referencing the root, if it isn't in dirties cache then Reference will have no effect + db.TrieDB().Reference(header.Root, common.Hash{}) + } + statedb, err := state.New(header.Root, db, snapshots) + if err != nil { + return nil, nil, err + } + if header.Root != (common.Hash{}) { + headerRoot := header.Root + return statedb, func() { db.TrieDB().Dereference(headerRoot) }, nil + } + return statedb, NoopStateRelease, nil + } } - state, lastHeader, err := FindLastAvailableState(ctx, bc, stateFor, header, nil, a.b.config.MaxRecreateStateDepth) + liveState, liveStateRelease, err := stateFor(bc.StateCache(), bc.Snapshots())(header) + if err == nil { + liveStatesReferencedCounter.Inc(1) + liveState.SetArbFinalizer(func(*state.ArbitrumExtraData) { + liveStateRelease() + liveStatesDereferencedCounter.Inc(1) + }) + return liveState, header, nil + } + // else err != nil => we don't need to call liveStateRelease + + // Create an ephemeral trie.Database for isolating the live one + // note: triedb cleans cache is disabled in trie.HashDefaults + // note: only states committed to diskdb can be found as we're creating new triedb + // note: snapshots are not used here + ephemeral := state.NewDatabaseWithConfig(a.ChainDb(), trie.HashDefaults) + lastState, lastHeader, lastStateRelease, err := FindLastAvailableState(ctx, bc, stateFor(ephemeral, nil), header, nil, a.b.config.MaxRecreateStateDepth) if err != nil { return nil, nil, err } + // make sure that we haven't found the state in diskdb if lastHeader == header { - return state, header, nil - } - state, err = AdvanceStateUpToBlock(ctx, bc, state, header, lastHeader, nil) + liveStatesReferencedCounter.Inc(1) + lastState.SetArbFinalizer(func(*state.ArbitrumExtraData) { + lastStateRelease() + liveStatesDereferencedCounter.Inc(1) + }) + return lastState, header, nil + } + defer lastStateRelease() + targetBlock := bc.GetBlockByNumber(header.Number.Uint64()) + if targetBlock == nil { + return nil, nil, errors.New("target block not found") + } + lastBlock := bc.GetBlockByNumber(lastHeader.Number.Uint64()) + if lastBlock == nil { + return nil, nil, errors.New("last block not found") + } + reexec := uint64(0) + checkLive := false + preferDisk := false // preferDisk is ignored in this case + statedb, release, err := eth.NewArbEthereum(a.b.arb.BlockChain(), a.ChainDb()).StateAtBlock(ctx, targetBlock, reexec, lastState, lastBlock, checkLive, preferDisk) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to recreate state: %w", err) } - return state, header, err + // we are setting finalizer instead of returning a StateReleaseFunc to avoid changing ethapi.Backend interface to minimize diff to upstream + recreatedStatesReferencedCounter.Inc(1) + statedb.SetArbFinalizer(func(*state.ArbitrumExtraData) { + release() + recreatedStatesDereferencedCounter.Inc(1) + }) + return statedb, header, err } func (a *APIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { @@ -468,6 +532,12 @@ func (a *APIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.Bloc func (a *APIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { header, err := a.HeaderByNumberOrHash(ctx, blockNrOrHash) + hash, ishash := blockNrOrHash.Hash() + bc := a.BlockChain() + // check if we are not trying to get recent state that is not yet triedb referenced or committed in Blockchain.writeBlockWithState + if ishash && header.Number.Cmp(bc.CurrentBlock().Number) > 0 && bc.GetCanonicalHash(header.Number.Uint64()) != hash { + return nil, nil, errors.New("requested block ahead of current block and the hash is not currently canonical") + } return a.stateAndHeaderFromHeader(ctx, header, err) } @@ -476,7 +546,7 @@ func (a *APIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexe return nil, nil, types.ErrUseFallback } // DEV: This assumes that `StateAtBlock` only accesses the blockchain and chainDb fields - return eth.NewArbEthereum(a.b.arb.BlockChain(), a.ChainDb()).StateAtBlock(ctx, block, reexec, base, checkLive, preferDisk) + return eth.NewArbEthereum(a.b.arb.BlockChain(), a.ChainDb()).StateAtBlock(ctx, block, reexec, base, nil, checkLive, preferDisk) } func (a *APIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { @@ -607,7 +677,7 @@ func (a *APIBackend) ChainConfig() *params.ChainConfig { } func (a *APIBackend) Engine() consensus.Engine { - return a.BlockChain().Engine() + return a.b.Engine() } func (b *APIBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { diff --git a/arbitrum/backend.go b/arbitrum/backend.go index b1bb9c89de03..e3f23ef71a95 100644 --- a/arbitrum/backend.go +++ b/arbitrum/backend.go @@ -3,7 +3,9 @@ package arbitrum import ( "context" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/arbitrum_types" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/types" @@ -12,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/shutdowncheck" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" ) type Backend struct { @@ -32,6 +35,8 @@ type Backend struct { chanTxs chan *types.Transaction chanClose chan struct{} //close coroutine chanNewBlock chan struct{} //create new L2 block unless empty + + filterSystem *filters.FilterSystem } func NewBackend(stack *node.Node, config *Config, chainDb ethdb.Database, publisher ArbInterface, filterConfig filters.Config) (*Backend, *filters.FilterSystem, error) { @@ -64,15 +69,22 @@ func NewBackend(stack *node.Node, config *Config, chainDb ethdb.Database, publis if err != nil { return nil, nil, err } + backend.filterSystem = filterSystem return backend, filterSystem, nil } -func (b *Backend) APIBackend() *APIBackend { - return b.apiBackend -} - -func (b *Backend) ChainDb() ethdb.Database { - return b.chainDb +func (b *Backend) AccountManager() *accounts.Manager { return b.stack.AccountManager() } +func (b *Backend) APIBackend() *APIBackend { return b.apiBackend } +func (b *Backend) APIs() []rpc.API { return b.apiBackend.GetAPIs(b.filterSystem) } +func (b *Backend) ArbInterface() ArbInterface { return b.arb } +func (b *Backend) BlockChain() *core.BlockChain { return b.arb.BlockChain() } +func (b *Backend) BloomIndexer() *core.ChainIndexer { return b.bloomIndexer } +func (b *Backend) ChainDb() ethdb.Database { return b.chainDb } +func (b *Backend) Engine() consensus.Engine { return b.arb.BlockChain().Engine() } +func (b *Backend) Stack() *node.Node { return b.stack } + +func (b *Backend) ResetWithGenesisBlock(gb *types.Block) { + b.arb.BlockChain().ResetWithGenesisBlock(gb) } func (b *Backend) EnqueueL2Message(ctx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error { @@ -83,14 +95,6 @@ func (b *Backend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscri return b.scope.Track(b.txFeed.Subscribe(ch)) } -func (b *Backend) Stack() *node.Node { - return b.stack -} - -func (b *Backend) ArbInterface() ArbInterface { - return b.arb -} - // TODO: this is used when registering backend as lifecycle in stack func (b *Backend) Start() error { b.startBloomHandlers(b.config.BloomBitsBlocks) diff --git a/arbitrum/recordingdb.go b/arbitrum/recordingdb.go index 701ab3a0ddc0..6da71a497e87 100644 --- a/arbitrum/recordingdb.go +++ b/arbitrum/recordingdb.go @@ -316,7 +316,12 @@ func (r *RecordingDatabase) PreimagesFromRecording(chainContextIf core.ChainCont } func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *types.Header, logFunc StateBuildingLogFunction) (*state.StateDB, error) { - state, currentHeader, err := FindLastAvailableState(ctx, r.bc, r.StateFor, header, logFunc, -1) + stateFor := func(header *types.Header) (*state.StateDB, StateReleaseFunc, error) { + state, err := r.StateFor(header) + // we don't use the release functor pattern here yet + return state, NoopStateRelease, err + } + state, currentHeader, _, err := FindLastAvailableState(ctx, r.bc, stateFor, header, logFunc, -1) if err != nil { return nil, err } diff --git a/arbitrum/recreatestate.go b/arbitrum/recreatestate.go index 1b2dbb1a1816..bd16c9d3a9a5 100644 --- a/arbitrum/recreatestate.go +++ b/arbitrum/recreatestate.go @@ -9,6 +9,7 @@ import ( "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/eth/tracers" "github.com/pkg/errors" ) @@ -16,51 +17,56 @@ var ( ErrDepthLimitExceeded = errors.New("state recreation l2 gas depth limit exceeded") ) +type StateReleaseFunc tracers.StateReleaseFunc + +var NoopStateRelease StateReleaseFunc = func() {} + type StateBuildingLogFunction func(targetHeader, header *types.Header, hasState bool) -type StateForHeaderFunction func(header *types.Header) (*state.StateDB, error) +type StateForHeaderFunction func(header *types.Header) (*state.StateDB, StateReleaseFunc, error) // finds last available state and header checking it first for targetHeader then looking backwards // if maxDepthInL2Gas is positive, it constitutes a limit for cumulative l2 gas used of the traversed blocks // else if maxDepthInL2Gas is -1, the traversal depth is not limited // otherwise only targetHeader state is checked and no search is performed -func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor StateForHeaderFunction, targetHeader *types.Header, logFunc StateBuildingLogFunction, maxDepthInL2Gas int64) (*state.StateDB, *types.Header, error) { +func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor StateForHeaderFunction, targetHeader *types.Header, logFunc StateBuildingLogFunction, maxDepthInL2Gas int64) (*state.StateDB, *types.Header, StateReleaseFunc, error) { genesis := bc.Config().ArbitrumChainParams.GenesisBlockNum currentHeader := targetHeader var state *state.StateDB var err error var l2GasUsed uint64 + release := NoopStateRelease for ctx.Err() == nil { lastHeader := currentHeader - state, err = stateFor(currentHeader) + state, release, err = stateFor(currentHeader) if err == nil { break } if maxDepthInL2Gas > 0 { receipts := bc.GetReceiptsByHash(currentHeader.Hash()) if receipts == nil { - return nil, lastHeader, fmt.Errorf("failed to get receipts for hash %v", currentHeader.Hash()) + return nil, lastHeader, nil, fmt.Errorf("failed to get receipts for hash %v", currentHeader.Hash()) } for _, receipt := range receipts { l2GasUsed += receipt.GasUsed - receipt.GasUsedForL1 } if l2GasUsed > uint64(maxDepthInL2Gas) { - return nil, lastHeader, ErrDepthLimitExceeded + return nil, lastHeader, nil, ErrDepthLimitExceeded } } else if maxDepthInL2Gas != InfiniteMaxRecreateStateDepth { - return nil, lastHeader, err + return nil, lastHeader, nil, err } if logFunc != nil { logFunc(targetHeader, currentHeader, false) } if currentHeader.Number.Uint64() <= genesis { - return nil, lastHeader, errors.Wrap(err, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d", targetHeader.Number.Uint64(), genesis)) + return nil, lastHeader, nil, errors.Wrap(err, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d", targetHeader.Number.Uint64(), genesis)) } currentHeader = bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1) if currentHeader == nil { - return nil, lastHeader, fmt.Errorf("chain doesn't contain parent of block %d hash %v", lastHeader.Number, lastHeader.Hash()) + return nil, lastHeader, nil, fmt.Errorf("chain doesn't contain parent of block %d hash %v", lastHeader.Number, lastHeader.Hash()) } } - return state, currentHeader, ctx.Err() + return state, currentHeader, release, ctx.Err() } func AdvanceStateByBlock(ctx context.Context, bc *core.BlockChain, state *state.StateDB, targetHeader *types.Header, blockToRecreate uint64, prevBlockHash common.Hash, logFunc StateBuildingLogFunction) (*state.StateDB, *types.Block, error) { diff --git a/core/blockchain.go b/core/blockchain.go index 790a860e3519..5f8c4aca688e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1071,7 +1071,8 @@ func (bc *BlockChain) Stop() { // - HEAD: So we don't need to reprocess any blocks in the general case // - HEAD-1: So we don't do large reorgs if our HEAD becomes an uncle // - HEAD-127: So we have a hard limit on the number of blocks reexecuted - if !bc.cacheConfig.TrieDirtyDisabled { + // It applies for both full node and sparse archive node + if !bc.cacheConfig.TrieDirtyDisabled || bc.cacheConfig.MaxNumberOfBlocksToSkipStateSaving > 0 || bc.cacheConfig.MaxAmountOfGasToSkipStateSaving > 0 { triedb := bc.triedb for _, offset := range []uint64{0, 1, bc.cacheConfig.TriesInMemory - 1, math.MaxUint64} { @@ -1496,7 +1497,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. return nil } // If we're running an archive node, flush - // If MaxNumberOfBlocksToSkipStateSaving or MaxAmountOfGasToSkipStateSaving is not zero, then flushing of some blocks will be skipped: + // Sparse archive: if MaxNumberOfBlocksToSkipStateSaving or MaxAmountOfGasToSkipStateSaving is not zero, then flushing of some blocks will be skipped: // * at most MaxNumberOfBlocksToSkipStateSaving block state commits will be skipped // * sum of gas used in skipped blocks will be at most MaxAmountOfGasToSkipStateSaving archiveNode := bc.cacheConfig.TrieDirtyDisabled @@ -1526,7 +1527,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // we are skipping saving the trie to diskdb, so we need to keep the trie in memory and garbage collect it later } - // Full node or archive node that's not keeping all states, do proper garbage collection + // Full node or sparse archive node that's not keeping all states, do proper garbage collection bc.triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive bc.triegc.Push(trieGcEntry{root, block.Header().Time}, -int64(block.NumberU64())) diff --git a/core/state/journal_arbitrum.go b/core/state/journal_arbitrum.go index 939b0167bbc9..0f47fc271500 100644 --- a/core/state/journal_arbitrum.go +++ b/core/state/journal_arbitrum.go @@ -7,7 +7,7 @@ type wasmActivation struct { } func (ch wasmActivation) revert(s *StateDB) { - delete(s.activatedWasms, ch.moduleHash) + delete(s.arbExtraData.activatedWasms, ch.moduleHash) } func (ch wasmActivation) dirtied() *common.Address { diff --git a/core/state/statedb.go b/core/state/statedb.go index 2e7484fa8054..e71a66f08417 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -63,13 +63,7 @@ type revision struct { // must be created with new root and updated database for accessing post- // commit states. type StateDB struct { - // Arbitrum - unexpectedBalanceDelta *big.Int // total balance change across all accounts - userWasms UserWasms // user wasms encountered during execution - openWasmPages uint16 // number of pages currently open - everWasmPages uint16 // largest number of pages ever allocated during this tx's execution - deterministic bool // whether the order in which deletes are committed should be deterministic - activatedWasms map[common.Hash]*ActivatedWasm // newly activated WASMs + arbExtraData *ArbitrumExtraData // must be a pointer - can't be a part of StateDB allocation, otherwise its finalizer might not get called db Database prefetcher *triePrefetcher @@ -150,6 +144,8 @@ type StateDB struct { // Testing hooks onCommit func(states *triestate.Set) // Hook invoked when commit is performed + + deterministic bool } // New creates a new state from a given trie. @@ -159,10 +155,12 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return nil, err } sdb := &StateDB{ - unexpectedBalanceDelta: new(big.Int), - openWasmPages: 0, - everWasmPages: 0, - activatedWasms: make(map[common.Hash]*ActivatedWasm), + arbExtraData: &ArbitrumExtraData{ + unexpectedBalanceDelta: new(big.Int), + openWasmPages: 0, + everWasmPages: 0, + activatedWasms: make(map[common.Hash]*ActivatedWasm), + }, db: db, trie: tr, @@ -393,7 +391,7 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool { func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - s.unexpectedBalanceDelta.Add(s.unexpectedBalanceDelta, amount) + s.arbExtraData.unexpectedBalanceDelta.Add(s.arbExtraData.unexpectedBalanceDelta, amount) stateObject.AddBalance(amount) } } @@ -402,7 +400,7 @@ func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - s.unexpectedBalanceDelta.Sub(s.unexpectedBalanceDelta, amount) + s.arbExtraData.unexpectedBalanceDelta.Sub(s.arbExtraData.unexpectedBalanceDelta, amount) stateObject.SubBalance(amount) } } @@ -414,8 +412,8 @@ func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) { amount = big.NewInt(0) } prevBalance := stateObject.Balance() - s.unexpectedBalanceDelta.Add(s.unexpectedBalanceDelta, amount) - s.unexpectedBalanceDelta.Sub(s.unexpectedBalanceDelta, prevBalance) + s.arbExtraData.unexpectedBalanceDelta.Add(s.arbExtraData.unexpectedBalanceDelta, amount) + s.arbExtraData.unexpectedBalanceDelta.Sub(s.arbExtraData.unexpectedBalanceDelta, prevBalance) stateObject.SetBalance(amount) } } @@ -424,7 +422,7 @@ func (s *StateDB) ExpectBalanceBurn(amount *big.Int) { if amount.Sign() < 0 { panic(fmt.Sprintf("ExpectBalanceBurn called with negative amount %v", amount)) } - s.unexpectedBalanceDelta.Add(s.unexpectedBalanceDelta, amount) + s.arbExtraData.unexpectedBalanceDelta.Add(s.arbExtraData.unexpectedBalanceDelta, amount) } func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { @@ -486,7 +484,7 @@ func (s *StateDB) SelfDestruct(addr common.Address) { }) stateObject.markSelfdestructed() - s.unexpectedBalanceDelta.Sub(s.unexpectedBalanceDelta, stateObject.data.Balance) + s.arbExtraData.unexpectedBalanceDelta.Sub(s.arbExtraData.unexpectedBalanceDelta, stateObject.data.Balance) stateObject.data.Balance = new(big.Int) } @@ -724,10 +722,12 @@ func (s *StateDB) CreateAccount(addr common.Address) { func (s *StateDB) Copy() *StateDB { // Copy all the basic fields, initialize the memory ones state := &StateDB{ - unexpectedBalanceDelta: new(big.Int).Set(s.unexpectedBalanceDelta), - activatedWasms: make(map[common.Hash]*ActivatedWasm, len(s.activatedWasms)), - openWasmPages: s.openWasmPages, - everWasmPages: s.everWasmPages, + arbExtraData: &ArbitrumExtraData{ + unexpectedBalanceDelta: new(big.Int).Set(s.arbExtraData.unexpectedBalanceDelta), + activatedWasms: make(map[common.Hash]*ActivatedWasm, len(s.arbExtraData.activatedWasms)), + openWasmPages: s.arbExtraData.openWasmPages, + everWasmPages: s.arbExtraData.everWasmPages, + }, db: s.db, trie: s.db.CopyTrie(s.trie), @@ -820,15 +820,15 @@ func (s *StateDB) Copy() *StateDB { state.transientStorage = s.transientStorage.Copy() // Arbitrum: copy wasm calls and activated WASMs - if s.userWasms != nil { - state.userWasms = make(UserWasms, len(s.userWasms)) - for call, wasm := range s.userWasms { - state.userWasms[call] = wasm + if s.arbExtraData.userWasms != nil { + state.arbExtraData.userWasms = make(UserWasms, len(s.arbExtraData.userWasms)) + for call, wasm := range s.arbExtraData.userWasms { + state.arbExtraData.userWasms[call] = wasm } } - for moduleHash, info := range s.activatedWasms { + for moduleHash, info := range s.arbExtraData.activatedWasms { // It's fine to skip a deep copy since activations are immutable. - state.activatedWasms[moduleHash] = info + state.arbExtraData.activatedWasms[moduleHash] = info } // If there's a prefetcher running, make an inactive copy of it that can @@ -844,7 +844,7 @@ func (s *StateDB) Copy() *StateDB { func (s *StateDB) Snapshot() int { id := s.nextRevisionId s.nextRevisionId++ - s.validRevisions = append(s.validRevisions, revision{id, s.journal.length(), new(big.Int).Set(s.unexpectedBalanceDelta)}) + s.validRevisions = append(s.validRevisions, revision{id, s.journal.length(), new(big.Int).Set(s.arbExtraData.unexpectedBalanceDelta)}) return id } @@ -859,7 +859,7 @@ func (s *StateDB) RevertToSnapshot(revid int) { } revision := s.validRevisions[idx] snapshot := revision.journalIndex - s.unexpectedBalanceDelta = new(big.Int).Set(revision.unexpectedBalanceDelta) + s.arbExtraData.unexpectedBalanceDelta = new(big.Int).Set(revision.unexpectedBalanceDelta) // Replay the journal to undo changes and remove invalidated snapshots s.journal.revert(s, snapshot) @@ -1006,8 +1006,8 @@ func (s *StateDB) SetTxContext(thash common.Hash, ti int) { s.txIndex = ti // Arbitrum: clear memory charging state for new tx - s.openWasmPages = 0 - s.everWasmPages = 0 + s.arbExtraData.openWasmPages = 0 + s.arbExtraData.everWasmPages = 0 } func (s *StateDB) clearJournalAndRefund() { @@ -1284,11 +1284,11 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er } // Arbitrum: write Stylus programs to disk - for moduleHash, info := range s.activatedWasms { + for moduleHash, info := range s.arbExtraData.activatedWasms { rawdb.WriteActivation(codeWriter, moduleHash, info.Asm, info.Module) } - if len(s.activatedWasms) > 0 { - s.activatedWasms = make(map[common.Hash]*ActivatedWasm) + if len(s.arbExtraData.activatedWasms) > 0 { + s.arbExtraData.activatedWasms = make(map[common.Hash]*ActivatedWasm) } if codeWriter.ValueSize() > 0 { @@ -1348,7 +1348,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er s.snap = nil } - s.unexpectedBalanceDelta.Set(new(big.Int)) + s.arbExtraData.unexpectedBalanceDelta.Set(new(big.Int)) if root == (common.Hash{}) { root = types.EmptyRootHash diff --git a/core/state/statedb_arbitrum.go b/core/state/statedb_arbitrum.go index 7626e8ddb4ed..876a99f815d8 100644 --- a/core/state/statedb_arbitrum.go +++ b/core/state/statedb_arbitrum.go @@ -23,6 +23,7 @@ import ( "math/big" "errors" + "runtime" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -74,11 +75,11 @@ func NewStylusPrefix(dictionary byte) []byte { } func (s *StateDB) ActivateWasm(moduleHash common.Hash, asm, module []byte) { - _, exists := s.activatedWasms[moduleHash] + _, exists := s.arbExtraData.activatedWasms[moduleHash] if exists { return } - s.activatedWasms[moduleHash] = &ActivatedWasm{ + s.arbExtraData.activatedWasms[moduleHash] = &ActivatedWasm{ Asm: asm, Module: module, } @@ -88,7 +89,7 @@ func (s *StateDB) ActivateWasm(moduleHash common.Hash, asm, module []byte) { } func (s *StateDB) GetActivatedAsm(moduleHash common.Hash) []byte { - info, exists := s.activatedWasms[moduleHash] + info, exists := s.arbExtraData.activatedWasms[moduleHash] if exists { return info.Asm } @@ -100,7 +101,7 @@ func (s *StateDB) GetActivatedAsm(moduleHash common.Hash) []byte { } func (s *StateDB) GetActivatedModule(moduleHash common.Hash) []byte { - info, exists := s.activatedWasms[moduleHash] + info, exists := s.arbExtraData.activatedWasms[moduleHash] if exists { return info.Module } @@ -112,27 +113,27 @@ func (s *StateDB) GetActivatedModule(moduleHash common.Hash) []byte { } func (s *StateDB) GetStylusPages() (uint16, uint16) { - return s.openWasmPages, s.everWasmPages + return s.arbExtraData.openWasmPages, s.arbExtraData.everWasmPages } func (s *StateDB) GetStylusPagesOpen() uint16 { - return s.openWasmPages + return s.arbExtraData.openWasmPages } func (s *StateDB) SetStylusPagesOpen(open uint16) { - s.openWasmPages = open + s.arbExtraData.openWasmPages = open } // Tracks that `new` additional pages have been opened, returning the previous counts func (s *StateDB) AddStylusPages(new uint16) (uint16, uint16) { open, ever := s.GetStylusPages() - s.openWasmPages = common.SaturatingUAdd(open, new) - s.everWasmPages = common.MaxInt(ever, s.openWasmPages) + s.arbExtraData.openWasmPages = common.SaturatingUAdd(open, new) + s.arbExtraData.everWasmPages = common.MaxInt(ever, s.arbExtraData.openWasmPages) return open, ever } func (s *StateDB) AddStylusPagesEver(new uint16) { - s.everWasmPages = common.SaturatingUAdd(s.everWasmPages, new) + s.arbExtraData.everWasmPages = common.SaturatingUAdd(s.arbExtraData.everWasmPages, new) } func NewDeterministic(root common.Hash, db Database) (*StateDB, error) { @@ -148,13 +149,25 @@ func (s *StateDB) Deterministic() bool { return s.deterministic } +type ArbitrumExtraData struct { + unexpectedBalanceDelta *big.Int // total balance change across all accounts + userWasms UserWasms // user wasms encountered during execution + openWasmPages uint16 // number of pages currently open + everWasmPages uint16 // largest number of pages ever allocated during this tx's execution + activatedWasms map[common.Hash]*ActivatedWasm // newly activated WASMs +} + +func (s *StateDB) SetArbFinalizer(f func(*ArbitrumExtraData)) { + runtime.SetFinalizer(s.arbExtraData, f) +} + func (s *StateDB) GetCurrentTxLogs() []*types.Log { return s.logs[s.thash] } // GetUnexpectedBalanceDelta returns the total unexpected change in balances since the last commit to the database. func (s *StateDB) GetUnexpectedBalanceDelta() *big.Int { - return new(big.Int).Set(s.unexpectedBalanceDelta) + return new(big.Int).Set(s.arbExtraData.unexpectedBalanceDelta) } func (s *StateDB) GetSelfDestructs() []common.Address { @@ -218,12 +231,12 @@ func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.H type UserWasms map[common.Hash]ActivatedWasm func (s *StateDB) StartRecording() { - s.userWasms = make(UserWasms) + s.arbExtraData.userWasms = make(UserWasms) } func (s *StateDB) RecordProgram(moduleHash common.Hash) { - if s.userWasms != nil { - s.userWasms[moduleHash] = ActivatedWasm{ + if s.arbExtraData.userWasms != nil { + s.arbExtraData.userWasms[moduleHash] = ActivatedWasm{ Asm: s.GetActivatedAsm(moduleHash), Module: s.GetActivatedModule(moduleHash), } @@ -231,5 +244,5 @@ func (s *StateDB) RecordProgram(moduleHash common.Hash) { } func (s *StateDB) UserWasms() UserWasms { - return s.userWasms + return s.arbExtraData.userWasms } diff --git a/eth/api_backend.go b/eth/api_backend.go index 7f88147df5dd..6104bccc4d2e 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -414,7 +414,7 @@ func (b *EthAPIBackend) StartMining() error { } func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) { - return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk) + return b.eth.stateAtBlock(ctx, block, reexec, base, nil, readOnly, preferDisk) } func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 72061c7fe318..439d88c2d8c4 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -30,14 +30,20 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/trie" ) +var ( + recreatedStatesCounter = metrics.NewRegisteredCounter("eth/stateaccessor/recreated/states", nil) + recreatedBytesMeter = metrics.NewRegisteredMeter("eth/stateaccessor/recreated/bytes", nil) +) + // noopReleaser is returned in case there is no operation expected // for releasing state. var noopReleaser = tracers.StateReleaseFunc(func() {}) -func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { +func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, baseBlock *types.Block, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { var ( current *types.Block database state.Database @@ -51,8 +57,10 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u // The state is available in live database, create a reference // on top to prevent garbage collection and return a release // function to deref it. + + // Try referencing the root, if it isn't in dirties cache then Reference will have no effect + eth.blockchain.TrieDB().Reference(block.Root(), common.Hash{}) if statedb, err = eth.blockchain.StateAt(block.Root()); err == nil { - eth.blockchain.TrieDB().Reference(block.Root(), common.Hash{}) return statedb, func() { eth.blockchain.TrieDB().Dereference(block.Root()) }, nil @@ -61,7 +69,9 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u // The state is both for reading and writing, or it's unavailable in disk, // try to construct/recover the state over an ephemeral trie.Database for // isolating the live one. - if base != nil { + if baseBlock != nil { + current, statedb, database, triedb, report = baseBlock, base, base.Database(), base.Database().TrieDB(), false + } else if base != nil { if preferDisk { // Create an ephemeral trie.Database for isolating the live one. Otherwise // the internal junks created by tracing will be persisted into the disk. @@ -70,7 +80,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u database = state.NewDatabaseWithConfig(eth.chainDb, trie.HashDefaults) if statedb, err = state.New(block.Root(), database, nil); err == nil { log.Info("Found disk backend for state trie", "root", block.Root(), "number", block.Number()) - return statedb, func() { database.TrieDB().Close() }, nil + return statedb, noopReleaser, nil } } // The optional base statedb is given, mark the start point as parent block @@ -93,7 +103,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u if !readOnly { statedb, err = state.New(current.Root(), database, nil) if err == nil { - return statedb, func() { database.TrieDB().Close() }, nil + return statedb, noopReleaser, nil } } // Database does not have the state for the given block, try to regenerate @@ -167,10 +177,13 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u } parent = root } + _, nodes, imgs := triedb.Size() // all memory is contained within the nodes return in hashdb if report { - _, nodes, imgs := triedb.Size() // all memory is contained within the nodes return in hashdb log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) } + recreatedStatesCounter.Inc(1) + recreatedBytesMeter.Mark(int64(nodes)) + return statedb, func() { triedb.Dereference(block.Root()) }, nil } @@ -202,22 +215,24 @@ func (eth *Ethereum) pathState(block *types.Block) (*state.StateDB, func(), erro // - reexec: The maximum number of blocks to reprocess trying to obtain the desired state // - base: If the caller is tracing multiple blocks, the caller can provide the parent // state continuously from the callsite. +// - baseBlock: Arbitrum specific: caller can provide the block from which reprocessing should +// start, if baseBlock is provided then base parameter is ignored // - readOnly: If true, then the live 'blockchain' state database is used. No mutation should // be made from caller, e.g. perform Commit or other 'save-to-disk' changes. // Otherwise, the trash generated by caller may be persisted permanently. // - preferDisk: This arg can be used by the caller to signal that even though the 'base' is // provided, it would be preferable to start from a fresh state, if we have it // on disk. -func (eth *Ethereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { +func (eth *Ethereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, baseBlock *types.Block, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { if eth.blockchain.TrieDB().Scheme() == rawdb.HashScheme { - return eth.hashState(ctx, block, reexec, base, readOnly, preferDisk) + return eth.hashState(ctx, block, reexec, base, baseBlock, readOnly, preferDisk) } return eth.pathState(block) } // arbitrum: exposing stateAtBlock function -func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { - return eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk) +func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, baseBlock *types.Block, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { + return eth.stateAtBlock(ctx, block, reexec, base, baseBlock, readOnly, preferDisk) } // stateAtTransaction returns the execution environment of a certain transaction. @@ -233,7 +248,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, } // Lookup the statedb of parent block from the live database, // otherwise regenerate it on the flight. - statedb, release, err := eth.stateAtBlock(ctx, parent, reexec, nil, true, false) + statedb, release, err := eth.stateAtBlock(ctx, parent, reexec, nil, nil, true, false) if err != nil { return nil, vm.BlockContext{}, nil, nil, err } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index cfbd683d894e..e8ba938d59ca 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -128,6 +128,10 @@ func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block if block == nil { return nil, fmt.Errorf("block %s not found", hash.Hex()) } + canonical := rawdb.ReadCanonicalHash(api.backend.ChainDb(), block.NumberU64()) + if hash != canonical { + return nil, fmt.Errorf("hash %s is not currently canonical", hash.Hex()) + } return block, nil } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2d3fe10d1172..744e43624bfc 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1178,13 +1178,14 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S func updateHeaderForPendingBlocks(blockNrOrHash rpc.BlockNumberOrHash, header *types.Header) *types.Header { if blockNrOrHash.BlockNumber != nil && *blockNrOrHash.BlockNumber == rpc.PendingBlockNumber { - headerCopy := *header + headerCopy := types.CopyHeader(header) now := uint64(time.Now().Unix()) if now > headerCopy.Time { headerCopy.Time = now } headerCopy.Number = new(big.Int).Add(headerCopy.Number, common.Big1) - return &headerCopy + headerCopy.ParentHash = header.Hash() + return headerCopy } return header } diff --git a/trie/triedb/hashdb/database.go b/trie/triedb/hashdb/database.go index be83c32e65c2..78df83de2d5e 100644 --- a/trie/triedb/hashdb/database.go +++ b/trie/triedb/hashdb/database.go @@ -82,11 +82,6 @@ var Defaults = &Config{ // Database is an intermediate write layer between the trie data structures and // the disk database. The aim is to accumulate trie writes in-memory and only // periodically flush a couple tries to disk, garbage collecting the remainder. -// -// Note, the trie Database is **not** thread safe in its mutations, but it **is** -// thread safe in providing individual, independent node access. The rationale -// behind this split design is to provide read access to RPC handlers and sync -// servers even while the trie is executing expensive garbage collection. type Database struct { diskdb ethdb.Database // Persistent storage for matured trie nodes resolver ChildResolver // The handler to resolve children of nodes @@ -113,7 +108,7 @@ type Database struct { // cachedNode is all the information we know about a single cached trie node // in the memory database write layer. type cachedNode struct { - node []byte // Encoded node blob + node []byte // Encoded node blob, immutable parents uint32 // Number of live nodes referencing this one external map[common.Hash]struct{} // The set of external children flushPrev common.Hash // Previous node in the flush-list @@ -152,9 +147,9 @@ func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Databas } } -// insert inserts a simplified trie node into the memory database. -// All nodes inserted by this function will be reference tracked -// and in theory should only used for **trie nodes** insertion. +// insert inserts a trie node into the memory database. All nodes inserted by +// this function will be reference tracked. This function assumes the lock is +// already held. func (db *Database) insert(hash common.Hash, node []byte) { // If the node's already cached, skip if _, ok := db.dirties[hash]; ok { @@ -183,9 +178,9 @@ func (db *Database) insert(hash common.Hash, node []byte) { db.dirtiesSize += common.StorageSize(common.HashLength + len(node)) } -// Node retrieves an encoded cached trie node from memory. If it cannot be found +// node retrieves an encoded cached trie node from memory. If it cannot be found // cached, the method queries the persistent database for the content. -func (db *Database) Node(hash common.Hash) ([]byte, error) { +func (db *Database) node(hash common.Hash) ([]byte, error) { // It doesn't make sense to retrieve the metaroot if hash == (common.Hash{}) { return nil, errors.New("not found") @@ -198,11 +193,14 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { return enc, nil } } - // Retrieve the node from the dirty cache if available + // Retrieve the node from the dirty cache if available. db.lock.RLock() dirty := db.dirties[hash] db.lock.RUnlock() + // Return the cached node if it's found in the dirty set. + // The dirty.node field is immutable and safe to read it + // even without lock guard. if dirty != nil { memcacheDirtyHitMeter.Mark(1) memcacheDirtyReadMeter.Mark(int64(len(dirty.node))) @@ -223,18 +221,9 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { return nil, errors.New("not found") } -// Nodes retrieves the hashes of all the nodes cached within the memory database. -// This method is extremely expensive and should only be used to validate internal -// states in test code. -func (db *Database) Nodes() []common.Hash { - db.lock.RLock() - defer db.lock.RUnlock() - - var hashes = make([]common.Hash, 0, len(db.dirties)) - for hash := range db.dirties { - hashes = append(hashes, hash) - } - return hashes +// arbitrum: exposing hashdb.Database.Node for triedb.Database.Node currently used by arbitrum.RecordingKV.Get +func (db *Database) Node(hash common.Hash) ([]byte, error) { + return db.node(hash) } // Reference adds a new reference from a parent node to a child node. @@ -344,33 +333,28 @@ func (db *Database) dereference(hash common.Hash) { // Cap iteratively flushes old but still referenced trie nodes until the total // memory usage goes below the given threshold. -// -// Note, this method is a non-synchronized mutator. It is unsafe to call this -// concurrently with other mutators. func (db *Database) Cap(limit common.StorageSize) error { + db.lock.Lock() + defer db.lock.Unlock() + // Create a database batch to flush persistent data out. It is important that // outside code doesn't see an inconsistent state (referenced data removed from // memory cache during commit but not yet in persistent storage). This is ensured // by only uncaching existing data when the database write finalizes. - start := time.Now() batch := db.diskdb.NewBatch() - db.lock.RLock() - nodes, storage := len(db.dirties), db.dirtiesSize + nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now() // db.dirtiesSize only contains the useful data in the cache, but when reporting // the total memory consumption, the maintenance metadata is also needed to be // counted. size := db.dirtiesSize + common.StorageSize(len(db.dirties)*cachedNodeSize) size += db.childrenSize - db.lock.RUnlock() // Keep committing nodes from the flush-list until we're below allowance oldest := db.oldest for size > limit && oldest != (common.Hash{}) { // Fetch the oldest referenced node and push into the batch - db.lock.RLock() node := db.dirties[oldest] - db.lock.RUnlock() rawdb.WriteLegacyTrieNode(batch, oldest, node.node) // If we exceeded the ideal batch size, commit and reset @@ -396,9 +380,6 @@ func (db *Database) Cap(limit common.StorageSize) error { return err } // Write successful, clear out the flushed data - db.lock.Lock() - defer db.lock.Unlock() - for db.oldest != oldest { node := db.dirties[db.oldest] delete(db.dirties, db.oldest) @@ -429,14 +410,13 @@ func (db *Database) Cap(limit common.StorageSize) error { // Commit iterates over all the children of a particular node, writes them out // to disk, forcefully tearing down all references in both directions. As a side // effect, all pre-images accumulated up to this point are also written. -// -// Note, this method is a non-synchronized mutator. It is unsafe to call this -// concurrently with other mutators. func (db *Database) Commit(node common.Hash, report bool) error { if node == (common.Hash{}) { // There's no data to commit in this node return nil } + db.lock.Lock() + defer db.lock.Unlock() // Create a database batch to flush persistent data out. It is important that // outside code doesn't see an inconsistent state (referenced data removed from @@ -446,9 +426,7 @@ func (db *Database) Commit(node common.Hash, report bool) error { batch := db.diskdb.NewBatch() // Move the trie itself into the batch, flushing if enough data is accumulated - db.lock.RLock() nodes, storage := len(db.dirties), db.dirtiesSize - db.lock.RUnlock() uncacher := &cleaner{db} if err := db.commit(node, batch, uncacher); err != nil { @@ -461,8 +439,6 @@ func (db *Database) Commit(node common.Hash, report bool) error { return err } // Uncache any leftovers in the last batch - db.lock.Lock() - defer db.lock.Unlock() if err := batch.Replay(uncacher); err != nil { return err } @@ -490,9 +466,7 @@ func (db *Database) Commit(node common.Hash, report bool) error { // commit is the private locked version of Commit. func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error { // If the node does not exist, it's a previously committed node - db.lock.RLock() node, ok := db.dirties[hash] - db.lock.RUnlock() if !ok { return nil } @@ -513,13 +487,11 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane if err := batch.Write(); err != nil { return err } - db.lock.Lock() err := batch.Replay(uncacher) - batch.Reset() - db.lock.Unlock() if err != nil { return err } + batch.Reset() } return nil } @@ -588,7 +560,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool { func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { // Ensure the parent state is present and signal a warning if not. if parent != types.EmptyRootHash { - if blob, _ := db.Node(parent); len(blob) == 0 { + if blob, _ := db.node(parent); len(blob) == 0 { log.Error("parent state is not present") } } @@ -669,7 +641,7 @@ func (db *Database) Scheme() string { // Reader retrieves a node reader belonging to the given state root. // An error will be returned if the requested state is not available. func (db *Database) Reader(root common.Hash) (*reader, error) { - if _, err := db.Node(root); err != nil { + if _, err := db.node(root); err != nil { return nil, fmt.Errorf("state %#x is not available, %v", root, err) } return &reader{db: db}, nil @@ -680,9 +652,9 @@ type reader struct { db *Database } -// Node retrieves the trie node with the given node hash. -// No error will be returned if the node is not found. +// Node retrieves the trie node with the given node hash. No error will be +// returned if the node is not found. func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) { - blob, _ := reader.db.Node(hash) + blob, _ := reader.db.node(hash) return blob, nil }