mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-13 02:11:34 +00:00
Merge remote-tracking branch 'origin/master' into bs/eip7975-peer
This commit is contained in:
commit
ff11531c12
148 changed files with 3172 additions and 1324 deletions
|
|
@ -1196,7 +1196,7 @@ func doWindowsInstaller(cmdline []string) {
|
|||
var (
|
||||
arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging")
|
||||
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`)
|
||||
signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`)
|
||||
signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`)
|
||||
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
|
||||
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -149,15 +149,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
isEIP4762 = chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp)
|
||||
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762)
|
||||
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
|
||||
gaspool = new(core.GasPool)
|
||||
gaspool = core.NewGasPool(pre.Env.GasLimit)
|
||||
blockHash = common.Hash{0x13, 0x37}
|
||||
rejectedTxs []*rejectedTx
|
||||
includedTxs types.Transactions
|
||||
gasUsed = uint64(0)
|
||||
blobGasUsed = uint64(0)
|
||||
receipts = make(types.Receipts, 0)
|
||||
)
|
||||
gaspool.AddGas(pre.Env.GasLimit)
|
||||
vmContext := vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
|
|
@ -258,14 +256,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
statedb.SetTxContext(tx.Hash(), len(receipts))
|
||||
var (
|
||||
snapshot = statedb.Snapshot()
|
||||
prevGas = gaspool.Gas()
|
||||
gp = gaspool.Snapshot()
|
||||
)
|
||||
receipt, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, &gasUsed, evm)
|
||||
receipt, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, evm)
|
||||
if err != nil {
|
||||
statedb.RevertToSnapshot(snapshot)
|
||||
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err)
|
||||
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})
|
||||
gaspool.SetGas(prevGas)
|
||||
gaspool.Set(gp)
|
||||
continue
|
||||
}
|
||||
if receipt.Logs == nil {
|
||||
|
|
@ -352,7 +350,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
Receipts: receipts,
|
||||
Rejected: rejectedTxs,
|
||||
Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty),
|
||||
GasUsed: (math.HexOrDecimal64)(gasUsed),
|
||||
GasUsed: (math.HexOrDecimal64)(gaspool.Used()),
|
||||
BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee),
|
||||
}
|
||||
if pre.Env.Withdrawals != nil {
|
||||
|
|
@ -367,10 +365,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
// Set requestsHash on block.
|
||||
h := types.CalcRequestsHash(requests)
|
||||
execRs.RequestsHash = &h
|
||||
for i := range requests {
|
||||
// remove prefix
|
||||
requests[i] = requests[i][1:]
|
||||
}
|
||||
execRs.Requests = requests
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
|
|
@ -177,9 +179,12 @@ func Transaction(ctx *cli.Context) error {
|
|||
r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits")
|
||||
}
|
||||
// Check whether the init code size has been exceeded.
|
||||
if chainConfig.IsShanghai(new(big.Int), 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
|
||||
r.Error = errors.New("max initcode size exceeded")
|
||||
if tx.To() == nil {
|
||||
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(tx.Data()))); err != nil {
|
||||
r.Error = err
|
||||
}
|
||||
}
|
||||
|
||||
if chainConfig.IsOsaka(new(big.Int), 0) && tx.Gas() > params.MaxTxGas {
|
||||
r.Error = errors.New("gas limit exceeds maximum")
|
||||
}
|
||||
|
|
|
|||
177
cmd/fetchpayload/main.go
Normal file
177
cmd/fetchpayload/main.go
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// fetchpayload queries an Ethereum node over RPC, fetches a block and its
|
||||
// execution witness, and writes the combined Payload (ChainID + Block +
|
||||
// Witness) to disk in the format consumed by cmd/keeper.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Payload is duplicated from cmd/keeper/main.go (package main, not importable).
|
||||
type Payload struct {
|
||||
ChainID uint64
|
||||
Block *types.Block
|
||||
Witness *stateless.Witness
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
rpcURL = flag.String("rpc", "http://localhost:8545", "RPC endpoint URL")
|
||||
blockArg = flag.String("block", "latest", `Block number: decimal, 0x-hex, or "latest"`)
|
||||
format = flag.String("format", "rlp", "Comma-separated output formats: rlp, hex, json")
|
||||
outDir = flag.String("out", "", "Output directory (default: current directory)")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Parse block number (nil means "latest" in ethclient).
|
||||
blockNum, err := parseBlockNumber(*blockArg)
|
||||
if err != nil {
|
||||
fatal("invalid block number %q: %v", *blockArg, err)
|
||||
}
|
||||
|
||||
// Connect to the node.
|
||||
client, err := ethclient.DialContext(ctx, *rpcURL)
|
||||
if err != nil {
|
||||
fatal("failed to connect to %s: %v", *rpcURL, err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
chainID, err := client.ChainID(ctx)
|
||||
if err != nil {
|
||||
fatal("failed to get chain ID: %v", err)
|
||||
}
|
||||
|
||||
// Fetch the block first so we have a concrete number for the witness call,
|
||||
// avoiding a race where "latest" advances between the two RPCs.
|
||||
block, err := client.BlockByNumber(ctx, blockNum)
|
||||
if err != nil {
|
||||
fatal("failed to fetch block: %v", err)
|
||||
}
|
||||
fmt.Printf("Fetched block %d (%#x)\n", block.NumberU64(), block.Hash())
|
||||
|
||||
// Fetch the execution witness via the debug namespace.
|
||||
var extWitness stateless.ExtWitness
|
||||
err = client.Client().CallContext(ctx, &extWitness, "debug_executionWitness", rpc.BlockNumber(block.NumberU64()))
|
||||
if err != nil {
|
||||
fatal("failed to fetch execution witness: %v", err)
|
||||
}
|
||||
|
||||
witness := new(stateless.Witness)
|
||||
err = witness.FromExtWitness(&extWitness)
|
||||
if err != nil {
|
||||
fatal("failed to convert witness: %v", err)
|
||||
}
|
||||
|
||||
payload := Payload{
|
||||
ChainID: chainID.Uint64(),
|
||||
Block: block,
|
||||
Witness: witness,
|
||||
}
|
||||
|
||||
// Encode payload as RLP (shared by "rlp" and "hex" formats).
|
||||
rlpBytes, err := rlp.EncodeToBytes(payload)
|
||||
if err != nil {
|
||||
fatal("failed to RLP-encode payload: %v", err)
|
||||
}
|
||||
|
||||
// Write one output file per requested format.
|
||||
blockHex := fmt.Sprintf("%x", block.NumberU64())
|
||||
for f := range strings.SplitSeq(*format, ",") {
|
||||
f = strings.TrimSpace(f)
|
||||
outPath := filepath.Join(*outDir, fmt.Sprintf("%s_payload.%s", blockHex, f))
|
||||
|
||||
var data []byte
|
||||
switch f {
|
||||
case "rlp":
|
||||
data = rlpBytes
|
||||
case "hex":
|
||||
data = []byte(hexutil.Encode(rlpBytes))
|
||||
case "json":
|
||||
data, err = marshalJSONPayload(chainID, block, &extWitness)
|
||||
if err != nil {
|
||||
fatal("failed to JSON-encode payload: %v", err)
|
||||
}
|
||||
default:
|
||||
fatal("unknown format %q (valid: rlp, hex, json)", f)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(outPath, data, 0644); err != nil {
|
||||
fatal("failed to write %s: %v", outPath, err)
|
||||
}
|
||||
fmt.Printf("Wrote %s (%d bytes)\n", outPath, len(data))
|
||||
}
|
||||
}
|
||||
|
||||
// parseBlockNumber converts a CLI string to *big.Int.
|
||||
// Returns nil for "latest" (ethclient convention for the head block).
|
||||
func parseBlockNumber(s string) (*big.Int, error) {
|
||||
if strings.EqualFold(s, "latest") {
|
||||
return nil, nil
|
||||
}
|
||||
n := new(big.Int)
|
||||
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
|
||||
if _, ok := n.SetString(s[2:], 16); !ok {
|
||||
return nil, fmt.Errorf("invalid hex number")
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
if _, ok := n.SetString(s, 10); !ok {
|
||||
return nil, fmt.Errorf("invalid decimal number")
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// jsonPayload is a JSON-friendly representation of Payload. It uses ExtWitness
|
||||
// instead of the internal Witness (which has no JSON marshaling).
|
||||
type jsonPayload struct {
|
||||
ChainID uint64 `json:"chainId"`
|
||||
Block *types.Block `json:"block"`
|
||||
Witness *stateless.ExtWitness `json:"witness"`
|
||||
}
|
||||
|
||||
func marshalJSONPayload(chainID *big.Int, block *types.Block, ext *stateless.ExtWitness) ([]byte, error) {
|
||||
return json.MarshalIndent(jsonPayload{
|
||||
ChainID: chainID.Uint64(),
|
||||
Block: block,
|
||||
Witness: ext,
|
||||
}, "", " ")
|
||||
}
|
||||
|
||||
func fatal(format string, args ...any) {
|
||||
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
@ -208,13 +208,19 @@ This command dumps out the state for a given block (or latest, if none provided)
|
|||
pruneHistoryCommand = &cli.Command{
|
||||
Action: pruneHistory,
|
||||
Name: "prune-history",
|
||||
Usage: "Prune blockchain history (block bodies and receipts) up to the merge block",
|
||||
Usage: "Prune blockchain history (block bodies and receipts) up to a specified point",
|
||||
ArgsUsage: "",
|
||||
Flags: utils.DatabaseFlags,
|
||||
Flags: slices.Concat(utils.DatabaseFlags, []cli.Flag{
|
||||
utils.ChainHistoryFlag,
|
||||
}),
|
||||
Description: `
|
||||
The prune-history command removes historical block bodies and receipts from the
|
||||
blockchain database up to the merge block, while preserving block headers. This
|
||||
helps reduce storage requirements for nodes that don't need full historical data.`,
|
||||
blockchain database up to a specified point, while preserving block headers. This
|
||||
helps reduce storage requirements for nodes that don't need full historical data.
|
||||
|
||||
The --history.chain flag is required to specify the pruning target:
|
||||
- postmerge: Prune up to the merge block. The node will keep the merge block and everything thereafter.
|
||||
- postprague: Prune up to the Prague (Pectra) upgrade block. The node will keep the prague block and everything thereafter.`,
|
||||
}
|
||||
|
||||
downloadEraCommand = &cli.Command{
|
||||
|
|
@ -703,47 +709,77 @@ func hashish(x string) bool {
|
|||
}
|
||||
|
||||
func pruneHistory(ctx *cli.Context) error {
|
||||
// Parse and validate the history mode flag.
|
||||
if !ctx.IsSet(utils.ChainHistoryFlag.Name) {
|
||||
return errors.New("--history.chain flag is required")
|
||||
}
|
||||
var mode history.HistoryMode
|
||||
if err := mode.UnmarshalText([]byte(ctx.String(utils.ChainHistoryFlag.Name))); err != nil {
|
||||
return err
|
||||
}
|
||||
if mode == history.KeepAll {
|
||||
return errors.New("--history.chain=all is not valid for pruning. To restore history, use 'geth import-history'")
|
||||
}
|
||||
|
||||
stack, _ := makeConfigNode(ctx)
|
||||
defer stack.Close()
|
||||
|
||||
// Open the chain database
|
||||
// Open the chain database.
|
||||
chain, chaindb := utils.MakeChain(ctx, stack, false)
|
||||
defer chaindb.Close()
|
||||
defer chain.Stop()
|
||||
|
||||
// Determine the prune point. This will be the first PoS block.
|
||||
prunePoint, ok := history.PrunePoints[chain.Genesis().Hash()]
|
||||
if !ok || prunePoint == nil {
|
||||
return errors.New("prune point not found")
|
||||
// Determine the prune point based on the history mode.
|
||||
genesisHash := chain.Genesis().Hash()
|
||||
policy, err := history.NewPolicy(mode, genesisHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if policy.Target == nil {
|
||||
return fmt.Errorf("prune point for %q not found for this network", mode.String())
|
||||
}
|
||||
var (
|
||||
mergeBlock = prunePoint.BlockNumber
|
||||
mergeBlockHash = prunePoint.BlockHash.Hex()
|
||||
targetBlock = policy.Target.BlockNumber
|
||||
targetBlockHash = policy.Target.BlockHash
|
||||
)
|
||||
|
||||
// Check we're far enough past merge to ensure all data is in freezer
|
||||
// Check the current freezer tail to see if pruning is needed/possible.
|
||||
freezerTail, _ := chaindb.Tail()
|
||||
if freezerTail > 0 {
|
||||
if freezerTail == targetBlock {
|
||||
log.Info("Database already pruned to target block", "tail", freezerTail)
|
||||
return nil
|
||||
}
|
||||
if freezerTail > targetBlock {
|
||||
// Database is pruned beyond the target - can't unprune.
|
||||
return fmt.Errorf("database is already pruned to block %d, which is beyond target %d. Cannot unprune. To restore history, use 'geth import-history'", freezerTail, targetBlock)
|
||||
}
|
||||
// freezerTail < targetBlock: we can prune further, continue below.
|
||||
}
|
||||
|
||||
// Check we're far enough past the target to ensure all data is in freezer.
|
||||
currentHeader := chain.CurrentHeader()
|
||||
if currentHeader == nil {
|
||||
return errors.New("current header not found")
|
||||
}
|
||||
if currentHeader.Number.Uint64() < mergeBlock+params.FullImmutabilityThreshold {
|
||||
return fmt.Errorf("chain not far enough past merge block, need %d more blocks",
|
||||
mergeBlock+params.FullImmutabilityThreshold-currentHeader.Number.Uint64())
|
||||
if currentHeader.Number.Uint64() < targetBlock+params.FullImmutabilityThreshold {
|
||||
return fmt.Errorf("chain not far enough past target block %d, need %d more blocks",
|
||||
targetBlock, targetBlock+params.FullImmutabilityThreshold-currentHeader.Number.Uint64())
|
||||
}
|
||||
|
||||
// Double-check the prune block in db has the expected hash.
|
||||
hash := rawdb.ReadCanonicalHash(chaindb, mergeBlock)
|
||||
if hash != common.HexToHash(mergeBlockHash) {
|
||||
return fmt.Errorf("merge block hash mismatch: got %s, want %s", hash.Hex(), mergeBlockHash)
|
||||
// Double-check the target block in db has the expected hash.
|
||||
hash := rawdb.ReadCanonicalHash(chaindb, targetBlock)
|
||||
if hash != targetBlockHash {
|
||||
return fmt.Errorf("target block hash mismatch: got %s, want %s", hash.Hex(), targetBlockHash.Hex())
|
||||
}
|
||||
|
||||
log.Info("Starting history pruning", "head", currentHeader.Number, "tail", mergeBlock, "tailHash", mergeBlockHash)
|
||||
log.Info("Starting history pruning", "head", currentHeader.Number, "target", targetBlock, "targetHash", targetBlockHash.Hex())
|
||||
start := time.Now()
|
||||
rawdb.PruneTransactionIndex(chaindb, mergeBlock)
|
||||
if _, err := chaindb.TruncateTail(mergeBlock); err != nil {
|
||||
rawdb.PruneTransactionIndex(chaindb, targetBlock)
|
||||
if _, err := chaindb.TruncateTail(targetBlock); err != nil {
|
||||
return fmt.Errorf("failed to truncate ancient data: %v", err)
|
||||
}
|
||||
log.Info("History pruning completed", "tail", mergeBlock, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
log.Info("History pruning completed", "tail", targetBlock, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
|
||||
// TODO(s1na): what if there is a crash between the two prune operations?
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ var (
|
|||
}
|
||||
removeChainDataFlag = &cli.BoolFlag{
|
||||
Name: "remove.chain",
|
||||
Usage: "If set, selects the state data for removal",
|
||||
Usage: "If set, selects the chain data for removal",
|
||||
}
|
||||
inspectTrieTopFlag = &cli.IntFlag{
|
||||
Name: "top",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
|
|
@ -316,18 +315,6 @@ func prepare(ctx *cli.Context) {
|
|||
case !ctx.IsSet(utils.NetworkIdFlag.Name):
|
||||
log.Info("Starting Geth on Ethereum mainnet...")
|
||||
}
|
||||
// If we're a full node on mainnet without --cache specified, bump default cache allowance
|
||||
if !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) {
|
||||
// Make sure we're not on any supported preconfigured testnet either
|
||||
if !ctx.IsSet(utils.HoleskyFlag.Name) &&
|
||||
!ctx.IsSet(utils.SepoliaFlag.Name) &&
|
||||
!ctx.IsSet(utils.HoodiFlag.Name) &&
|
||||
!ctx.IsSet(utils.DeveloperFlag.Name) {
|
||||
// Nope, we're really on mainnet. Bump that cache up!
|
||||
log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096)
|
||||
ctx.Set(utils.CacheFlag.Name, strconv.Itoa(4096))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// geth is the main entry point into the system if no special subcommand is run.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -105,7 +106,9 @@ information about the specified address.
|
|||
Usage: "Traverse the state with given root hash and perform quick verification",
|
||||
ArgsUsage: "<root>",
|
||||
Action: traverseState,
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.AccountFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: `
|
||||
geth snapshot traverse-state <state-root>
|
||||
will traverse the whole state from the given state root and will abort if any
|
||||
|
|
@ -113,6 +116,8 @@ referenced trie node or contract code is missing. This command can be used for
|
|||
state integrity verification. The default checking target is the HEAD state.
|
||||
|
||||
It's also usable without snapshot enabled.
|
||||
|
||||
If --account is specified, only the storage trie of that account is traversed.
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
|
@ -120,7 +125,9 @@ It's also usable without snapshot enabled.
|
|||
Usage: "Traverse the state with given root hash and perform detailed verification",
|
||||
ArgsUsage: "<root>",
|
||||
Action: traverseRawState,
|
||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.AccountFlag,
|
||||
}, utils.NetworkFlags, utils.DatabaseFlags),
|
||||
Description: `
|
||||
geth snapshot traverse-rawstate <state-root>
|
||||
will traverse the whole state from the given root and will abort if any referenced
|
||||
|
|
@ -129,6 +136,8 @@ verification. The default checking target is the HEAD state. It's basically iden
|
|||
to traverse-state, but the check granularity is smaller.
|
||||
|
||||
It's also usable without snapshot enabled.
|
||||
|
||||
If --account is specified, only the storage trie of that account is traversed.
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
|
@ -272,6 +281,120 @@ func checkDanglingStorage(ctx *cli.Context) error {
|
|||
return snapshot.CheckDanglingStorage(db)
|
||||
}
|
||||
|
||||
// parseAccount parses the account flag value as either an address (20 bytes)
|
||||
// or an account hash (32 bytes) and returns the hashed account key.
|
||||
func parseAccount(input string) (common.Hash, error) {
|
||||
switch len(input) {
|
||||
case 40, 42: // address
|
||||
return crypto.Keccak256Hash(common.HexToAddress(input).Bytes()), nil
|
||||
case 64, 66: // hash
|
||||
return common.HexToHash(input), nil
|
||||
default:
|
||||
return common.Hash{}, errors.New("malformed account address or hash")
|
||||
}
|
||||
}
|
||||
|
||||
// lookupAccount resolves the account from the state trie using the given
|
||||
// account hash.
|
||||
func lookupAccount(accountHash common.Hash, tr *trie.Trie) (*types.StateAccount, error) {
|
||||
accData, err := tr.Get(accountHash.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get account %s: %w", accountHash, err)
|
||||
}
|
||||
if accData == nil {
|
||||
return nil, fmt.Errorf("account not found: %s", accountHash)
|
||||
}
|
||||
var acc types.StateAccount
|
||||
if err := rlp.DecodeBytes(accData, &acc); err != nil {
|
||||
return nil, fmt.Errorf("invalid account data %s: %w", accountHash, err)
|
||||
}
|
||||
return &acc, nil
|
||||
}
|
||||
|
||||
func traverseStorage(id *trie.ID, db *triedb.Database, report bool, detail bool) error {
|
||||
tr, err := trie.NewStateTrie(id, db)
|
||||
if err != nil {
|
||||
log.Error("Failed to open storage trie", "account", id.Owner, "root", id.Root, "err", err)
|
||||
return err
|
||||
}
|
||||
var (
|
||||
slots int
|
||||
nodes int
|
||||
lastReport time.Time
|
||||
start = time.Now()
|
||||
)
|
||||
it, err := tr.NodeIterator(nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to open storage iterator", "account", id.Owner, "root", id.Root, "err", err)
|
||||
return err
|
||||
}
|
||||
logger := log.Debug
|
||||
if report {
|
||||
logger = log.Info
|
||||
}
|
||||
logger("Start traversing storage trie", "account", id.Owner, "storageRoot", id.Root)
|
||||
|
||||
if !detail {
|
||||
iter := trie.NewIterator(it)
|
||||
for iter.Next() {
|
||||
slots += 1
|
||||
if time.Since(lastReport) > time.Second*8 {
|
||||
logger("Traversing storage", "account", id.Owner, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
lastReport = time.Now()
|
||||
}
|
||||
}
|
||||
if iter.Err != nil {
|
||||
log.Error("Failed to traverse storage trie", "root", id.Root, "err", iter.Err)
|
||||
return iter.Err
|
||||
}
|
||||
logger("Storage is complete", "account", id.Owner, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
} else {
|
||||
reader, err := db.NodeReader(id.StateRoot)
|
||||
if err != nil {
|
||||
log.Error("Failed to open state reader", "err", err)
|
||||
return err
|
||||
}
|
||||
var (
|
||||
buffer = make([]byte, 32)
|
||||
hasher = crypto.NewKeccakState()
|
||||
)
|
||||
for it.Next(true) {
|
||||
nodes += 1
|
||||
node := it.Hash()
|
||||
|
||||
// Check the presence for non-empty hash node(embedded node doesn't
|
||||
// have their own hash).
|
||||
if node != (common.Hash{}) {
|
||||
blob, _ := reader.Node(id.Owner, it.Path(), node)
|
||||
if len(blob) == 0 {
|
||||
log.Error("Missing trie node(storage)", "hash", node)
|
||||
return errors.New("missing storage")
|
||||
}
|
||||
hasher.Reset()
|
||||
hasher.Write(blob)
|
||||
hasher.Read(buffer)
|
||||
if !bytes.Equal(buffer, node.Bytes()) {
|
||||
log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob)
|
||||
return errors.New("invalid storage node")
|
||||
}
|
||||
}
|
||||
if it.Leaf() {
|
||||
slots += 1
|
||||
}
|
||||
if time.Since(lastReport) > time.Second*8 {
|
||||
logger("Traversing storage", "account", id.Owner, "nodes", nodes, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
lastReport = time.Now()
|
||||
}
|
||||
}
|
||||
if err := it.Error(); err != nil {
|
||||
log.Error("Failed to traverse storage trie", "root", id.Root, "err", err)
|
||||
return err
|
||||
}
|
||||
logger("Storage is complete", "account", id.Owner, "nodes", nodes, "slots", slots, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// traverseState is a helper function used for pruning verification.
|
||||
// Basically it just iterates the trie, ensure all nodes and associated
|
||||
// contract codes are present.
|
||||
|
|
@ -309,6 +432,30 @@ func traverseState(ctx *cli.Context) error {
|
|||
root = headBlock.Root()
|
||||
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
|
||||
}
|
||||
// If --account is specified, only traverse the storage trie of that account.
|
||||
if accountStr := ctx.String(utils.AccountFlag.Name); accountStr != "" {
|
||||
accountHash, err := parseAccount(accountStr)
|
||||
if err != nil {
|
||||
log.Error("Failed to parse account", "err", err)
|
||||
return err
|
||||
}
|
||||
// Use raw trie since the account key is already hashed.
|
||||
t, err := trie.New(trie.StateTrieID(root), triedb)
|
||||
if err != nil {
|
||||
log.Error("Failed to open state trie", "root", root, "err", err)
|
||||
return err
|
||||
}
|
||||
acc, err := lookupAccount(accountHash, t)
|
||||
if err != nil {
|
||||
log.Error("Failed to look up account", "hash", accountHash, "err", err)
|
||||
return err
|
||||
}
|
||||
if acc.Root == types.EmptyRootHash {
|
||||
log.Info("Account has no storage", "hash", accountHash)
|
||||
return nil
|
||||
}
|
||||
return traverseStorage(trie.StorageTrieID(root, accountHash, acc.Root), triedb, true, false)
|
||||
}
|
||||
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
|
||||
if err != nil {
|
||||
log.Error("Failed to open trie", "root", root, "err", err)
|
||||
|
|
@ -335,30 +482,10 @@ func traverseState(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
if acc.Root != types.EmptyRootHash {
|
||||
id := trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root)
|
||||
storageTrie, err := trie.NewStateTrie(id, triedb)
|
||||
err := traverseStorage(trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root), triedb, false, false)
|
||||
if err != nil {
|
||||
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
|
||||
return err
|
||||
}
|
||||
storageIt, err := storageTrie.NodeIterator(nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
|
||||
return err
|
||||
}
|
||||
storageIter := trie.NewIterator(storageIt)
|
||||
for storageIter.Next() {
|
||||
slots += 1
|
||||
|
||||
if time.Since(lastReport) > time.Second*8 {
|
||||
log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
lastReport = time.Now()
|
||||
}
|
||||
}
|
||||
if storageIter.Err != nil {
|
||||
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err)
|
||||
return storageIter.Err
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
|
||||
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
|
||||
|
|
@ -418,6 +545,30 @@ func traverseRawState(ctx *cli.Context) error {
|
|||
root = headBlock.Root()
|
||||
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
|
||||
}
|
||||
// If --account is specified, only traverse the storage trie of that account.
|
||||
if accountStr := ctx.String(utils.AccountFlag.Name); accountStr != "" {
|
||||
accountHash, err := parseAccount(accountStr)
|
||||
if err != nil {
|
||||
log.Error("Failed to parse account", "err", err)
|
||||
return err
|
||||
}
|
||||
// Use raw trie since the account key is already hashed.
|
||||
t, err := trie.New(trie.StateTrieID(root), triedb)
|
||||
if err != nil {
|
||||
log.Error("Failed to open state trie", "root", root, "err", err)
|
||||
return err
|
||||
}
|
||||
acc, err := lookupAccount(accountHash, t)
|
||||
if err != nil {
|
||||
log.Error("Failed to look up account", "hash", accountHash, "err", err)
|
||||
return err
|
||||
}
|
||||
if acc.Root == types.EmptyRootHash {
|
||||
log.Info("Account has no storage", "hash", accountHash)
|
||||
return nil
|
||||
}
|
||||
return traverseStorage(trie.StorageTrieID(root, accountHash, acc.Root), triedb, true, true)
|
||||
}
|
||||
t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
|
||||
if err != nil {
|
||||
log.Error("Failed to open trie", "root", root, "err", err)
|
||||
|
|
@ -473,50 +624,10 @@ func traverseRawState(ctx *cli.Context) error {
|
|||
return errors.New("invalid account")
|
||||
}
|
||||
if acc.Root != types.EmptyRootHash {
|
||||
id := trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root)
|
||||
storageTrie, err := trie.NewStateTrie(id, triedb)
|
||||
err := traverseStorage(trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root), triedb, false, true)
|
||||
if err != nil {
|
||||
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
|
||||
return errors.New("missing storage trie")
|
||||
}
|
||||
storageIter, err := storageTrie.NodeIterator(nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to open storage iterator", "root", acc.Root, "err", err)
|
||||
return err
|
||||
}
|
||||
for storageIter.Next(true) {
|
||||
nodes += 1
|
||||
node := storageIter.Hash()
|
||||
|
||||
// Check the presence for non-empty hash node(embedded node doesn't
|
||||
// have their own hash).
|
||||
if node != (common.Hash{}) {
|
||||
blob, _ := reader.Node(common.BytesToHash(accIter.LeafKey()), storageIter.Path(), node)
|
||||
if len(blob) == 0 {
|
||||
log.Error("Missing trie node(storage)", "hash", node)
|
||||
return errors.New("missing storage")
|
||||
}
|
||||
hasher.Reset()
|
||||
hasher.Write(blob)
|
||||
hasher.Read(got)
|
||||
if !bytes.Equal(got, node.Bytes()) {
|
||||
log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob)
|
||||
return errors.New("invalid storage node")
|
||||
}
|
||||
}
|
||||
// Bump the counter if it's leaf node.
|
||||
if storageIter.Leaf() {
|
||||
slots += 1
|
||||
}
|
||||
if time.Since(lastReport) > time.Second*8 {
|
||||
log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
lastReport = time.Now()
|
||||
}
|
||||
}
|
||||
if storageIter.Error() != nil {
|
||||
log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error())
|
||||
return storageIter.Error()
|
||||
}
|
||||
}
|
||||
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
|
||||
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ require (
|
|||
github.com/bits-and-blooms/bitset v1.20.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/consensys/gnark-crypto v0.18.1 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect
|
||||
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/emicklei/dot v1.6.2 // indirect
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAK
|
|||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||
github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDdoGqFt3fI=
|
||||
github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
|
||||
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0 h1:FYRiJMJG2iv+2Dy3fi14SVGjcPteZ5HAAUe4YWlJygc=
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||
|
|
|
|||
|
|
@ -218,17 +218,15 @@ var (
|
|||
Usage: "Max number of elements (0 = no limit)",
|
||||
Value: 0,
|
||||
}
|
||||
TopFlag = &cli.IntFlag{
|
||||
Name: "top",
|
||||
Usage: "Print the top N results",
|
||||
Value: 5,
|
||||
AccountFlag = &cli.StringFlag{
|
||||
Name: "account",
|
||||
Usage: "Specifies the account address or hash to traverse a single storage trie",
|
||||
}
|
||||
OutputFileFlag = &cli.StringFlag{
|
||||
Name: "output",
|
||||
Usage: "Writes the result in json to the output",
|
||||
Value: "",
|
||||
}
|
||||
|
||||
SnapshotFlag = &cli.BoolFlag{
|
||||
Name: "snapshot",
|
||||
Usage: `Enables snapshot-database mode (default = enable)`,
|
||||
|
|
@ -325,7 +323,7 @@ var (
|
|||
}
|
||||
ChainHistoryFlag = &cli.StringFlag{
|
||||
Name: "history.chain",
|
||||
Usage: `Blockchain history retention ("all" or "postmerge")`,
|
||||
Usage: `Blockchain history retention ("all", "postmerge", or "postprague")`,
|
||||
Value: ethconfig.Defaults.HistoryMode.String(),
|
||||
Category: flags.StateCategory,
|
||||
}
|
||||
|
|
@ -490,8 +488,8 @@ var (
|
|||
// Performance tuning settings
|
||||
CacheFlag = &cli.IntFlag{
|
||||
Name: "cache",
|
||||
Usage: "Megabytes of memory allocated to internal caching (default = 4096 mainnet full node, 128 light mode)",
|
||||
Value: 1024,
|
||||
Usage: "Megabytes of memory allocated to internal caching",
|
||||
Value: 4096,
|
||||
Category: flags.PerfCategory,
|
||||
}
|
||||
CacheDatabaseFlag = &cli.IntFlag{
|
||||
|
|
@ -2475,8 +2473,6 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
|||
}
|
||||
vmcfg := vm.Config{
|
||||
EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name),
|
||||
EnableWitnessStats: ctx.Bool(VMWitnessStatsFlag.Name),
|
||||
StatelessSelfValidation: ctx.Bool(VMStatelessSelfValidationFlag.Name) || ctx.Bool(VMWitnessStatsFlag.Name),
|
||||
}
|
||||
if ctx.IsSet(VMTraceFlag.Name) {
|
||||
if name := ctx.String(VMTraceFlag.Name); name != "" {
|
||||
|
|
@ -2490,6 +2486,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
|
|||
}
|
||||
options.VmConfig = vmcfg
|
||||
|
||||
options.StatelessSelfValidation = ctx.Bool(VMStatelessSelfValidationFlag.Name) || ctx.Bool(VMWitnessStatsFlag.Name)
|
||||
options.EnableWitnessStats = ctx.Bool(VMWitnessStatsFlag.Name)
|
||||
|
||||
chain, err := core.NewBlockChain(chainDb, gspec, engine, options)
|
||||
if err != nil {
|
||||
Fatalf("Can't create BlockChain: %v", err)
|
||||
|
|
|
|||
|
|
@ -155,7 +155,9 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
|||
}
|
||||
|
||||
cfg.historyPruneBlock = new(uint64)
|
||||
*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
|
||||
if p, err := history.NewPolicy(history.KeepPostMerge, params.MainnetGenesisHash); err == nil {
|
||||
*cfg.historyPruneBlock = p.Target.BlockNumber
|
||||
}
|
||||
case ctx.Bool(testSepoliaFlag.Name):
|
||||
cfg.fsys = builtinTestFiles
|
||||
if ctx.IsSet(filterQueryFileFlag.Name) {
|
||||
|
|
@ -180,7 +182,9 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
|||
}
|
||||
|
||||
cfg.historyPruneBlock = new(uint64)
|
||||
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
|
||||
if p, err := history.NewPolicy(history.KeepPostMerge, params.SepoliaGenesisHash); err == nil {
|
||||
*cfg.historyPruneBlock = p.Target.BlockNumber
|
||||
}
|
||||
default:
|
||||
cfg.fsys = os.DirFS(".")
|
||||
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
|
@ -29,6 +30,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/internal/telemetry"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/holiman/uint256"
|
||||
|
|
@ -351,9 +353,17 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
|
||||
// FinalizeAndAssemble implements consensus.Engine, setting the final state and
|
||||
// assembling the block.
|
||||
func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (beacon *Beacon) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (result *types.Block, err error) {
|
||||
ctx, _, spanEnd := telemetry.StartSpan(ctx, "consensus.beacon.FinalizeAndAssemble",
|
||||
telemetry.Int64Attribute("block.number", int64(header.Number.Uint64())),
|
||||
telemetry.Int64Attribute("txs.count", int64(len(body.Transactions))),
|
||||
telemetry.Int64Attribute("withdrawals.count", int64(len(body.Withdrawals))),
|
||||
)
|
||||
defer spanEnd(&err)
|
||||
|
||||
if !beacon.IsPoSHeader(header) {
|
||||
return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts)
|
||||
block, delegateErr := beacon.ethone.FinalizeAndAssemble(ctx, chain, header, state, body, receipts)
|
||||
return block, delegateErr
|
||||
}
|
||||
shanghai := chain.Config().IsShanghai(header.Number, header.Time)
|
||||
if shanghai {
|
||||
|
|
@ -367,13 +377,20 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
|||
}
|
||||
}
|
||||
// Finalize and assemble the block.
|
||||
_, _, finalizeSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.Finalize")
|
||||
beacon.Finalize(chain, header, state, body)
|
||||
finalizeSpanEnd(nil)
|
||||
|
||||
// Assign the final state root to header.
|
||||
_, _, rootSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.IntermediateRoot")
|
||||
header.Root = state.IntermediateRoot(true)
|
||||
rootSpanEnd(nil)
|
||||
|
||||
// Assemble the final block.
|
||||
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil
|
||||
_, _, blockSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.NewBlock")
|
||||
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
|
||||
blockSpanEnd(nil)
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// Seal generates a new sealing request for the given input block and pushes
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package clique
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -581,7 +582,7 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
|
|||
|
||||
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
|
||||
// nor block rewards given, and returns the final block.
|
||||
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (c *Clique) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
if len(body.Withdrawals) > 0 {
|
||||
return nil, errors.New("clique does not support withdrawals")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package consensus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -92,7 +93,7 @@ type Engine interface {
|
|||
//
|
||||
// Note: The block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
|
||||
FinalizeAndAssemble(ctx context.Context, chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
|
||||
|
||||
// Seal generates a new sealing request for the given input block and pushes
|
||||
// the result into the given channel.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package ethash
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
|
@ -513,7 +514,7 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.
|
|||
|
||||
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and
|
||||
// uncle rewards, setting the final state and assembling the block.
|
||||
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
func (ethash *Ethash) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
|
||||
if len(body.Withdrawals) > 0 {
|
||||
return nil, errors.New("ethash does not support withdrawals")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, str
|
|||
for ; start > 0; start-- {
|
||||
// Skip all methods and namespaces (i.e. including the dot)
|
||||
c := line[start]
|
||||
if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '1' && c <= '9') {
|
||||
if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
|
||||
continue
|
||||
}
|
||||
// We've hit an unexpected character, autocomplete form here
|
||||
|
|
|
|||
|
|
@ -93,9 +93,7 @@ var (
|
|||
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
|
||||
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
|
||||
codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil)
|
||||
|
||||
snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
|
||||
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
|
||||
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
|
||||
|
||||
blockInsertTimer = metrics.NewRegisteredResettingTimer("chain/inserts", nil)
|
||||
blockValidationTimer = metrics.NewRegisteredResettingTimer("chain/validation", nil)
|
||||
|
|
@ -196,9 +194,8 @@ type BlockChainConfig struct {
|
|||
SnapshotNoBuild bool // Whether the background generation is allowed
|
||||
SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
|
||||
|
||||
// This defines the cutoff block for history expiry.
|
||||
// Blocks before this number may be unavailable in the chain database.
|
||||
ChainHistoryMode history.HistoryMode
|
||||
// HistoryPolicy defines the chain history pruning intent.
|
||||
HistoryPolicy history.HistoryPolicy
|
||||
|
||||
// Misc options
|
||||
NoPrefetch bool // Whether to disable heuristic state prefetching when processing blocks
|
||||
|
|
@ -219,19 +216,23 @@ type BlockChainConfig struct {
|
|||
// detailed statistics will be logged. Negative value means disabled (default),
|
||||
// zero logs all blocks, positive value filters blocks by execution time.
|
||||
SlowBlockThreshold time.Duration
|
||||
|
||||
// Execution configs
|
||||
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
|
||||
EnableWitnessStats bool // Whether trie access statistics collection is enabled
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default config.
|
||||
// Note the returned object is safe to modify!
|
||||
func DefaultConfig() *BlockChainConfig {
|
||||
return &BlockChainConfig{
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
StateScheme: rawdb.HashScheme,
|
||||
SnapshotLimit: 256,
|
||||
SnapshotWait: true,
|
||||
ChainHistoryMode: history.KeepAll,
|
||||
TrieCleanLimit: 256,
|
||||
TrieDirtyLimit: 256,
|
||||
TrieTimeLimit: 5 * time.Minute,
|
||||
StateScheme: rawdb.HashScheme,
|
||||
SnapshotLimit: 256,
|
||||
SnapshotWait: true,
|
||||
HistoryPolicy: history.HistoryPolicy{Mode: history.KeepAll},
|
||||
// Transaction indexing is disabled by default.
|
||||
// This is appropriate for most unit tests.
|
||||
TxLookupLimit: -1,
|
||||
|
|
@ -322,7 +323,7 @@ type BlockChain struct {
|
|||
lastWrite uint64 // Last block when the state was flushed
|
||||
flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state
|
||||
triedb *triedb.Database // The database handler for maintaining trie nodes.
|
||||
statedb *state.CachingDB // State database to reuse between imports (contains state cache)
|
||||
codedb *state.CodeDB // The database handler for maintaining contract codes.
|
||||
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
|
||||
|
||||
hc *HeaderChain
|
||||
|
|
@ -404,6 +405,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
cfg: cfg,
|
||||
db: db,
|
||||
triedb: triedb,
|
||||
codedb: state.NewCodeDB(db),
|
||||
triegc: prque.New[int64, common.Hash](nil),
|
||||
chainmu: syncx.NewClosableMutex(),
|
||||
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
|
||||
|
|
@ -420,7 +422,6 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
return nil, err
|
||||
}
|
||||
bc.flushInterval.Store(int64(cfg.TrieTimeLimit))
|
||||
bc.statedb = state.NewDatabase(bc.triedb, nil)
|
||||
bc.validator = NewBlockValidator(chainConfig, bc)
|
||||
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
|
||||
bc.processor = NewStateProcessor(bc.hc)
|
||||
|
|
@ -597,9 +598,6 @@ func (bc *BlockChain) setupSnapshot() {
|
|||
AsyncBuild: !bc.cfg.SnapshotWait,
|
||||
}
|
||||
bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)
|
||||
|
||||
// Re-initialize the state database with snapshot
|
||||
bc.statedb = state.NewDatabase(bc.triedb, bc.snaps)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -717,45 +715,43 @@ func (bc *BlockChain) loadLastState() error {
|
|||
// initializeHistoryPruning sets bc.historyPrunePoint.
|
||||
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
|
||||
freezerTail, _ := bc.db.Tail()
|
||||
policy := bc.cfg.HistoryPolicy
|
||||
|
||||
switch bc.cfg.ChainHistoryMode {
|
||||
switch policy.Mode {
|
||||
case history.KeepAll:
|
||||
if freezerTail == 0 {
|
||||
return nil
|
||||
if freezerTail > 0 {
|
||||
// Database was pruned externally. Record the actual state.
|
||||
log.Warn("Chain history database is pruned", "tail", freezerTail, "mode", policy.Mode)
|
||||
bc.historyPrunePoint.Store(&history.PrunePoint{
|
||||
BlockNumber: freezerTail,
|
||||
BlockHash: bc.GetCanonicalHash(freezerTail),
|
||||
})
|
||||
}
|
||||
// The database was pruned somehow, so we need to figure out if it's a known
|
||||
// configuration or an error.
|
||||
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()]
|
||||
if predefinedPoint == nil || freezerTail != predefinedPoint.BlockNumber {
|
||||
log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail)
|
||||
return errors.New("unexpected database tail")
|
||||
}
|
||||
bc.historyPrunePoint.Store(predefinedPoint)
|
||||
return nil
|
||||
|
||||
case history.KeepPostMerge:
|
||||
if freezerTail == 0 && latest != 0 {
|
||||
// This is the case where a user is trying to run with --history.chain
|
||||
// postmerge directly on an existing DB. We could just trigger the pruning
|
||||
// here, but it'd be a bit dangerous since they may not have intended this
|
||||
// action to happen. So just tell them how to do it.
|
||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String()))
|
||||
log.Error(fmt.Sprintf("Run 'geth prune-history' to prune pre-merge history."))
|
||||
return errors.New("history pruning requested via configuration")
|
||||
case history.KeepPostMerge, history.KeepPostPrague:
|
||||
target := policy.Target
|
||||
// Already at the target.
|
||||
if freezerTail == target.BlockNumber {
|
||||
bc.historyPrunePoint.Store(target)
|
||||
return nil
|
||||
}
|
||||
predefinedPoint := history.PrunePoints[bc.genesisBlock.Hash()]
|
||||
if predefinedPoint == nil {
|
||||
log.Error("Chain history pruning is not supported for this network", "genesis", bc.genesisBlock.Hash())
|
||||
return errors.New("history pruning requested for unknown network")
|
||||
} else if freezerTail > 0 && freezerTail != predefinedPoint.BlockNumber {
|
||||
log.Error("Chain history database is pruned to unknown block", "tail", freezerTail)
|
||||
return errors.New("unexpected database tail")
|
||||
// Database is pruned beyond the target.
|
||||
if freezerTail > target.BlockNumber {
|
||||
return fmt.Errorf("database pruned beyond requested history (tail=%d, target=%d)", freezerTail, target.BlockNumber)
|
||||
}
|
||||
bc.historyPrunePoint.Store(predefinedPoint)
|
||||
// Database needs pruning (freezerTail < target).
|
||||
if latest != 0 {
|
||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned to the target block.", policy.Mode.String()))
|
||||
log.Error(fmt.Sprintf("Run 'geth prune-history --history.chain %s' to prune history.", policy.Mode.String()))
|
||||
return errors.New("history pruning required")
|
||||
}
|
||||
// Fresh database (latest == 0), will sync from target point.
|
||||
bc.historyPrunePoint.Store(target)
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid history mode: %d", bc.cfg.ChainHistoryMode)
|
||||
return fmt.Errorf("invalid history mode: %d", policy.Mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1279,6 +1275,8 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
|
|||
func (bc *BlockChain) writeHeadBlock(block *types.Block) {
|
||||
// Add the block to the canonical chain number scheme and mark as the head
|
||||
batch := bc.db.NewBatch()
|
||||
defer batch.Close()
|
||||
|
||||
rawdb.WriteHeadHeaderHash(batch, block.Hash())
|
||||
rawdb.WriteHeadFastBlockHash(batch, block.Hash())
|
||||
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
|
||||
|
|
@ -1653,6 +1651,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
|||
batch = bc.db.NewBatch()
|
||||
start = time.Now()
|
||||
)
|
||||
defer batch.Close()
|
||||
|
||||
rawdb.WriteBlock(batch, block)
|
||||
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
|
||||
rawdb.WritePreimages(batch, statedb.Preimages())
|
||||
|
|
@ -1990,7 +1990,15 @@ func (bc *BlockChain) insertChain(ctx context.Context, chain types.Blocks, setHe
|
|||
}
|
||||
// The traced section of block import.
|
||||
start := time.Now()
|
||||
res, err := bc.ProcessBlock(ctx, parent.Root, block, setHead, makeWitness && len(chain) == 1)
|
||||
config := ExecuteConfig{
|
||||
WriteState: true,
|
||||
WriteHead: setHead,
|
||||
EnableTracer: true,
|
||||
MakeWitness: makeWitness && len(chain) == 1,
|
||||
StatelessSelfValidation: bc.cfg.StatelessSelfValidation,
|
||||
EnableWitnessStats: bc.cfg.EnableWitnessStats,
|
||||
}
|
||||
res, err := bc.ProcessBlock(ctx, parent.Root, block, config)
|
||||
if err != nil {
|
||||
return nil, it.index, err
|
||||
}
|
||||
|
|
@ -2073,19 +2081,47 @@ func (bpr *blockProcessingResult) Stats() *ExecuteStats {
|
|||
return bpr.stats
|
||||
}
|
||||
|
||||
// ExecuteConfig defines optional behaviors during execution.
|
||||
type ExecuteConfig struct {
|
||||
// WriteState controls whether the computed state changes are persisted to
|
||||
// the underlying storage. If false, execution is performed in-memory only.
|
||||
WriteState bool
|
||||
|
||||
// WriteHead indicates whether the execution result should update the canonical
|
||||
// chain head. It's only relevant with WriteState == True.
|
||||
WriteHead bool
|
||||
|
||||
// EnableTracer enables execution tracing. This is typically used for debugging
|
||||
// or analysis and may significantly impact performance.
|
||||
EnableTracer bool
|
||||
|
||||
// MakeWitness indicates whether to generate execution witness data during
|
||||
// execution. Enabling this may introduce additional memory and CPU overhead.
|
||||
MakeWitness bool
|
||||
|
||||
// StatelessSelfValidation indicates whether the execution witnesses generation
|
||||
// and self-validation (testing purpose) is enabled.
|
||||
StatelessSelfValidation bool
|
||||
|
||||
// EnableWitnessStats indicates whether to enable collection of witness trie
|
||||
// access statistics
|
||||
EnableWitnessStats bool
|
||||
}
|
||||
|
||||
// ProcessBlock executes and validates the given block. If there was no error
|
||||
// it writes the block and associated state to database.
|
||||
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (result *blockProcessingResult, blockEndErr error) {
|
||||
func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, config ExecuteConfig) (result *blockProcessingResult, blockEndErr error) {
|
||||
var (
|
||||
err error
|
||||
startTime = time.Now()
|
||||
statedb *state.StateDB
|
||||
interrupt atomic.Bool
|
||||
sdb = state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps)
|
||||
)
|
||||
defer interrupt.Store(true) // terminate the prefetch at the end
|
||||
|
||||
if bc.cfg.NoPrefetch {
|
||||
statedb, err = state.New(parentRoot, bc.statedb)
|
||||
statedb, err = state.New(parentRoot, sdb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -2095,23 +2131,27 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
//
|
||||
// Note: the main processor and prefetcher share the same reader with a local
|
||||
// cache for mitigating the overhead of state access.
|
||||
prefetch, process, err := bc.statedb.ReadersWithCacheStats(parentRoot)
|
||||
prefetch, process, err := sdb.ReadersWithCacheStats(parentRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, prefetch)
|
||||
throwaway, err := state.NewWithReader(parentRoot, sdb, prefetch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statedb, err = state.NewWithReader(parentRoot, bc.statedb, process)
|
||||
statedb, err = state.NewWithReader(parentRoot, sdb, process)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Upload the statistics of reader at the end
|
||||
defer func() {
|
||||
if result != nil {
|
||||
result.stats.StatePrefetchCacheStats = prefetch.GetStats()
|
||||
result.stats.StateReadCacheStats = process.GetStats()
|
||||
if stater, ok := prefetch.(state.ReaderStater); ok {
|
||||
result.stats.StatePrefetchCacheStats = stater.GetStats()
|
||||
}
|
||||
if stater, ok := process.(state.ReaderStater); ok {
|
||||
result.stats.StateReadCacheStats = stater.GetStats()
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
|
||||
|
|
@ -2138,12 +2178,12 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
// Generate witnesses either if we're self-testing, or if it's the
|
||||
// only block being inserted. A bit crude, but witnesses are huge,
|
||||
// so we refuse to make an entire chain of them.
|
||||
if bc.cfg.VmConfig.StatelessSelfValidation || makeWitness {
|
||||
if config.StatelessSelfValidation || config.MakeWitness {
|
||||
witness, err = stateless.NewWitness(block.Header(), bc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bc.cfg.VmConfig.EnableWitnessStats {
|
||||
if config.EnableWitnessStats {
|
||||
witnessStats = stateless.NewWitnessStats()
|
||||
}
|
||||
}
|
||||
|
|
@ -2151,17 +2191,20 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
defer statedb.StopPrefetcher()
|
||||
}
|
||||
|
||||
if bc.logger != nil && bc.logger.OnBlockStart != nil {
|
||||
bc.logger.OnBlockStart(tracing.BlockEvent{
|
||||
Block: block,
|
||||
Finalized: bc.CurrentFinalBlock(),
|
||||
Safe: bc.CurrentSafeBlock(),
|
||||
})
|
||||
}
|
||||
if bc.logger != nil && bc.logger.OnBlockEnd != nil {
|
||||
defer func() {
|
||||
bc.logger.OnBlockEnd(blockEndErr)
|
||||
}()
|
||||
// Instrument the blockchain tracing
|
||||
if config.EnableTracer {
|
||||
if bc.logger != nil && bc.logger.OnBlockStart != nil {
|
||||
bc.logger.OnBlockStart(tracing.BlockEvent{
|
||||
Block: block,
|
||||
Finalized: bc.CurrentFinalBlock(),
|
||||
Safe: bc.CurrentSafeBlock(),
|
||||
})
|
||||
}
|
||||
if bc.logger != nil && bc.logger.OnBlockEnd != nil {
|
||||
defer func() {
|
||||
bc.logger.OnBlockEnd(blockEndErr)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Process block using the parent state as reference point
|
||||
|
|
@ -2191,7 +2234,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
// witness builder/runner, which would otherwise be impossible due to the
|
||||
// various invalid chain states/behaviors being contained in those tests.
|
||||
xvstart := time.Now()
|
||||
if witness := statedb.Witness(); witness != nil && bc.cfg.VmConfig.StatelessSelfValidation {
|
||||
if witness := statedb.Witness(); witness != nil && config.StatelessSelfValidation {
|
||||
log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash())
|
||||
|
||||
// Remove critical computed fields from the block to force true recalculation
|
||||
|
|
@ -2244,31 +2287,28 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
stats.CrossValidation = xvtime // The time spent on stateless cross validation
|
||||
|
||||
// Write the block to the chain and get the status.
|
||||
var (
|
||||
wstart = time.Now()
|
||||
status WriteStatus
|
||||
)
|
||||
if !setHead {
|
||||
// Don't set the head, only insert the block
|
||||
err = bc.writeBlockWithState(block, res.Receipts, statedb)
|
||||
} else {
|
||||
status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var status WriteStatus
|
||||
if config.WriteState {
|
||||
wstart := time.Now()
|
||||
if !config.WriteHead {
|
||||
// Don't set the head, only insert the block
|
||||
err = bc.writeBlockWithState(block, res.Receipts, statedb)
|
||||
} else {
|
||||
status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Update the metrics touched during block commit
|
||||
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
|
||||
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
|
||||
stats.DatabaseCommit = statedb.DatabaseCommits // Database commits are complete, we can mark them
|
||||
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.DatabaseCommits
|
||||
}
|
||||
// Report the collected witness statistics
|
||||
if witnessStats != nil {
|
||||
witnessStats.ReportMetrics(block.NumberU64())
|
||||
}
|
||||
|
||||
// Update the metrics touched during block commit
|
||||
stats.AccountCommits = statedb.AccountCommits // Account commits are complete, we can mark them
|
||||
stats.StorageCommits = statedb.StorageCommits // Storage commits are complete, we can mark them
|
||||
stats.SnapshotCommit = statedb.SnapshotCommits // Snapshot commits are complete, we can mark them
|
||||
stats.TrieDBCommit = statedb.TrieDBCommits // Trie database commits are complete, we can mark them
|
||||
stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits
|
||||
|
||||
elapsed := time.Since(startTime) + 1 // prevent zero division
|
||||
stats.TotalTime = elapsed
|
||||
stats.MgasPerSecond = float64(res.GasUsed) * 1000 / float64(elapsed)
|
||||
|
|
@ -2549,6 +2589,7 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
|
|||
// as the txlookups should be changed atomically, and all subsequent
|
||||
// reads should be blocked until the mutation is complete.
|
||||
bc.txLookupLock.Lock()
|
||||
defer bc.txLookupLock.Unlock()
|
||||
|
||||
// Reorg can be executed, start reducing the chain's old blocks and appending
|
||||
// the new blocks
|
||||
|
|
@ -2626,6 +2667,8 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
|
|||
// Delete useless indexes right now which includes the non-canonical
|
||||
// transaction indexes, canonical chain indexes which above the head.
|
||||
batch := bc.db.NewBatch()
|
||||
defer batch.Close()
|
||||
|
||||
for _, tx := range types.HashDifference(deletedTxs, rebirthTxs) {
|
||||
rawdb.DeleteTxLookupEntry(batch, tx)
|
||||
}
|
||||
|
|
@ -2649,9 +2692,6 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
|
|||
// Reset the tx lookup cache to clear stale txlookup cache.
|
||||
bc.txLookupCache.Purge()
|
||||
|
||||
// Release the tx-lookup lock after mutation.
|
||||
bc.txLookupLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ func (bc *BlockChain) TxIndexDone() bool {
|
|||
|
||||
// HasState checks if state trie is fully present in the database or not.
|
||||
func (bc *BlockChain) HasState(hash common.Hash) bool {
|
||||
_, err := bc.statedb.OpenTrie(hash)
|
||||
_, err := bc.triedb.NodeReader(hash)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
|
|
@ -403,7 +403,7 @@ func (bc *BlockChain) stateRecoverable(root common.Hash) bool {
|
|||
func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) []byte {
|
||||
// TODO(rjl493456442) The associated account address is also required
|
||||
// in Verkle scheme. Fix it once snap-sync is supported for Verkle.
|
||||
return bc.statedb.ContractCodeWithPrefix(common.Address{}, hash)
|
||||
return bc.codedb.Reader().CodeWithPrefix(common.Address{}, hash)
|
||||
}
|
||||
|
||||
// State returns a new mutable state based on the current HEAD block.
|
||||
|
|
@ -413,14 +413,14 @@ func (bc *BlockChain) State() (*state.StateDB, error) {
|
|||
|
||||
// StateAt returns a new mutable state based on a particular point in time.
|
||||
func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {
|
||||
return state.New(root, bc.statedb)
|
||||
return state.New(root, state.NewDatabase(bc.triedb, bc.codedb).WithSnapshot(bc.snaps))
|
||||
}
|
||||
|
||||
// HistoricState returns a historic state specified by the given root.
|
||||
// Live states are not available and won't be served, please use `State`
|
||||
// or `StateAt` instead.
|
||||
func (bc *BlockChain) HistoricState(root common.Hash) (*state.StateDB, error) {
|
||||
return state.New(root, state.NewHistoricDatabase(bc.db, bc.triedb))
|
||||
return state.New(root, state.NewHistoricDatabase(bc.triedb, bc.codedb))
|
||||
}
|
||||
|
||||
// Config retrieves the chain's fork configuration.
|
||||
|
|
@ -444,11 +444,6 @@ func (bc *BlockChain) Processor() Processor {
|
|||
return bc.processor
|
||||
}
|
||||
|
||||
// StateCache returns the caching database underpinning the blockchain instance.
|
||||
func (bc *BlockChain) StateCache() state.Database {
|
||||
return bc.statedb
|
||||
}
|
||||
|
||||
// GasLimit returns the gas limit of the current HEAD block.
|
||||
func (bc *BlockChain) GasLimit() uint64 {
|
||||
return bc.CurrentBlock().GasLimit
|
||||
|
|
@ -492,6 +487,11 @@ func (bc *BlockChain) TrieDB() *triedb.Database {
|
|||
return bc.triedb
|
||||
}
|
||||
|
||||
// CodeDB retrieves the low level contract code database used for data storage.
|
||||
func (bc *BlockChain) CodeDB() *state.CodeDB {
|
||||
return bc.codedb
|
||||
}
|
||||
|
||||
// HeaderChain returns the underlying header chain.
|
||||
func (bc *BlockChain) HeaderChain() *HeaderChain {
|
||||
return bc.hc
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb/pebble"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -2041,7 +2040,6 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
|
|||
dbconfig.HashDB = hashdb.Defaults
|
||||
}
|
||||
chain.triedb = triedb.NewDatabase(chain.db, dbconfig)
|
||||
chain.statedb = state.NewDatabase(chain.triedb, chain.snaps)
|
||||
|
||||
// Force run a freeze cycle
|
||||
type freezer interface {
|
||||
|
|
|
|||
|
|
@ -52,8 +52,7 @@ type ExecuteStats struct {
|
|||
Execution time.Duration // Time spent on the EVM execution
|
||||
Validation time.Duration // Time spent on the block validation
|
||||
CrossValidation time.Duration // Optional, time spent on the block cross validation
|
||||
SnapshotCommit time.Duration // Time spent on snapshot commit
|
||||
TrieDBCommit time.Duration // Time spent on database commit
|
||||
DatabaseCommit time.Duration // Time spent on database commit
|
||||
BlockWrite time.Duration // Time spent on block write
|
||||
TotalTime time.Duration // The total time spent on block execution
|
||||
MgasPerSecond float64 // The million gas processed per second
|
||||
|
|
@ -87,22 +86,21 @@ func (s *ExecuteStats) reportMetrics() {
|
|||
blockExecutionTimer.Update(s.Execution) // The time spent on EVM processing
|
||||
blockValidationTimer.Update(s.Validation) // The time spent on block validation
|
||||
blockCrossValidationTimer.Update(s.CrossValidation) // The time spent on stateless cross validation
|
||||
snapshotCommitTimer.Update(s.SnapshotCommit) // Snapshot commits are complete, we can mark them
|
||||
triedbCommitTimer.Update(s.TrieDBCommit) // Trie database commits are complete, we can mark them
|
||||
triedbCommitTimer.Update(s.DatabaseCommit) // Trie database commits are complete, we can mark them
|
||||
blockWriteTimer.Update(s.BlockWrite) // The time spent on block write
|
||||
blockInsertTimer.Update(s.TotalTime) // The total time spent on block execution
|
||||
chainMgaspsMeter.Update(time.Duration(s.MgasPerSecond)) // TODO(rjl493456442) generalize the ResettingTimer
|
||||
|
||||
// Cache hit rates
|
||||
accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheHit)
|
||||
accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheMiss)
|
||||
storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheHit)
|
||||
storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheMiss)
|
||||
accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheHit)
|
||||
accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheMiss)
|
||||
storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheHit)
|
||||
storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheMiss)
|
||||
|
||||
accountCacheHitMeter.Mark(s.StateReadCacheStats.AccountCacheHit)
|
||||
accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss)
|
||||
storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit)
|
||||
storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss)
|
||||
accountCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheHit)
|
||||
accountCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheMiss)
|
||||
storageCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheHit)
|
||||
storageCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheMiss)
|
||||
}
|
||||
|
||||
// slowBlockLog represents the JSON structure for slow block logging.
|
||||
|
|
@ -177,14 +175,6 @@ type slowBlockCodeCacheEntry struct {
|
|||
MissBytes int64 `json:"miss_bytes"`
|
||||
}
|
||||
|
||||
// calculateHitRate computes the cache hit rate as a percentage (0-100).
|
||||
func calculateHitRate(hits, misses int64) float64 {
|
||||
if total := hits + misses; total > 0 {
|
||||
return float64(hits) / float64(total) * 100.0
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// durationToMs converts a time.Duration to milliseconds as a float64
|
||||
// with sub-millisecond precision for accurate cross-client metrics.
|
||||
func durationToMs(d time.Duration) float64 {
|
||||
|
|
@ -216,7 +206,7 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
|||
ExecutionMs: durationToMs(s.Execution),
|
||||
StateReadMs: durationToMs(s.AccountReads + s.StorageReads + s.CodeReads),
|
||||
StateHashMs: durationToMs(s.AccountHashes + s.AccountUpdates + s.StorageUpdates),
|
||||
CommitMs: durationToMs(max(s.AccountCommits, s.StorageCommits) + s.TrieDBCommit + s.SnapshotCommit + s.BlockWrite),
|
||||
CommitMs: durationToMs(max(s.AccountCommits, s.StorageCommits) + s.DatabaseCommit + s.BlockWrite),
|
||||
TotalMs: durationToMs(s.TotalTime),
|
||||
},
|
||||
Throughput: slowBlockThru{
|
||||
|
|
@ -238,19 +228,19 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
|||
},
|
||||
Cache: slowBlockCache{
|
||||
Account: slowBlockCacheEntry{
|
||||
Hits: s.StateReadCacheStats.AccountCacheHit,
|
||||
Misses: s.StateReadCacheStats.AccountCacheMiss,
|
||||
HitRate: calculateHitRate(s.StateReadCacheStats.AccountCacheHit, s.StateReadCacheStats.AccountCacheMiss),
|
||||
Hits: s.StateReadCacheStats.StateStats.AccountCacheHit,
|
||||
Misses: s.StateReadCacheStats.StateStats.AccountCacheMiss,
|
||||
HitRate: s.StateReadCacheStats.StateStats.AccountCacheHitRate(),
|
||||
},
|
||||
Storage: slowBlockCacheEntry{
|
||||
Hits: s.StateReadCacheStats.StorageCacheHit,
|
||||
Misses: s.StateReadCacheStats.StorageCacheMiss,
|
||||
HitRate: calculateHitRate(s.StateReadCacheStats.StorageCacheHit, s.StateReadCacheStats.StorageCacheMiss),
|
||||
Hits: s.StateReadCacheStats.StateStats.StorageCacheHit,
|
||||
Misses: s.StateReadCacheStats.StateStats.StorageCacheMiss,
|
||||
HitRate: s.StateReadCacheStats.StateStats.StorageCacheHitRate(),
|
||||
},
|
||||
Code: slowBlockCodeCacheEntry{
|
||||
Hits: s.StateReadCacheStats.CodeStats.CacheHit,
|
||||
Misses: s.StateReadCacheStats.CodeStats.CacheMiss,
|
||||
HitRate: calculateHitRate(s.StateReadCacheStats.CodeStats.CacheHit, s.StateReadCacheStats.CodeStats.CacheMiss),
|
||||
HitRate: s.StateReadCacheStats.CodeStats.HitRate(),
|
||||
HitBytes: s.StateReadCacheStats.CodeStats.CacheHitBytes,
|
||||
MissBytes: s.StateReadCacheStats.CodeStats.CacheMissBytes,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/history"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -157,7 +156,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.statedb)
|
||||
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), state.NewDatabase(blockchain.triedb, blockchain.codedb))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -4337,26 +4336,13 @@ func TestInsertChainWithCutoff(t *testing.T) {
|
|||
func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64, genesis *Genesis, blocks []*types.Block, receipts []types.Receipts) {
|
||||
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true)))
|
||||
|
||||
// Add a known pruning point for the duration of the test.
|
||||
ghash := genesis.ToBlock().Hash()
|
||||
cutoffBlock := blocks[cutoff-1]
|
||||
history.PrunePoints[ghash] = &history.PrunePoint{
|
||||
BlockNumber: cutoffBlock.NumberU64(),
|
||||
BlockHash: cutoffBlock.Hash(),
|
||||
}
|
||||
defer func() {
|
||||
delete(history.PrunePoints, ghash)
|
||||
}()
|
||||
|
||||
// Enable pruning in cache config.
|
||||
config := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||
config.ChainHistoryMode = history.KeepPostMerge
|
||||
|
||||
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||
defer db.Close()
|
||||
|
||||
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||
chain, _ := NewBlockChain(db, genesis, beacon.New(ethash.NewFaker()), options)
|
||||
chain, _ := NewBlockChain(db, genesis, beacon.New(ethash.NewFaker()), DefaultConfig().WithStateScheme(rawdb.PathScheme))
|
||||
defer chain.Stop()
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
|
|
@ -63,7 +64,7 @@ func (b *BlockGen) SetCoinbase(addr common.Address) {
|
|||
panic("coinbase can only be set once")
|
||||
}
|
||||
b.header.Coinbase = addr
|
||||
b.gasPool = new(GasPool).AddGas(b.header.GasLimit)
|
||||
b.gasPool = NewGasPool(b.header.GasLimit)
|
||||
}
|
||||
|
||||
// SetExtra sets the extra data field of the generated block.
|
||||
|
|
@ -117,10 +118,12 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
|
|||
evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig)
|
||||
)
|
||||
b.statedb.SetTxContext(tx.Hash(), len(b.txs))
|
||||
receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed)
|
||||
receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b.header.GasUsed = b.gasPool.Used()
|
||||
|
||||
// Merge the tx-local access event into the "block-local" one, in order to collect
|
||||
// all values, so that the witness can be built.
|
||||
if b.statedb.Database().TrieDB().IsVerkle() {
|
||||
|
|
@ -409,7 +412,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
}
|
||||
|
||||
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
|
||||
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts)
|
||||
block, err := b.engine.FinalizeAndAssemble(context.Background(), cm, b.header, statedb, &body, b.receipts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -479,13 +482,14 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
|
|||
if genesis.Config != nil && genesis.Config.IsVerkle(genesis.Config.ChainID, 0) {
|
||||
triedbConfig = triedb.VerkleDefaults
|
||||
}
|
||||
triedb := triedb.NewDatabase(db, triedbConfig)
|
||||
defer triedb.Close()
|
||||
_, err := genesis.Commit(db, triedb, nil)
|
||||
genesisTriedb := triedb.NewDatabase(db, triedbConfig)
|
||||
block, err := genesis.Commit(db, genesisTriedb, nil)
|
||||
if err != nil {
|
||||
genesisTriedb.Close()
|
||||
panic(err)
|
||||
}
|
||||
blocks, receipts := GenerateChain(genesis.Config, genesis.ToBlock(), engine, db, n, gen)
|
||||
genesisTriedb.Close()
|
||||
blocks, receipts := GenerateChain(genesis.Config, block, engine, db, n, gen)
|
||||
return db, blocks, receipts
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ var (
|
|||
// by a transaction is higher than what's left in the block.
|
||||
ErrGasLimitReached = errors.New("gas limit reached")
|
||||
|
||||
// ErrGasLimitOverflow is returned by the gas pool if the remaining gas
|
||||
// exceeds the maximum value of uint64.
|
||||
ErrGasLimitOverflow = errors.New("gas limit overflow")
|
||||
|
||||
// ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't
|
||||
// have enough funds for transfer(topmost call only).
|
||||
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
|
||||
|
|
|
|||
|
|
@ -21,39 +21,87 @@ import (
|
|||
"math"
|
||||
)
|
||||
|
||||
// GasPool tracks the amount of gas available during execution of the transactions
|
||||
// in a block. The zero value is a pool with zero gas available.
|
||||
type GasPool uint64
|
||||
// GasPool tracks the amount of gas available for transaction execution
|
||||
// within a block, along with the cumulative gas consumed.
|
||||
type GasPool struct {
|
||||
remaining uint64
|
||||
initial uint64
|
||||
cumulativeUsed uint64
|
||||
}
|
||||
|
||||
// AddGas makes gas available for execution.
|
||||
func (gp *GasPool) AddGas(amount uint64) *GasPool {
|
||||
if uint64(*gp) > math.MaxUint64-amount {
|
||||
panic("gas pool pushed above uint64")
|
||||
// NewGasPool initializes the gasPool with the given amount.
|
||||
func NewGasPool(amount uint64) *GasPool {
|
||||
return &GasPool{
|
||||
remaining: amount,
|
||||
initial: amount,
|
||||
}
|
||||
*(*uint64)(gp) += amount
|
||||
return gp
|
||||
}
|
||||
|
||||
// SubGas deducts the given amount from the pool if enough gas is
|
||||
// available and returns an error otherwise.
|
||||
func (gp *GasPool) SubGas(amount uint64) error {
|
||||
if uint64(*gp) < amount {
|
||||
if gp.remaining < amount {
|
||||
return ErrGasLimitReached
|
||||
}
|
||||
*(*uint64)(gp) -= amount
|
||||
gp.remaining -= amount
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReturnGas adds the refunded gas back to the pool and updates
|
||||
// the cumulative gas usage accordingly.
|
||||
func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error {
|
||||
if gp.remaining > math.MaxUint64-returned {
|
||||
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
|
||||
}
|
||||
// The returned gas calculation differs across forks.
|
||||
//
|
||||
// - Pre-Amsterdam:
|
||||
// returned = purchased - remaining (refund included)
|
||||
//
|
||||
// - Post-Amsterdam:
|
||||
// returned = purchased - gasUsed (refund excluded)
|
||||
gp.remaining += returned
|
||||
|
||||
// gasUsed = max(txGasUsed - gasRefund, calldataFloorGasCost)
|
||||
// regardless of Amsterdam is activated or not.
|
||||
gp.cumulativeUsed += gasUsed
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gas returns the amount of gas remaining in the pool.
|
||||
func (gp *GasPool) Gas() uint64 {
|
||||
return uint64(*gp)
|
||||
return gp.remaining
|
||||
}
|
||||
|
||||
// SetGas sets the amount of gas with the provided number.
|
||||
func (gp *GasPool) SetGas(gas uint64) {
|
||||
*(*uint64)(gp) = gas
|
||||
// CumulativeUsed returns the amount of cumulative consumed gas (refunded included).
|
||||
func (gp *GasPool) CumulativeUsed() uint64 {
|
||||
return gp.cumulativeUsed
|
||||
}
|
||||
|
||||
// Used returns the amount of consumed gas.
|
||||
func (gp *GasPool) Used() uint64 {
|
||||
if gp.initial < gp.remaining {
|
||||
panic("gas used underflow")
|
||||
}
|
||||
return gp.initial - gp.remaining
|
||||
}
|
||||
|
||||
// Snapshot returns the deep-copied object as the snapshot.
|
||||
func (gp *GasPool) Snapshot() *GasPool {
|
||||
return &GasPool{
|
||||
initial: gp.initial,
|
||||
remaining: gp.remaining,
|
||||
cumulativeUsed: gp.cumulativeUsed,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets the content of gasPool with the provided one.
|
||||
func (gp *GasPool) Set(other *GasPool) {
|
||||
gp.initial = other.initial
|
||||
gp.remaining = other.remaining
|
||||
gp.cumulativeUsed = other.cumulativeUsed
|
||||
}
|
||||
|
||||
func (gp *GasPool) String() string {
|
||||
return fmt.Sprintf("%d", *gp)
|
||||
return fmt.Sprintf("initial: %d, remaining: %d, cumulative used: %d", gp.initial, gp.remaining, gp.cumulativeUsed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
expected := common.FromHex("b94812c1674dcf4f2bc98f4503d15f4cc674265135bcf3be6e4417b60881042a")
|
||||
expected := common.FromHex("1fd154971d9a386c4ec75fe7138c17efb569bfc2962e46e94a376ba997e3fadc")
|
||||
got := genesis.ToBlock().Root().Bytes()
|
||||
if !bytes.Equal(got, expected) {
|
||||
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
|
||||
|
|
|
|||
|
|
@ -32,10 +32,13 @@ const (
|
|||
|
||||
// KeepPostMerge sets the history pruning point to the merge activation block.
|
||||
KeepPostMerge
|
||||
|
||||
// KeepPostPrague sets the history pruning point to the Prague (Pectra) activation block.
|
||||
KeepPostPrague
|
||||
)
|
||||
|
||||
func (m HistoryMode) IsValid() bool {
|
||||
return m <= KeepPostMerge
|
||||
return m <= KeepPostPrague
|
||||
}
|
||||
|
||||
func (m HistoryMode) String() string {
|
||||
|
|
@ -44,6 +47,8 @@ func (m HistoryMode) String() string {
|
|||
return "all"
|
||||
case KeepPostMerge:
|
||||
return "postmerge"
|
||||
case KeepPostPrague:
|
||||
return "postprague"
|
||||
default:
|
||||
return fmt.Sprintf("invalid HistoryMode(%d)", m)
|
||||
}
|
||||
|
|
@ -64,33 +69,73 @@ func (m *HistoryMode) UnmarshalText(text []byte) error {
|
|||
*m = KeepAll
|
||||
case "postmerge":
|
||||
*m = KeepPostMerge
|
||||
case "postprague":
|
||||
*m = KeepPostPrague
|
||||
default:
|
||||
return fmt.Errorf(`unknown sync mode %q, want "all" or "postmerge"`, text)
|
||||
return fmt.Errorf(`unknown history mode %q, want "all", "postmerge", or "postprague"`, text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrunePoint identifies a specific block for history pruning.
|
||||
type PrunePoint struct {
|
||||
BlockNumber uint64
|
||||
BlockHash common.Hash
|
||||
}
|
||||
|
||||
// PrunePoints the pre-defined history pruning cutoff blocks for known networks.
|
||||
// They point to the first post-merge block. Any pruning should truncate *up to* but excluding
|
||||
// given block.
|
||||
var PrunePoints = map[common.Hash]*PrunePoint{
|
||||
// mainnet
|
||||
params.MainnetGenesisHash: {
|
||||
BlockNumber: 15537393,
|
||||
BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"),
|
||||
// staticPrunePoints contains the pre-defined history pruning cutoff blocks for
|
||||
// known networks, keyed by history mode and genesis hash. They point to the first
|
||||
// block after the respective fork. Any pruning should truncate *up to* but
|
||||
// excluding the given block.
|
||||
var staticPrunePoints = map[HistoryMode]map[common.Hash]*PrunePoint{
|
||||
KeepPostMerge: {
|
||||
params.MainnetGenesisHash: {
|
||||
BlockNumber: 15537393,
|
||||
BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"),
|
||||
},
|
||||
params.SepoliaGenesisHash: {
|
||||
BlockNumber: 1450409,
|
||||
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
|
||||
},
|
||||
},
|
||||
// sepolia
|
||||
params.SepoliaGenesisHash: {
|
||||
BlockNumber: 1450409,
|
||||
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
|
||||
KeepPostPrague: {
|
||||
params.MainnetGenesisHash: {
|
||||
BlockNumber: 22431084,
|
||||
BlockHash: common.HexToHash("0x50c8cab760b2948349c590461b166773c45d8f4858cccf5a43025ab2960152e8"),
|
||||
},
|
||||
params.SepoliaGenesisHash: {
|
||||
BlockNumber: 7836331,
|
||||
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// HistoryPolicy describes the configured history pruning strategy. It captures
|
||||
// user intent as opposed to the actual DB state.
|
||||
type HistoryPolicy struct {
|
||||
Mode HistoryMode
|
||||
// Static prune point for PostMerge/PostPrague, nil otherwise.
|
||||
Target *PrunePoint
|
||||
}
|
||||
|
||||
// NewPolicy constructs a HistoryPolicy from the given mode and genesis hash.
|
||||
func NewPolicy(mode HistoryMode, genesisHash common.Hash) (HistoryPolicy, error) {
|
||||
switch mode {
|
||||
case KeepAll:
|
||||
return HistoryPolicy{Mode: KeepAll}, nil
|
||||
|
||||
case KeepPostMerge, KeepPostPrague:
|
||||
point := staticPrunePoints[mode][genesisHash]
|
||||
if point == nil {
|
||||
return HistoryPolicy{}, fmt.Errorf("%s history pruning not available for network %s", mode, genesisHash.Hex())
|
||||
}
|
||||
return HistoryPolicy{Mode: mode, Target: point}, nil
|
||||
|
||||
default:
|
||||
return HistoryPolicy{}, fmt.Errorf("invalid history mode: %d", mode)
|
||||
}
|
||||
}
|
||||
|
||||
// PrunedHistoryError is returned by APIs when the requested history is pruned.
|
||||
type PrunedHistoryError struct{}
|
||||
|
||||
|
|
|
|||
58
core/history/historymode_test.go
Normal file
58
core/history/historymode_test.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2026 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 history
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func TestNewPolicy(t *testing.T) {
|
||||
// KeepAll: no target.
|
||||
p, err := NewPolicy(KeepAll, params.MainnetGenesisHash)
|
||||
if err != nil {
|
||||
t.Fatalf("KeepAll: %v", err)
|
||||
}
|
||||
if p.Mode != KeepAll || p.Target != nil {
|
||||
t.Errorf("KeepAll: unexpected policy %+v", p)
|
||||
}
|
||||
|
||||
// PostMerge: resolves known mainnet prune point.
|
||||
p, err = NewPolicy(KeepPostMerge, params.MainnetGenesisHash)
|
||||
if err != nil {
|
||||
t.Fatalf("PostMerge: %v", err)
|
||||
}
|
||||
if p.Target == nil || p.Target.BlockNumber != 15537393 {
|
||||
t.Errorf("PostMerge: unexpected target %+v", p.Target)
|
||||
}
|
||||
|
||||
// PostPrague: resolves known mainnet prune point.
|
||||
p, err = NewPolicy(KeepPostPrague, params.MainnetGenesisHash)
|
||||
if err != nil {
|
||||
t.Fatalf("PostPrague: %v", err)
|
||||
}
|
||||
if p.Target == nil || p.Target.BlockNumber != 22431084 {
|
||||
t.Errorf("PostPrague: unexpected target %+v", p.Target)
|
||||
}
|
||||
|
||||
// PostMerge on unknown network: error.
|
||||
if _, err = NewPolicy(KeepPostMerge, common.HexToHash("0xdeadbeef")); err == nil {
|
||||
t.Fatal("PostMerge unknown network: expected error")
|
||||
}
|
||||
}
|
||||
|
|
@ -260,6 +260,46 @@ func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to write ancient data %v", err)
|
||||
}
|
||||
|
||||
// Write should work after truncating from tail but over the head
|
||||
db.TruncateTail(200)
|
||||
head, err := db.Ancients()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve head ancients %v", err)
|
||||
}
|
||||
tail, err := db.Tail()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve tail ancients %v", err)
|
||||
}
|
||||
if head != 200 || tail != 200 {
|
||||
t.Fatalf("Ancient head and tail are not expected")
|
||||
}
|
||||
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
offset := uint64(200)
|
||||
for i := 0; i < 100; i++ {
|
||||
if err := op.AppendRaw("a", offset+uint64(i), dataA[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw("b", offset+uint64(i), dataB[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write ancient data %v", err)
|
||||
}
|
||||
head, err = db.Ancients()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve head ancients %v", err)
|
||||
}
|
||||
tail, err = db.Tail()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve tail ancients %v", err)
|
||||
}
|
||||
if head != 300 || tail != 200 {
|
||||
t.Fatalf("Ancient head and tail are not expected")
|
||||
}
|
||||
}
|
||||
|
||||
func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
||||
|
|
|
|||
|
|
@ -478,9 +478,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
|||
bodies.add(size)
|
||||
case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
|
||||
receipts.add(size)
|
||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix):
|
||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix) && len(key) == (len(headerPrefix)+8+common.HashLength+len(headerTDSuffix)):
|
||||
tds.add(size)
|
||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix):
|
||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix) && len(key) == (len(headerPrefix)+8+common.HashLength+len(headerHashSuffix)):
|
||||
numHashPairings.add(size)
|
||||
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
|
||||
hashNumPairings.add(size)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ const freezerTableSize = 2 * 1000 * 1000 * 1000
|
|||
// - The in-order data ensures that disk reads are always optimized.
|
||||
type Freezer struct {
|
||||
datadir string
|
||||
frozen atomic.Uint64 // Number of items already frozen
|
||||
head atomic.Uint64 // Number of items stored (including items removed from tail)
|
||||
tail atomic.Uint64 // Number of the first stored item in the freezer
|
||||
|
||||
// This lock synchronizes writers and the truncate operation, as well as
|
||||
|
|
@ -97,12 +97,12 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui
|
|||
return nil, errSymlinkDatadir
|
||||
}
|
||||
}
|
||||
// Leveldb/Pebble uses LOCK as the filelock filename. To prevent the
|
||||
// name collision, we use FLOCK as the lock name.
|
||||
flockFile := filepath.Join(datadir, "FLOCK")
|
||||
if err := os.MkdirAll(filepath.Dir(flockFile), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Leveldb uses LOCK as the filelock filename. To prevent the
|
||||
// name collision, we use FLOCK as the lock name.
|
||||
lock := flock.New(flockFile)
|
||||
tryLock := lock.TryLock
|
||||
if readonly {
|
||||
|
|
@ -213,7 +213,7 @@ func (f *Freezer) AncientBytes(kind string, id, offset, length uint64) ([]byte,
|
|||
|
||||
// Ancients returns the length of the frozen items.
|
||||
func (f *Freezer) Ancients() (uint64, error) {
|
||||
return f.frozen.Load(), nil
|
||||
return f.head.Load(), nil
|
||||
}
|
||||
|
||||
// Tail returns the number of first stored item in the freezer.
|
||||
|
|
@ -252,7 +252,7 @@ func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize
|
|||
defer f.writeLock.Unlock()
|
||||
|
||||
// Roll back all tables to the starting position in case of error.
|
||||
prevItem := f.frozen.Load()
|
||||
prevItem := f.head.Load()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// The write operation has failed. Go back to the previous item position.
|
||||
|
|
@ -273,7 +273,7 @@ func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.frozen.Store(item)
|
||||
f.head.Store(item)
|
||||
return writeSize, nil
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +286,7 @@ func (f *Freezer) TruncateHead(items uint64) (uint64, error) {
|
|||
f.writeLock.Lock()
|
||||
defer f.writeLock.Unlock()
|
||||
|
||||
oitems := f.frozen.Load()
|
||||
oitems := f.head.Load()
|
||||
if oitems <= items {
|
||||
return oitems, nil
|
||||
}
|
||||
|
|
@ -295,7 +295,7 @@ func (f *Freezer) TruncateHead(items uint64) (uint64, error) {
|
|||
return 0, err
|
||||
}
|
||||
}
|
||||
f.frozen.Store(items)
|
||||
f.head.Store(items)
|
||||
return oitems, nil
|
||||
}
|
||||
|
||||
|
|
@ -320,6 +320,11 @@ func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {
|
|||
}
|
||||
}
|
||||
f.tail.Store(tail)
|
||||
|
||||
// Update the head if the requested tail exceeds the current head
|
||||
if f.head.Load() < tail {
|
||||
f.head.Store(tail)
|
||||
}
|
||||
return old, nil
|
||||
}
|
||||
|
||||
|
|
@ -379,7 +384,7 @@ func (f *Freezer) validate() error {
|
|||
prunedTail = &tmp
|
||||
}
|
||||
|
||||
f.frozen.Store(head)
|
||||
f.head.Store(head)
|
||||
f.tail.Store(*prunedTail)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -414,7 +419,7 @@ func (f *Freezer) repair() error {
|
|||
}
|
||||
}
|
||||
|
||||
f.frozen.Store(head)
|
||||
f.head.Store(head)
|
||||
f.tail.Store(prunedTail)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ func (t *memoryTable) truncateTail(items uint64) error {
|
|||
return nil
|
||||
}
|
||||
if t.items < items {
|
||||
return errors.New("truncation above head")
|
||||
return t.reset(items)
|
||||
}
|
||||
for i := uint64(0); i < items-t.offset; i++ {
|
||||
if t.size > uint64(len(t.data[i])) {
|
||||
|
|
@ -127,6 +127,16 @@ func (t *memoryTable) truncateTail(items uint64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// reset clears the entire table and sets both the head and tail to the given
|
||||
// value. It assumes the caller holds the lock and that tail > t.items.
|
||||
func (t *memoryTable) reset(offset uint64) error {
|
||||
t.size = 0
|
||||
t.data = nil
|
||||
t.items = offset
|
||||
t.offset = offset
|
||||
return nil
|
||||
}
|
||||
|
||||
// commit merges the given item batch into table. It's presumed that the
|
||||
// batch is ordered and continuous with table.
|
||||
func (t *memoryTable) commit(batch [][]byte) error {
|
||||
|
|
@ -387,6 +397,9 @@ func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
|
|||
}
|
||||
}
|
||||
f.tail = tail
|
||||
if f.items < tail {
|
||||
f.items = tail
|
||||
}
|
||||
return old, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -707,12 +707,13 @@ func (t *freezerTable) truncateTail(items uint64) error {
|
|||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
// Ensure the given truncate target falls in the correct range
|
||||
// Short-circuit if the requested tail deletion points to a stale position
|
||||
if t.itemHidden.Load() >= items {
|
||||
return nil
|
||||
}
|
||||
// If the requested tail exceeds the current head, reset the entire table
|
||||
if t.items.Load() < items {
|
||||
return errors.New("truncation above head")
|
||||
return t.resetTo(items)
|
||||
}
|
||||
// Load the new tail index by the given new tail position
|
||||
var (
|
||||
|
|
@ -822,10 +823,9 @@ func (t *freezerTable) truncateTail(items uint64) error {
|
|||
shorten := indexEntrySize * int64(newDeleted-deleted)
|
||||
if t.metadata.flushOffset <= shorten {
|
||||
return fmt.Errorf("invalid index flush offset: %d, shorten: %d", t.metadata.flushOffset, shorten)
|
||||
} else {
|
||||
if err := t.metadata.setFlushOffset(t.metadata.flushOffset-shorten, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := t.metadata.setFlushOffset(t.metadata.flushOffset-shorten, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Retrieve the new size and update the total size counter
|
||||
newSize, err := t.sizeNolock()
|
||||
|
|
@ -836,6 +836,59 @@ func (t *freezerTable) truncateTail(items uint64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// resetTo clears the entire table and sets both the head and tail to the given
|
||||
// value. It assumes the caller holds the lock and that tail > t.items.
|
||||
func (t *freezerTable) resetTo(tail uint64) error {
|
||||
// Sync the entire table before resetting, eliminating the potential
|
||||
// data corruption.
|
||||
err := t.doSync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Update the index file to reflect the new offset
|
||||
if err := t.index.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
entry := &indexEntry{
|
||||
filenum: t.headId + 1,
|
||||
offset: uint32(tail),
|
||||
}
|
||||
if err := reset(t.index.Name(), entry.append(nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.metadata.setVirtualTail(tail, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.metadata.setFlushOffset(indexEntrySize, true); err != nil {
|
||||
return err
|
||||
}
|
||||
t.index, err = openFreezerFileForAppend(t.index.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Purge all the existing data file
|
||||
if err := t.head.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.headId = t.headId + 1
|
||||
t.tailId = t.headId
|
||||
t.headBytes = 0
|
||||
|
||||
t.head, err = t.openFile(t.headId, openFreezerFileTruncated)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.releaseFilesBefore(t.headId, true)
|
||||
|
||||
t.items.Store(tail)
|
||||
t.itemOffset.Store(tail)
|
||||
t.itemHidden.Store(tail)
|
||||
t.sizeGauge.Update(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes all opened files and finalizes the freezer table for use.
|
||||
// This operation must be completed before shutdown to prevent the loss of
|
||||
// recent writes.
|
||||
|
|
@ -1247,25 +1300,20 @@ func (t *freezerTable) doSync() error {
|
|||
if t.index == nil || t.head == nil || t.metadata.file == nil {
|
||||
return errClosed
|
||||
}
|
||||
var err error
|
||||
trackError := func(e error) {
|
||||
if e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
if err := t.index.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.head.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
trackError(t.index.Sync())
|
||||
trackError(t.head.Sync())
|
||||
|
||||
// A crash may occur before the offset is updated, leaving the offset
|
||||
// points to a old position. If so, the extra items above the offset
|
||||
// points to an old position. If so, the extra items above the offset
|
||||
// will be truncated during the next run.
|
||||
stat, err := t.index.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset := stat.Size()
|
||||
trackError(t.metadata.setFlushOffset(offset, true))
|
||||
return err
|
||||
return t.metadata.setFlushOffset(stat.Size(), true)
|
||||
}
|
||||
|
||||
func (t *freezerTable) dumpIndexStdout(start, stop int64) {
|
||||
|
|
|
|||
|
|
@ -1139,6 +1139,7 @@ const (
|
|||
opTruncateHeadAll
|
||||
opTruncateTail
|
||||
opTruncateTailAll
|
||||
opTruncateTailOverHead
|
||||
opCheckAll
|
||||
opMax // boundary value, not an actual op
|
||||
)
|
||||
|
|
@ -1226,6 +1227,11 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value {
|
|||
step.target = deleted + uint64(len(items))
|
||||
items = items[:0]
|
||||
deleted = step.target
|
||||
case opTruncateTailOverHead:
|
||||
newDeleted := deleted + uint64(len(items)) + 10
|
||||
step.target = newDeleted
|
||||
deleted = newDeleted
|
||||
items = items[:0]
|
||||
}
|
||||
steps = append(steps, step)
|
||||
}
|
||||
|
|
@ -1268,7 +1274,7 @@ func runRandTest(rt randTest) bool {
|
|||
for i := 0; i < len(step.items); i++ {
|
||||
batch.AppendRaw(step.items[i], step.blobs[i])
|
||||
}
|
||||
batch.commit()
|
||||
rt[i].err = batch.commit()
|
||||
values = append(values, step.blobs...)
|
||||
|
||||
case opRetrieve:
|
||||
|
|
@ -1290,24 +1296,28 @@ func runRandTest(rt randTest) bool {
|
|||
}
|
||||
|
||||
case opTruncateHead:
|
||||
f.truncateHead(step.target)
|
||||
rt[i].err = f.truncateHead(step.target)
|
||||
|
||||
length := f.items.Load() - f.itemHidden.Load()
|
||||
values = values[:length]
|
||||
|
||||
case opTruncateHeadAll:
|
||||
f.truncateHead(step.target)
|
||||
rt[i].err = f.truncateHead(step.target)
|
||||
values = nil
|
||||
|
||||
case opTruncateTail:
|
||||
prev := f.itemHidden.Load()
|
||||
f.truncateTail(step.target)
|
||||
rt[i].err = f.truncateTail(step.target)
|
||||
|
||||
truncated := f.itemHidden.Load() - prev
|
||||
values = values[truncated:]
|
||||
|
||||
case opTruncateTailAll:
|
||||
f.truncateTail(step.target)
|
||||
rt[i].err = f.truncateTail(step.target)
|
||||
values = nil
|
||||
|
||||
case opTruncateTailOverHead:
|
||||
rt[i].err = f.truncateTail(step.target)
|
||||
values = nil
|
||||
}
|
||||
// Abort the test on error.
|
||||
|
|
@ -1633,3 +1643,43 @@ func TestFreezerAncientBytes(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateOverHead(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fn := fmt.Sprintf("t-%d", rand.Uint64())
|
||||
f, err := newTable(os.TempDir(), fn, metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 100, freezerTableConfig{noSnappy: true}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Tail truncation on an empty table
|
||||
if err := f.truncateTail(10); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
batch := f.newBatch()
|
||||
data := getChunk(10, 1)
|
||||
require.NoError(t, batch.AppendRaw(uint64(10), data))
|
||||
require.NoError(t, batch.commit())
|
||||
|
||||
got, err := f.RetrieveItems(uint64(10), 1, 0)
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got[0], data) {
|
||||
t.Fatalf("Unexpected bytes, want: %v, got: %v", data, got[0])
|
||||
}
|
||||
|
||||
// Tail truncation on the non-empty table
|
||||
if err := f.truncateTail(20); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
batch = f.newBatch()
|
||||
data = getChunk(10, 1)
|
||||
require.NoError(t, batch.AppendRaw(uint64(20), data))
|
||||
require.NoError(t, batch.commit())
|
||||
|
||||
got, err = f.RetrieveItems(uint64(20), 1, 0)
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got[0], data) {
|
||||
t.Fatalf("Unexpected bytes, want: %v, got: %v", data, got[0])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,19 @@ import (
|
|||
"path/filepath"
|
||||
)
|
||||
|
||||
func atomicRename(src, dest string) error {
|
||||
if err := os.Rename(src, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
dir, err := os.Open(filepath.Dir(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
return dir.Sync()
|
||||
}
|
||||
|
||||
// copyFrom copies data from 'srcPath' at offset 'offset' into 'destPath'.
|
||||
// The 'destPath' is created if it doesn't exist, otherwise it is overwritten.
|
||||
// Before the copy is executed, there is a callback can be registered to
|
||||
|
|
@ -73,13 +86,48 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e
|
|||
return err
|
||||
}
|
||||
f = nil
|
||||
return os.Rename(fname, destPath)
|
||||
|
||||
return atomicRename(fname, destPath)
|
||||
}
|
||||
|
||||
// reset atomically replaces the file at the given path with the provided content.
|
||||
func reset(path string, content []byte) error {
|
||||
// Create a temp file in the same dir where we want it to wind up
|
||||
f, err := os.CreateTemp(filepath.Dir(path), "*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fname := f.Name()
|
||||
|
||||
// Clean up the leftover file
|
||||
defer func() {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
os.Remove(fname)
|
||||
}()
|
||||
|
||||
// Write the content into the temp file
|
||||
_, err = f.Write(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Permanently persist the content into disk
|
||||
if err := f.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
f = nil
|
||||
|
||||
return atomicRename(fname, path)
|
||||
}
|
||||
|
||||
// openFreezerFileForAppend opens a freezer table file and seeks to the end
|
||||
func openFreezerFileForAppend(filename string) (*os.File, error) {
|
||||
// Open the file without the O_APPEND flag
|
||||
// because it has differing behaviour during Truncate operations
|
||||
// because it has differing behavior during Truncate operations
|
||||
// on different OS's
|
||||
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -253,6 +253,11 @@ func (b *tableBatch) Reset() {
|
|||
b.batch.Reset()
|
||||
}
|
||||
|
||||
// Close closes the batch and releases all associated resources.
|
||||
func (b *tableBatch) Close() {
|
||||
b.batch.Close()
|
||||
}
|
||||
|
||||
// tableReplayer is a wrapper around a batch replayer which truncates
|
||||
// the added prefix.
|
||||
type tableReplayer struct {
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/overlay"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||
|
|
@ -34,14 +34,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/triedb"
|
||||
)
|
||||
|
||||
const (
|
||||
// Number of codehash->size associations to keep.
|
||||
codeSizeCacheSize = 1_000_000 // 4 megabytes in total
|
||||
|
||||
// Cache size granted for caching clean code.
|
||||
codeCacheSize = 256 * 1024 * 1024
|
||||
)
|
||||
|
||||
// Database wraps access to tries and contract code.
|
||||
type Database interface {
|
||||
// Reader returns a state reader associated with the specified state root.
|
||||
|
|
@ -58,6 +50,11 @@ type Database interface {
|
|||
|
||||
// Snapshot returns the underlying state snapshot.
|
||||
Snapshot() *snapshot.Tree
|
||||
|
||||
// Commit flushes all pending writes and finalizes the state transition,
|
||||
// committing the changes to the underlying storage. It returns an error
|
||||
// if the commit fails.
|
||||
Commit(update *stateUpdate) error
|
||||
}
|
||||
|
||||
// Trie is a Ethereum Merkle Patricia trie.
|
||||
|
|
@ -149,32 +146,34 @@ type Trie interface {
|
|||
// state snapshot to provide functionalities for state access. It's meant to be a
|
||||
// long-live object and has a few caches inside for sharing between blocks.
|
||||
type CachingDB struct {
|
||||
disk ethdb.KeyValueStore
|
||||
triedb *triedb.Database
|
||||
snap *snapshot.Tree
|
||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||
codeSizeCache *lru.Cache[common.Hash, int]
|
||||
|
||||
// Transition-specific fields
|
||||
TransitionStatePerRoot *lru.Cache[common.Hash, *overlay.TransitionState]
|
||||
triedb *triedb.Database
|
||||
codedb *CodeDB
|
||||
snap *snapshot.Tree
|
||||
}
|
||||
|
||||
// NewDatabase creates a state database with the provided data sources.
|
||||
func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
|
||||
func NewDatabase(triedb *triedb.Database, codedb *CodeDB) *CachingDB {
|
||||
if codedb == nil {
|
||||
codedb = NewCodeDB(triedb.Disk())
|
||||
}
|
||||
return &CachingDB{
|
||||
disk: triedb.Disk(),
|
||||
triedb: triedb,
|
||||
snap: snap,
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||
TransitionStatePerRoot: lru.NewCache[common.Hash, *overlay.TransitionState](1000),
|
||||
triedb: triedb,
|
||||
codedb: codedb,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDatabaseForTesting is similar to NewDatabase, but it initializes the caching
|
||||
// db by using an ephemeral memory db with default config for testing.
|
||||
func NewDatabaseForTesting() *CachingDB {
|
||||
return NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil)
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
return NewDatabase(triedb.NewDatabase(db, nil), NewCodeDB(db))
|
||||
}
|
||||
|
||||
// WithSnapshot configures the provided contract code cache. Note that this
|
||||
// registration must be performed before the cachingDB is used.
|
||||
func (db *CachingDB) WithSnapshot(snapshot *snapshot.Tree) *CachingDB {
|
||||
db.snap = snapshot
|
||||
return db
|
||||
}
|
||||
|
||||
// StateReader returns a state reader associated with the specified state root.
|
||||
|
|
@ -218,21 +217,20 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), sr), nil
|
||||
return newReader(db.codedb.Reader(), sr), nil
|
||||
}
|
||||
|
||||
// ReadersWithCacheStats creates a pair of state readers that share the same
|
||||
// underlying state reader and internal state cache, while maintaining separate
|
||||
// statistics respectively.
|
||||
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) {
|
||||
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (Reader, Reader, error) {
|
||||
r, err := db.StateReader(stateRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sr := newStateReaderWithCache(r)
|
||||
|
||||
ra := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache))
|
||||
rb := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache))
|
||||
ra := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
|
||||
rb := newReader(db.codedb.Reader(), newStateReaderWithStats(sr))
|
||||
return ra, rb, nil
|
||||
}
|
||||
|
||||
|
|
@ -268,22 +266,6 @@ func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Addre
|
|||
return tr, nil
|
||||
}
|
||||
|
||||
// ContractCodeWithPrefix retrieves a particular contract's code. If the
|
||||
// code can't be found in the cache, then check the existence with **new**
|
||||
// db scheme.
|
||||
func (db *CachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) []byte {
|
||||
code, _ := db.codeCache.Get(codeHash)
|
||||
if len(code) > 0 {
|
||||
return code
|
||||
}
|
||||
code = rawdb.ReadCodeWithPrefix(db.disk, codeHash)
|
||||
if len(code) > 0 {
|
||||
db.codeCache.Add(codeHash, code)
|
||||
db.codeSizeCache.Add(codeHash, len(code))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// TrieDB retrieves any intermediate trie-node caching layer.
|
||||
func (db *CachingDB) TrieDB() *triedb.Database {
|
||||
return db.triedb
|
||||
|
|
@ -294,6 +276,40 @@ func (db *CachingDB) Snapshot() *snapshot.Tree {
|
|||
return db.snap
|
||||
}
|
||||
|
||||
// Commit flushes all pending writes and finalizes the state transition,
|
||||
// committing the changes to the underlying storage. It returns an error
|
||||
// if the commit fails.
|
||||
func (db *CachingDB) Commit(update *stateUpdate) error {
|
||||
// Short circuit if nothing to commit
|
||||
if update.empty() {
|
||||
return nil
|
||||
}
|
||||
// Commit dirty contract code if any exists
|
||||
if len(update.codes) > 0 {
|
||||
batch := db.codedb.NewBatchWithSize(len(update.codes))
|
||||
for _, code := range update.codes {
|
||||
batch.Put(code.hash, code.blob)
|
||||
}
|
||||
if err := batch.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// If snapshotting is enabled, update the snapshot tree with this new version
|
||||
if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil {
|
||||
if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil {
|
||||
log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
|
||||
}
|
||||
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||
// - head layer is paired with HEAD state
|
||||
// - head-1 layer is paired with HEAD-1 state
|
||||
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
||||
if err := db.snap.Cap(update.root, TriesInMemory); err != nil {
|
||||
log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err)
|
||||
}
|
||||
}
|
||||
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
|
||||
}
|
||||
|
||||
// mustCopyTrie returns a deep-copied trie.
|
||||
func mustCopyTrie(t Trie) Trie {
|
||||
switch t := t.(type) {
|
||||
|
|
|
|||
231
core/state/database_code.go
Normal file
231
core/state/database_code.go
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
// Copyright 2026 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 state
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
const (
|
||||
// Number of codeHash->size associations to keep.
|
||||
codeSizeCacheSize = 1_000_000
|
||||
|
||||
// Cache size granted for caching clean code.
|
||||
codeCacheSize = 256 * 1024 * 1024
|
||||
)
|
||||
|
||||
// CodeCache maintains cached contract code that is shared across blocks, enabling
|
||||
// fast access for external calls such as RPCs and state transitions.
|
||||
//
|
||||
// It is thread-safe and has a bounded size.
|
||||
type codeCache struct {
|
||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||
codeSizeCache *lru.Cache[common.Hash, int]
|
||||
}
|
||||
|
||||
// newCodeCache initializes the contract code cache with the predefined capacity.
|
||||
func newCodeCache() *codeCache {
|
||||
return &codeCache{
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the contract code associated with the provided code hash.
|
||||
func (c *codeCache) Get(hash common.Hash) ([]byte, bool) {
|
||||
return c.codeCache.Get(hash)
|
||||
}
|
||||
|
||||
// GetSize returns the contract code size associated with the provided code hash.
|
||||
func (c *codeCache) GetSize(hash common.Hash) (int, bool) {
|
||||
return c.codeSizeCache.Get(hash)
|
||||
}
|
||||
|
||||
// Put adds the provided contract code along with its size information into the cache.
|
||||
func (c *codeCache) Put(hash common.Hash, code []byte) {
|
||||
c.codeCache.Add(hash, code)
|
||||
c.codeSizeCache.Add(hash, len(code))
|
||||
}
|
||||
|
||||
// CodeReader implements state.ContractCodeReader, accessing contract code either in
|
||||
// local key-value store or the shared code cache.
|
||||
//
|
||||
// Reader is safe for concurrent access.
|
||||
type CodeReader struct {
|
||||
db ethdb.KeyValueReader
|
||||
cache *codeCache
|
||||
|
||||
// Cache statistics
|
||||
hit atomic.Int64 // Number of code lookups found in the cache
|
||||
miss atomic.Int64 // Number of code lookups not found in the cache
|
||||
hitBytes atomic.Int64 // Total number of bytes read from cache
|
||||
missBytes atomic.Int64 // Total number of bytes read from database
|
||||
}
|
||||
|
||||
// newCodeReader constructs the code reader with provided key value store and the cache.
|
||||
func newCodeReader(db ethdb.KeyValueReader, cache *codeCache) *CodeReader {
|
||||
return &CodeReader{
|
||||
db: db,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// Has returns the flag indicating whether the contract code with
|
||||
// specified address and hash exists or not.
|
||||
func (r *CodeReader) Has(addr common.Address, codeHash common.Hash) bool {
|
||||
return len(r.Code(addr, codeHash)) > 0
|
||||
}
|
||||
|
||||
// Code implements state.ContractCodeReader, retrieving a particular contract's code.
|
||||
// Null is returned if the contract code is not present.
|
||||
func (r *CodeReader) Code(addr common.Address, codeHash common.Hash) []byte {
|
||||
code, _ := r.cache.Get(codeHash)
|
||||
if len(code) > 0 {
|
||||
r.hit.Add(1)
|
||||
r.hitBytes.Add(int64(len(code)))
|
||||
return code
|
||||
}
|
||||
r.miss.Add(1)
|
||||
|
||||
code = rawdb.ReadCode(r.db, codeHash)
|
||||
if len(code) > 0 {
|
||||
r.cache.Put(codeHash, code)
|
||||
r.missBytes.Add(int64(len(code)))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// CodeSize implements state.ContractCodeReader, retrieving a particular contract
|
||||
// code's size. Zero is returned if the contract code is not present.
|
||||
func (r *CodeReader) CodeSize(addr common.Address, codeHash common.Hash) int {
|
||||
if cached, ok := r.cache.GetSize(codeHash); ok {
|
||||
r.hit.Add(1)
|
||||
return cached
|
||||
}
|
||||
return len(r.Code(addr, codeHash))
|
||||
}
|
||||
|
||||
// CodeWithPrefix retrieves the contract code for the specified account address
|
||||
// and code hash. It is almost identical to Code, but uses rawdb.ReadCodeWithPrefix
|
||||
// for database lookups. The intention is to gradually deprecate the old
|
||||
// contract code scheme.
|
||||
func (r *CodeReader) CodeWithPrefix(addr common.Address, codeHash common.Hash) []byte {
|
||||
code, _ := r.cache.Get(codeHash)
|
||||
if len(code) > 0 {
|
||||
r.hit.Add(1)
|
||||
r.hitBytes.Add(int64(len(code)))
|
||||
return code
|
||||
}
|
||||
r.miss.Add(1)
|
||||
|
||||
code = rawdb.ReadCodeWithPrefix(r.db, codeHash)
|
||||
if len(code) > 0 {
|
||||
r.cache.Put(codeHash, code)
|
||||
r.missBytes.Add(int64(len(code)))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// GetCodeStats implements ContractCodeReaderStater, returning the statistics
|
||||
// of the code reader.
|
||||
func (r *CodeReader) GetCodeStats() ContractCodeReaderStats {
|
||||
return ContractCodeReaderStats{
|
||||
CacheHit: r.hit.Load(),
|
||||
CacheMiss: r.miss.Load(),
|
||||
CacheHitBytes: r.hitBytes.Load(),
|
||||
CacheMissBytes: r.missBytes.Load(),
|
||||
}
|
||||
}
|
||||
|
||||
type CodeBatch struct {
|
||||
db *CodeDB
|
||||
codes [][]byte
|
||||
codeHashes []common.Hash
|
||||
}
|
||||
|
||||
// newCodeBatch constructs the batch for writing contract code.
|
||||
func newCodeBatch(db *CodeDB) *CodeBatch {
|
||||
return &CodeBatch{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// newCodeBatchWithSize constructs the batch with a pre-allocated capacity.
|
||||
func newCodeBatchWithSize(db *CodeDB, size int) *CodeBatch {
|
||||
return &CodeBatch{
|
||||
db: db,
|
||||
codes: make([][]byte, 0, size),
|
||||
codeHashes: make([]common.Hash, 0, size),
|
||||
}
|
||||
}
|
||||
|
||||
// Put inserts the given contract code into the writer, waiting for commit.
|
||||
func (b *CodeBatch) Put(codeHash common.Hash, code []byte) {
|
||||
b.codes = append(b.codes, code)
|
||||
b.codeHashes = append(b.codeHashes, codeHash)
|
||||
}
|
||||
|
||||
// Commit flushes the accumulated dirty contract code into the database and
|
||||
// also place them in the cache.
|
||||
func (b *CodeBatch) Commit() error {
|
||||
batch := b.db.db.NewBatch()
|
||||
for i, code := range b.codes {
|
||||
rawdb.WriteCode(batch, b.codeHashes[i], code)
|
||||
b.db.cache.Put(b.codeHashes[i], code)
|
||||
}
|
||||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
b.codes = b.codes[:0]
|
||||
b.codeHashes = b.codeHashes[:0]
|
||||
return nil
|
||||
}
|
||||
|
||||
// CodeDB is responsible for managing the contract code and provides the access
|
||||
// to it. It can be used as a global object, sharing it between multiple entities.
|
||||
type CodeDB struct {
|
||||
db ethdb.KeyValueStore
|
||||
cache *codeCache
|
||||
}
|
||||
|
||||
// NewCodeDB constructs the contract code database with the provided key value store.
|
||||
func NewCodeDB(db ethdb.KeyValueStore) *CodeDB {
|
||||
return &CodeDB{
|
||||
db: db,
|
||||
cache: newCodeCache(),
|
||||
}
|
||||
}
|
||||
|
||||
// Reader returns the contract code reader.
|
||||
func (d *CodeDB) Reader() *CodeReader {
|
||||
return newCodeReader(d.db, d.cache)
|
||||
}
|
||||
|
||||
// NewBatch returns the batch for flushing contract codes.
|
||||
func (d *CodeDB) NewBatch() *CodeBatch {
|
||||
return newCodeBatch(d)
|
||||
}
|
||||
|
||||
// NewBatchWithSize returns the batch with pre-allocated capacity.
|
||||
func (d *CodeDB) NewBatchWithSize(size int) *CodeBatch {
|
||||
return newCodeBatchWithSize(d, size)
|
||||
}
|
||||
|
|
@ -17,15 +17,14 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
|
|
@ -221,19 +220,15 @@ func (r *historicalTrieReader) Storage(addr common.Address, key common.Hash) (co
|
|||
// HistoricDB is the implementation of Database interface, with the ability to
|
||||
// access historical state.
|
||||
type HistoricDB struct {
|
||||
disk ethdb.KeyValueStore
|
||||
triedb *triedb.Database
|
||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||
codeSizeCache *lru.Cache[common.Hash, int]
|
||||
triedb *triedb.Database
|
||||
codedb *CodeDB
|
||||
}
|
||||
|
||||
// NewHistoricDatabase creates a historic state database.
|
||||
func NewHistoricDatabase(disk ethdb.KeyValueStore, triedb *triedb.Database) *HistoricDB {
|
||||
func NewHistoricDatabase(triedb *triedb.Database, codedb *CodeDB) *HistoricDB {
|
||||
return &HistoricDB{
|
||||
disk: disk,
|
||||
triedb: triedb,
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||
triedb: triedb,
|
||||
codedb: codedb,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +253,7 @@ func (db *HistoricDB) Reader(stateRoot common.Hash) (Reader, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
|
||||
return newReader(db.codedb.Reader(), combined), nil
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie. It's not supported by historic database.
|
||||
|
|
@ -298,3 +293,10 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
|
|||
func (db *HistoricDB) Snapshot() *snapshot.Tree {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit flushes all pending writes and finalizes the state transition,
|
||||
// committing the changes to the underlying storage. It returns an error
|
||||
// if the commit fails.
|
||||
func (db *HistoricDB) Commit(update *stateUpdate) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,10 +144,7 @@ func (it *nodeIterator) step() error {
|
|||
}
|
||||
if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
|
||||
it.codeHash = common.BytesToHash(account.CodeHash)
|
||||
it.code, err = it.state.reader.Code(address, common.BytesToHash(account.CodeHash))
|
||||
if err != nil {
|
||||
return fmt.Errorf("code %x: %v", account.CodeHash, err)
|
||||
}
|
||||
it.code = it.state.reader.Code(address, common.BytesToHash(account.CodeHash))
|
||||
if len(it.code) == 0 {
|
||||
return fmt.Errorf("code is not found: %x", account.CodeHash)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,17 +18,13 @@ package state
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/overlay"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||
|
|
@ -38,55 +34,26 @@ import (
|
|||
)
|
||||
|
||||
// ContractCodeReader defines the interface for accessing contract code.
|
||||
//
|
||||
// ContractCodeReader is supposed to be thread-safe.
|
||||
type ContractCodeReader interface {
|
||||
// Has returns the flag indicating whether the contract code with
|
||||
// specified address and hash exists or not.
|
||||
Has(addr common.Address, codeHash common.Hash) bool
|
||||
|
||||
// Code retrieves a particular contract's code.
|
||||
//
|
||||
// - Returns nil code along with nil error if the requested contract code
|
||||
// doesn't exist
|
||||
// - Returns an error only if an unexpected issue occurs
|
||||
Code(addr common.Address, codeHash common.Hash) ([]byte, error)
|
||||
// Code retrieves a particular contract's code. Returns nil code if the
|
||||
// requested contract code doesn't exist.
|
||||
Code(addr common.Address, codeHash common.Hash) []byte
|
||||
|
||||
// CodeSize retrieves a particular contracts code's size.
|
||||
//
|
||||
// - Returns zero code size along with nil error if the requested contract code
|
||||
// doesn't exist
|
||||
// - Returns an error only if an unexpected issue occurs
|
||||
CodeSize(addr common.Address, codeHash common.Hash) (int, error)
|
||||
}
|
||||
|
||||
// ContractCodeReaderStats aggregates statistics for the contract code reader.
|
||||
type ContractCodeReaderStats struct {
|
||||
CacheHit int64 // Number of cache hits
|
||||
CacheMiss int64 // Number of cache misses
|
||||
CacheHitBytes int64 // Total bytes served from cache
|
||||
CacheMissBytes int64 // Total bytes read on cache misses
|
||||
}
|
||||
|
||||
// HitRate returns the cache hit rate.
|
||||
func (s ContractCodeReaderStats) HitRate() float64 {
|
||||
if s.CacheHit == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(s.CacheHit) / float64(s.CacheHit+s.CacheMiss)
|
||||
}
|
||||
|
||||
// ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to
|
||||
// expose statistics of code reader.
|
||||
type ContractCodeReaderWithStats interface {
|
||||
ContractCodeReader
|
||||
|
||||
GetStats() ContractCodeReaderStats
|
||||
// CodeSize retrieves a particular contracts code's size. Returns zero code
|
||||
// size if the requested contract code doesn't exist.
|
||||
CodeSize(addr common.Address, codeHash common.Hash) int
|
||||
}
|
||||
|
||||
// StateReader defines the interface for accessing accounts and storage slots
|
||||
// associated with a specific state.
|
||||
//
|
||||
// StateReader is assumed to be thread-safe and implementation must take care
|
||||
// of the concurrency issue by themselves.
|
||||
// StateReader is supposed to be thread-safe.
|
||||
type StateReader interface {
|
||||
// Account retrieves the account associated with a particular address.
|
||||
//
|
||||
|
|
@ -114,119 +81,6 @@ type Reader interface {
|
|||
StateReader
|
||||
}
|
||||
|
||||
// ReaderStats wraps the statistics of reader.
|
||||
type ReaderStats struct {
|
||||
AccountCacheHit int64
|
||||
AccountCacheMiss int64
|
||||
StorageCacheHit int64
|
||||
StorageCacheMiss int64
|
||||
CodeStats ContractCodeReaderStats
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer, returning string format statistics.
|
||||
func (s ReaderStats) String() string {
|
||||
var (
|
||||
accountCacheHitRate float64
|
||||
storageCacheHitRate float64
|
||||
)
|
||||
if s.AccountCacheHit > 0 {
|
||||
accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100
|
||||
}
|
||||
if s.StorageCacheHit > 0 {
|
||||
storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100
|
||||
}
|
||||
msg := fmt.Sprintf("Reader statistics\n")
|
||||
msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate)
|
||||
msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate)
|
||||
msg += fmt.Sprintf("code: hit: %d(%v), miss: %d(%v), rate: %.2f\n", s.CodeStats.CacheHit, common.StorageSize(s.CodeStats.CacheHitBytes), s.CodeStats.CacheMiss, common.StorageSize(s.CodeStats.CacheMissBytes), s.CodeStats.HitRate())
|
||||
return msg
|
||||
}
|
||||
|
||||
// ReaderWithStats wraps the additional method to retrieve the reader statistics from.
|
||||
type ReaderWithStats interface {
|
||||
Reader
|
||||
GetStats() ReaderStats
|
||||
}
|
||||
|
||||
// cachingCodeReader implements ContractCodeReader, accessing contract code either in
|
||||
// local key-value store or the shared code cache.
|
||||
//
|
||||
// cachingCodeReader is safe for concurrent access.
|
||||
type cachingCodeReader struct {
|
||||
db ethdb.KeyValueReader
|
||||
|
||||
// These caches could be shared by multiple code reader instances,
|
||||
// they are natively thread-safe.
|
||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||
codeSizeCache *lru.Cache[common.Hash, int]
|
||||
|
||||
// Cache statistics
|
||||
hit atomic.Int64 // Number of code lookups found in the cache
|
||||
miss atomic.Int64 // Number of code lookups not found in the cache
|
||||
hitBytes atomic.Int64 // Total number of bytes read from cache
|
||||
missBytes atomic.Int64 // Total number of bytes read from database
|
||||
}
|
||||
|
||||
// newCachingCodeReader constructs the code reader.
|
||||
func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstrainedCache[common.Hash, []byte], codeSizeCache *lru.Cache[common.Hash, int]) *cachingCodeReader {
|
||||
return &cachingCodeReader{
|
||||
db: db,
|
||||
codeCache: codeCache,
|
||||
codeSizeCache: codeSizeCache,
|
||||
}
|
||||
}
|
||||
|
||||
// Code implements ContractCodeReader, retrieving a particular contract's code.
|
||||
// If the contract code doesn't exist, no error will be returned.
|
||||
func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) {
|
||||
code, _ := r.codeCache.Get(codeHash)
|
||||
if len(code) > 0 {
|
||||
r.hit.Add(1)
|
||||
r.hitBytes.Add(int64(len(code)))
|
||||
return code, nil
|
||||
}
|
||||
r.miss.Add(1)
|
||||
|
||||
code = rawdb.ReadCode(r.db, codeHash)
|
||||
if len(code) > 0 {
|
||||
r.codeCache.Add(codeHash, code)
|
||||
r.codeSizeCache.Add(codeHash, len(code))
|
||||
r.missBytes.Add(int64(len(code)))
|
||||
}
|
||||
return code, nil
|
||||
}
|
||||
|
||||
// CodeSize implements ContractCodeReader, retrieving a particular contracts code's size.
|
||||
// If the contract code doesn't exist, no error will be returned.
|
||||
func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) {
|
||||
if cached, ok := r.codeSizeCache.Get(codeHash); ok {
|
||||
r.hit.Add(1)
|
||||
return cached, nil
|
||||
}
|
||||
code, err := r.Code(addr, codeHash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(code), nil
|
||||
}
|
||||
|
||||
// Has returns the flag indicating whether the contract code with
|
||||
// specified address and hash exists or not.
|
||||
func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool {
|
||||
code, _ := r.Code(addr, codeHash)
|
||||
return len(code) > 0
|
||||
}
|
||||
|
||||
// GetStats returns the statistics of the code reader.
|
||||
func (r *cachingCodeReader) GetStats() ContractCodeReaderStats {
|
||||
return ContractCodeReaderStats{
|
||||
CacheHit: r.hit.Load(),
|
||||
CacheMiss: r.miss.Load(),
|
||||
CacheHitBytes: r.hitBytes.Load(),
|
||||
CacheMissBytes: r.missBytes.Load(),
|
||||
}
|
||||
}
|
||||
|
||||
// flatReader wraps a database state reader and is safe for concurrent access.
|
||||
type flatReader struct {
|
||||
reader database.StateReader
|
||||
|
|
@ -244,7 +98,7 @@ func newFlatReader(reader database.StateReader) *flatReader {
|
|||
//
|
||||
// The returned account might be nil if it's not existent.
|
||||
func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
account, err := r.reader.Account(crypto.Keccak256Hash(addr.Bytes()))
|
||||
account, err := r.reader.Account(crypto.Keccak256Hash(addr[:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -274,8 +128,8 @@ func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
|
|||
//
|
||||
// The returned storage slot might be empty if it's not existent.
|
||||
func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
|
||||
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||
slotHash := crypto.Keccak256Hash(key.Bytes())
|
||||
addrHash := crypto.Keccak256Hash(addr[:])
|
||||
slotHash := crypto.Keccak256Hash(key[:])
|
||||
ret, err := r.reader.Storage(addrHash, slotHash)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
|
|
@ -495,20 +349,6 @@ func (r *multiStateReader) Storage(addr common.Address, slot common.Hash) (commo
|
|||
return common.Hash{}, errors.Join(errs...)
|
||||
}
|
||||
|
||||
// reader is the wrapper of ContractCodeReader and StateReader interface.
|
||||
type reader struct {
|
||||
ContractCodeReader
|
||||
StateReader
|
||||
}
|
||||
|
||||
// newReader constructs a reader with the supplied code reader and state reader.
|
||||
func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
|
||||
return &reader{
|
||||
ContractCodeReader: codeReader,
|
||||
StateReader: stateReader,
|
||||
}
|
||||
}
|
||||
|
||||
// stateReaderWithCache is a wrapper around StateReader that maintains additional
|
||||
// state caches to support concurrent state access.
|
||||
type stateReaderWithCache struct {
|
||||
|
|
@ -619,9 +459,10 @@ func (r *stateReaderWithCache) Storage(addr common.Address, slot common.Hash) (c
|
|||
return value, err
|
||||
}
|
||||
|
||||
type readerWithStats struct {
|
||||
// stateReaderWithStats is a wrapper over the stateReaderWithCache, tracking
|
||||
// the cache hit statistics of the reader.
|
||||
type stateReaderWithStats struct {
|
||||
*stateReaderWithCache
|
||||
ContractCodeReaderWithStats
|
||||
|
||||
accountCacheHit atomic.Int64
|
||||
accountCacheMiss atomic.Int64
|
||||
|
|
@ -629,11 +470,10 @@ type readerWithStats struct {
|
|||
storageCacheMiss atomic.Int64
|
||||
}
|
||||
|
||||
// newReaderWithStats constructs the reader with additional statistics tracked.
|
||||
func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats) *readerWithStats {
|
||||
return &readerWithStats{
|
||||
stateReaderWithCache: sr,
|
||||
ContractCodeReaderWithStats: cr,
|
||||
// newReaderWithStats constructs the state reader with additional statistics tracked.
|
||||
func newStateReaderWithStats(sr *stateReaderWithCache) *stateReaderWithStats {
|
||||
return &stateReaderWithStats{
|
||||
stateReaderWithCache: sr,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -641,7 +481,7 @@ func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats
|
|||
// The returned account might be nil if it's not existent.
|
||||
//
|
||||
// An error will be returned if the state is corrupted in the underlying reader.
|
||||
func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
func (r *stateReaderWithStats) Account(addr common.Address) (*types.StateAccount, error) {
|
||||
account, incache, err := r.stateReaderWithCache.account(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -659,7 +499,7 @@ func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, err
|
|||
// existent.
|
||||
//
|
||||
// An error will be returned if the state is corrupted in the underlying reader.
|
||||
func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
|
||||
func (r *stateReaderWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
|
||||
value, incache, err := r.stateReaderWithCache.storage(addr, slot)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
|
|
@ -672,13 +512,51 @@ func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common
|
|||
return value, nil
|
||||
}
|
||||
|
||||
// GetStats implements ReaderWithStats, returning the statistics of state reader.
|
||||
func (r *readerWithStats) GetStats() ReaderStats {
|
||||
return ReaderStats{
|
||||
// GetStateStats implements StateReaderStater, returning the statistics of the
|
||||
// state reader.
|
||||
func (r *stateReaderWithStats) GetStateStats() StateReaderStats {
|
||||
return StateReaderStats{
|
||||
AccountCacheHit: r.accountCacheHit.Load(),
|
||||
AccountCacheMiss: r.accountCacheMiss.Load(),
|
||||
StorageCacheHit: r.storageCacheHit.Load(),
|
||||
StorageCacheMiss: r.storageCacheMiss.Load(),
|
||||
CodeStats: r.ContractCodeReaderWithStats.GetStats(),
|
||||
}
|
||||
}
|
||||
|
||||
// reader aggregates a code reader and a state reader into a single object.
|
||||
type reader struct {
|
||||
ContractCodeReader
|
||||
StateReader
|
||||
}
|
||||
|
||||
// newReader constructs a reader with the supplied code reader and state reader.
|
||||
func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader {
|
||||
return &reader{
|
||||
ContractCodeReader: codeReader,
|
||||
StateReader: stateReader,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCodeStats returns the statistics of code access.
|
||||
func (r *reader) GetCodeStats() ContractCodeReaderStats {
|
||||
if stater, ok := r.ContractCodeReader.(ContractCodeReaderStater); ok {
|
||||
return stater.GetCodeStats()
|
||||
}
|
||||
return ContractCodeReaderStats{}
|
||||
}
|
||||
|
||||
// GetStateStats returns the statistics of state access.
|
||||
func (r *reader) GetStateStats() StateReaderStats {
|
||||
if stater, ok := r.StateReader.(StateReaderStater); ok {
|
||||
return stater.GetStateStats()
|
||||
}
|
||||
return StateReaderStats{}
|
||||
}
|
||||
|
||||
// GetStats returns the aggregated statistics for both state and code access.
|
||||
func (r *reader) GetStats() ReaderStats {
|
||||
return ReaderStats{
|
||||
CodeStats: r.GetCodeStats(),
|
||||
StateStats: r.GetStateStats(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
82
core/state/reader_stater.go
Normal file
82
core/state/reader_stater.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2026 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 state
|
||||
|
||||
// ContractCodeReaderStats aggregates statistics for the contract code reader.
|
||||
type ContractCodeReaderStats struct {
|
||||
CacheHit int64 // Number of cache hits
|
||||
CacheMiss int64 // Number of cache misses
|
||||
CacheHitBytes int64 // Total bytes served from cache
|
||||
CacheMissBytes int64 // Total bytes read on cache misses
|
||||
}
|
||||
|
||||
// HitRate returns the cache hit rate in percentage.
|
||||
func (s ContractCodeReaderStats) HitRate() float64 {
|
||||
total := s.CacheHit + s.CacheMiss
|
||||
if total == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(s.CacheHit) / float64(total) * 100
|
||||
}
|
||||
|
||||
// ContractCodeReaderStater wraps the method to retrieve the statistics of
|
||||
// contract code reader.
|
||||
type ContractCodeReaderStater interface {
|
||||
GetCodeStats() ContractCodeReaderStats
|
||||
}
|
||||
|
||||
// StateReaderStats aggregates statistics for the state reader.
|
||||
type StateReaderStats struct {
|
||||
AccountCacheHit int64 // Number of account cache hits
|
||||
AccountCacheMiss int64 // Number of account cache misses
|
||||
StorageCacheHit int64 // Number of storage cache hits
|
||||
StorageCacheMiss int64 // Number of storage cache misses
|
||||
}
|
||||
|
||||
// AccountCacheHitRate returns the cache hit rate of account requests in percentage.
|
||||
func (s StateReaderStats) AccountCacheHitRate() float64 {
|
||||
total := s.AccountCacheHit + s.AccountCacheMiss
|
||||
if total == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(s.AccountCacheHit) / float64(total) * 100
|
||||
}
|
||||
|
||||
// StorageCacheHitRate returns the cache hit rate of storage requests in percentage.
|
||||
func (s StateReaderStats) StorageCacheHitRate() float64 {
|
||||
total := s.StorageCacheHit + s.StorageCacheMiss
|
||||
if total == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(s.StorageCacheHit) / float64(total) * 100
|
||||
}
|
||||
|
||||
// StateReaderStater wraps the method to retrieve the statistics of state reader.
|
||||
type StateReaderStater interface {
|
||||
GetStateStats() StateReaderStats
|
||||
}
|
||||
|
||||
// ReaderStats wraps the statistics of reader.
|
||||
type ReaderStats struct {
|
||||
CodeStats ContractCodeReaderStats
|
||||
StateStats StateReaderStats
|
||||
}
|
||||
|
||||
// ReaderStater defines the capability to retrieve aggregated statistics.
|
||||
type ReaderStater interface {
|
||||
GetStats() ReaderStats
|
||||
}
|
||||
|
|
@ -474,6 +474,14 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
|
|||
s.origin = s.data.Copy()
|
||||
return op, nil, nil
|
||||
}
|
||||
// In Verkle/binary trie mode, all state objects share one unified trie.
|
||||
// The main account trie commit in stateDB.commit() already calls
|
||||
// CollectNodes on this trie, so calling Commit here again would
|
||||
// redundantly traverse and serialize the entire tree per dirty account.
|
||||
if s.db.GetTrie().IsVerkle() {
|
||||
s.origin = s.data.Copy()
|
||||
return op, nil, nil
|
||||
}
|
||||
root, nodes := s.trie.Commit(false)
|
||||
s.data.Root = root
|
||||
s.origin = s.data.Copy()
|
||||
|
|
@ -564,10 +572,7 @@ func (s *stateObject) Code() []byte {
|
|||
s.db.CodeLoadBytes += len(s.code)
|
||||
}(time.Now())
|
||||
|
||||
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if err != nil {
|
||||
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
|
||||
}
|
||||
code := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if len(code) == 0 {
|
||||
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
|
||||
}
|
||||
|
|
@ -590,10 +595,7 @@ func (s *stateObject) CodeSize() int {
|
|||
s.db.CodeReads += time.Since(start)
|
||||
}(time.Now())
|
||||
|
||||
size, err := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if err != nil {
|
||||
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
|
||||
}
|
||||
size := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
|
||||
if size == 0 {
|
||||
s.db.setError(fmt.Errorf("code is not found %x", s.CodeHash()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
|
|
@ -148,8 +147,7 @@ type StateDB struct {
|
|||
StorageReads time.Duration
|
||||
StorageUpdates time.Duration
|
||||
StorageCommits time.Duration
|
||||
SnapshotCommits time.Duration
|
||||
TrieDBCommits time.Duration
|
||||
DatabaseCommits time.Duration
|
||||
CodeReads time.Duration
|
||||
|
||||
AccountLoaded int // Number of accounts retrieved from the database during the state transition
|
||||
|
|
@ -1333,41 +1331,14 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
// Commit dirty contract code if any exists
|
||||
if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 {
|
||||
batch := db.NewBatch()
|
||||
for _, code := range ret.codes {
|
||||
rawdb.WriteCode(batch, code.hash, code.blob)
|
||||
}
|
||||
if err := batch.Write(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !ret.empty() {
|
||||
// If snapshotting is enabled, update the snapshot tree with this new version
|
||||
if snap := s.db.Snapshot(); snap != nil && snap.Snapshot(ret.originRoot) != nil {
|
||||
start := time.Now()
|
||||
if err := snap.Update(ret.root, ret.originRoot, ret.accounts, ret.storages); err != nil {
|
||||
log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err)
|
||||
}
|
||||
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||
// - head layer is paired with HEAD state
|
||||
// - head-1 layer is paired with HEAD-1 state
|
||||
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
||||
if err := snap.Cap(ret.root, TriesInMemory); err != nil {
|
||||
log.Warn("Failed to cap snapshot tree", "root", ret.root, "layers", TriesInMemory, "err", err)
|
||||
}
|
||||
s.SnapshotCommits += time.Since(start)
|
||||
}
|
||||
// If trie database is enabled, commit the state update as a new layer
|
||||
if db := s.db.TrieDB(); db != nil {
|
||||
start := time.Now()
|
||||
if err := db.Update(ret.root, ret.originRoot, block, ret.nodes, ret.stateSet()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.TrieDBCommits += time.Since(start)
|
||||
}
|
||||
start := time.Now()
|
||||
if err := s.db.Commit(ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.DatabaseCommits = time.Since(start)
|
||||
|
||||
// The reader update must be performed as the final step, otherwise,
|
||||
// the new state would not be visible before db.commit.
|
||||
s.reader, _ = s.db.Reader(s.originalRoot)
|
||||
return ret, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ func (test *stateTest) run() bool {
|
|||
if i != 0 {
|
||||
root = roots[len(roots)-1]
|
||||
}
|
||||
state, err := New(root, NewDatabase(tdb, snaps))
|
||||
state, err := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1276,7 +1276,7 @@ func TestDeleteStorage(t *testing.T) {
|
|||
disk = rawdb.NewMemoryDatabase()
|
||||
tdb = triedb.NewDatabase(disk, nil)
|
||||
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
|
||||
db = NewDatabase(tdb, snaps)
|
||||
db = NewDatabase(tdb, nil).WithSnapshot(snaps)
|
||||
state, _ = New(types.EmptyRootHash, db)
|
||||
addr = common.HexToAddress("0x1")
|
||||
)
|
||||
|
|
@ -1290,7 +1290,7 @@ func TestDeleteStorage(t *testing.T) {
|
|||
}
|
||||
root, _ := state.Commit(0, true, false)
|
||||
// Init phase done, create two states, one with snap and one without
|
||||
fastState, _ := New(root, NewDatabase(tdb, snaps))
|
||||
fastState, _ := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
|
||||
slowState, _ := New(root, NewDatabase(tdb, nil))
|
||||
|
||||
obj := fastState.getOrNewStateObject(addr)
|
||||
|
|
|
|||
|
|
@ -211,9 +211,9 @@ func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
|
|||
cache := make(map[common.Hash]bool)
|
||||
for addr, code := range sc.codes {
|
||||
if code.originHash != types.EmptyCodeHash {
|
||||
blob, err := reader.Code(addr, code.originHash)
|
||||
if err != nil {
|
||||
return err
|
||||
blob := reader.Code(addr, code.originHash)
|
||||
if len(blob) == 0 {
|
||||
return fmt.Errorf("original code of %x is empty", addr)
|
||||
}
|
||||
code.originBlob = blob
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,8 +222,8 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool, s
|
|||
codeResults = make([]trie.CodeSyncResult, len(codeElements))
|
||||
)
|
||||
for i, element := range codeElements {
|
||||
data, err := cReader.Code(common.Address{}, element.code)
|
||||
if err != nil || len(data) == 0 {
|
||||
data := cReader.Code(common.Address{}, element.code)
|
||||
if len(data) == 0 {
|
||||
t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code)
|
||||
}
|
||||
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
|
||||
|
|
@ -346,8 +346,8 @@ func testIterativeDelayedStateSync(t *testing.T, scheme string) {
|
|||
if len(codeElements) > 0 {
|
||||
codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1)
|
||||
for i, element := range codeElements[:len(codeResults)] {
|
||||
data, err := cReader.Code(common.Address{}, element.code)
|
||||
if err != nil || len(data) == 0 {
|
||||
data := cReader.Code(common.Address{}, element.code)
|
||||
if len(data) == 0 {
|
||||
t.Fatalf("failed to retrieve contract bytecode for %x", element.code)
|
||||
}
|
||||
codeResults[i] = trie.CodeSyncResult{Hash: element.code, Data: data}
|
||||
|
|
@ -452,8 +452,8 @@ func testIterativeRandomStateSync(t *testing.T, count int, scheme string) {
|
|||
if len(codeQueue) > 0 {
|
||||
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
|
||||
for hash := range codeQueue {
|
||||
data, err := cReader.Code(common.Address{}, hash)
|
||||
if err != nil || len(data) == 0 {
|
||||
data := cReader.Code(common.Address{}, hash)
|
||||
if len(data) == 0 {
|
||||
t.Fatalf("failed to retrieve node data for %x", hash)
|
||||
}
|
||||
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
|
||||
|
|
@ -551,8 +551,8 @@ func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) {
|
|||
for hash := range codeQueue {
|
||||
delete(codeQueue, hash)
|
||||
|
||||
data, err := cReader.Code(common.Address{}, hash)
|
||||
if err != nil || len(data) == 0 {
|
||||
data := cReader.Code(common.Address{}, hash)
|
||||
if len(data) == 0 {
|
||||
t.Fatalf("failed to retrieve node data for %x", hash)
|
||||
}
|
||||
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
|
||||
|
|
@ -671,8 +671,8 @@ func testIncompleteStateSync(t *testing.T, scheme string) {
|
|||
if len(codeQueue) > 0 {
|
||||
results := make([]trie.CodeSyncResult, 0, len(codeQueue))
|
||||
for hash := range codeQueue {
|
||||
data, err := cReader.Code(common.Address{}, hash)
|
||||
if err != nil || len(data) == 0 {
|
||||
data := cReader.Code(common.Address{}, hash)
|
||||
if len(data) == 0 {
|
||||
t.Fatalf("failed to retrieve node data for %x", hash)
|
||||
}
|
||||
results = append(results, trie.CodeSyncResult{Hash: hash, Data: data})
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
|
|||
|
||||
// We attempt to apply a transaction. The goal is not to execute
|
||||
// the transaction successfully, rather to warm up touched data slots.
|
||||
if _, err := ApplyMessage(evm, msg, new(GasPool).AddGas(block.GasLimit())); err != nil {
|
||||
if _, err := ApplyMessage(evm, msg, nil); err != nil {
|
||||
fails.Add(1)
|
||||
return nil // Ugh, something went horribly wrong, bail out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,14 +63,12 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
var (
|
||||
config = p.chainConfig()
|
||||
receipts types.Receipts
|
||||
usedGas = new(uint64)
|
||||
header = block.Header()
|
||||
blockHash = block.Hash()
|
||||
blockNumber = block.Number()
|
||||
allLogs []*types.Log
|
||||
gp = new(GasPool).AddGas(block.GasLimit())
|
||||
gp = NewGasPool(block.GasLimit())
|
||||
)
|
||||
|
||||
var tracingStateDB = vm.StateDB(statedb)
|
||||
if hooks := cfg.Tracer; hooks != nil {
|
||||
tracingStateDB = state.NewHookedState(statedb, hooks)
|
||||
|
|
@ -107,13 +105,15 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
telemetry.StringAttribute("tx.hash", tx.Hash().Hex()),
|
||||
telemetry.Int64Attribute("tx.index", int64(i)),
|
||||
)
|
||||
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm)
|
||||
spanEnd(&err)
|
||||
|
||||
receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm)
|
||||
if err != nil {
|
||||
spanEnd(&err)
|
||||
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||
}
|
||||
receipts = append(receipts, receipt)
|
||||
allLogs = append(allLogs, receipt.Logs...)
|
||||
spanEnd(nil)
|
||||
}
|
||||
requests, err := postExecution(ctx, config, block, allLogs, evm)
|
||||
if err != nil {
|
||||
|
|
@ -127,7 +127,7 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
Receipts: receipts,
|
||||
Requests: requests,
|
||||
Logs: allLogs,
|
||||
GasUsed: *usedGas,
|
||||
GasUsed: gp.Used(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ func postExecution(ctx context.Context, config *params.ChainConfig, block *types
|
|||
// ApplyTransactionWithEVM attempts to apply a transaction to the given state database
|
||||
// and uses the input parameters for its environment similar to ApplyTransaction. However,
|
||||
// this method takes an already created EVM instance as input.
|
||||
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) {
|
||||
func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (receipt *types.Receipt, err error) {
|
||||
if hooks := evm.Config.Tracer; hooks != nil {
|
||||
if hooks.OnTxStart != nil {
|
||||
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
|
|
@ -180,27 +180,31 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
|
|||
} else {
|
||||
root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes()
|
||||
}
|
||||
*usedGas += result.UsedGas
|
||||
|
||||
// Merge the tx-local access event into the "block-local" one, in order to collect
|
||||
// all values, so that the witness can be built.
|
||||
if statedb.Database().TrieDB().IsVerkle() {
|
||||
statedb.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil
|
||||
return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil
|
||||
}
|
||||
|
||||
// MakeReceipt generates the receipt object for a transaction given its execution result.
|
||||
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
|
||||
// Create a new receipt for the transaction, storing the intermediate root and gas used
|
||||
// by the tx.
|
||||
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas}
|
||||
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, cumulativeGas uint64, root []byte) *types.Receipt {
|
||||
// Create a new receipt for the transaction, storing the intermediate root
|
||||
// and gas used by the tx.
|
||||
//
|
||||
// The cumulative gas used equals the sum of gasUsed across all preceding
|
||||
// txs with refunded gas deducted.
|
||||
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: cumulativeGas}
|
||||
if result.Failed() {
|
||||
receipt.Status = types.ReceiptStatusFailed
|
||||
} else {
|
||||
receipt.Status = types.ReceiptStatusSuccessful
|
||||
}
|
||||
receipt.TxHash = tx.Hash()
|
||||
|
||||
// GasUsed = max(tx_gas_used - gas_refund, calldata_floor_gas_cost), unchanged
|
||||
// in the Amsterdam fork.
|
||||
receipt.GasUsed = result.UsedGas
|
||||
|
||||
if tx.Type() == types.BlobTxType {
|
||||
|
|
@ -224,15 +228,15 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b
|
|||
|
||||
// ApplyTransaction attempts to apply a transaction to the given state database
|
||||
// and uses the input parameters for its environment. It returns the receipt
|
||||
// for the transaction, gas used and an error if the transaction failed,
|
||||
// for the transaction and an error if the transaction failed,
|
||||
// indicating the block was invalid.
|
||||
func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, error) {
|
||||
func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (*types.Receipt, error) {
|
||||
msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create a new context to be used in the EVM environment
|
||||
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, usedGas, evm)
|
||||
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm)
|
||||
}
|
||||
|
||||
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import (
|
|||
// ExecutionResult includes all output after executing given evm
|
||||
// message no matter the execution itself is successful or not.
|
||||
type ExecutionResult struct {
|
||||
UsedGas uint64 // Total used gas, not including the refunded gas
|
||||
UsedGas uint64 // Total used gas, refunded gas is deducted
|
||||
MaxUsedGas uint64 // Maximum gas consumed during execution, excluding gas refunds.
|
||||
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
|
||||
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
|
||||
|
|
@ -210,6 +210,11 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
|
|||
// indicates a core error meaning that the message would always fail for that particular
|
||||
// state and would never be accepted within a block.
|
||||
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
|
||||
// Do not panic if the gas pool is nil. This is allowed when executing
|
||||
// a single message via RPC invocation.
|
||||
if gp == nil {
|
||||
gp = NewGasPool(msg.GasLimit)
|
||||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
return newStateTransition(evm, msg, gp).execute()
|
||||
}
|
||||
|
|
@ -300,8 +305,8 @@ func (st *stateTransition) buyGas() error {
|
|||
st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, tracing.GasChangeTxInitialBalance)
|
||||
}
|
||||
st.gasRemaining = st.msg.GasLimit
|
||||
|
||||
st.initialGas = st.msg.GasLimit
|
||||
|
||||
mgvalU256, _ := uint256.FromBig(mgval)
|
||||
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
|
||||
return nil
|
||||
|
|
@ -483,8 +488,10 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
|
||||
// Check whether the init code size has been exceeded.
|
||||
if rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSize {
|
||||
return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize)
|
||||
if contractCreation {
|
||||
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the preparatory steps for state transition which includes:
|
||||
|
|
@ -542,8 +549,20 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
peakGasUsed = floorDataGas
|
||||
}
|
||||
}
|
||||
// Return gas to the user
|
||||
st.returnGas()
|
||||
|
||||
// Return gas to the gas pool
|
||||
if rules.IsAmsterdam {
|
||||
// Refund is excluded for returning
|
||||
err = st.gp.ReturnGas(st.initialGas-peakGasUsed, st.gasUsed())
|
||||
} else {
|
||||
// Refund is included for returning
|
||||
err = st.gp.ReturnGas(st.gasRemaining, st.gasUsed())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
effectiveTip := msg.GasPrice
|
||||
if rules.IsLondon {
|
||||
effectiveTip = new(big.Int).Sub(msg.GasPrice, st.evm.Context.BaseFee)
|
||||
|
|
@ -564,7 +583,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
|
||||
}
|
||||
}
|
||||
|
||||
return &ExecutionResult{
|
||||
UsedGas: st.gasUsed(),
|
||||
MaxUsedGas: peakGasUsed,
|
||||
|
|
@ -660,10 +678,6 @@ func (st *stateTransition) returnGas() {
|
|||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
|
||||
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
|
||||
}
|
||||
|
||||
// Also return remaining gas to the block gas counter so it is
|
||||
// available for the next transaction.
|
||||
st.gp.AddGas(st.gasRemaining)
|
||||
}
|
||||
|
||||
// gasUsed returns the amount of gas used up by the state transition.
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig
|
|||
}
|
||||
// Create and populate the state database to serve as the stateless backend
|
||||
memdb := witness.MakeHashDB()
|
||||
db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil))
|
||||
db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), state.NewCodeDB(memdb)))
|
||||
if err != nil {
|
||||
return common.Hash{}, common.Hash{}, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ func (w *Witness) ToExtWitness() *ExtWitness {
|
|||
return ext
|
||||
}
|
||||
|
||||
// fromExtWitness converts the consensus witness format into our internal one.
|
||||
func (w *Witness) fromExtWitness(ext *ExtWitness) error {
|
||||
// FromExtWitness converts the consensus witness format into our internal one.
|
||||
func (w *Witness) FromExtWitness(ext *ExtWitness) error {
|
||||
w.Headers = ext.Headers
|
||||
|
||||
w.Codes = make(map[string]struct{}, len(ext.Codes))
|
||||
|
|
@ -66,7 +66,7 @@ func (w *Witness) DecodeRLP(s *rlp.Stream) error {
|
|||
if err := s.Decode(&ext); err != nil {
|
||||
return err
|
||||
}
|
||||
return w.fromExtWitness(&ext)
|
||||
return w.FromExtWitness(&ext)
|
||||
}
|
||||
|
||||
// ExtWitness is a witness RLP encoding for transferring across clients.
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ const (
|
|||
// this generates an increase in gas. There is at most one of such gas change per transaction.
|
||||
GasChangeTxRefunds GasChangeReason = 3
|
||||
// GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned
|
||||
// to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas
|
||||
// to the account. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas
|
||||
// left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller.
|
||||
// There is at most one of such gas change per transaction.
|
||||
GasChangeTxLeftOverReturned GasChangeReason = 4
|
||||
|
|
|
|||
|
|
@ -155,10 +155,18 @@ func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reaso
|
|||
}
|
||||
|
||||
func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason) {
|
||||
// When a contract is created, the nonce of the creator is incremented.
|
||||
// This change is not reverted when the creation fails.
|
||||
if reason != NonceChangeContractCreator {
|
||||
j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new})
|
||||
j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new})
|
||||
if reason == NonceChangeContractCreator {
|
||||
// When a contract is created via CREATE/CREATE2, the creator's nonce is
|
||||
// incremented. The EVM does not revert this when the CREATE frame itself
|
||||
// fails (the nonce change happens before the EVM snapshot). However, if
|
||||
// a parent frame reverts, the nonce must be reverted along with everything
|
||||
// else.
|
||||
//
|
||||
// To achieve this, advance the current frame's revision point past this
|
||||
// entry. The CREATE frame's revert won't touch it (it's below the revision),
|
||||
// but a parent frame's revert will (it's above the parent's revision).
|
||||
j.revisions[len(j.revisions)-1] = len(j.entries)
|
||||
}
|
||||
if j.hooks.OnNonceChangeV2 != nil {
|
||||
j.hooks.OnNonceChangeV2(addr, prev, new, reason)
|
||||
|
|
|
|||
|
|
@ -219,6 +219,42 @@ func TestNonceIncOnCreate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestNonceIncOnCreateParentReverts checks that the creator's nonce increment
|
||||
// from CREATE survives the CREATE frame's own revert but is properly reverted
|
||||
// when the parent call frame reverts.
|
||||
func TestNonceIncOnCreateParentReverts(t *testing.T) {
|
||||
const opCREATE = 0xf0
|
||||
|
||||
tr := &testTracer{t: t}
|
||||
wr, err := WrapWithJournal(&Hooks{OnNonceChange: tr.OnNonceChange})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to wrap test tracer: %v", err)
|
||||
}
|
||||
|
||||
addr := common.HexToAddress("0x1234")
|
||||
{
|
||||
// Parent call frame
|
||||
wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0))
|
||||
{
|
||||
// CREATE frame — creator nonce incremented, then CREATE reverts
|
||||
wr.OnEnter(1, opCREATE, addr, addr, nil, 1000, big.NewInt(0))
|
||||
wr.OnNonceChangeV2(addr, 0, 1, NonceChangeContractCreator)
|
||||
wr.OnExit(1, nil, 100, errors.New("revert"), true)
|
||||
}
|
||||
// After CREATE reverts, nonce should still be 1
|
||||
if tr.nonce != 1 {
|
||||
t.Fatalf("nonce after CREATE revert: got %v, want 1", tr.nonce)
|
||||
}
|
||||
// Parent frame also reverts
|
||||
wr.OnExit(0, nil, 150, errors.New("revert"), true)
|
||||
}
|
||||
|
||||
// After parent reverts, nonce should be back to 0
|
||||
if tr.nonce != 0 {
|
||||
t.Fatalf("nonce after parent revert: got %v, want 0", tr.nonce)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnNonceChangeV2(t *testing.T) {
|
||||
tr := &testTracer{t: t}
|
||||
wr, err := WrapWithJournal(&Hooks{OnNonceChangeV2: tr.OnNonceChangeV2})
|
||||
|
|
|
|||
|
|
@ -1865,11 +1865,11 @@ func (p *BlobPool) drop() {
|
|||
//
|
||||
// The transactions can also be pre-filtered by the dynamic fee components to
|
||||
// reduce allocations and load on downstream subsystems.
|
||||
func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction {
|
||||
func (p *BlobPool) Pending(filter txpool.PendingFilter) (map[common.Address][]*txpool.LazyTransaction, int) {
|
||||
// If only plain transactions are requested, this pool is unsuitable as it
|
||||
// contains none, don't even bother.
|
||||
if !filter.BlobTxs {
|
||||
return nil
|
||||
return nil, 0
|
||||
}
|
||||
// Track the amount of time waiting to retrieve the list of pending blob txs
|
||||
// from the pool and the amount of time actually spent on assembling the data.
|
||||
|
|
@ -1885,6 +1885,7 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx
|
|||
pendtimeHist.Update(time.Since(execStart).Nanoseconds())
|
||||
}()
|
||||
|
||||
var count int
|
||||
pending := make(map[common.Address][]*txpool.LazyTransaction, len(p.index))
|
||||
for addr, txs := range p.index {
|
||||
lazies := make([]*txpool.LazyTransaction, 0, len(txs))
|
||||
|
|
@ -1930,9 +1931,10 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx
|
|||
}
|
||||
if len(lazies) > 0 {
|
||||
pending[addr] = lazies
|
||||
count += len(lazies)
|
||||
}
|
||||
}
|
||||
return pending
|
||||
return pending, count
|
||||
}
|
||||
|
||||
// updateStorageMetrics retrieves a bunch of stats from the data store and pushes
|
||||
|
|
|
|||
|
|
@ -2122,7 +2122,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
|
|||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
p := pool.Pending(txpool.PendingFilter{
|
||||
p, _ := pool.Pending(txpool.PendingFilter{
|
||||
MinTip: uint256.NewInt(1),
|
||||
BaseFee: chain.basefee,
|
||||
BlobFee: chain.blobfee,
|
||||
|
|
|
|||
|
|
@ -494,15 +494,16 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction,
|
|||
//
|
||||
// The transactions can also be pre-filtered by the dynamic fee components to
|
||||
// reduce allocations and load on downstream subsystems.
|
||||
func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction {
|
||||
func (pool *LegacyPool) Pending(filter txpool.PendingFilter) (map[common.Address][]*txpool.LazyTransaction, int) {
|
||||
// If only blob transactions are requested, this pool is unsuitable as it
|
||||
// contains none, don't even bother.
|
||||
if filter.BlobTxs {
|
||||
return nil
|
||||
return nil, 0
|
||||
}
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
|
||||
var count int
|
||||
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
|
||||
for addr, list := range pool.pending {
|
||||
txs := list.Flatten()
|
||||
|
|
@ -539,9 +540,10 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
|
|||
}
|
||||
}
|
||||
pending[addr] = lazies
|
||||
count += len(lazies)
|
||||
}
|
||||
}
|
||||
return pending
|
||||
return pending, count
|
||||
}
|
||||
|
||||
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ type SubPool interface {
|
|||
//
|
||||
// The transactions can also be pre-filtered by the dynamic fee components to
|
||||
// reduce allocations and load on downstream subsystems.
|
||||
Pending(filter PendingFilter) map[common.Address][]*LazyTransaction
|
||||
Pending(filter PendingFilter) (map[common.Address][]*LazyTransaction, int)
|
||||
|
||||
// SubscribeTransactions subscribes to new transaction events. The subscriber
|
||||
// can decide whether to receive notifications only for newly seen transactions
|
||||
|
|
|
|||
|
|
@ -359,14 +359,17 @@ func (p *TxPool) Add(txs []*types.Transaction, sync bool) []error {
|
|||
//
|
||||
// The transactions can also be pre-filtered by the dynamic fee components to
|
||||
// reduce allocations and load on downstream subsystems.
|
||||
func (p *TxPool) Pending(filter PendingFilter) map[common.Address][]*LazyTransaction {
|
||||
func (p *TxPool) Pending(filter PendingFilter) (map[common.Address][]*LazyTransaction, int) {
|
||||
var count int
|
||||
txs := make(map[common.Address][]*LazyTransaction)
|
||||
for _, subpool := range p.subpools {
|
||||
for addr, set := range subpool.Pending(filter) {
|
||||
txs[addr] = set
|
||||
set, n := subpool.Pending(filter)
|
||||
for addr, list := range set {
|
||||
txs[addr] = list
|
||||
}
|
||||
count += n
|
||||
}
|
||||
return txs
|
||||
return txs, count
|
||||
}
|
||||
|
||||
// SubscribeTransactions registers a subscription for new transaction events,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core"
|
||||
"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/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -86,8 +87,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
return fmt.Errorf("%w: type %d rejected, pool not yet in Prague", core.ErrTxTypeNotSupported, tx.Type())
|
||||
}
|
||||
// Check whether the init code size has been exceeded
|
||||
if rules.IsShanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
|
||||
return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize)
|
||||
if tx.To() == nil {
|
||||
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(tx.Data()))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rules.IsOsaka && tx.Gas() > params.MaxTxGas {
|
||||
return fmt.Errorf("%w (cap: %d, tx: %d)", core.ErrGasLimitTooHigh, params.MaxTxGas, tx.Gas())
|
||||
|
|
|
|||
|
|
@ -317,11 +317,15 @@ func (tx *Transaction) To() *common.Address {
|
|||
|
||||
// Cost returns (gas * gasPrice) + (blobGas * blobGasPrice) + value.
|
||||
func (tx *Transaction) Cost() *big.Int {
|
||||
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
|
||||
if tx.Type() == BlobTxType {
|
||||
total.Add(total, new(big.Int).Mul(tx.BlobGasFeeCap(), new(big.Int).SetUint64(tx.BlobGas())))
|
||||
// Avoid allocating copies via tx.GasPrice()/tx.Value(); use inner values directly.
|
||||
total := new(big.Int).SetUint64(tx.inner.gas())
|
||||
total.Mul(total, tx.inner.gasPrice())
|
||||
if blobtx, ok := tx.inner.(*BlobTx); ok {
|
||||
tmp := new(big.Int).SetUint64(blobtx.blobGas())
|
||||
tmp.Mul(tmp, blobtx.BlobFeeCap.ToBig())
|
||||
total.Add(total, tmp)
|
||||
}
|
||||
total.Add(total, tx.Value())
|
||||
total.Add(total, tx.inner.value())
|
||||
return total
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,43 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// CheckMaxInitCodeSize checks the size of contract initcode against the protocol-defined limit.
|
||||
func CheckMaxInitCodeSize(rules *params.Rules, size uint64) error {
|
||||
if rules.IsAmsterdam {
|
||||
if size > params.MaxInitCodeSizeAmsterdam {
|
||||
return fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, size, params.MaxInitCodeSizeAmsterdam)
|
||||
}
|
||||
} else if rules.IsShanghai {
|
||||
if size > params.MaxInitCodeSize {
|
||||
return fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, size, params.MaxInitCodeSize)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckMaxCodeSize checks the size of contract code against the protocol-defined limit.
|
||||
func CheckMaxCodeSize(rules *params.Rules, size uint64) error {
|
||||
if rules.IsAmsterdam {
|
||||
if size > params.MaxCodeSizeAmsterdam {
|
||||
return fmt.Errorf("%w: code size %v limit %v", ErrMaxCodeSizeExceeded, size, params.MaxCodeSizeAmsterdam)
|
||||
}
|
||||
} else if rules.IsEIP158 {
|
||||
if size > params.MaxCodeSize {
|
||||
return fmt.Errorf("%w: code size %v limit %v", ErrMaxCodeSizeExceeded, size, params.MaxCodeSize)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// calcMemSize64 calculates the required memory size, and returns
|
||||
// the size and whether the result overflowed uint64
|
||||
func calcMemSize64(off, l *uint256.Int) (uint64, bool) {
|
||||
|
|
|
|||
|
|
@ -597,8 +597,8 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
|
|||
}
|
||||
|
||||
// Check whether the max code size has been exceeded, assign err if the case.
|
||||
if evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
|
||||
return ret, ErrMaxCodeSizeExceeded
|
||||
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Reject code starting with 0xEF if EIP-3541 is enabled.
|
||||
|
|
|
|||
|
|
@ -49,6 +49,5 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u
|
|||
if !callCost.IsUint64() {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
return callCost.Uint64(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package vm
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
|
|
@ -318,10 +317,10 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
|||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if size > params.MaxInitCodeSize {
|
||||
return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size)
|
||||
if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
|
||||
// Since size <= the protocol-defined maximum initcode size limit, these multiplication cannot overflow
|
||||
moreGas := params.InitCodeWordGas * ((size + 31) / 32)
|
||||
if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
|
|
@ -337,10 +336,10 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
|
|||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if size > params.MaxInitCodeSize {
|
||||
return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size)
|
||||
if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
|
||||
// Since size <= the protocol-defined maximum initcode size limit, these multiplication cannot overflow
|
||||
moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32)
|
||||
if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
|
|
@ -374,7 +373,32 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
|
|||
return gas, nil
|
||||
}
|
||||
|
||||
func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var (
|
||||
gasCall = makeCallVariantGasCost(gasCallIntrinsic)
|
||||
gasCallCode = makeCallVariantGasCost(gasCallCodeIntrinsic)
|
||||
gasDelegateCall = makeCallVariantGasCost(gasDelegateCallIntrinsic)
|
||||
gasStaticCall = makeCallVariantGasCost(gasStaticCallIntrinsic)
|
||||
)
|
||||
|
||||
func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, intrinsic, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
gas, overflow := math.SafeAdd(intrinsic, evm.callGasTemp)
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
}
|
||||
|
||||
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var (
|
||||
gas uint64
|
||||
transfersValue = !stack.Back(2).IsZero()
|
||||
|
|
@ -383,38 +407,40 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
|||
if evm.readOnly && transfersValue {
|
||||
return 0, ErrWriteProtection
|
||||
}
|
||||
|
||||
if evm.chainRules.IsEIP158 {
|
||||
if transfersValue && evm.StateDB.Empty(address) {
|
||||
gas += params.CallNewAccountGas
|
||||
}
|
||||
} else if !evm.StateDB.Exist(address) {
|
||||
gas += params.CallNewAccountGas
|
||||
}
|
||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||
gas += params.CallValueTransferGas
|
||||
}
|
||||
// Stateless check
|
||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var transferGas uint64
|
||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||
transferGas = params.CallValueTransferGas
|
||||
}
|
||||
var overflow bool
|
||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||
// it can effectively prevent accessing the states in the following steps.
|
||||
if contract.Gas < gas {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
// Stateful check
|
||||
var stateGas uint64
|
||||
if evm.chainRules.IsEIP158 {
|
||||
if transfersValue && evm.StateDB.Empty(address) {
|
||||
stateGas += params.CallNewAccountGas
|
||||
}
|
||||
} else if !evm.StateDB.Exist(address) {
|
||||
stateGas += params.CallNewAccountGas
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, stateGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
@ -429,46 +455,15 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
|||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var overflow bool
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
return memoryGasCost(mem, memorySize)
|
||||
}
|
||||
|
||||
func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var overflow bool
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
return memoryGasCost(mem, memorySize)
|
||||
}
|
||||
|
||||
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
|
|
|
|||
|
|
@ -148,11 +148,15 @@ func TestCreateGas(t *testing.T) {
|
|||
BlockNumber: big.NewInt(0),
|
||||
}
|
||||
config := Config{}
|
||||
chainConfig := params.AllEthashProtocolChanges
|
||||
if tt.eip3860 {
|
||||
config.ExtraEips = []int{3860}
|
||||
vmctx.Random = new(common.Hash)
|
||||
|
||||
chainConfig = params.MergedTestChainConfig
|
||||
}
|
||||
|
||||
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, config)
|
||||
evm := NewEVM(vmctx, statedb, chainConfig, config)
|
||||
var startGas = uint64(testGas)
|
||||
ret, gas, err := evm.Call(common.Address{}, address, nil, startGas, new(uint256.Int))
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -946,24 +946,34 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
|||
return nil, errStopToken
|
||||
}
|
||||
|
||||
// decodeSingle decodes the immediate operand of a backward-compatible DUPN or SWAPN instruction (EIP-8024)
|
||||
// https://eips.ethereum.org/EIPS/eip-8024
|
||||
func decodeSingle(x byte) int {
|
||||
if x <= 90 {
|
||||
return int(x) + 17
|
||||
}
|
||||
return int(x) - 20
|
||||
// Depths 1-16 are already covered by the legacy opcodes. The forbidden byte range [91, 127] removes
|
||||
// 37 values from the 256 possible immediates, leaving 219 usable values, so this encoding covers depths
|
||||
// 17 through 235. The immediate is encoded as (x + 111) % 256, where 111 is chosen so that these values
|
||||
// avoid the forbidden range. Decoding is simply the modular inverse (i.e. 111+145=256).
|
||||
return (int(x) + 145) % 256
|
||||
}
|
||||
|
||||
// decodePair decodes the immediate operand of a backward-compatible EXCHANGE
|
||||
// instruction (EIP-8024) into stack indices (n, m) where 1 <= n < m
|
||||
// and n + m <= 30. The forbidden byte range [82, 127] removes 46 values from
|
||||
// the 256 possible immediates, leaving exactly 210 usable bytes.
|
||||
// https://eips.ethereum.org/EIPS/eip-8024
|
||||
func decodePair(x byte) (int, int) {
|
||||
var k int
|
||||
if x <= 79 {
|
||||
k = int(x)
|
||||
} else {
|
||||
k = int(x) - 48
|
||||
}
|
||||
// XOR with 143 remaps the forbidden bytes [82, 127] to an unused corner
|
||||
// of the 16x16 grid below.
|
||||
k := int(x ^ 143)
|
||||
// Split into row q and column r of a 16x16 grid. The 210 valid pairs
|
||||
// occupy two triangles within this grid.
|
||||
q, r := k/16, k%16
|
||||
// Upper triangle (q < r): pairs where m <= 16, encoded directly as
|
||||
// (q+1, r+1).
|
||||
if q < r {
|
||||
return q + 1, r + 1
|
||||
}
|
||||
// Lower triangle: pairs where m > 16, recovered as (r+1, 29-q).
|
||||
return r + 1, 29 - q
|
||||
}
|
||||
|
||||
|
|
@ -1034,8 +1044,8 @@ func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
}
|
||||
|
||||
// This range is excluded both to preserve compatibility with existing opcodes
|
||||
// and to keep decode_pair’s 16-aligned arithmetic mapping valid (0–79, 128–255).
|
||||
if x > 79 && x < 128 {
|
||||
// and to keep decode_pair’s 16-aligned arithmetic mapping valid (0–81, 128–255).
|
||||
if x > 81 && x < 128 {
|
||||
return nil, &ErrInvalidOpCode{opcode: OpCode(x)}
|
||||
}
|
||||
n, m := decodePair(x)
|
||||
|
|
|
|||
|
|
@ -1022,16 +1022,7 @@ func TestEIP8024_Execution(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "DUPN",
|
||||
codeHex: "60016000808080808080808080808080808080e600",
|
||||
wantVals: []uint64{
|
||||
1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DUPN_MISSING_IMMEDIATE",
|
||||
codeHex: "60016000808080808080808080808080808080e6",
|
||||
codeHex: "60016000808080808080808080808080808080e680",
|
||||
wantVals: []uint64{
|
||||
1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
|
@ -1040,7 +1031,7 @@ func TestEIP8024_Execution(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "SWAPN",
|
||||
codeHex: "600160008080808080808080808080808080806002e700",
|
||||
codeHex: "600160008080808080808080808080808080806002e780",
|
||||
wantVals: []uint64{
|
||||
1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
|
@ -1048,22 +1039,23 @@ func TestEIP8024_Execution(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "SWAPN_MISSING_IMMEDIATE",
|
||||
codeHex: "600160008080808080808080808080808080806002e7",
|
||||
name: "EXCHANGE_MISSING_IMMEDIATE",
|
||||
codeHex: "600260008080808080600160008080808080808080e8",
|
||||
wantVals: []uint64{
|
||||
1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
2,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
2, // 10th from top
|
||||
0, 0, 0, 0, 0, 0,
|
||||
1, // bottom
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE",
|
||||
codeHex: "600060016002e801",
|
||||
codeHex: "600060016002e88e",
|
||||
wantVals: []uint64{2, 0, 1},
|
||||
},
|
||||
{
|
||||
name: "EXCHANGE_MISSING_IMMEDIATE",
|
||||
codeHex: "600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060016002e8",
|
||||
name: "EXCHANGE",
|
||||
codeHex: "600080808080808080808080808080808080808080808080808080808060016002e88f",
|
||||
wantVals: []uint64{
|
||||
2,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
|
@ -1077,68 +1069,31 @@ func TestEIP8024_Execution(t *testing.T) {
|
|||
wantOpcode: SWAPN,
|
||||
},
|
||||
{
|
||||
name: "JUMP over INVALID_DUPN",
|
||||
name: "JUMP_OVER_INVALID_DUPN",
|
||||
codeHex: "600456e65b",
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "UNDERFLOW_DUPN_1",
|
||||
codeHex: "6000808080808080808080808080808080e600",
|
||||
name: "EXCHANGE",
|
||||
codeHex: "60008080e88e15",
|
||||
wantVals: []uint64{1, 0, 0},
|
||||
},
|
||||
{
|
||||
name: "INVALID_EXCHANGE",
|
||||
codeHex: "e852",
|
||||
wantErr: &ErrInvalidOpCode{},
|
||||
wantOpcode: EXCHANGE,
|
||||
},
|
||||
{
|
||||
name: "UNDERFLOW_DUPN",
|
||||
codeHex: "6000808080808080808080808080808080e680",
|
||||
wantErr: &ErrStackUnderflow{},
|
||||
wantOpcode: DUPN,
|
||||
},
|
||||
// Additional test cases
|
||||
{
|
||||
name: "INVALID_DUPN_LOW",
|
||||
codeHex: "e65b",
|
||||
wantErr: &ErrInvalidOpCode{},
|
||||
wantOpcode: DUPN,
|
||||
},
|
||||
{
|
||||
name: "INVALID_EXCHANGE_LOW",
|
||||
codeHex: "e850",
|
||||
wantErr: &ErrInvalidOpCode{},
|
||||
wantOpcode: EXCHANGE,
|
||||
},
|
||||
{
|
||||
name: "INVALID_DUPN_HIGH",
|
||||
codeHex: "e67f",
|
||||
wantErr: &ErrInvalidOpCode{},
|
||||
wantOpcode: DUPN,
|
||||
},
|
||||
{
|
||||
name: "INVALID_SWAPN_HIGH",
|
||||
codeHex: "e77f",
|
||||
wantErr: &ErrInvalidOpCode{},
|
||||
wantOpcode: SWAPN,
|
||||
},
|
||||
{
|
||||
name: "INVALID_EXCHANGE_HIGH",
|
||||
codeHex: "e87f",
|
||||
wantErr: &ErrInvalidOpCode{},
|
||||
wantOpcode: EXCHANGE,
|
||||
},
|
||||
{
|
||||
name: "UNDERFLOW_DUPN_2",
|
||||
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items, have 16)
|
||||
wantErr: &ErrStackUnderflow{},
|
||||
wantOpcode: DUPN,
|
||||
},
|
||||
{
|
||||
name: "UNDERFLOW_SWAPN",
|
||||
codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items, have 17)
|
||||
wantErr: &ErrStackUnderflow{},
|
||||
wantOpcode: SWAPN,
|
||||
},
|
||||
{
|
||||
name: "UNDERFLOW_EXCHANGE",
|
||||
codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2
|
||||
wantErr: &ErrStackUnderflow{},
|
||||
wantOpcode: EXCHANGE,
|
||||
},
|
||||
{
|
||||
name: "PC_INCREMENT",
|
||||
codeHex: "600060006000e80115",
|
||||
codeHex: "600060006000e88e15",
|
||||
wantVals: []uint64{1, 0, 0},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,13 +27,11 @@ import (
|
|||
|
||||
// Config are the configuration options for the Interpreter
|
||||
type Config struct {
|
||||
Tracer *tracing.Hooks
|
||||
Tracer *tracing.Hooks
|
||||
|
||||
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
|
||||
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
|
||||
ExtraEips []int // Additional EIPS that are to be enabled
|
||||
|
||||
StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose)
|
||||
EnableWitnessStats bool // Whether trie access statistics collection is enabled
|
||||
}
|
||||
|
||||
// ScopeContext contains the things that are per-call, such as stack and memory,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) {
|
|||
switch {
|
||||
case rules.IsVerkle:
|
||||
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet")
|
||||
case rules.IsAmsterdam:
|
||||
return newAmsterdamInstructionSet(), nil
|
||||
case rules.IsOsaka:
|
||||
return newOsakaInstructionSet(), nil
|
||||
case rules.IsPrague:
|
||||
|
|
|
|||
|
|
@ -256,10 +256,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
|
|||
}
|
||||
|
||||
var (
|
||||
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
|
||||
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
|
||||
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
|
||||
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
|
||||
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic)
|
||||
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
|
||||
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic)
|
||||
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic)
|
||||
)
|
||||
|
||||
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
|
|
@ -274,62 +274,85 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
|
|||
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
||||
}
|
||||
|
||||
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
|
||||
func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var (
|
||||
total uint64 // total dynamic gas used
|
||||
addr = common.Address(stack.Back(1).Bytes20())
|
||||
eip2929Cost uint64
|
||||
eip7702Cost uint64
|
||||
addr = common.Address(stack.Back(1).Bytes20())
|
||||
)
|
||||
|
||||
// Check slot presence in the access list
|
||||
// Perform EIP-2929 checks (stateless), checking address presence
|
||||
// in the accessList and charge the cold access accordingly.
|
||||
if !evm.StateDB.AddressInAccessList(addr) {
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
|
||||
// the cost to charge for cold access, if any, is Cold - Warm
|
||||
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
|
||||
// Charge the remaining difference here already, to correctly calculate available
|
||||
// gas for call
|
||||
if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
|
||||
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form
|
||||
// of a constant cost, so the cost to charge for cold access, if any,
|
||||
// is Cold - Warm
|
||||
eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
|
||||
|
||||
// Charge the remaining difference here already, to correctly calculate
|
||||
// available gas for call
|
||||
if !contract.UseGas(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
total += coldCost
|
||||
}
|
||||
|
||||
// Perform the intrinsic cost calculation including:
|
||||
//
|
||||
// - transfer value
|
||||
// - memory expansion
|
||||
// - create new account
|
||||
intrinsicCost, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||
// it can effectively prevent accessing the states in the following steps.
|
||||
// It's an essential safeguard before any stateful check.
|
||||
if contract.Gas < intrinsicCost {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
|
||||
// Check if code is a delegation and if so, charge for resolution.
|
||||
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
||||
var cost uint64
|
||||
if evm.StateDB.AddressInAccessList(target) {
|
||||
cost = params.WarmStorageReadCostEIP2929
|
||||
eip7702Cost = params.WarmStorageReadCostEIP2929
|
||||
} else {
|
||||
evm.StateDB.AddAddressToAccessList(target)
|
||||
cost = params.ColdAccountAccessCostEIP2929
|
||||
eip7702Cost = params.ColdAccountAccessCostEIP2929
|
||||
}
|
||||
if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
if !contract.UseGas(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
total += cost
|
||||
}
|
||||
|
||||
// Now call the old calculator, which takes into account
|
||||
// - create new account
|
||||
// - transfer value
|
||||
// - memory expansion
|
||||
// - 63/64ths rule
|
||||
old, err := oldCalculator(evm, contract, stack, mem, memorySize)
|
||||
// Calculate the gas budget for the nested call. The costs defined by
|
||||
// EIP-2929 and EIP-7702 have already been applied.
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, intrinsicCost, stack.Back(0))
|
||||
if err != nil {
|
||||
return old, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Temporarily add the gas charge back to the contract and return value. By
|
||||
// adding it to the return, it will be charged outside of this function, as
|
||||
// part of the dynamic gas. This will ensure it is correctly reported to
|
||||
// tracers.
|
||||
contract.Gas += total
|
||||
contract.Gas += eip2929Cost + eip7702Cost
|
||||
|
||||
var overflow bool
|
||||
if total, overflow = math.SafeAdd(old, total); overflow {
|
||||
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
|
||||
// the CALL opcode itself, and the cost incurred by nested calls.
|
||||
var (
|
||||
overflow bool
|
||||
totalCost uint64
|
||||
)
|
||||
if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return total, nil
|
||||
if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return totalCost, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
|
|||
}
|
||||
|
||||
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
|
||||
pending := b.eth.txPool.Pending(txpool.PendingFilter{})
|
||||
pending, _ := b.eth.txPool.Pending(txpool.PendingFilter{})
|
||||
var txs types.Transactions
|
||||
for _, batch := range pending {
|
||||
for _, lazy := range batch {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
|
|
@ -493,34 +494,22 @@ func (api *DebugAPI) StateSize(blockHashOrNumber *rpc.BlockNumberOrHash) (interf
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (api *DebugAPI) ExecutionWitness(bn rpc.BlockNumber) (*stateless.ExtWitness, error) {
|
||||
func (api *DebugAPI) ExecutionWitness(bn rpc.BlockNumberOrHash) (*stateless.ExtWitness, error) {
|
||||
bc := api.eth.blockchain
|
||||
block, err := api.eth.APIBackend.BlockByNumber(context.Background(), bn)
|
||||
block, err := api.eth.APIBackend.BlockByNumberOrHash(context.Background(), bn)
|
||||
if err != nil {
|
||||
return &stateless.ExtWitness{}, fmt.Errorf("block number %v not found", bn)
|
||||
return &stateless.ExtWitness{}, fmt.Errorf("block %v not found", bn)
|
||||
}
|
||||
parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
||||
if parent == nil {
|
||||
return &stateless.ExtWitness{}, fmt.Errorf("block number %v found, but parent missing", bn)
|
||||
return &stateless.ExtWitness{}, fmt.Errorf("block %v found, but parent missing", bn)
|
||||
}
|
||||
result, err := bc.ProcessBlock(context.Background(), parent.Root, block, false, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.Witness().ToExtWitness(), nil
|
||||
}
|
||||
|
||||
func (api *DebugAPI) ExecutionWitnessByHash(hash common.Hash) (*stateless.ExtWitness, error) {
|
||||
bc := api.eth.blockchain
|
||||
block := bc.GetBlockByHash(hash)
|
||||
if block == nil {
|
||||
return &stateless.ExtWitness{}, fmt.Errorf("block hash %x not found", hash)
|
||||
}
|
||||
parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
||||
if parent == nil {
|
||||
return &stateless.ExtWitness{}, fmt.Errorf("block number %x found, but parent missing", hash)
|
||||
}
|
||||
result, err := bc.ProcessBlock(context.Background(), parent.Root, block, false, true)
|
||||
config := core.ExecuteConfig{
|
||||
WriteState: false,
|
||||
EnableTracer: false,
|
||||
MakeWitness: true,
|
||||
}
|
||||
result, err := bc.ProcessBlock(context.Background(), parent.Root, block, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/filtermaps"
|
||||
"github.com/ethereum/go-ethereum/core/history"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/pruner"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
|
|
@ -175,7 +176,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
|
||||
// Here we determine genesis hash and active ChainConfig.
|
||||
// We need these to figure out the consensus parameters and to set up history pruning.
|
||||
chainConfig, _, err := core.LoadChainConfig(chainDb, config.Genesis)
|
||||
chainConfig, genesisHash, err := core.LoadChainConfig(chainDb, config.Genesis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -220,6 +221,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
|
||||
}
|
||||
}
|
||||
histPolicy, err := history.NewPolicy(config.HistoryMode, genesisHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
options = &core.BlockChainConfig{
|
||||
TrieCleanLimit: config.TrieCleanCache,
|
||||
|
|
@ -233,12 +238,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
TrienodeHistory: config.TrienodeHistory,
|
||||
NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
|
||||
StateScheme: scheme,
|
||||
ChainHistoryMode: config.HistoryMode,
|
||||
HistoryPolicy: histPolicy,
|
||||
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
|
||||
VmConfig: vm.Config{
|
||||
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||
EnableWitnessStats: config.EnableWitnessStats,
|
||||
StatelessSelfValidation: config.StatelessSelfValidation,
|
||||
},
|
||||
// Enables file journaling for the trie database. The journal files will be stored
|
||||
// within the data directory. The corresponding paths will be either:
|
||||
|
|
@ -247,6 +250,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
TrieJournalDirectory: stack.ResolvePath("triedb"),
|
||||
StateSizeTracking: config.EnableStateSizeTracking,
|
||||
SlowBlockThreshold: config.SlowBlockThreshold,
|
||||
|
||||
StatelessSelfValidation: config.StatelessSelfValidation,
|
||||
EnableWitnessStats: config.EnableWitnessStats,
|
||||
}
|
||||
)
|
||||
if config.VMTrace != "" {
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
|
|||
//
|
||||
// If there are payloadAttributes: we try to assemble a block with the payloadAttributes
|
||||
// and return its payloadID.
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV1(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if payloadAttributes != nil {
|
||||
switch {
|
||||
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
|
||||
|
|
@ -171,12 +171,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
|
|||
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
|
||||
}
|
||||
}
|
||||
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false)
|
||||
return api.forkchoiceUpdated(ctx, update, payloadAttributes, engine.PayloadV1, false)
|
||||
}
|
||||
|
||||
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload
|
||||
// attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2.
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV2(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if params != nil {
|
||||
switch {
|
||||
case params.BeaconRoot != nil:
|
||||
|
|
@ -189,12 +189,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
|
|||
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
|
||||
}
|
||||
}
|
||||
return api.forkchoiceUpdated(update, params, engine.PayloadV2, false)
|
||||
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV2, false)
|
||||
}
|
||||
|
||||
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root
|
||||
// in the payload attributes. It supports only PayloadAttributesV3.
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV3(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if params != nil {
|
||||
switch {
|
||||
case params.Withdrawals == nil:
|
||||
|
|
@ -209,12 +209,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
|
|||
// hash, even if params are wrong. To do this we need to split up
|
||||
// forkchoiceUpdate into a function that only updates the head and then a
|
||||
// function that kicks off block construction.
|
||||
return api.forkchoiceUpdated(update, params, engine.PayloadV3, false)
|
||||
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV3, false)
|
||||
}
|
||||
|
||||
// ForkchoiceUpdatedV4 is equivalent to V3 with the addition of slot number
|
||||
// in the payload attributes. It supports only PayloadAttributesV4.
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV4(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV4(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if params != nil {
|
||||
switch {
|
||||
case params.Withdrawals == nil:
|
||||
|
|
@ -231,10 +231,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV4(update engine.ForkchoiceStateV1, pa
|
|||
// hash, even if params are wrong. To do this we need to split up
|
||||
// forkchoiceUpdate into a function that only updates the head and then a
|
||||
// function that kicks off block construction.
|
||||
return api.forkchoiceUpdated(update, params, engine.PayloadV4, false)
|
||||
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV4, false)
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) {
|
||||
func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (result engine.ForkChoiceResponse, err error) {
|
||||
ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.forkchoiceUpdated")
|
||||
defer spanEnd(&err)
|
||||
api.forkchoiceLock.Lock()
|
||||
defer api.forkchoiceLock.Unlock()
|
||||
|
||||
|
|
@ -255,12 +257,9 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
|||
if res := api.checkInvalidAncestor(update.HeadBlockHash, update.HeadBlockHash); res != nil {
|
||||
return engine.ForkChoiceResponse{PayloadStatus: *res, PayloadID: nil}, nil
|
||||
}
|
||||
// If the head hash is unknown (was not given to us in a newPayload request),
|
||||
// we cannot resolve the header, so not much to do. This could be extended in
|
||||
// the future to resolve from the `eth` network, but it's an unexpected case
|
||||
// that should be fixed, not papered over.
|
||||
header := api.remoteBlocks.get(update.HeadBlockHash)
|
||||
if header == nil {
|
||||
// The head hash is unknown locally, try to resolve it from the `eth` network
|
||||
log.Warn("Fetching the unknown forkchoice head from network", "hash", update.HeadBlockHash)
|
||||
retrievedHead, err := api.eth.Downloader().GetHeader(update.HeadBlockHash)
|
||||
if err != nil {
|
||||
|
|
@ -273,7 +272,9 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
|||
// If the finalized hash is known, we can direct the downloader to move
|
||||
// potentially more data to the freezer from the get go.
|
||||
finalized := api.remoteBlocks.get(update.FinalizedBlockHash)
|
||||
|
||||
if finalized == nil {
|
||||
finalized = api.eth.BlockChain().GetHeaderByHash(update.FinalizedBlockHash)
|
||||
}
|
||||
// Header advertised via a past newPayload request. Start syncing to it.
|
||||
context := []interface{}{"number", header.Number, "hash", header.Hash()}
|
||||
if update.FinalizedBlockHash != (common.Hash{}) {
|
||||
|
|
@ -376,7 +377,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
|||
if api.localBlocks.has(id) {
|
||||
return valid(&id), nil
|
||||
}
|
||||
payload, err := api.eth.Miner().BuildPayload(args, payloadWitness)
|
||||
payload, err := api.eth.Miner().BuildPayload(ctx, args, payloadWitness)
|
||||
if err != nil {
|
||||
log.Error("Failed to build payload", "err", err)
|
||||
return valid(nil), engine.InvalidPayloadAttributes.With(err)
|
||||
|
|
@ -435,7 +436,7 @@ func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.Execu
|
|||
payloadID,
|
||||
false,
|
||||
[]engine.PayloadVersion{engine.PayloadV1, engine.PayloadV2},
|
||||
[]forks.Fork{forks.Shanghai},
|
||||
[]forks.Fork{forks.Paris, forks.Shanghai},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
|
|||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
}
|
||||
_, err := api.ForkchoiceUpdatedV1(fcState, &blockParams)
|
||||
_, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, &blockParams)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
|
|
@ -270,7 +270,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) {
|
|||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
}
|
||||
_, err := api.ForkchoiceUpdatedV1(fcState, ¶ms)
|
||||
_, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, ¶ms)
|
||||
if test.shouldErr && err == nil {
|
||||
t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err)
|
||||
} else if !test.shouldErr && err != nil {
|
||||
|
|
@ -329,7 +329,7 @@ func TestEth2NewBlock(t *testing.T) {
|
|||
SafeBlockHash: block.Hash(),
|
||||
FinalizedBlockHash: block.Hash(),
|
||||
}
|
||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
||||
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want {
|
||||
|
|
@ -369,7 +369,7 @@ func TestEth2NewBlock(t *testing.T) {
|
|||
SafeBlockHash: block.Hash(),
|
||||
FinalizedBlockHash: block.Hash(),
|
||||
}
|
||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
||||
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() {
|
||||
|
|
@ -515,7 +515,7 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He
|
|||
SafeBlockHash: payload.ParentHash,
|
||||
FinalizedBlockHash: payload.ParentHash,
|
||||
}
|
||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
||||
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number {
|
||||
|
|
@ -629,7 +629,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
|
|||
err error
|
||||
)
|
||||
for i := 0; ; i++ {
|
||||
if resp, err = api.ForkchoiceUpdatedV1(fcState, ¶ms); err != nil {
|
||||
if resp, err = api.ForkchoiceUpdatedV1(context.Background(), fcState, ¶ms); err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
if resp.PayloadStatus.Status != engine.VALID {
|
||||
|
|
@ -660,7 +660,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
|
|||
SafeBlockHash: payload.ExecutionPayload.ParentHash,
|
||||
FinalizedBlockHash: payload.ExecutionPayload.ParentHash,
|
||||
}
|
||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
||||
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.ExecutionPayload.Number {
|
||||
|
|
@ -679,7 +679,7 @@ func assembleEnvelope(api *ConsensusAPI, parentHash common.Hash, params *engine.
|
|||
Withdrawals: params.Withdrawals,
|
||||
BeaconRoot: params.BeaconRoot,
|
||||
}
|
||||
payload, err := api.eth.Miner().BuildPayload(args, false)
|
||||
payload, err := api.eth.Miner().BuildPayload(context.Background(), args, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -867,7 +867,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
|
|||
t.Error("invalid status: VALID on an invalid chain")
|
||||
}
|
||||
// Now reorg to the head of the invalid chain
|
||||
resp, err := apiB.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil)
|
||||
resp, err := apiB.ForkchoiceUpdatedV1(context.Background(), engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -970,7 +970,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
|
|||
for ii := 0; ii < 10; ii++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
|
||||
if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
|
||||
errMu.Lock()
|
||||
testErr = fmt.Errorf("failed to insert block: %w", err)
|
||||
errMu.Unlock()
|
||||
|
|
@ -1011,7 +1011,7 @@ func TestWithdrawals(t *testing.T) {
|
|||
fcState := engine.ForkchoiceStateV1{
|
||||
HeadBlockHash: parent.Hash(),
|
||||
}
|
||||
resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
|
||||
resp, err := api.ForkchoiceUpdatedV2(context.Background(), fcState, &blockParams)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
|
|
@ -1063,7 +1063,7 @@ func TestWithdrawals(t *testing.T) {
|
|||
},
|
||||
}
|
||||
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
|
||||
_, err = api.ForkchoiceUpdatedV2(fcState, &blockParams)
|
||||
_, err = api.ForkchoiceUpdatedV2(context.Background(), fcState, &blockParams)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
|
|
@ -1090,7 +1090,7 @@ func TestWithdrawals(t *testing.T) {
|
|||
|
||||
// 11: set block as head.
|
||||
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
|
||||
_, err = api.ForkchoiceUpdatedV2(fcState, nil)
|
||||
_, err = api.ForkchoiceUpdatedV2(context.Background(), fcState, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
|
|
@ -1196,10 +1196,10 @@ func TestNilWithdrawals(t *testing.T) {
|
|||
)
|
||||
if !shanghai {
|
||||
payloadVersion = engine.PayloadV1
|
||||
_, err = api.ForkchoiceUpdatedV1(fcState, &test.blockParams)
|
||||
_, err = api.ForkchoiceUpdatedV1(context.Background(), fcState, &test.blockParams)
|
||||
} else {
|
||||
payloadVersion = engine.PayloadV2
|
||||
_, err = api.ForkchoiceUpdatedV2(fcState, &test.blockParams)
|
||||
_, err = api.ForkchoiceUpdatedV2(context.Background(), fcState, &test.blockParams)
|
||||
}
|
||||
if test.wantErr {
|
||||
if err == nil {
|
||||
|
|
@ -1219,6 +1219,11 @@ func TestNilWithdrawals(t *testing.T) {
|
|||
Random: test.blockParams.Random,
|
||||
Version: payloadVersion,
|
||||
}).Id()
|
||||
if !shanghai {
|
||||
if _, err := api.GetPayloadV2(payloadID); err != nil {
|
||||
t.Fatalf("GetPayloadV2 rejected pre-shanghai payload: %v", err)
|
||||
}
|
||||
}
|
||||
execData, err := api.getPayload(payloadID, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error getting payload, err=%v", err)
|
||||
|
|
@ -1574,7 +1579,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
|
|||
fcState := engine.ForkchoiceStateV1{
|
||||
HeadBlockHash: parent.Hash(),
|
||||
}
|
||||
resp, err := api.ForkchoiceUpdatedV3(fcState, &blockParams)
|
||||
resp, err := api.ForkchoiceUpdatedV3(context.Background(), fcState, &blockParams)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
|
||||
}
|
||||
|
|
@ -1605,7 +1610,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
|
|||
}
|
||||
|
||||
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
|
||||
resp, err = api.ForkchoiceUpdatedV3(fcState, nil)
|
||||
resp, err = api.ForkchoiceUpdatedV3(context.Background(), fcState, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
|
||||
}
|
||||
|
|
@ -1661,7 +1666,7 @@ func TestWitnessCreationAndConsumption(t *testing.T) {
|
|||
SafeBlockHash: common.Hash{},
|
||||
FinalizedBlockHash: common.Hash{},
|
||||
}
|
||||
_, err := api.ForkchoiceUpdatedWithWitnessV3(fcState, &blockParams)
|
||||
_, err := api.ForkchoiceUpdatedWithWitnessV3(context.Background(), fcState, &blockParams)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,8 @@ type SimulatedBeacon struct {
|
|||
|
||||
func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion {
|
||||
switch config.LatestFork(time) {
|
||||
case forks.Amsterdam:
|
||||
return engine.PayloadV4
|
||||
case forks.BPO5, forks.BPO4, forks.BPO3, forks.BPO2, forks.BPO1, forks.Osaka, forks.Prague, forks.Cancun:
|
||||
return engine.PayloadV3
|
||||
case forks.Paris, forks.Shanghai:
|
||||
|
|
@ -124,7 +126,7 @@ func NewSimulatedBeacon(period uint64, feeRecipient common.Address, eth *eth.Eth
|
|||
// if genesis block, send forkchoiceUpdated to trigger transition to PoS
|
||||
if block.Number.Sign() == 0 {
|
||||
version := payloadVersion(eth.BlockChain().Config(), block.Time)
|
||||
if _, err := engineAPI.forkchoiceUpdated(current, nil, version, false); err != nil {
|
||||
if _, err := engineAPI.forkchoiceUpdated(context.Background(), current, nil, version, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -198,13 +200,28 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
|
|||
|
||||
var random [32]byte
|
||||
rand.Read(random[:])
|
||||
fcResponse, err := c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, &engine.PayloadAttributes{
|
||||
|
||||
attribute := &engine.PayloadAttributes{
|
||||
Timestamp: timestamp,
|
||||
SuggestedFeeRecipient: feeRecipient,
|
||||
Withdrawals: withdrawals,
|
||||
Random: random,
|
||||
BeaconRoot: &common.Hash{},
|
||||
}, version, false)
|
||||
}
|
||||
if c.eth.BlockChain().Config().LatestFork(timestamp) == forks.Amsterdam {
|
||||
slotNumber := uint64(0)
|
||||
attribute.SlotNumber = &slotNumber
|
||||
}
|
||||
|
||||
// Create a server span for forkchoiceUpdated with payload attributes,
|
||||
// simulating an incoming engine API request from a real consensus client.
|
||||
fcCtx, fcSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
|
||||
System: "jsonrpc",
|
||||
Service: "engine",
|
||||
Method: "forkchoiceUpdatedV" + fmt.Sprintf("%d", version),
|
||||
})
|
||||
fcResponse, err := c.engineAPI.forkchoiceUpdated(fcCtx, c.curForkchoiceState, attribute, version, false)
|
||||
fcSpanEnd(&err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -218,7 +235,15 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
|
|||
return nil
|
||||
}
|
||||
|
||||
// Create a server span for getPayload, simulating the consensus client
|
||||
// coming back to retrieve the built payload.
|
||||
_, gpSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
|
||||
System: "jsonrpc",
|
||||
Service: "engine",
|
||||
Method: "getPayloadV" + fmt.Sprintf("%d", version),
|
||||
})
|
||||
envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true, nil, nil)
|
||||
gpSpanEnd(&err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -266,6 +291,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
|
|||
Service: "engine",
|
||||
Method: "newPayloadV" + fmt.Sprintf("%d", version),
|
||||
})
|
||||
|
||||
// Mark the payload as canon
|
||||
_, err = c.engineAPI.newPayload(npCtx, *payload, blobHashes, beaconRoot, requests, false)
|
||||
npSpanEnd(&err)
|
||||
|
|
@ -274,8 +300,16 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
|
|||
}
|
||||
c.setCurrentState(payload.BlockHash, finalizedHash)
|
||||
|
||||
// Mark the block containing the payload as canonical
|
||||
if _, err = c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, nil, version, false); err != nil {
|
||||
// Create a server span for the final forkchoiceUpdated (no payload attributes),
|
||||
// which sets the new block as the canonical chain head.
|
||||
fcuCtx, fcuSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
|
||||
System: "jsonrpc",
|
||||
Service: "engine",
|
||||
Method: "forkchoiceUpdatedV" + fmt.Sprintf("%d", version),
|
||||
})
|
||||
_, err = c.engineAPI.forkchoiceUpdated(fcuCtx, c.curForkchoiceState, nil, version, false)
|
||||
fcuSpanEnd(&err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.lastBlockTime = payload.Timestamp
|
||||
|
|
@ -341,7 +375,7 @@ func (c *SimulatedBeacon) Rollback() {
|
|||
func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
|
||||
// Ensure no pending transactions.
|
||||
c.eth.TxPool().Sync()
|
||||
if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 {
|
||||
if pending, _ := c.eth.TxPool().Pending(txpool.PendingFilter{}); len(pending) != 0 {
|
||||
return errors.New("pending block dirty")
|
||||
}
|
||||
|
||||
|
|
@ -355,7 +389,7 @@ func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
|
|||
|
||||
// AdjustTime creates a new block with an adjusted timestamp.
|
||||
func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
|
||||
if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 {
|
||||
if pending, _ := c.eth.TxPool().Pending(txpool.PendingFilter{}); len(pending) != 0 {
|
||||
return errors.New("could not adjust time on non-empty block")
|
||||
}
|
||||
parent := c.eth.BlockChain().CurrentBlock()
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import (
|
|||
|
||||
// ForkchoiceUpdatedWithWitnessV1 is analogous to ForkchoiceUpdatedV1, only it
|
||||
// generates an execution witness too if block building was requested.
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if payloadAttributes != nil {
|
||||
switch {
|
||||
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
|
||||
|
|
@ -44,12 +44,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.Forkchoice
|
|||
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
|
||||
}
|
||||
}
|
||||
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, true)
|
||||
return api.forkchoiceUpdated(ctx, update, payloadAttributes, engine.PayloadV1, true)
|
||||
}
|
||||
|
||||
// ForkchoiceUpdatedWithWitnessV2 is analogous to ForkchoiceUpdatedV2, only it
|
||||
// generates an execution witness too if block building was requested.
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if params != nil {
|
||||
switch {
|
||||
case params.BeaconRoot != nil:
|
||||
|
|
@ -62,12 +62,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.Forkchoice
|
|||
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
|
||||
}
|
||||
}
|
||||
return api.forkchoiceUpdated(update, params, engine.PayloadV2, true)
|
||||
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV2, true)
|
||||
}
|
||||
|
||||
// ForkchoiceUpdatedWithWitnessV3 is analogous to ForkchoiceUpdatedV3, only it
|
||||
// generates an execution witness too if block building was requested.
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if params != nil {
|
||||
switch {
|
||||
case params.Withdrawals == nil:
|
||||
|
|
@ -82,7 +82,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice
|
|||
// hash, even if params are wrong. To do this we need to split up
|
||||
// forkchoiceUpdate into a function that only updates the head and then a
|
||||
// function that kicks off block construction.
|
||||
return api.forkchoiceUpdated(update, params, engine.PayloadV3, true)
|
||||
return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV3, true)
|
||||
}
|
||||
|
||||
// NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates
|
||||
|
|
|
|||
|
|
@ -959,29 +959,6 @@ func (d *Downloader) processSnapSyncContent() error {
|
|||
} else { // results already piled up, consume before handling pivot move
|
||||
results = append(append([]*fetchResult{oldPivot}, oldTail...), results...)
|
||||
}
|
||||
// Split around the pivot block and process the two sides via snap/full sync
|
||||
if !d.committed.Load() {
|
||||
latest := results[len(results)-1].Header
|
||||
// If the height is above the pivot block by 2 sets, it means the pivot
|
||||
// became stale in the network, and it was garbage collected, move to a
|
||||
// new pivot.
|
||||
//
|
||||
// Note, we have `reorgProtHeaderDelay` number of blocks withheld, Those
|
||||
// need to be taken into account, otherwise we're detecting the pivot move
|
||||
// late and will drop peers due to unavailable state!!!
|
||||
if height := latest.Number.Uint64(); height >= pivot.Number.Uint64()+2*uint64(fsMinFullBlocks)-uint64(reorgProtHeaderDelay) {
|
||||
log.Warn("Pivot became stale, moving", "old", pivot.Number.Uint64(), "new", height-uint64(fsMinFullBlocks)+uint64(reorgProtHeaderDelay))
|
||||
pivot = results[len(results)-1-fsMinFullBlocks+reorgProtHeaderDelay].Header // must exist as lower old pivot is uncommitted
|
||||
|
||||
d.pivotLock.Lock()
|
||||
d.pivotHeader = pivot
|
||||
d.pivotLock.Unlock()
|
||||
|
||||
// Write out the pivot into the database so a rollback beyond it will
|
||||
// reenable snap sync
|
||||
rawdb.WriteLastPivotNumber(d.stateDB, pivot.Number.Uint64())
|
||||
}
|
||||
}
|
||||
P, beforeP, afterP := splitAroundPivot(pivot.Number.Uint64(), results)
|
||||
if err := d.commitSnapSyncData(beforeP, sync); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ var Defaults = Config{
|
|||
StateHistory: pathdb.Defaults.StateHistory,
|
||||
TrienodeHistory: pathdb.Defaults.TrienodeHistory,
|
||||
NodeFullValueCheckpoint: pathdb.Defaults.FullValueCheckpoint,
|
||||
DatabaseCache: 512,
|
||||
TrieCleanCache: 154,
|
||||
TrieDirtyCache: 256,
|
||||
DatabaseCache: 2048,
|
||||
TrieCleanCache: 614,
|
||||
TrieDirtyCache: 1024,
|
||||
SnapshotCache: 409,
|
||||
TrieTimeout: 60 * time.Minute,
|
||||
SnapshotCache: 102,
|
||||
FilterLogCacheSize: 32,
|
||||
LogQueryLimit: 1000,
|
||||
Miner: miner.DefaultConfig,
|
||||
|
|
|
|||
|
|
@ -442,7 +442,9 @@ func (f *TxFetcher) loop() {
|
|||
if f.chain != nil {
|
||||
headEventCh = make(chan core.ChainEvent, 10)
|
||||
sub := f.chain.SubscribeChainEvent(headEventCh)
|
||||
defer sub.Unsubscribe()
|
||||
if sub != nil {
|
||||
defer sub.Unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
|
|
|
|||
|
|
@ -187,11 +187,13 @@ func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool)
|
|||
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
|
||||
}
|
||||
|
||||
rpcSub := notifier.CreateSubscription()
|
||||
var (
|
||||
rpcSub = notifier.CreateSubscription()
|
||||
txs = make(chan []*types.Transaction, 128)
|
||||
pendingTxSub = api.events.SubscribePendingTxs(txs)
|
||||
)
|
||||
|
||||
go func() {
|
||||
txs := make(chan []*types.Transaction, 128)
|
||||
pendingTxSub := api.events.SubscribePendingTxs(txs)
|
||||
defer pendingTxSub.Unsubscribe()
|
||||
|
||||
chainConfig := api.sys.backend.ChainConfig()
|
||||
|
|
@ -260,11 +262,13 @@ func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) {
|
|||
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
|
||||
}
|
||||
|
||||
rpcSub := notifier.CreateSubscription()
|
||||
var (
|
||||
rpcSub = notifier.CreateSubscription()
|
||||
headers = make(chan *types.Header)
|
||||
headersSub = api.events.SubscribeNewHeads(headers)
|
||||
)
|
||||
|
||||
go func() {
|
||||
headers := make(chan *types.Header)
|
||||
headersSub := api.events.SubscribeNewHeads(headers)
|
||||
defer headersSub.Unsubscribe()
|
||||
|
||||
for {
|
||||
|
|
@ -532,6 +536,9 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo
|
|||
if f.crit.ToBlock != nil {
|
||||
end = f.crit.ToBlock.Int64()
|
||||
}
|
||||
if begin >= 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
|
||||
return nil, &history.PrunedHistoryError{}
|
||||
}
|
||||
// Construct the range filter
|
||||
filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics, api.rangeLimit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -390,7 +390,7 @@ func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([
|
|||
}
|
||||
|
||||
if firstBlock > lastBlock {
|
||||
return nil, nil
|
||||
return nil, errInvalidBlockRange
|
||||
}
|
||||
mb := f.sys.backend.NewMatcherBackend()
|
||||
defer mb.Close()
|
||||
|
|
|
|||
|
|
@ -357,7 +357,8 @@ func testFilters(t *testing.T, history uint64, noHistory bool) {
|
|||
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","blockTimestamp":"0x2706","logIndex":"0x0","removed":false}]`,
|
||||
},
|
||||
{
|
||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil, 0),
|
||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil, 0),
|
||||
err: errInvalidBlockRange.Error(),
|
||||
},
|
||||
{
|
||||
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -268,7 +267,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
|
|||
evm.Cancel()
|
||||
}()
|
||||
// Execute the call, returning a wrapped error or the result
|
||||
result, err := core.ApplyMessage(evm, call, new(core.GasPool).AddGas(math.MaxUint64))
|
||||
result, err := core.ApplyMessage(evm, call, nil)
|
||||
if vmerr := dirtyState.Error(); vmerr != nil {
|
||||
return nil, vmerr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ type txPool interface {
|
|||
|
||||
// Pending should return pending transactions.
|
||||
// The slice should be modifiable by the caller.
|
||||
Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction
|
||||
Pending(filter txpool.PendingFilter) (map[common.Address][]*txpool.LazyTransaction, int)
|
||||
|
||||
// SubscribeTransactions subscribes to new transaction events. The subscriber
|
||||
// can decide whether to receive notifications only for newly seen transactions
|
||||
|
|
|
|||
|
|
@ -128,10 +128,11 @@ func (p *testTxPool) Add(txs []*types.Transaction, sync bool) []error {
|
|||
}
|
||||
|
||||
// Pending returns all the transactions known to the pool
|
||||
func (p *testTxPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction {
|
||||
func (p *testTxPool) Pending(filter txpool.PendingFilter) (map[common.Address][]*txpool.LazyTransaction, int) {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
var count int
|
||||
batches := make(map[common.Address][]*types.Transaction)
|
||||
for _, tx := range p.pool {
|
||||
from, _ := types.Sender(types.HomesteadSigner{}, tx)
|
||||
|
|
@ -152,9 +153,10 @@ func (p *testTxPool) Pending(filter txpool.PendingFilter) map[common.Address][]*
|
|||
Gas: tx.Gas(),
|
||||
BlobGas: tx.BlobGas(),
|
||||
})
|
||||
count++
|
||||
}
|
||||
}
|
||||
return pending
|
||||
return pending, count
|
||||
}
|
||||
|
||||
// SubscribeTransactions should return an event subscription of NewTxsEvent and
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
|
|||
|
||||
// Not yet the searched for transaction, execute on top of the current state
|
||||
statedb.SetTxContext(tx.Hash(), idx)
|
||||
if _, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
||||
if _, err := core.ApplyMessage(evm, msg, nil); err != nil {
|
||||
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||
}
|
||||
// Ensure any modifications are committed to the state
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ import (
|
|||
// syncTransactions starts sending all currently pending transactions to the given peer.
|
||||
func (h *handler) syncTransactions(p *eth.Peer) {
|
||||
var hashes []common.Hash
|
||||
for _, batch := range h.txpool.Pending(txpool.PendingFilter{BlobTxs: false}) {
|
||||
pending, _ := h.txpool.Pending(txpool.PendingFilter{BlobTxs: false})
|
||||
for _, batch := range pending {
|
||||
for _, tx := range batch {
|
||||
hashes = append(hashes, tx.Hash)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -551,7 +551,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
|||
}
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
if _, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil {
|
||||
if _, err := core.ApplyMessage(evm, msg, nil); err != nil {
|
||||
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
|
||||
// We intentionally don't return the error here: if we do, then the RPC server will not
|
||||
// return the roots. Most likely, the caller already knows that a certain transaction fails to
|
||||
|
|
@ -707,7 +707,7 @@ txloop:
|
|||
// Generate the next state snapshot fast without tracing
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
if _, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil {
|
||||
if _, err := core.ApplyMessage(evm, msg, nil); err != nil {
|
||||
failed = err
|
||||
break txloop
|
||||
}
|
||||
|
|
@ -792,7 +792,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
if txHash != (common.Hash{}) && tx.Hash() != txHash {
|
||||
// Process the tx to update state, but don't trace it.
|
||||
_, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
||||
_, err := core.ApplyMessage(evm, msg, nil)
|
||||
if err != nil {
|
||||
return dumps, err
|
||||
}
|
||||
|
|
@ -827,7 +827,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
if tracer.OnTxStart != nil {
|
||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
}
|
||||
_, err = core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
|
||||
_, err = core.ApplyMessage(evm, msg, nil)
|
||||
if writer != nil {
|
||||
writer.Flush()
|
||||
}
|
||||
|
|
@ -1011,7 +1011,6 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
|
|||
tracer *Tracer
|
||||
err error
|
||||
timeout = defaultTraceTimeout
|
||||
usedGas uint64
|
||||
)
|
||||
if config == nil {
|
||||
config = &TraceConfig{}
|
||||
|
|
@ -1055,7 +1054,8 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor
|
|||
|
||||
// Call Prepare to clear out the statedb access list
|
||||
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
|
||||
_, err = core.ApplyTransactionWithEVM(message, new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, &usedGas, evm)
|
||||
|
||||
_, err = core.ApplyTransactionWithEVM(message, core.NewGasPool(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, evm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tracing failed: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
|
|||
return tx, context, statedb, release, nil
|
||||
}
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
if _, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
||||
if _, err := core.ApplyMessage(evm, msg, nil); err != nil {
|
||||
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||
}
|
||||
statedb.Finalise(evm.ChainConfig().IsEIP158(block.Number()))
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
|||
}
|
||||
evm := vm.NewEVM(context, logState, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
|
||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
vmRet, err := core.ApplyMessage(evm, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
|
|
@ -224,7 +224,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
|||
if tracer.OnTxStart != nil {
|
||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
}
|
||||
_, err = core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
_, err = core.ApplyMessage(evm, msg, nil)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
|
|
@ -374,7 +374,7 @@ func TestInternals(t *testing.T) {
|
|||
t.Fatalf("test %v: failed to create message: %v", tc.name, err)
|
||||
}
|
||||
tc.tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
vmRet, err := core.ApplyMessage(evm, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ func TestErc7562Tracer(t *testing.T) {
|
|||
}
|
||||
evm := vm.NewEVM(context, logState, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
|
||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
vmRet, err := core.ApplyMessage(evm, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
|
|||
}
|
||||
evm := vm.NewEVM(context, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
|
||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
vmRet, err := core.ApplyMessage(evm, msg, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute transaction: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ func testPrestateTracer(tracerName string, dirPath string, t *testing.T) {
|
|||
}
|
||||
evm := vm.NewEVM(context, state.StateDB, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks})
|
||||
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
|
||||
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
vmRet, err := core.ApplyMessage(evm, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -620,8 +620,7 @@ func TestSelfdestructStateTracer(t *testing.T) {
|
|||
}
|
||||
context := core.NewEVMBlockContext(block.Header(), blockchain, nil)
|
||||
evm := vm.NewEVM(context, hookedState, tt.genesis.Config, vm.Config{Tracer: tracer.Hooks()})
|
||||
usedGas := uint64(0)
|
||||
_, err = core.ApplyTransactionWithEVM(msg, new(core.GasPool).AddGas(tx.Gas()), statedb, block.Number(), block.Hash(), block.Time(), tx, &usedGas, evm)
|
||||
_, err = core.ApplyTransactionWithEVM(msg, core.NewGasPool(msg.GasLimit), statedb, block.Number(), block.Hash(), block.Time(), tx, evm)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,11 +85,14 @@ func (al accessList) equal(other accessList) bool {
|
|||
func (al accessList) accessList() types.AccessList {
|
||||
acl := make(types.AccessList, 0, len(al))
|
||||
for addr, slots := range al {
|
||||
tuple := types.AccessTuple{Address: addr, StorageKeys: []common.Hash{}}
|
||||
for slot := range slots {
|
||||
tuple.StorageKeys = append(tuple.StorageKeys, slot)
|
||||
}
|
||||
keys := slices.SortedFunc(maps.Keys(slots), common.Hash.Cmp)
|
||||
// Ensure keys is never nil to avoid JSON serialization issues.
|
||||
// When slots is empty, slices.SortedFunc returns nil, but JSON marshaling
|
||||
// will serialize nil slice as null instead of [], which breaks clients
|
||||
// that expect storageKeys to always be an array.
|
||||
if keys == nil {
|
||||
keys = []common.Hash{}
|
||||
}
|
||||
acl = append(acl, types.AccessTuple{Address: addr, StorageKeys: keys})
|
||||
}
|
||||
slices.SortFunc(acl, func(a, b types.AccessTuple) int { return a.Address.Cmp(b.Address) })
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ func BenchmarkTransactionTraceV2(b *testing.B) {
|
|||
evm.Config.Tracer = tracer
|
||||
|
||||
snap := state.StateDB.Snapshot()
|
||||
_, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||
_, err := core.ApplyMessage(evm, msg, nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue