@@ -30,73 +30,76 @@ import (
3030 "github.com/ethereum/go-ethereum/trie"
3131)
3232
33+ // noopReleaser is returned in case there is no operation expected
34+ // for releasing state.
35+ var noopReleaser = func () {}
36+
3337// StateAtBlock retrieves the state database associated with a certain block.
3438// If no state is locally available for the given block, a number of blocks
3539// are attempted to be reexecuted to generate the desired state. The optional
36- // base layer statedb can be passed then it's regarded as the statedb of the
40+ // base layer statedb can be provided which is regarded as the statedb of the
3741// parent block.
42+ //
43+ // An additional release function will be returned if the requested state is
44+ // available. The release is expected be invoked when the state is used, though
45+ // it can be noop in some cases, otherwise the resources leaking can happen.
46+ //
3847// Parameters:
39- // - block: The block for which we want the state (== state at the stateRoot of the parent)
40- // - reexec: The maximum number of blocks to reprocess trying to obtain the desired state
41- // - base: If the caller is tracing multiple blocks, the caller can provide the parent state
42- // continuously from the callsite.
43- // - checklive: if true, then the live 'blockchain' state database is used. If the caller want to
44- // perform Commit or other 'save-to-disk' changes, this should be set to false to avoid
45- // storing trash persistently
46- // - preferDisk: this arg can be used by the caller to signal that even though the 'base' is provided,
47- // it would be preferrable to start from a fresh state, if we have it on disk.
48- func (eth * Ethereum ) StateAtBlock (block * types.Block , reexec uint64 , base * state.StateDB , checkLive bool , preferDisk bool ) (statedb * state.StateDB , err error ) {
48+ // - block: The block for which we want the state(state = block.Root)
49+ // - reexec: The maximum number of blocks to reprocess trying to obtain the desired state
50+ // - base: If the caller is tracing multiple blocks, the caller can provide the parent
51+ // state continuously from the callsite.
52+ // - readOnly: If true, then the live 'blockchain' state database is used. No mutation should
53+ // be made from caller, e.g. perform Commit or other 'save-to-disk' changes.
54+ // Otherwise, the trash generated by caller may be persisted permanently.
55+ func (eth * Ethereum ) StateAtBlock (block * types.Block , reexec uint64 , base * state.StateDB , readOnly bool ) (statedb * state.StateDB , rel func (), err error ) {
4956 var (
5057 current * types.Block
5158 database state.Database
5259 report = true
5360 origin = block .NumberU64 ()
5461 )
55- // Check the live database first if we have the state fully available, use that.
56- if checkLive {
57- statedb , err = eth .blockchain .StateAt (block .Root ())
58- if err == nil {
59- return statedb , nil
62+ // The state is only for reading purposes, check the state presence in
63+ // live database.
64+ if readOnly {
65+ // The state is available in live database, create a reference
66+ // on top to prevent garbage collection and return a release
67+ // function to deref it.
68+ if statedb , err = eth .blockchain .StateAt (block .Root ()); err == nil {
69+ statedb .Database ().TrieDB ().Reference (block .Root (), common.Hash {})
70+ return statedb , func () { statedb .Database ().TrieDB ().Dereference (block .Root ()) }, nil
6071 }
6172 }
73+ // The state is both for reading and writing, construct an ephemeral
74+ // trie.Database for isolating the live one and check the state presence.
75+ database = state .NewDatabaseWithConfig (eth .chainDb , & trie.Config {Cache : 16 })
76+
77+ // If the requested state is available in disk, return it with noop
78+ // release function. It's also beneficial to switch to disk state with
79+ // a fresh created ephemeral trie database when tracing multiple blocks
80+ // to avoid caching too many junks in memory.
81+ statedb , err = state .New (current .Root (), database , nil )
82+ if err == nil {
83+ return statedb , noopReleaser , nil
84+ }
85+ // The state is not available in disk, try to recover it. The state will
86+ // be constructed on the ephemeral trie database.
6287 if base != nil {
63- if preferDisk {
64- // Create an ephemeral trie.Database for isolating the live one. Otherwise
65- // the internal junks created by tracing will be persisted into the disk.
66- database = state .NewDatabaseWithConfig (eth .chainDb , & trie.Config {Cache : 16 })
67- if statedb , err = state .New (block .Root (), database , nil ); err == nil {
68- log .Info ("Found disk backend for state trie" , "root" , block .Root (), "number" , block .Number ())
69- return statedb , nil
70- }
71- }
7288 // The optional base statedb is given, mark the start point as parent block
7389 statedb , database , report = base , base .Database (), false
7490 current = eth .blockchain .GetBlock (block .ParentHash (), block .NumberU64 ()- 1 )
7591 } else {
76- // Otherwise try to reexec blocks until we find a state or reach our limit
92+ // Otherwise, try to reexec blocks until we find a state or reach our limit
7793 current = block
7894
79- // Create an ephemeral trie.Database for isolating the live one. Otherwise
80- // the internal junks created by tracing will be persisted into the disk.
81- database = state .NewDatabaseWithConfig (eth .chainDb , & trie.Config {Cache : 16 })
82-
83- // If we didn't check the dirty database, do check the clean one, otherwise
84- // we would rewind past a persisted block (specific corner case is chain
85- // tracing from the genesis).
86- if ! checkLive {
87- statedb , err = state .New (current .Root (), database , nil )
88- if err == nil {
89- return statedb , nil
90- }
91- }
9295 // Database does not have the state for the given block, try to regenerate
9396 for i := uint64 (0 ); i < reexec ; i ++ {
9497 if current .NumberU64 () == 0 {
95- return nil , errors .New ("genesis state is missing" )
98+ return nil , nil , errors .New ("genesis state is missing" )
9699 }
97100 parent := eth .blockchain .GetBlock (current .ParentHash (), current .NumberU64 ()- 1 )
98101 if parent == nil {
99- return nil , fmt .Errorf ("missing block %v %d" , current .ParentHash (), current .NumberU64 ()- 1 )
102+ return nil , nil , fmt .Errorf ("missing block %v %d" , current .ParentHash (), current .NumberU64 ()- 1 )
100103 }
101104 current = parent
102105
@@ -108,13 +111,14 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
108111 if err != nil {
109112 switch err .(type ) {
110113 case * trie.MissingNodeError :
111- return nil , fmt .Errorf ("required historical state unavailable (reexec=%d)" , reexec )
114+ return nil , nil , fmt .Errorf ("required historical state unavailable (reexec=%d)" , reexec )
112115 default :
113- return nil , err
116+ return nil , nil , err
114117 }
115118 }
116119 }
117- // State was available at historical point, regenerate
120+ // State is available at historical point, re-execute the blocks on top for
121+ // the desired state.
118122 var (
119123 start = time .Now ()
120124 logged time.Time
@@ -129,22 +133,24 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
129133 // Retrieve the next block to regenerate and process it
130134 next := current .NumberU64 () + 1
131135 if current = eth .blockchain .GetBlockByNumber (next ); current == nil {
132- return nil , fmt .Errorf ("block #%d not found" , next )
136+ return nil , nil , fmt .Errorf ("block #%d not found" , next )
133137 }
134138 _ , _ , _ , err := eth .blockchain .Processor ().Process (current , statedb , vm.Config {})
135139 if err != nil {
136- return nil , fmt .Errorf ("processing block %d failed: %v" , current .NumberU64 (), err )
140+ return nil , nil , fmt .Errorf ("processing block %d failed: %v" , current .NumberU64 (), err )
137141 }
138142 // Finalize the state so any modifications are written to the trie
139143 root , err := statedb .Commit (eth .blockchain .Config ().IsEIP158 (current .Number ()))
140144 if err != nil {
141- return nil , fmt .Errorf ("stateAtBlock commit failed, number %d root %v: %w" ,
145+ return nil , nil , fmt .Errorf ("stateAtBlock commit failed, number %d root %v: %w" ,
142146 current .NumberU64 (), current .Root ().Hex (), err )
143147 }
144148 statedb , err = state .New (root , database , nil )
145149 if err != nil {
146- return nil , fmt .Errorf ("state reset after block %d failed: %v" , current .NumberU64 (), err )
150+ return nil , nil , fmt .Errorf ("state reset after block %d failed: %v" , current .NumberU64 (), err )
147151 }
152+ // Hold the state reference and also drop the parent state
153+ // to prevent accumulating too many nodes in memory.
148154 database .TrieDB ().Reference (root , common.Hash {})
149155 if parent != (common.Hash {}) {
150156 database .TrieDB ().Dereference (parent )
@@ -155,28 +161,28 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
155161 nodes , imgs := database .TrieDB ().Size ()
156162 log .Info ("Historical state regenerated" , "block" , current .NumberU64 (), "elapsed" , time .Since (start ), "nodes" , nodes , "preimages" , imgs )
157163 }
158- return statedb , nil
164+ return statedb , func () { database . TrieDB (). Dereference ( block . Root ()) }, nil
159165}
160166
161167// stateAtTransaction returns the execution environment of a certain transaction.
162- func (eth * Ethereum ) stateAtTransaction (block * types.Block , txIndex int , reexec uint64 ) (core.Message , vm.BlockContext , * state.StateDB , error ) {
168+ func (eth * Ethereum ) stateAtTransaction (block * types.Block , txIndex int , reexec uint64 ) (core.Message , vm.BlockContext , * state.StateDB , func (), error ) {
163169 // Short circuit if it's genesis block.
164170 if block .NumberU64 () == 0 {
165- return nil , vm.BlockContext {}, nil , errors .New ("no transaction in genesis" )
171+ return nil , vm.BlockContext {}, nil , nil , errors .New ("no transaction in genesis" )
166172 }
167173 // Create the parent state database
168174 parent := eth .blockchain .GetBlock (block .ParentHash (), block .NumberU64 ()- 1 )
169175 if parent == nil {
170- return nil , vm.BlockContext {}, nil , fmt .Errorf ("parent %#x not found" , block .ParentHash ())
176+ return nil , vm.BlockContext {}, nil , nil , fmt .Errorf ("parent %#x not found" , block .ParentHash ())
171177 }
172178 // Lookup the statedb of parent block from the live database,
173179 // otherwise regenerate it on the flight.
174- statedb , err := eth .StateAtBlock (parent , reexec , nil , true , false )
180+ statedb , rel , err := eth .StateAtBlock (parent , reexec , nil , true )
175181 if err != nil {
176- return nil , vm.BlockContext {}, nil , err
182+ return nil , vm.BlockContext {}, nil , nil , err
177183 }
178184 if txIndex == 0 && len (block .Transactions ()) == 0 {
179- return nil , vm.BlockContext {}, statedb , nil
185+ return nil , vm.BlockContext {}, statedb , rel , nil
180186 }
181187 // Recompute transactions up to the target index.
182188 signer := types .MakeSigner (eth .blockchain .Config (), block .Number ())
@@ -186,17 +192,17 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
186192 txContext := core .NewEVMTxContext (msg )
187193 context := core .NewEVMBlockContext (block .Header (), eth .blockchain , nil )
188194 if idx == txIndex {
189- return msg , context , statedb , nil
195+ return msg , context , statedb , rel , nil
190196 }
191197 // Not yet the searched for transaction, execute on top of the current state
192198 vmenv := vm .NewEVM (context , txContext , statedb , eth .blockchain .Config (), vm.Config {})
193199 statedb .Prepare (tx .Hash (), idx )
194200 if _ , err := core .ApplyMessage (vmenv , msg , new (core.GasPool ).AddGas (tx .Gas ())); err != nil {
195- return nil , vm.BlockContext {}, nil , fmt .Errorf ("transaction %#x failed: %v" , tx .Hash (), err )
201+ return nil , vm.BlockContext {}, nil , nil , fmt .Errorf ("transaction %#x failed: %v" , tx .Hash (), err )
196202 }
197203 // Ensure any modifications are committed to the state
198204 // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
199205 statedb .Finalise (vmenv .ChainConfig ().IsEIP158 (block .Number ()))
200206 }
201- return nil , vm.BlockContext {}, nil , fmt .Errorf ("transaction index %d out of range for block %#x" , txIndex , block .Hash ())
207+ return nil , vm.BlockContext {}, nil , nil , fmt .Errorf ("transaction index %d out of range for block %#x" , txIndex , block .Hash ())
202208}
0 commit comments