@@ -30,73 +30,76 @@ import (
30
30
"github.com/ethereum/go-ethereum/trie"
31
31
)
32
32
33
+ // noopReleaser is returned in case there is no operation expected
34
+ // for releasing state.
35
+ var noopReleaser = func () {}
36
+
33
37
// StateAtBlock retrieves the state database associated with a certain block.
34
38
// If no state is locally available for the given block, a number of blocks
35
39
// 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
37
41
// 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
+ //
38
47
// 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 ) {
49
56
var (
50
57
current * types.Block
51
58
database state.Database
52
59
report = true
53
60
origin = block .NumberU64 ()
54
61
)
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
60
71
}
61
72
}
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.
62
87
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
- }
72
88
// The optional base statedb is given, mark the start point as parent block
73
89
statedb , database , report = base , base .Database (), false
74
90
current = eth .blockchain .GetBlock (block .ParentHash (), block .NumberU64 ()- 1 )
75
91
} 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
77
93
current = block
78
94
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
- }
92
95
// Database does not have the state for the given block, try to regenerate
93
96
for i := uint64 (0 ); i < reexec ; i ++ {
94
97
if current .NumberU64 () == 0 {
95
- return nil , errors .New ("genesis state is missing" )
98
+ return nil , nil , errors .New ("genesis state is missing" )
96
99
}
97
100
parent := eth .blockchain .GetBlock (current .ParentHash (), current .NumberU64 ()- 1 )
98
101
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 )
100
103
}
101
104
current = parent
102
105
@@ -108,13 +111,14 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
108
111
if err != nil {
109
112
switch err .(type ) {
110
113
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 )
112
115
default :
113
- return nil , err
116
+ return nil , nil , err
114
117
}
115
118
}
116
119
}
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.
118
122
var (
119
123
start = time .Now ()
120
124
logged time.Time
@@ -129,22 +133,24 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
129
133
// Retrieve the next block to regenerate and process it
130
134
next := current .NumberU64 () + 1
131
135
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 )
133
137
}
134
138
_ , _ , _ , err := eth .blockchain .Processor ().Process (current , statedb , vm.Config {})
135
139
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 )
137
141
}
138
142
// Finalize the state so any modifications are written to the trie
139
143
root , err := statedb .Commit (eth .blockchain .Config ().IsEIP158 (current .Number ()))
140
144
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" ,
142
146
current .NumberU64 (), current .Root ().Hex (), err )
143
147
}
144
148
statedb , err = state .New (root , database , nil )
145
149
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 )
147
151
}
152
+ // Hold the state reference and also drop the parent state
153
+ // to prevent accumulating too many nodes in memory.
148
154
database .TrieDB ().Reference (root , common.Hash {})
149
155
if parent != (common.Hash {}) {
150
156
database .TrieDB ().Dereference (parent )
@@ -155,28 +161,28 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
155
161
nodes , imgs := database .TrieDB ().Size ()
156
162
log .Info ("Historical state regenerated" , "block" , current .NumberU64 (), "elapsed" , time .Since (start ), "nodes" , nodes , "preimages" , imgs )
157
163
}
158
- return statedb , nil
164
+ return statedb , func () { database . TrieDB (). Dereference ( block . Root ()) }, nil
159
165
}
160
166
161
167
// 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 ) {
163
169
// Short circuit if it's genesis block.
164
170
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" )
166
172
}
167
173
// Create the parent state database
168
174
parent := eth .blockchain .GetBlock (block .ParentHash (), block .NumberU64 ()- 1 )
169
175
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 ())
171
177
}
172
178
// Lookup the statedb of parent block from the live database,
173
179
// 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 )
175
181
if err != nil {
176
- return nil , vm.BlockContext {}, nil , err
182
+ return nil , vm.BlockContext {}, nil , nil , err
177
183
}
178
184
if txIndex == 0 && len (block .Transactions ()) == 0 {
179
- return nil , vm.BlockContext {}, statedb , nil
185
+ return nil , vm.BlockContext {}, statedb , rel , nil
180
186
}
181
187
// Recompute transactions up to the target index.
182
188
signer := types .MakeSigner (eth .blockchain .Config (), block .Number ())
@@ -186,17 +192,17 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
186
192
txContext := core .NewEVMTxContext (msg )
187
193
context := core .NewEVMBlockContext (block .Header (), eth .blockchain , nil )
188
194
if idx == txIndex {
189
- return msg , context , statedb , nil
195
+ return msg , context , statedb , rel , nil
190
196
}
191
197
// Not yet the searched for transaction, execute on top of the current state
192
198
vmenv := vm .NewEVM (context , txContext , statedb , eth .blockchain .Config (), vm.Config {})
193
199
statedb .Prepare (tx .Hash (), idx )
194
200
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 )
196
202
}
197
203
// Ensure any modifications are committed to the state
198
204
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
199
205
statedb .Finalise (vmenv .ChainConfig ().IsEIP158 (block .Number ()))
200
206
}
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 ())
202
208
}
0 commit comments