state_accessor.go 11.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Copyright 2021 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 <http://www.gnu.org/licenses/>.

package eth

import (
20
	"context"
21 22 23 24 25 26
	"errors"
	"fmt"
	"time"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core"
27
	"github.com/ethereum/go-ethereum/core/rawdb"
28 29 30
	"github.com/ethereum/go-ethereum/core/state"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/core/vm"
31
	"github.com/ethereum/go-ethereum/eth/tracers"
32 33 34 35
	"github.com/ethereum/go-ethereum/log"
	"github.com/ethereum/go-ethereum/trie"
)

36 37 38 39
// noopReleaser is returned in case there is no operation expected
// for releasing state.
var noopReleaser = tracers.StateReleaseFunc(func() {})

40
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) {
41 42 43
	var (
		current  *types.Block
		database state.Database
44
		triedb   *trie.Database
45 46 47
		report   = true
		origin   = block.NumberU64()
	)
48 49 50 51 52 53 54
	// The state is only for reading purposes, check the state presence in
	// live database.
	if readOnly {
		// The state is available in live database, create a reference
		// on top to prevent garbage collection and return a release
		// function to deref it.
		if statedb, err = eth.blockchain.StateAt(block.Root()); err == nil {
55
			eth.blockchain.TrieDB().Reference(block.Root(), common.Hash{})
56
			return statedb, func() {
57
				eth.blockchain.TrieDB().Dereference(block.Root())
58
			}, nil
59
		}
60
	}
61 62 63
	// 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.
64
	if base != nil {
65 66 67
		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.
68 69 70
			// TODO(rjl493456442), clean cache is disabled to prevent memory leak,
			// please re-enable it for better performance.
			database = state.NewDatabaseWithConfig(eth.chainDb, trie.HashDefaults)
71 72
			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())
73
				return statedb, noopReleaser, nil
74 75
			}
		}
76
		// The optional base statedb is given, mark the start point as parent block
77
		statedb, database, triedb, report = base, base.Database(), base.Database().TrieDB(), false
78 79
		current = eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
	} else {
80
		// Otherwise, try to reexec blocks until we find a state or reach our limit
81
		current = block
82

83 84
		// Create an ephemeral trie.Database for isolating the live one. Otherwise
		// the internal junks created by tracing will be persisted into the disk.
85 86 87 88
		// TODO(rjl493456442), clean cache is disabled to prevent memory leak,
		// please re-enable it for better performance.
		triedb = trie.NewDatabase(eth.chainDb, trie.HashDefaults)
		database = state.NewDatabaseWithNodeDB(eth.chainDb, triedb)
89

90 91 92 93
		// If we didn't check the live database, do check state over ephemeral database,
		// otherwise we would rewind past a persisted block (specific corner case is
		// chain tracing from the genesis).
		if !readOnly {
94 95
			statedb, err = state.New(current.Root(), database, nil)
			if err == nil {
96
				return statedb, noopReleaser, nil
97 98 99
			}
		}
		// Database does not have the state for the given block, try to regenerate
100
		for i := uint64(0); i < reexec; i++ {
101 102 103
			if err := ctx.Err(); err != nil {
				return nil, nil, err
			}
104
			if current.NumberU64() == 0 {
105
				return nil, nil, errors.New("genesis state is missing")
106 107 108
			}
			parent := eth.blockchain.GetBlock(current.ParentHash(), current.NumberU64()-1)
			if parent == nil {
109
				return nil, nil, fmt.Errorf("missing block %v %d", current.ParentHash(), current.NumberU64()-1)
110 111 112 113 114 115 116
			}
			current = parent

			statedb, err = state.New(current.Root(), database, nil)
			if err == nil {
				break
			}
117
		}
118 119 120
		if err != nil {
			switch err.(type) {
			case *trie.MissingNodeError:
121
				return nil, nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec)
122
			default:
123
				return nil, nil, err
124
			}
125 126
		}
	}
127 128
	// State is available at historical point, re-execute the blocks on top for
	// the desired state.
129 130 131 132 133
	var (
		start  = time.Now()
		logged time.Time
		parent common.Hash
	)
