Skip to content

Commit 9da9e2e

Browse files
committed
eth, les: rework chain tracer
1 parent 7c7cd41 commit 9da9e2e

File tree

7 files changed

+279
-174
lines changed

7 files changed

+279
-174
lines changed

eth/api.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,10 +407,11 @@ func (api *DebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, contract
407407
if block == nil {
408408
return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash)
409409
}
410-
_, _, statedb, err := api.eth.stateAtTransaction(block, txIndex, 0)
410+
_, _, statedb, rel, err := api.eth.stateAtTransaction(block, txIndex, 0)
411411
if err != nil {
412412
return StorageRangeResult{}, err
413413
}
414+
defer rel()
414415
st := statedb.StorageTrie(contractAddress)
415416
if st == nil {
416417
return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress)

eth/api_backend.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,10 @@ func (b *EthAPIBackend) StartMining(threads int) error {
359359
return b.eth.StartMining(threads)
360360
}
361361

362-
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) {
363-
return b.eth.StateAtBlock(block, reexec, base, checkLive, preferDisk)
362+
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool) (*state.StateDB, func(), error) {
363+
return b.eth.StateAtBlock(block, reexec, base, readOnly)
364364
}
365365

366-
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
366+
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error) {
367367
return b.eth.stateAtTransaction(block, txIndex, reexec)
368368
}

eth/state_accessor.go

Lines changed: 64 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)