134
	for current.NumberU64() < origin {
135 136 137
		if err := ctx.Err(); err != nil {
			return nil, nil, err
		}
138
		// Print progress logs if long enough time elapsed
139 140
		if time.Since(logged) > 8*time.Second && report {
			log.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-current.NumberU64()-1, "elapsed", time.Since(start))
141 142 143
			logged = time.Now()
		}
		// Retrieve the next block to regenerate and process it
144 145
		next := current.NumberU64() + 1
		if current = eth.blockchain.GetBlockByNumber(next); current == nil {
146
			return nil, nil, fmt.Errorf("block #%d not found", next)
147
		}
148
		_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
149
		if err != nil {
150
			return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
151 152
		}
		// Finalize the state so any modifications are written to the trie
153
		root, err := statedb.Commit(current.NumberU64(), eth.blockchain.Config().IsEIP158(current.Number()))
154
		if err != nil {
155
			return nil, nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w",
156
				current.NumberU64(), current.Root().Hex(), err)
157 158 159
		}
		statedb, err = state.New(root, database, nil)
		if err != nil {
160
			return nil, nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err)
161
		}
162 163
		// Hold the state reference and also drop the parent state
		// to prevent accumulating too many nodes in memory.
164
		triedb.Reference(root, common.Hash{})
165
		if parent != (common.Hash{}) {
166
			triedb.Dereference(parent)
167 168 169
		}
		parent = root
	}
170
	if report {
171
		_, nodes, imgs := triedb.Size() // all memory is contained within the nodes return in hashdb
172
		log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs)
173
	}
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
	return statedb, func() { triedb.Dereference(block.Root()) }, nil
}

func (eth *Ethereum) pathState(block *types.Block) (*state.StateDB, func(), error) {
	// Check if the requested state is available in the live chain.
	statedb, err := eth.blockchain.StateAt(block.Root())
	if err == nil {
		return statedb, noopReleaser, nil
	}
	// TODO historic state is not supported in path-based scheme.
	// Fully archive node in pbss will be implemented by relying
	// on state history, but needs more work on top.
	return nil, nil, errors.New("historical state not available in path scheme yet")
}

// stateAtBlock retrieves the state database associated with a certain block.
// If no state is locally available for the given block, a number of blocks
// are attempted to be reexecuted to generate the desired state. The optional
// base layer statedb can be provided which is regarded as the statedb of the
// parent block.
//
// An additional release function will be returned if the requested state is
// available. Release is expected to be invoked when the returned state is no
// longer needed. Its purpose is to prevent resource leaking. Though it can be
// noop in some cases.
//
// Parameters:
//   - block:      The block for which we want the state(state = block.Root)
//   - 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.
//   - 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) {
	if eth.blockchain.TrieDB().Scheme() == rawdb.HashScheme {
		return eth.hashState(ctx, block, reexec, base, readOnly, preferDisk)
	}
	return eth.pathState(block)
216 217 218
}

// stateAtTransaction returns the execution environment of a certain transaction.
219
func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
220 221
	// Short circuit if it's genesis block.
	if block.NumberU64() == 0 {
222
		return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis")
223 224 225 226
	}
	// Create the parent state database
	parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
	if parent == nil {
227
		return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("parent %#x not found", block.ParentHash())
228
	}
229 230
	// Lookup the statedb of parent block from the live database,
	// otherwise regenerate it on the flight.
231
	statedb, release, err := eth.stateAtBlock(ctx, parent, reexec, nil, true, false)
232
	if err != nil {
233
		return nil, vm.BlockContext{}, nil, nil, err
234 235
	}
	if txIndex == 0 && len(block.Transactions()) == 0 {
236
		return nil, vm.BlockContext{}, statedb, release, nil
237 238
	}
	// Recompute transactions up to the target index.
239
	signer := types.MakeSigner(eth.blockchain.Config(), block.Number(), block.Time())
240 241
	for idx, tx := range block.Transactions() {
		// Assemble the transaction call message and return if the requested offset
242
		msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
243 244 245
		txContext := core.NewEVMTxContext(msg)
		context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
		if idx == txIndex {
246
			return msg, context, statedb, release, nil
247 248 249
		}
		// Not yet the searched for transaction, execute on top of the current state
		vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
250
		statedb.SetTxContext(tx.Hash(), idx)
251
		if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
252
			return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
253 254 255 256 257
		}
		// Ensure any modifications are committed to the state
		// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
		statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
	}
258
	return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
259
}