mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
fix: merge conflicts
This commit is contained in:
commit
b524bd3eb8
49 changed files with 1448 additions and 440 deletions
|
|
@ -218,11 +218,15 @@ func (tc *conn) read(c net.PacketConn) v5wire.Packet {
|
|||
if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil {
|
||||
return &readError{err}
|
||||
}
|
||||
n, fromAddr, err := c.ReadFrom(buf)
|
||||
n, _, err := c.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return &readError{err}
|
||||
}
|
||||
_, _, p, err := tc.codec.Decode(buf[:n], fromAddr.String())
|
||||
// Always use tc.remoteAddr for session lookup. The actual source address of
|
||||
// the packet may differ from tc.remoteAddr when the remote node is reachable
|
||||
// via multiple networks (e.g. Docker bridge vs. overlay), but the codec's
|
||||
// session cache is keyed by the address used during Encode.
|
||||
_, _, p, err := tc.codec.Decode(buf[:n], tc.remoteAddr.String())
|
||||
if err != nil {
|
||||
return &readError{err}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -731,13 +731,16 @@ func pruneHistory(ctx *cli.Context) error {
|
|||
|
||||
// Determine the prune point based on the history mode.
|
||||
genesisHash := chain.Genesis().Hash()
|
||||
prunePoint := history.GetPrunePoint(genesisHash, mode)
|
||||
if prunePoint == nil {
|
||||
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 (
|
||||
targetBlock = prunePoint.BlockNumber
|
||||
targetBlockHash = prunePoint.BlockHash
|
||||
targetBlock = policy.Target.BlockNumber
|
||||
targetBlockHash = policy.Target.BlockHash
|
||||
)
|
||||
|
||||
// Check the current freezer tail to see if pruning is needed/possible.
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
|||
len(checksums), len(entries))
|
||||
}
|
||||
|
||||
// Determine resume point from last successfully imported block
|
||||
// Determine resume point from last successfully imported block.
|
||||
var resumeBlock uint64
|
||||
if tail := rawdb.ReadEraImportTail(db); tail != nil {
|
||||
resumeBlock = *tail
|
||||
|
|
@ -278,9 +278,8 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
|||
reported = time.Now()
|
||||
imported = 0
|
||||
h = sha256.New()
|
||||
scratch = bytes.NewBuffer(nil)
|
||||
buf = bytes.NewBuffer(nil)
|
||||
)
|
||||
|
||||
for i, file := range entries {
|
||||
err := func() error {
|
||||
path := filepath.Join(dir, file)
|
||||
|
|
@ -291,33 +290,36 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
// Peek at era block range to see if we can skip entirely
|
||||
// Peek at era block range to see if we can skip entirely.
|
||||
e, err := from(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening era: %w", err)
|
||||
}
|
||||
eraStart := e.Start()
|
||||
eraEnd := eraStart + e.Count() - 1
|
||||
e.Close()
|
||||
|
||||
// Skip era files fully behind resume point
|
||||
// Skip era files fully behind resume point.
|
||||
if resumeBlock > 0 && eraEnd <= resumeBlock {
|
||||
log.Debug("Skipping already imported Era file", "file", file, "eraEnd", eraEnd, "resumeBlock", resumeBlock)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate against checksum file in directory.
|
||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("seek %s: %w", path, err)
|
||||
}
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return fmt.Errorf("checksum %s: %w", path, err)
|
||||
}
|
||||
got := common.BytesToHash(h.Sum(scratch.Bytes()[:])).Hex()
|
||||
want := checksums[i]
|
||||
got := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex()
|
||||
h.Reset()
|
||||
scratch.Reset()
|
||||
if got != want {
|
||||
return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, want)
|
||||
buf.Reset()
|
||||
if got != checksums[i] {
|
||||
return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, checksums[i])
|
||||
}
|
||||
|
||||
// Import all block data from Era1.
|
||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("seek %s: %w", path, err)
|
||||
}
|
||||
|
|
@ -325,11 +327,42 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
|||
if err != nil {
|
||||
return fmt.Errorf("error opening era: %w", err)
|
||||
}
|
||||
defer e.Close()
|
||||
|
||||
it, err := e.Iterator()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating iterator: %w", err)
|
||||
}
|
||||
var (
|
||||
blocks = make([]*types.Block, 0, importBatchSize)
|
||||
receiptsList = make([]types.Receipts, 0, importBatchSize)
|
||||
flush = func() error {
|
||||
if len(blocks) == 0 {
|
||||
return nil
|
||||
}
|
||||
enc := types.EncodeBlockReceiptLists(receiptsList)
|
||||
if _, err := chain.InsertReceiptChain(blocks, enc, math.MaxUint64); err != nil {
|
||||
return fmt.Errorf("error inserting blocks %d-%d: %w",
|
||||
blocks[0].NumberU64(), blocks[len(blocks)-1].NumberU64(), err)
|
||||
}
|
||||
// Track the last successfully imported block for resume support.
|
||||
lastBlock := blocks[len(blocks)-1].NumberU64()
|
||||
rawdb.WriteEraImportTail(db, lastBlock)
|
||||
resumeBlock = lastBlock
|
||||
|
||||
imported += len(blocks)
|
||||
if time.Since(reported) >= 8*time.Second {
|
||||
head := blocks[len(blocks)-1].NumberU64()
|
||||
log.Info("Importing Era files", "head", head, "imported", imported,
|
||||
"elapsed", common.PrettyDuration(time.Since(start)))
|
||||
imported = 0
|
||||
reported = time.Now()
|
||||
}
|
||||
blocks = blocks[:0]
|
||||
receiptsList = receiptsList[:0]
|
||||
return nil
|
||||
}
|
||||
)
|
||||
for it.Next() {
|
||||
block, err := it.Block()
|
||||
if err != nil {
|
||||
|
|
@ -338,7 +371,7 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
|||
if block.Number().BitLen() == 0 {
|
||||
continue // skip genesis
|
||||
}
|
||||
// Skip blocks already imported (mid-epoch resume)
|
||||
// Skip blocks already imported (mid-era resume).
|
||||
if resumeBlock > 0 && block.Number().Uint64() <= resumeBlock {
|
||||
continue
|
||||
}
|
||||
|
|
@ -346,25 +379,18 @@ func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, networ
|
|||
if err != nil {
|
||||
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
|
||||
}
|
||||
enc := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
|
||||
if _, err := chain.InsertReceiptChain([]*types.Block{block}, enc, math.MaxUint64); err != nil {
|
||||
return fmt.Errorf("error inserting body %d: %w", it.Number(), err)
|
||||
}
|
||||
rawdb.WriteEraImportTail(db, block.Number().Uint64())
|
||||
resumeBlock = block.Number().Uint64()
|
||||
imported++
|
||||
|
||||
if time.Since(reported) >= 8*time.Second {
|
||||
log.Info("Importing Era files", "head", it.Number(), "imported", imported,
|
||||
"elapsed", common.PrettyDuration(time.Since(start)))
|
||||
imported = 0
|
||||
reported = time.Now()
|
||||
blocks = append(blocks, block)
|
||||
receiptsList = append(receiptsList, receipts)
|
||||
if len(blocks) == importBatchSize {
|
||||
if err := flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := it.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return flush()
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -218,6 +218,10 @@ var (
|
|||
Usage: "Max number of elements (0 = no limit)",
|
||||
Value: 0,
|
||||
}
|
||||
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",
|
||||
|
|
@ -1580,7 +1584,9 @@ func setOpenTelemetry(ctx *cli.Context, cfg *node.Config) {
|
|||
if ctx.IsSet(RPCTelemetryTagsFlag.Name) {
|
||||
tcfg.Tags = ctx.String(RPCTelemetryTagsFlag.Name)
|
||||
}
|
||||
tcfg.SampleRatio = ctx.Float64(RPCTelemetrySampleRatioFlag.Name)
|
||||
if ctx.IsSet(RPCTelemetrySampleRatioFlag.Name) {
|
||||
tcfg.SampleRatio = ctx.Float64(RPCTelemetrySampleRatioFlag.Name)
|
||||
}
|
||||
|
||||
if tcfg.Endpoint != "" && !tcfg.Enabled {
|
||||
log.Warn(fmt.Sprintf("OpenTelemetry endpoint configured but telemetry is not enabled, use --%s to enable.", RPCTelemetryFlag.Name))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -194,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
|
||||
|
|
@ -227,13 +226,13 @@ type BlockChainConfig struct {
|
|||
// 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,
|
||||
|
|
@ -715,82 +714,44 @@ func (bc *BlockChain) loadLastState() error {
|
|||
|
||||
// initializeHistoryPruning sets bc.historyPrunePoint.
|
||||
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
|
||||
var (
|
||||
freezerTail, _ = bc.db.Tail()
|
||||
genesisHash = bc.genesisBlock.Hash()
|
||||
mergePoint = history.MergePrunePoints[genesisHash]
|
||||
praguePoint = history.PraguePrunePoints[genesisHash]
|
||||
)
|
||||
switch bc.cfg.ChainHistoryMode {
|
||||
case history.KeepAll:
|
||||
if freezerTail == 0 {
|
||||
return nil
|
||||
}
|
||||
// The database was pruned somehow, so we need to figure out if it's a known
|
||||
// configuration or an error.
|
||||
if mergePoint != nil && freezerTail == mergePoint.BlockNumber {
|
||||
bc.historyPrunePoint.Store(mergePoint)
|
||||
return nil
|
||||
}
|
||||
if praguePoint != nil && freezerTail == praguePoint.BlockNumber {
|
||||
bc.historyPrunePoint.Store(praguePoint)
|
||||
return nil
|
||||
}
|
||||
log.Error("Chain history database is pruned with unknown configuration", "tail", freezerTail)
|
||||
return errors.New("unexpected database tail")
|
||||
freezerTail, _ := bc.db.Tail()
|
||||
policy := bc.cfg.HistoryPolicy
|
||||
|
||||
case history.KeepPostMerge:
|
||||
if mergePoint == nil {
|
||||
return errors.New("history pruning requested for unknown network")
|
||||
switch policy.Mode {
|
||||
case history.KeepAll:
|
||||
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),
|
||||
})
|
||||
}
|
||||
if freezerTail == 0 && latest != 0 {
|
||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String()))
|
||||
log.Error("Run 'geth prune-history --history.chain postmerge' to prune pre-merge history.")
|
||||
return errors.New("history pruning requested via configuration")
|
||||
}
|
||||
// Check if DB is pruned further than requested (to Prague).
|
||||
if praguePoint != nil && freezerTail == praguePoint.BlockNumber {
|
||||
log.Error("Chain history database is pruned to Prague block, but postmerge mode was requested.")
|
||||
log.Error("History cannot be unpruned. To restore history, use 'geth import-history'.")
|
||||
log.Error("If you intended to keep post-Prague history, use '--history.chain postprague' instead.")
|
||||
return errors.New("database pruned beyond requested history mode")
|
||||
}
|
||||
if freezerTail > 0 && freezerTail != mergePoint.BlockNumber {
|
||||
return errors.New("chain history database pruned to unknown block")
|
||||
}
|
||||
bc.historyPrunePoint.Store(mergePoint)
|
||||
return nil
|
||||
|
||||
case history.KeepPostPrague:
|
||||
if praguePoint == nil {
|
||||
return errors.New("history pruning requested for unknown network")
|
||||
}
|
||||
// Check if already at the prague prune point.
|
||||
if freezerTail == praguePoint.BlockNumber {
|
||||
bc.historyPrunePoint.Store(praguePoint)
|
||||
case history.KeepPostMerge, history.KeepPostPrague:
|
||||
target := policy.Target
|
||||
// Already at the target.
|
||||
if freezerTail == target.BlockNumber {
|
||||
bc.historyPrunePoint.Store(target)
|
||||
return nil
|
||||
}
|
||||
// Check if database needs pruning.
|
||||
if latest != 0 {
|
||||
if freezerTail == 0 {
|
||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is not pruned.", bc.cfg.ChainHistoryMode.String()))
|
||||
log.Error("Run 'geth prune-history --history.chain postprague' to prune pre-Prague history.")
|
||||
return errors.New("history pruning requested via configuration")
|
||||
}
|
||||
if mergePoint != nil && freezerTail == mergePoint.BlockNumber {
|
||||
log.Error(fmt.Sprintf("Chain history mode is configured as %q, but database is only pruned to merge block.", bc.cfg.ChainHistoryMode.String()))
|
||||
log.Error("Run 'geth prune-history --history.chain postprague' to prune pre-Prague history.")
|
||||
return errors.New("history pruning requested via configuration")
|
||||
}
|
||||
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)
|
||||
}
|
||||
// Fresh database (latest == 0), will sync from prague point.
|
||||
bc.historyPrunePoint.Store(praguePoint)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2209,24 +2170,18 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
// If we are past Byzantium, enable prefetching to pull in trie node paths
|
||||
// while processing transactions. Before Byzantium the prefetcher is mostly
|
||||
// useless due to the intermediate root hashing after each transaction.
|
||||
var (
|
||||
witness *stateless.Witness
|
||||
witnessStats *stateless.WitnessStats
|
||||
)
|
||||
var witness *stateless.Witness
|
||||
if bc.chainConfig.IsByzantium(block.Number()) {
|
||||
// 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 config.StatelessSelfValidation || config.MakeWitness {
|
||||
witness, err = stateless.NewWitness(block.Header(), bc)
|
||||
witness, err = stateless.NewWitness(block.Header(), bc, config.EnableWitnessStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config.EnableWitnessStats {
|
||||
witnessStats = stateless.NewWitnessStats()
|
||||
}
|
||||
}
|
||||
statedb.StartPrefetcher("chain", witness, witnessStats)
|
||||
statedb.StartPrefetcher("chain", witness)
|
||||
defer statedb.StopPrefetcher()
|
||||
}
|
||||
|
||||
|
|
@ -2345,8 +2300,8 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
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())
|
||||
if witness != nil {
|
||||
witness.ReportMetrics(block.NumberU64())
|
||||
}
|
||||
elapsed := time.Since(startTime) + 1 // prevent zero division
|
||||
stats.TotalTime = elapsed
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -66,10 +66,6 @@ var (
|
|||
// have enough funds for transfer(topmost call only).
|
||||
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
|
||||
|
||||
// ErrMaxInitCodeSizeExceeded is returned if creation transaction provides the init code bigger
|
||||
// than init code size limit.
|
||||
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
|
||||
|
||||
// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
|
||||
// funds to cover the transfer, but not enough to pay for witness access/modification
|
||||
// costs for the transaction
|
||||
|
|
|
|||
169
core/eth_transfer_logs_test.go
Normal file
169
core/eth_transfer_logs_test.go
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
// 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 core
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
var ethTransferTestCode = common.FromHex("6080604052600436106100345760003560e01c8063574ffc311461003957806366e41cb714610090578063f8a8fd6d1461009a575b600080fd5b34801561004557600080fd5b5061004e6100a4565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100986100ac565b005b6100a26100f5565b005b63deadbeef81565b7f38e80b5c85ba49b7280ccc8f22548faa62ae30d5a008a1b168fba5f47f5d1ee560405160405180910390a1631234567873ffffffffffffffffffffffffffffffffffffffff16ff5b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405160405180910390a163deadbeef73ffffffffffffffffffffffffffffffffffffffff166002348161014657fe5b046040516024016040516020818303038152906040527f66e41cb7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b602083106101fd57805182526020820191506020810190506020830392506101da565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d806000811461025f576040519150601f19603f3d011682016040523d82523d6000602084013e610264565b606091505b50505056fea265627a7a723158202cce817a434785d8560c200762f972d453ccd30694481be7545f9035a512826364736f6c63430005100032")
|
||||
|
||||
/*
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
|
||||
contract TestLogs {
|
||||
|
||||
address public constant target_contract = 0x00000000000000000000000000000000DeaDBeef;
|
||||
address payable constant selfdestruct_addr = 0x0000000000000000000000000000000012345678;
|
||||
|
||||
event Response(bool success, bytes data);
|
||||
event TestEvent();
|
||||
event TestEvent2();
|
||||
|
||||
function test() public payable {
|
||||
emit TestEvent();
|
||||
target_contract.call.value(msg.value/2)(abi.encodeWithSignature("test2()"));
|
||||
}
|
||||
function test2() public payable {
|
||||
emit TestEvent2();
|
||||
selfdestruct(selfdestruct_addr);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// TestEthTransferLogs tests EIP-7708 ETH transfer log output by simulating a
|
||||
// scenario including transaction, CALL and SELFDESTRUCT value transfers, and
|
||||
// also "ordinary" logs emitted. The same scenario is also tested with no value
|
||||
// transferred.
|
||||
func TestEthTransferLogs(t *testing.T) {
|
||||
testEthTransferLogs(t, 1_000_000_000)
|
||||
testEthTransferLogs(t, 0)
|
||||
}
|
||||
|
||||
func testEthTransferLogs(t *testing.T, value uint64) {
|
||||
var (
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 = common.HexToAddress("cafebabe") // caller
|
||||
addr3 = common.HexToAddress("deadbeef") // callee
|
||||
addr4 = common.HexToAddress("12345678") // selfdestruct target
|
||||
testEvent = crypto.Keccak256Hash([]byte("TestEvent()"))
|
||||
testEvent2 = crypto.Keccak256Hash([]byte("TestEvent2()"))
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
)
|
||||
|
||||
//TODO remove this hacky config initialization when final Amsterdam config is available
|
||||
config.AmsterdamTime = new(uint64)
|
||||
blobConfig := *config.BlobScheduleConfig
|
||||
blobConfig.Amsterdam = blobConfig.Osaka
|
||||
config.BlobScheduleConfig = &blobConfig
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
addr1: {Balance: newGwei(1000000000)},
|
||||
addr2: {Code: ethTransferTestCode},
|
||||
addr3: {Code: ethTransferTestCode},
|
||||
},
|
||||
}
|
||||
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{
|
||||
ChainID: gspec.Config.ChainID,
|
||||
Nonce: 0,
|
||||
To: &addr2,
|
||||
Gas: 500_000,
|
||||
GasFeeCap: newGwei(5),
|
||||
GasTipCap: newGwei(5),
|
||||
Value: big.NewInt(int64(value)),
|
||||
Data: common.FromHex("f8a8fd6d"),
|
||||
})
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
blockHash := blocks[0].Hash()
|
||||
txHash := blocks[0].Transactions()[0].Hash()
|
||||
addr2hash := func(addr common.Address) (hash common.Hash) {
|
||||
copy(hash[12:], addr[:])
|
||||
return
|
||||
}
|
||||
u256 := func(amount uint64) []byte {
|
||||
data := make([]byte, 32)
|
||||
binary.BigEndian.PutUint64(data[24:], amount)
|
||||
return data
|
||||
}
|
||||
|
||||
var expLogs = []*types.Log{
|
||||
{
|
||||
Address: params.SystemAddress,
|
||||
Topics: []common.Hash{params.EthTransferLogEvent, addr2hash(addr1), addr2hash(addr2)},
|
||||
Data: u256(value),
|
||||
},
|
||||
{
|
||||
Address: addr2,
|
||||
Topics: []common.Hash{testEvent},
|
||||
Data: nil,
|
||||
},
|
||||
{
|
||||
Address: params.SystemAddress,
|
||||
Topics: []common.Hash{params.EthTransferLogEvent, addr2hash(addr2), addr2hash(addr3)},
|
||||
Data: u256(value / 2),
|
||||
},
|
||||
{
|
||||
Address: addr3,
|
||||
Topics: []common.Hash{testEvent2},
|
||||
Data: nil,
|
||||
},
|
||||
{
|
||||
Address: params.SystemAddress,
|
||||
Topics: []common.Hash{params.EthTransferLogEvent, addr2hash(addr3), addr2hash(addr4)},
|
||||
Data: u256(value / 2),
|
||||
},
|
||||
}
|
||||
if value == 0 {
|
||||
// no ETH transfer logs expected with zero value
|
||||
expLogs = []*types.Log{expLogs[1], expLogs[3]}
|
||||
}
|
||||
for i, log := range expLogs {
|
||||
log.BlockNumber = 1
|
||||
log.BlockHash = blockHash
|
||||
log.BlockTimestamp = 10
|
||||
log.TxIndex = 0
|
||||
log.TxHash = txHash
|
||||
log.Index = uint(i)
|
||||
}
|
||||
|
||||
if len(expLogs) != len(receipts[0][0].Logs) {
|
||||
t.Fatalf("Incorrect number of logs (expected: %d, got: %d)", len(expLogs), len(receipts[0][0].Logs))
|
||||
}
|
||||
for i, log := range receipts[0][0].Logs {
|
||||
if !reflect.DeepEqual(expLogs[i], log) {
|
||||
t.Fatalf("Incorrect log at index %d (expected: %v, got: %v)", i, expLogs[i], log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,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/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
@ -138,7 +139,10 @@ func CanTransfer(db vm.StateDB, addr common.Address, amount *uint256.Int) bool {
|
|||
}
|
||||
|
||||
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
|
||||
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *uint256.Int) {
|
||||
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *uint256.Int, rules *params.Rules) {
|
||||
db.SubBalance(sender, amount, tracing.BalanceChangeTransfer)
|
||||
db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer)
|
||||
if rules.IsAmsterdam && !amount.IsZero() && sender != recipient {
|
||||
db.AddLog(types.EthTransferLog(sender, recipient, amount))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,57 +77,62 @@ func (m *HistoryMode) UnmarshalText(text []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// PrunePoint identifies a specific block for history pruning.
|
||||
type PrunePoint struct {
|
||||
BlockNumber uint64
|
||||
BlockHash common.Hash
|
||||
}
|
||||
|
||||
// MergePrunePoints contains 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
|
||||
// the given block.
|
||||
var MergePrunePoints = 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"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// PraguePrunePoints contains the pre-defined history pruning cutoff blocks for the Prague
|
||||
// (Pectra) upgrade. They point to the first post-Prague block. Any pruning should truncate
|
||||
// *up to* but excluding the given block.
|
||||
var PraguePrunePoints = map[common.Hash]*PrunePoint{
|
||||
// mainnet - first Prague block (May 7, 2025)
|
||||
params.MainnetGenesisHash: {
|
||||
BlockNumber: 22431084,
|
||||
BlockHash: common.HexToHash("0x50c8cab760b2948349c590461b166773c45d8f4858cccf5a43025ab2960152e8"),
|
||||
},
|
||||
// sepolia - first Prague block (March 5, 2025)
|
||||
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
|
||||
}
|
||||
|
||||
// PrunePoints is an alias for MergePrunePoints for backward compatibility.
|
||||
// Deprecated: Use GetPrunePoint or MergePrunePoints directly.
|
||||
var PrunePoints = MergePrunePoints
|
||||
|
||||
// GetPrunePoint returns the prune point for the given genesis hash and history mode.
|
||||
// Returns nil if no prune point is defined for the given combination.
|
||||
func GetPrunePoint(genesisHash common.Hash, mode HistoryMode) *PrunePoint {
|
||||
// NewPolicy constructs a HistoryPolicy from the given mode and genesis hash.
|
||||
func NewPolicy(mode HistoryMode, genesisHash common.Hash) (HistoryPolicy, error) {
|
||||
switch mode {
|
||||
case KeepPostMerge:
|
||||
return MergePrunePoints[genesisHash]
|
||||
case KeepPostPrague:
|
||||
return PraguePrunePoints[genesisHash]
|
||||
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 nil
|
||||
return HistoryPolicy{}, fmt.Errorf("invalid history mode: %d", mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
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")
|
||||
}
|
||||
}
|
||||
|
|
@ -480,7 +480,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
|||
receipts.add(size)
|
||||
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) && len(key) == (len(headerPrefix)+8+common.HashLength+len(headerHashSuffix)):
|
||||
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix) && len(key) == (len(headerPrefix)+8+len(headerHashSuffix)):
|
||||
numHashPairings.add(size)
|
||||
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
|
||||
hashNumPairings.add(size)
|
||||
|
|
|
|||
|
|
@ -135,8 +135,7 @@ type StateDB struct {
|
|||
journal *journal
|
||||
|
||||
// State witness if cross validation is needed
|
||||
witness *stateless.Witness
|
||||
witnessStats *stateless.WitnessStats
|
||||
witness *stateless.Witness
|
||||
|
||||
// Measurements gathered during execution for debugging purposes
|
||||
AccountReads time.Duration
|
||||
|
|
@ -201,13 +200,12 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
|||
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
||||
// state trie concurrently while the state is mutated so that when we reach the
|
||||
// commit phase, most of the needed data is already hot.
|
||||
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness, witnessStats *stateless.WitnessStats) {
|
||||
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) {
|
||||
// Terminate any previously running prefetcher
|
||||
s.StopPrefetcher()
|
||||
|
||||
// Enable witness collection if requested
|
||||
s.witness = witness
|
||||
s.witnessStats = witnessStats
|
||||
|
||||
// With the switch to the Proof-of-Stake consensus algorithm, block production
|
||||
// rewards are now handled at the consensus layer. Consequently, a block may
|
||||
|
|
@ -743,6 +741,41 @@ func (s *StateDB) GetRefund() uint64 {
|
|||
return s.refund
|
||||
}
|
||||
|
||||
type removedAccountWithBalance struct {
|
||||
address common.Address
|
||||
balance *uint256.Int
|
||||
}
|
||||
|
||||
// EmitLogsForBurnAccounts emits the eth burn logs for accounts scheduled for
|
||||
// removal which still have positive balance. The purpose of this function is
|
||||
// to handle a corner case of EIP-7708 where a self-destructed account might
|
||||
// still receive funds between sending/burning its previous balance and actual
|
||||
// removal. In this case the burning of these remaining balances still need to
|
||||
// be logged.
|
||||
// Specification EIP-7708: https://eips.ethereum.org/EIPS/eip-7708
|
||||
//
|
||||
// This function should only be invoked at the transaction boundary, specifically
|
||||
// before the Finalise.
|
||||
func (s *StateDB) EmitLogsForBurnAccounts() {
|
||||
var list []removedAccountWithBalance
|
||||
for addr := range s.journal.dirties {
|
||||
if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() {
|
||||
list = append(list, removedAccountWithBalance{
|
||||
address: obj.address,
|
||||
balance: obj.Balance(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if list != nil {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].address.Cmp(list[j].address) < 0
|
||||
})
|
||||
}
|
||||
for _, acct := range list {
|
||||
s.AddLog(types.EthBurnLog(acct.address, acct.balance))
|
||||
}
|
||||
}
|
||||
|
||||
// Finalise finalises the state by removing the destructed objects and clears
|
||||
// the journal as well as the refunds. Finalise, however, will not push any updates
|
||||
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||
|
|
@ -824,32 +857,65 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
workers errgroup.Group
|
||||
)
|
||||
if s.db.TrieDB().IsVerkle() {
|
||||
// Whilst MPT storage tries are independent, Verkle has one single trie
|
||||
// for all the accounts and all the storage slots merged together. The
|
||||
// former can thus be simply parallelized, but updating the latter will
|
||||
// need concurrency support within the trie itself. That's a TODO for a
|
||||
// later time.
|
||||
workers.SetLimit(1)
|
||||
}
|
||||
for addr, op := range s.mutations {
|
||||
if op.applied || op.isDelete() {
|
||||
continue
|
||||
// Bypass per-account updateTrie() for binary trie. In binary trie mode
|
||||
// there is only one unified trie (OpenStorageTrie returns self), so the
|
||||
// per-account trie setup in updateTrie() (getPrefetchedTrie, getTrie,
|
||||
// prefetcher.used) is redundant overhead. Apply all storage updates
|
||||
// directly in a single pass.
|
||||
for addr, op := range s.mutations {
|
||||
if op.applied || op.isDelete() {
|
||||
continue
|
||||
}
|
||||
obj := s.stateObjects[addr]
|
||||
if len(obj.uncommittedStorage) == 0 {
|
||||
continue
|
||||
}
|
||||
for key, origin := range obj.uncommittedStorage {
|
||||
value, exist := obj.pendingStorage[key]
|
||||
if value == origin || !exist {
|
||||
continue
|
||||
}
|
||||
if (value != common.Hash{}) {
|
||||
if err := s.trie.UpdateStorage(addr, key[:], common.TrimLeftZeroes(value[:])); err != nil {
|
||||
s.setError(err)
|
||||
}
|
||||
} else {
|
||||
if err := s.trie.DeleteStorage(addr, key[:]); err != nil {
|
||||
s.setError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
obj := s.stateObjects[addr] // closure for the task runner below
|
||||
workers.Go(func() error {
|
||||
if s.db.TrieDB().IsVerkle() {
|
||||
obj.updateTrie()
|
||||
} else {
|
||||
// Clear uncommittedStorage and assign trie on each touched object.
|
||||
// obj.trie must be set because this path bypasses updateTrie(), which
|
||||
// is where obj.trie normally gets lazily loaded via getTrie().
|
||||
for addr, op := range s.mutations {
|
||||
if op.applied || op.isDelete() {
|
||||
continue
|
||||
}
|
||||
obj := s.stateObjects[addr]
|
||||
if len(obj.uncommittedStorage) > 0 {
|
||||
obj.uncommittedStorage = make(Storage)
|
||||
}
|
||||
obj.trie = s.trie
|
||||
}
|
||||
} else {
|
||||
for addr, op := range s.mutations {
|
||||
if op.applied || op.isDelete() {
|
||||
continue
|
||||
}
|
||||
obj := s.stateObjects[addr] // closure for the task runner below
|
||||
workers.Go(func() error {
|
||||
obj.updateRoot()
|
||||
|
||||
// If witness building is enabled and the state object has a trie,
|
||||
// gather the witnesses for its specific storage trie
|
||||
if s.witness != nil && obj.trie != nil {
|
||||
s.witness.AddState(obj.trie.Witness())
|
||||
s.witness.AddState(obj.trie.Witness(), obj.addrHash())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
// If witness building is enabled, gather all the read-only accesses.
|
||||
// Skip witness collection in Verkle mode, they will be gathered
|
||||
|
|
@ -862,17 +928,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
continue
|
||||
}
|
||||
if trie := obj.getPrefetchedTrie(); trie != nil {
|
||||
witness := trie.Witness()
|
||||
s.witness.AddState(witness)
|
||||
if s.witnessStats != nil {
|
||||
s.witnessStats.Add(witness, obj.addrHash())
|
||||
}
|
||||
s.witness.AddState(trie.Witness(), obj.addrHash())
|
||||
} else if obj.trie != nil {
|
||||
witness := obj.trie.Witness()
|
||||
s.witness.AddState(witness)
|
||||
if s.witnessStats != nil {
|
||||
s.witnessStats.Add(witness, obj.addrHash())
|
||||
}
|
||||
s.witness.AddState(obj.trie.Witness(), obj.addrHash())
|
||||
}
|
||||
}
|
||||
// Pull in only-read and non-destructed trie witnesses
|
||||
|
|
@ -886,17 +944,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
continue
|
||||
}
|
||||
if trie := obj.getPrefetchedTrie(); trie != nil {
|
||||
witness := trie.Witness()
|
||||
s.witness.AddState(witness)
|
||||
if s.witnessStats != nil {
|
||||
s.witnessStats.Add(witness, obj.addrHash())
|
||||
}
|
||||
s.witness.AddState(trie.Witness(), obj.addrHash())
|
||||
} else if obj.trie != nil {
|
||||
witness := obj.trie.Witness()
|
||||
s.witness.AddState(witness)
|
||||
if s.witnessStats != nil {
|
||||
s.witnessStats.Add(witness, obj.addrHash())
|
||||
}
|
||||
s.witness.AddState(obj.trie.Witness(), obj.addrHash())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -911,7 +961,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
// only a single trie is used for state hashing. Replacing a non-nil verkle tree
|
||||
// here could result in losing uncommitted changes from storage.
|
||||
start = time.Now()
|
||||
if s.prefetcher != nil {
|
||||
if s.prefetcher != nil && !s.db.TrieDB().IsVerkle() {
|
||||
if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
|
||||
log.Error("Failed to retrieve account pre-fetcher trie")
|
||||
} else {
|
||||
|
|
@ -969,11 +1019,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
|
||||
// If witness building is enabled, gather the account trie witness
|
||||
if s.witness != nil {
|
||||
witness := s.trie.Witness()
|
||||
s.witness.AddState(witness)
|
||||
if s.witnessStats != nil {
|
||||
s.witnessStats.Add(witness, common.Hash{})
|
||||
}
|
||||
s.witness.AddState(s.trie.Witness(), common.Hash{})
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,6 +229,10 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) EmitLogsForBurnAccounts() {
|
||||
s.inner.EmitLogsForBurnAccounts()
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) {
|
||||
if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil {
|
||||
// Short circuit if no relevant hooks are set.
|
||||
|
|
|
|||
|
|
@ -583,6 +583,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
|
||||
}
|
||||
}
|
||||
if rules.IsAmsterdam {
|
||||
st.evm.StateDB.EmitLogsForBurnAccounts()
|
||||
}
|
||||
return &ExecutionResult{
|
||||
UsedGas: st.gasUsed(),
|
||||
MaxUsedGas: peakGasUsed,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,13 @@ func NewWitnessStats() *WitnessStats {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *WitnessStats) copy() *WitnessStats {
|
||||
return &WitnessStats{
|
||||
accountTrie: s.accountTrie.Copy(),
|
||||
storageTrie: s.storageTrie.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WitnessStats) init() {
|
||||
if s.accountTrie == nil {
|
||||
s.accountTrie = trie.NewLevelStats()
|
||||
|
|
|
|||
|
|
@ -42,12 +42,13 @@ type Witness struct {
|
|||
Codes map[string]struct{} // Set of bytecodes ran or accessed
|
||||
State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
|
||||
|
||||
chain HeaderReader // Chain reader to convert block hash ops to header proofs
|
||||
lock sync.Mutex // Lock to allow concurrent state insertions
|
||||
chain HeaderReader // Chain reader to convert block hash ops to header proofs
|
||||
stats *WitnessStats // Optional statistics collector
|
||||
lock sync.Mutex // Lock to allow concurrent state insertions
|
||||
}
|
||||
|
||||
// NewWitness creates an empty witness ready for population.
|
||||
func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
|
||||
func NewWitness(context *types.Header, chain HeaderReader, enableStats bool) (*Witness, error) {
|
||||
// When building witnesses, retrieve the parent header, which will *always*
|
||||
// be included to act as a trustless pre-root hash container
|
||||
var headers []*types.Header
|
||||
|
|
@ -59,13 +60,17 @@ func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
|
|||
headers = append(headers, parent)
|
||||
}
|
||||
// Create the witness with a reconstructed gutted out block
|
||||
return &Witness{
|
||||
w := &Witness{
|
||||
context: context,
|
||||
Headers: headers,
|
||||
Codes: make(map[string]struct{}),
|
||||
State: make(map[string]struct{}),
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
if enableStats {
|
||||
w.stats = NewWitnessStats()
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// AddBlockHash adds a "blockhash" to the witness with the designated offset from
|
||||
|
|
@ -87,8 +92,11 @@ func (w *Witness) AddCode(code []byte) {
|
|||
w.Codes[string(code)] = struct{}{}
|
||||
}
|
||||
|
||||
// AddState inserts a batch of MPT trie nodes into the witness.
|
||||
func (w *Witness) AddState(nodes map[string][]byte) {
|
||||
// AddState inserts a batch of MPT trie nodes into the witness. The owner
|
||||
// identifies which trie the nodes belong to: the zero hash for the account
|
||||
// trie, or the hashed address for a storage trie. This is used for optional
|
||||
// statistics collection.
|
||||
func (w *Witness) AddState(nodes map[string][]byte, owner common.Hash) {
|
||||
if len(nodes) == 0 {
|
||||
return
|
||||
}
|
||||
|
|
@ -98,6 +106,17 @@ func (w *Witness) AddState(nodes map[string][]byte) {
|
|||
for _, value := range nodes {
|
||||
w.State[string(value)] = struct{}{}
|
||||
}
|
||||
if w.stats != nil {
|
||||
w.stats.Add(nodes, owner)
|
||||
}
|
||||
}
|
||||
|
||||
// ReportMetrics reports the collected statistics to the global metrics registry.
|
||||
func (w *Witness) ReportMetrics(blockNumber uint64) {
|
||||
if w.stats == nil {
|
||||
return
|
||||
}
|
||||
w.stats.ReportMetrics(blockNumber)
|
||||
}
|
||||
|
||||
func (w *Witness) AddKey() {
|
||||
|
|
@ -113,6 +132,9 @@ func (w *Witness) Copy() *Witness {
|
|||
State: maps.Clone(w.State),
|
||||
chain: w.chain,
|
||||
}
|
||||
if w.stats != nil {
|
||||
cpy.stats = w.stats.copy()
|
||||
}
|
||||
if w.context != nil {
|
||||
cpy.context = types.CopyHeader(w.context)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ const (
|
|||
// NonceChangeNewContract is the nonce change of a newly created contract.
|
||||
NonceChangeNewContract NonceChangeReason = 4
|
||||
|
||||
// NonceChangeTransaction is the nonce change due to a EIP-7702 authorization.
|
||||
// NonceChangeAuthorization is the nonce change due to a EIP-7702 authorization.
|
||||
NonceChangeAuthorization NonceChangeReason = 5
|
||||
|
||||
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.
|
||||
|
|
|
|||
|
|
@ -998,11 +998,7 @@ func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus {
|
|||
|
||||
// Get returns a transaction if it is contained in the pool and nil otherwise.
|
||||
func (pool *LegacyPool) Get(hash common.Hash) *types.Transaction {
|
||||
tx := pool.get(hash)
|
||||
if tx == nil {
|
||||
return nil
|
||||
}
|
||||
return tx
|
||||
return pool.get(hash)
|
||||
}
|
||||
|
||||
// get returns a transaction if it is contained in the pool and nil otherwise.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package types
|
|||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
//go:generate go run ../../rlp/rlpgen -type Log -out gen_log_rlp.go
|
||||
|
|
@ -62,3 +64,32 @@ type logMarshaling struct {
|
|||
BlockTimestamp hexutil.Uint64
|
||||
Index hexutil.Uint
|
||||
}
|
||||
|
||||
// EthTransferLog creates and ETH transfer log according to EIP-7708.
|
||||
// Specification: https://eips.ethereum.org/EIPS/eip-7708
|
||||
func EthTransferLog(from, to common.Address, amount *uint256.Int) *Log {
|
||||
amount32 := amount.Bytes32()
|
||||
return &Log{
|
||||
Address: params.SystemAddress,
|
||||
Topics: []common.Hash{
|
||||
params.EthTransferLogEvent,
|
||||
common.BytesToHash(from.Bytes()),
|
||||
common.BytesToHash(to.Bytes()),
|
||||
},
|
||||
Data: amount32[:],
|
||||
}
|
||||
}
|
||||
|
||||
// EthBurnLog creates an ETH burn log according to EIP-7708.
|
||||
// Specification: https://eips.ethereum.org/EIPS/eip-7708
|
||||
func EthBurnLog(from common.Address, amount *uint256.Int) *Log {
|
||||
amount32 := amount.Bytes32()
|
||||
return &Log{
|
||||
Address: params.SystemAddress,
|
||||
Topics: []common.Hash{
|
||||
params.EthBurnLogEvent,
|
||||
common.BytesToHash(from.Bytes()),
|
||||
},
|
||||
Data: amount32[:],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type (
|
|||
// CanTransferFunc is the signature of a transfer guard function
|
||||
CanTransferFunc func(StateDB, common.Address, *uint256.Int) bool
|
||||
// TransferFunc is the signature of a transfer function
|
||||
TransferFunc func(StateDB, common.Address, common.Address, *uint256.Int)
|
||||
TransferFunc func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules)
|
||||
// GetHashFunc returns the n'th block hash in the blockchain
|
||||
// and is used by the BLOCKHASH EVM op code.
|
||||
GetHashFunc func(uint64) common.Hash
|
||||
|
|
@ -283,8 +283,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
// Calling this is required even for zero-value transfers,
|
||||
// to ensure the state clearing mechanism is applied.
|
||||
if !syscall {
|
||||
evm.Context.Transfer(evm.StateDB, caller, addr, value)
|
||||
evm.Context.Transfer(evm.StateDB, caller, addr, value, &evm.chainRules)
|
||||
}
|
||||
|
||||
if isPrecompile {
|
||||
var stateDB StateDB
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
|
|
@ -567,7 +568,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
|
|||
}
|
||||
gas = gas - consumed
|
||||
}
|
||||
evm.Context.Transfer(evm.StateDB, caller, address, value)
|
||||
evm.Context.Transfer(evm.StateDB, caller, address, value, &evm.chainRules)
|
||||
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,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()
|
||||
|
|
@ -382,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
|
||||
|
|
@ -428,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) {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ func TestEIP2200(t *testing.T) {
|
|||
|
||||
vmctx := BlockContext{
|
||||
CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true },
|
||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {},
|
||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
||||
}
|
||||
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ func TestCreateGas(t *testing.T) {
|
|||
statedb.Finalise(true)
|
||||
vmctx := BlockContext{
|
||||
CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true },
|
||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {},
|
||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
||||
BlockNumber: big.NewInt(0),
|
||||
}
|
||||
config := Config{}
|
||||
|
|
|
|||
|
|
@ -934,6 +934,13 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
|||
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
||||
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
|
||||
}
|
||||
if evm.chainRules.IsAmsterdam && !balance.IsZero() {
|
||||
if this != beneficiary {
|
||||
evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance))
|
||||
} else if newContract {
|
||||
evm.StateDB.AddLog(types.EthBurnLog(this, balance))
|
||||
}
|
||||
}
|
||||
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
if tracer.OnEnter != nil {
|
||||
|
|
@ -1086,9 +1093,6 @@ func makeLog(size int) executionFunc {
|
|||
Address: scope.Contract.Address(),
|
||||
Topics: topics,
|
||||
Data: d,
|
||||
// This is a non-consensus field, but assigned here because
|
||||
// core/state doesn't know the current block number.
|
||||
BlockNumber: evm.Context.BlockNumber.Uint64(),
|
||||
})
|
||||
|
||||
return nil, nil
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ type StateDB interface {
|
|||
Snapshot() int
|
||||
|
||||
AddLog(*types.Log)
|
||||
EmitLogsForBurnAccounts()
|
||||
AddPreimage(common.Hash, []byte)
|
||||
|
||||
Witness() *stateless.Witness
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ var loopInterruptTests = []string{
|
|||
func TestLoopInterrupt(t *testing.T) {
|
||||
address := common.BytesToAddress([]byte("contract"))
|
||||
vmctx := BlockContext{
|
||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {},
|
||||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
||||
}
|
||||
|
||||
for i, tt := range loopInterruptTests {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,7 +238,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ func txValidationError(err error) *invalidTxError {
|
|||
return &invalidTxError{Message: err.Error(), Code: errCodeIntrinsicGas}
|
||||
case errors.Is(err, core.ErrInsufficientFundsForTransfer):
|
||||
return &invalidTxError{Message: err.Error(), Code: errCodeInsufficientFunds}
|
||||
case errors.Is(err, core.ErrMaxInitCodeSizeExceeded):
|
||||
case errors.Is(err, vm.ErrMaxInitCodeSizeExceeded):
|
||||
return &invalidTxError{Message: err.Error(), Code: errCodeMaxInitCodeSizeExceeded}
|
||||
}
|
||||
return &invalidTxError{
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@ func (miner *Miner) BuildTestingPayload(args *BuildPayloadArgs, transactions []*
|
|||
random: args.Random,
|
||||
withdrawals: args.Withdrawals,
|
||||
beaconRoot: args.BeaconRoot,
|
||||
slotNum: args.SlotNum,
|
||||
noTxs: empty,
|
||||
forceOverrides: true,
|
||||
overrideExtraData: extraData,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ type environment struct {
|
|||
witness *stateless.Witness
|
||||
}
|
||||
|
||||
// txFits reports whether the transaction fits into the block size limit.
|
||||
// txFitsSize reports whether the transaction fits into the block size limit.
|
||||
func (env *environment) txFitsSize(tx *types.Transaction) bool {
|
||||
return env.size+tx.Size() < params.MaxBlockSize-maxBlockSizeBufferZone
|
||||
}
|
||||
|
|
@ -330,12 +330,12 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase
|
|||
}
|
||||
var bundle *stateless.Witness
|
||||
if witness {
|
||||
bundle, err = stateless.NewWitness(header, miner.chain)
|
||||
bundle, err = stateless.NewWitness(header, miner.chain, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
state.StartPrefetcher("miner", bundle, nil)
|
||||
state.StartPrefetcher("miner", bundle)
|
||||
// Note the passed coinbase may be different with header.Coinbase.
|
||||
return &environment{
|
||||
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ function coverbuild {
|
|||
sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go
|
||||
|
||||
cat << DOG > $OUT/$fuzzer
|
||||
#/bin/sh
|
||||
#!/bin/sh
|
||||
|
||||
cd $OUT/$path
|
||||
go test -run Test${function}Corpus -v $tags -coverprofile \$1 -coverpkg $coverpkg
|
||||
|
|
|
|||
67
p2p/dial.go
67
p2p/dial.go
|
|
@ -76,6 +76,7 @@ var (
|
|||
errSelf = errors.New("is self")
|
||||
errAlreadyDialing = errors.New("already dialing")
|
||||
errAlreadyConnected = errors.New("already connected")
|
||||
errPendingInbound = errors.New("peer has pending inbound connection")
|
||||
errRecentlyDialed = errors.New("recently dialed")
|
||||
errNetRestrict = errors.New("not contained in netrestrict list")
|
||||
errNoPort = errors.New("node does not provide TCP port")
|
||||
|
|
@ -104,12 +105,15 @@ type dialScheduler struct {
|
|||
remStaticCh chan *enode.Node
|
||||
addPeerCh chan *conn
|
||||
remPeerCh chan *conn
|
||||
addPendingCh chan enode.ID
|
||||
remPendingCh chan enode.ID
|
||||
|
||||
// Everything below here belongs to loop and
|
||||
// should only be accessed by code on the loop goroutine.
|
||||
dialing map[enode.ID]*dialTask // active tasks
|
||||
peers map[enode.ID]struct{} // all connected peers
|
||||
dialPeers int // current number of dialed peers
|
||||
dialing map[enode.ID]*dialTask // active tasks
|
||||
peers map[enode.ID]struct{} // all connected peers
|
||||
pendingInbound map[enode.ID]struct{} // in-progress inbound connections
|
||||
dialPeers int // current number of dialed peers
|
||||
|
||||
// The static map tracks all static dial tasks. The subset of usable static dial tasks
|
||||
// (i.e. those passing checkDial) is kept in staticPool. The scheduler prefers
|
||||
|
|
@ -163,19 +167,22 @@ func (cfg dialConfig) withDefaults() dialConfig {
|
|||
func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler {
|
||||
cfg := config.withDefaults()
|
||||
d := &dialScheduler{
|
||||
dialConfig: cfg,
|
||||
historyTimer: mclock.NewAlarm(cfg.clock),
|
||||
setupFunc: setupFunc,
|
||||
dnsLookupFunc: net.DefaultResolver.LookupNetIP,
|
||||
dialing: make(map[enode.ID]*dialTask),
|
||||
static: make(map[enode.ID]*dialTask),
|
||||
peers: make(map[enode.ID]struct{}),
|
||||
doneCh: make(chan *dialTask),
|
||||
nodesIn: make(chan *enode.Node),
|
||||
addStaticCh: make(chan *enode.Node),
|
||||
remStaticCh: make(chan *enode.Node),
|
||||
addPeerCh: make(chan *conn),
|
||||
remPeerCh: make(chan *conn),
|
||||
dialConfig: cfg,
|
||||
historyTimer: mclock.NewAlarm(cfg.clock),
|
||||
setupFunc: setupFunc,
|
||||
dnsLookupFunc: net.DefaultResolver.LookupNetIP,
|
||||
dialing: make(map[enode.ID]*dialTask),
|
||||
static: make(map[enode.ID]*dialTask),
|
||||
peers: make(map[enode.ID]struct{}),
|
||||
pendingInbound: make(map[enode.ID]struct{}),
|
||||
doneCh: make(chan *dialTask),
|
||||
nodesIn: make(chan *enode.Node),
|
||||
addStaticCh: make(chan *enode.Node),
|
||||
remStaticCh: make(chan *enode.Node),
|
||||
addPeerCh: make(chan *conn),
|
||||
remPeerCh: make(chan *conn),
|
||||
addPendingCh: make(chan enode.ID),
|
||||
remPendingCh: make(chan enode.ID),
|
||||
}
|
||||
d.lastStatsLog = d.clock.Now()
|
||||
d.ctx, d.cancel = context.WithCancel(context.Background())
|
||||
|
|
@ -223,6 +230,22 @@ func (d *dialScheduler) peerRemoved(c *conn) {
|
|||
}
|
||||
}
|
||||
|
||||
// inboundPending notifies the scheduler about a pending inbound connection.
|
||||
func (d *dialScheduler) inboundPending(id enode.ID) {
|
||||
select {
|
||||
case d.addPendingCh <- id:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// inboundCompleted notifies the scheduler that an inbound connection completed or failed.
|
||||
func (d *dialScheduler) inboundCompleted(id enode.ID) {
|
||||
select {
|
||||
case d.remPendingCh <- id:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// loop is the main loop of the dialer.
|
||||
func (d *dialScheduler) loop(it enode.Iterator) {
|
||||
var (
|
||||
|
|
@ -276,6 +299,15 @@ loop:
|
|||
delete(d.peers, c.node.ID())
|
||||
d.updateStaticPool(c.node.ID())
|
||||
|
||||
case id := <-d.addPendingCh:
|
||||
d.pendingInbound[id] = struct{}{}
|
||||
d.log.Trace("Marked node as pending inbound", "id", id)
|
||||
|
||||
case id := <-d.remPendingCh:
|
||||
delete(d.pendingInbound, id)
|
||||
d.updateStaticPool(id)
|
||||
d.log.Trace("Unmarked node as pending inbound", "id", id)
|
||||
|
||||
case node := <-d.addStaticCh:
|
||||
id := node.ID()
|
||||
_, exists := d.static[id]
|
||||
|
|
@ -390,6 +422,9 @@ func (d *dialScheduler) checkDial(n *enode.Node) error {
|
|||
if _, ok := d.peers[n.ID()]; ok {
|
||||
return errAlreadyConnected
|
||||
}
|
||||
if _, ok := d.pendingInbound[n.ID()]; ok {
|
||||
return errPendingInbound
|
||||
}
|
||||
if d.netRestrict != nil && !d.netRestrict.ContainsAddr(n.IPAddr()) {
|
||||
return errNetRestrict
|
||||
}
|
||||
|
|
|
|||
|
|
@ -423,6 +423,82 @@ func TestDialSchedDNSHostname(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// This test checks that nodes with pending inbound connections are not dialed.
|
||||
func TestDialSchedPendingInbound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
config := dialConfig{
|
||||
maxActiveDials: 5,
|
||||
maxDialPeers: 4,
|
||||
}
|
||||
runDialTest(t, config, []dialTestRound{
|
||||
// 2 peers are connected, leaving 2 dial slots.
|
||||
// Node 0x03 has a pending inbound connection.
|
||||
// Discovered nodes 0x03, 0x04, 0x05 but only 0x04 and 0x05 should be dialed.
|
||||
{
|
||||
peersAdded: []*conn{
|
||||
{flags: dynDialedConn, node: newNode(uintID(0x01), "127.0.0.1:30303")},
|
||||
{flags: dynDialedConn, node: newNode(uintID(0x02), "127.0.0.2:30303")},
|
||||
},
|
||||
update: func(d *dialScheduler) {
|
||||
d.inboundPending(uintID(0x03))
|
||||
},
|
||||
discovered: []*enode.Node{
|
||||
newNode(uintID(0x03), "127.0.0.3:30303"), // not dialed because pending inbound
|
||||
newNode(uintID(0x04), "127.0.0.4:30303"),
|
||||
newNode(uintID(0x05), "127.0.0.5:30303"),
|
||||
},
|
||||
wantNewDials: []*enode.Node{
|
||||
newNode(uintID(0x04), "127.0.0.4:30303"),
|
||||
newNode(uintID(0x05), "127.0.0.5:30303"),
|
||||
},
|
||||
},
|
||||
// Pending inbound connection for 0x03 completes successfully.
|
||||
// Node 0x03 becomes a connected peer.
|
||||
// One dial slot remains, node 0x06 is dialed.
|
||||
{
|
||||
update: func(d *dialScheduler) {
|
||||
// Pending inbound completes
|
||||
d.inboundCompleted(uintID(0x03))
|
||||
},
|
||||
peersAdded: []*conn{
|
||||
{flags: inboundConn, node: newNode(uintID(0x03), "127.0.0.3:30303")},
|
||||
},
|
||||
succeeded: []enode.ID{
|
||||
uintID(0x04),
|
||||
},
|
||||
failed: []enode.ID{
|
||||
uintID(0x05),
|
||||
},
|
||||
discovered: []*enode.Node{
|
||||
newNode(uintID(0x03), "127.0.0.3:30303"), // not dialed, now connected
|
||||
newNode(uintID(0x06), "127.0.0.6:30303"),
|
||||
},
|
||||
wantNewDials: []*enode.Node{
|
||||
newNode(uintID(0x06), "127.0.0.6:30303"),
|
||||
},
|
||||
},
|
||||
// Inbound peer 0x03 disconnects.
|
||||
// Another pending inbound starts for 0x07.
|
||||
// Only 0x03 should be dialed, not 0x07.
|
||||
{
|
||||
peersRemoved: []enode.ID{
|
||||
uintID(0x03),
|
||||
},
|
||||
update: func(d *dialScheduler) {
|
||||
d.inboundPending(uintID(0x07))
|
||||
},
|
||||
discovered: []*enode.Node{
|
||||
newNode(uintID(0x03), "127.0.0.3:30303"),
|
||||
newNode(uintID(0x07), "127.0.0.7:30303"), // not dialed because pending inbound
|
||||
},
|
||||
wantNewDials: []*enode.Node{
|
||||
newNode(uintID(0x03), "127.0.0.3:30303"),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// -------
|
||||
// Code below here is the framework for the tests above.
|
||||
|
||||
|
|
|
|||
|
|
@ -686,8 +686,11 @@ running:
|
|||
// Ensure that the trusted flag is set before checking against MaxPeers.
|
||||
c.flags |= trustedConn
|
||||
}
|
||||
// TODO: track in-progress inbound node IDs (pre-Peer) to avoid dialing them.
|
||||
c.cont <- srv.postHandshakeChecks(peers, inboundCount, c)
|
||||
err := srv.postHandshakeChecks(peers, inboundCount, c)
|
||||
if err == nil && c.flags&inboundConn != 0 {
|
||||
srv.dialsched.inboundPending(c.node.ID())
|
||||
}
|
||||
c.cont <- err
|
||||
|
||||
case c := <-srv.checkpointAddPeer:
|
||||
// At this point the connection is past the protocol handshake.
|
||||
|
|
@ -870,6 +873,11 @@ func (srv *Server) checkInboundConn(remoteIP netip.Addr) error {
|
|||
// or the handshakes have failed.
|
||||
func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error {
|
||||
c := &conn{fd: fd, flags: flags, cont: make(chan error)}
|
||||
defer func() {
|
||||
if c.is(inboundConn) && c.node != nil {
|
||||
srv.dialsched.inboundCompleted(c.node.ID())
|
||||
}
|
||||
}()
|
||||
if dialDest == nil {
|
||||
c.transport = srv.newTransport(fd, nil)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -222,3 +222,11 @@ var (
|
|||
ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")
|
||||
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")
|
||||
)
|
||||
|
||||
// System log events.
|
||||
var (
|
||||
// EIP-7708 - System logs emitted for ETH transfer and burn
|
||||
EthTransferLogEvent = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") // keccak256('Transfer(address,address,uint256)')
|
||||
EthBurnLogEvent = common.HexToHash("0xcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5") // keccak256('Burn(address,uint256)')
|
||||
|
||||
)
|
||||
|
|
|
|||
|
|
@ -366,6 +366,16 @@ func (w *EncoderBuffer) AppendToBytes(dst []byte) []byte {
|
|||
return out
|
||||
}
|
||||
|
||||
// Size returns the total size of the content that was encoded up to this point.
|
||||
// Note this does not count the size of any lists which are still 'open' (i.e. for
|
||||
// which ListEnd has not been called yet).
|
||||
func (w EncoderBuffer) Size() int {
|
||||
if w.buf == nil {
|
||||
return 0
|
||||
}
|
||||
return w.buf.size()
|
||||
}
|
||||
|
||||
// Write appends b directly to the encoder output.
|
||||
func (w EncoderBuffer) Write(b []byte) (int, error) {
|
||||
return w.buf.Write(b)
|
||||
|
|
|
|||
|
|
@ -507,6 +507,39 @@ func TestEncodeToReaderReturnToPool(t *testing.T) {
|
|||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestEncoderBufferSize(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
eb := NewEncoderBuffer(&output)
|
||||
|
||||
assertSize := func(state string, expectedSize int) {
|
||||
t.Helper()
|
||||
if s := eb.Size(); s != expectedSize {
|
||||
t.Fatalf("wrong size %s: %d", state, s)
|
||||
}
|
||||
}
|
||||
|
||||
assertSize("empty buffer", 0)
|
||||
outerList := eb.List()
|
||||
assertSize("after outer List()", 0)
|
||||
eb.WriteString("abc")
|
||||
assertSize("after string write", 4)
|
||||
innerList := eb.List()
|
||||
assertSize("after inner List()", 4)
|
||||
eb.WriteUint64(1)
|
||||
eb.WriteUint64(2)
|
||||
assertSize("after inner list writes", 6)
|
||||
eb.ListEnd(innerList)
|
||||
assertSize("after end of inner list", 7)
|
||||
eb.ListEnd(outerList)
|
||||
assertSize("after end of outer list", 8)
|
||||
eb.Flush()
|
||||
assertSize("after Flush()", 0)
|
||||
|
||||
if output.Len() != 8 {
|
||||
t.Fatalf("wrong final output size %d", output.Len())
|
||||
}
|
||||
}
|
||||
|
||||
var sink interface{}
|
||||
|
||||
func BenchmarkIntsize(b *testing.B) {
|
||||
|
|
|
|||
12
rlp/raw.go
12
rlp/raw.go
|
|
@ -168,6 +168,18 @@ func (r *RawList[T]) AppendRaw(b []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// AppendList appends all items from another RawList to this list.
|
||||
func (r *RawList[T]) AppendList(other *RawList[T]) {
|
||||
if other.enc == nil || other.length == 0 {
|
||||
return
|
||||
}
|
||||
if r.enc == nil {
|
||||
r.enc = make([]byte, 9)
|
||||
}
|
||||
r.enc = append(r.enc, other.Content()...)
|
||||
r.length += other.length
|
||||
}
|
||||
|
||||
// StringSize returns the encoded size of a string.
|
||||
func StringSize(s string) uint64 {
|
||||
switch n := len(s); n {
|
||||
|
|
|
|||
|
|
@ -246,6 +246,54 @@ func TestRawListAppendRaw(t *testing.T) {
|
|||
t.Fatalf("wrong Len %d after invalid appends, want 2", rl.Len())
|
||||
}
|
||||
}
|
||||
func TestRawListAppendList(t *testing.T) {
|
||||
var rl1 RawList[uint64]
|
||||
if err := rl1.Append(uint64(1)); err != nil {
|
||||
t.Fatal("append 1 failed:", err)
|
||||
}
|
||||
if err := rl1.Append(uint64(2)); err != nil {
|
||||
t.Fatal("append 2 failed:", err)
|
||||
}
|
||||
|
||||
var rl2 RawList[uint64]
|
||||
if err := rl2.Append(uint64(3)); err != nil {
|
||||
t.Fatal("append 3 failed:", err)
|
||||
}
|
||||
if err := rl2.Append(uint64(4)); err != nil {
|
||||
t.Fatal("append 4 failed:", err)
|
||||
}
|
||||
|
||||
rl1.AppendList(&rl2)
|
||||
|
||||
if rl1.Len() != 4 {
|
||||
t.Fatalf("wrong Len %d, want 4", rl1.Len())
|
||||
}
|
||||
if rl1.Size() != 5 {
|
||||
t.Fatalf("wrong Size %d, want 5", rl1.Size())
|
||||
}
|
||||
|
||||
items, err := rl1.Items()
|
||||
if err != nil {
|
||||
t.Fatal("Items failed:", err)
|
||||
}
|
||||
if !reflect.DeepEqual(items, []uint64{1, 2, 3, 4}) {
|
||||
t.Fatalf("wrong items: %v", items)
|
||||
}
|
||||
|
||||
var empty RawList[uint64]
|
||||
prevLen := rl1.Len()
|
||||
rl1.AppendList(&empty)
|
||||
|
||||
if rl1.Len() != prevLen {
|
||||
t.Fatalf("appending empty list changed Len: got %d, want %d", rl1.Len(), prevLen)
|
||||
}
|
||||
|
||||
empty.AppendList(&rl1)
|
||||
|
||||
if empty.Len() != 4 {
|
||||
t.Fatalf("wrong Len %d, want 4", empty.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRawListDecodeInvalid(t *testing.T) {
|
||||
tests := []struct {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,33 @@
|
|||
package bintrie
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// parallelDepth returns the tree depth below which Hash() spawns goroutines.
|
||||
func parallelDepth() int {
|
||||
return min(bits.Len(uint(runtime.NumCPU())), 8)
|
||||
}
|
||||
|
||||
// isDirty reports whether a BinaryNode child needs rehashing.
|
||||
func isDirty(n BinaryNode) bool {
|
||||
switch v := n.(type) {
|
||||
case *InternalNode:
|
||||
return v.mustRecompute
|
||||
case *StemNode:
|
||||
return v.mustRecompute
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func keyToPath(depth int, key []byte) ([]byte, error) {
|
||||
if depth > 31*8 {
|
||||
return nil, errors.New("node too deep")
|
||||
|
|
@ -124,6 +145,29 @@ func (bt *InternalNode) Hash() common.Hash {
|
|||
return bt.hash
|
||||
}
|
||||
|
||||
// At shallow depths, parallelize when both children need rehashing:
|
||||
// hash left subtree in a goroutine, right subtree inline, then combine.
|
||||
// Skip goroutine overhead when only one child is dirty (common case
|
||||
// for narrow state updates that touch a single path through the trie).
|
||||
if bt.depth < parallelDepth() && isDirty(bt.left) && isDirty(bt.right) {
|
||||
var input [64]byte
|
||||
var lh common.Hash
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
lh = bt.left.Hash()
|
||||
}()
|
||||
rh := bt.right.Hash()
|
||||
copy(input[32:], rh[:])
|
||||
wg.Wait()
|
||||
copy(input[:32], lh[:])
|
||||
bt.hash = sha256.Sum256(input[:])
|
||||
bt.mustRecompute = false
|
||||
return bt.hash
|
||||
}
|
||||
|
||||
// Deeper nodes: sequential using pooled hasher (goroutine overhead > hash cost)
|
||||
h := newSha256()
|
||||
defer returnSha256(h)
|
||||
if bt.left != nil {
|
||||
|
|
|
|||
|
|
@ -119,10 +119,17 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
|||
return it.Next(descend)
|
||||
case HashedNode:
|
||||
// resolve the node
|
||||
data, err := it.trie.nodeResolver(it.Path(), common.Hash(node))
|
||||
resolverPath := it.Path()
|
||||
data, err := it.trie.nodeResolver(resolverPath, common.Hash(node))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if data == nil {
|
||||
// Empty/nil node — treat as Empty, backtrack
|
||||
it.current = Empty{}
|
||||
it.stack[len(it.stack)-1].Node = it.current
|
||||
return it.Next(descend)
|
||||
}
|
||||
it.current, err = DeserializeNodeWithHash(data, len(it.stack)-1, common.Hash(node))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
@ -130,16 +137,25 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
|
|||
|
||||
// update the stack and parent with the resolved node
|
||||
it.stack[len(it.stack)-1].Node = it.current
|
||||
parent := &it.stack[len(it.stack)-2]
|
||||
if parent.Index == 0 {
|
||||
parent.Node.(*InternalNode).left = it.current
|
||||
} else {
|
||||
parent.Node.(*InternalNode).right = it.current
|
||||
if len(it.stack) >= 2 {
|
||||
parent := &it.stack[len(it.stack)-2]
|
||||
if parent.Index == 0 {
|
||||
parent.Node.(*InternalNode).left = it.current
|
||||
} else {
|
||||
parent.Node.(*InternalNode).right = it.current
|
||||
}
|
||||
}
|
||||
return it.Next(descend)
|
||||
case Empty:
|
||||
// do nothing
|
||||
return false
|
||||
// Empty node - go back to parent and continue
|
||||
if len(it.stack) <= 1 {
|
||||
it.lastErr = errIteratorEnd
|
||||
return false
|
||||
}
|
||||
it.stack = it.stack[:len(it.stack)-1]
|
||||
it.current = it.stack[len(it.stack)-1].Node
|
||||
it.stack[len(it.stack)-1].Index++
|
||||
return it.Next(descend)
|
||||
default:
|
||||
panic("invalid node type")
|
||||
}
|
||||
|
|
|
|||
239
trie/bintrie/iterator_test.go
Normal file
239
trie/bintrie/iterator_test.go
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
// Copyright 2026 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 bintrie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
// makeTrie creates a BinaryTrie populated with the given key-value pairs.
|
||||
func makeTrie(t *testing.T, entries [][2]common.Hash) *BinaryTrie {
|
||||
t.Helper()
|
||||
tr := &BinaryTrie{
|
||||
root: NewBinaryNode(),
|
||||
tracer: trie.NewPrevalueTracer(),
|
||||
}
|
||||
for _, kv := range entries {
|
||||
var err error
|
||||
tr.root, err = tr.root.Insert(kv[0][:], kv[1][:], nil, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
// countLeaves iterates the trie and returns the number of leaves visited.
|
||||
func countLeaves(t *testing.T, tr *BinaryTrie) int {
|
||||
t.Helper()
|
||||
it, err := newBinaryNodeIterator(tr, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
leaves := 0
|
||||
for it.Next(true) {
|
||||
if it.Leaf() {
|
||||
leaves++
|
||||
}
|
||||
}
|
||||
if it.Error() != nil {
|
||||
t.Fatalf("iterator error: %v", it.Error())
|
||||
}
|
||||
return leaves
|
||||
}
|
||||
|
||||
// TestIteratorEmptyTrie verifies that iterating over an empty trie returns
|
||||
// no nodes and reports no error.
|
||||
func TestIteratorEmptyTrie(t *testing.T) {
|
||||
tr := &BinaryTrie{
|
||||
root: Empty{},
|
||||
tracer: trie.NewPrevalueTracer(),
|
||||
}
|
||||
it, err := newBinaryNodeIterator(tr, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if it.Next(true) {
|
||||
t.Fatal("expected no iteration over empty trie")
|
||||
}
|
||||
if it.Error() != nil {
|
||||
t.Fatalf("unexpected error: %v", it.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// TestIteratorSingleStem verifies iteration over a trie with a single stem
|
||||
// node containing multiple values.
|
||||
func TestIteratorSingleStem(t *testing.T) {
|
||||
tr := makeTrie(t, [][2]common.Hash{
|
||||
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000003"), oneKey},
|
||||
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000007"), oneKey},
|
||||
{common.HexToHash("00000000000000000000000000000000000000000000000000000000000000FF"), oneKey},
|
||||
})
|
||||
if leaves := countLeaves(t, tr); leaves != 3 {
|
||||
t.Fatalf("expected 3 leaves, got %d", leaves)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIteratorTwoStems verifies iteration over a trie with two stems
|
||||
// separated by internal nodes, ensuring all leaves from both stems are visited.
|
||||
func TestIteratorTwoStems(t *testing.T) {
|
||||
tr := makeTrie(t, [][2]common.Hash{
|
||||
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000002"), oneKey},
|
||||
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000002"), oneKey},
|
||||
})
|
||||
if leaves := countLeaves(t, tr); leaves != 4 {
|
||||
t.Fatalf("expected 4 leaves, got %d", leaves)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIteratorLeafKeyAndBlob verifies that the iterator returns correct
|
||||
// leaf keys and values.
|
||||
func TestIteratorLeafKeyAndBlob(t *testing.T) {
|
||||
key := common.HexToHash("0000000000000000000000000000000000000000000000000000000000000005")
|
||||
val := common.HexToHash("00000000000000000000000000000000000000000000000000000000deadbeef")
|
||||
tr := makeTrie(t, [][2]common.Hash{{key, val}})
|
||||
|
||||
it, err := newBinaryNodeIterator(tr, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for it.Next(true) {
|
||||
if it.Leaf() {
|
||||
found = true
|
||||
if !bytes.Equal(it.LeafKey(), key[:]) {
|
||||
t.Fatalf("leaf key mismatch: got %x, want %x", it.LeafKey(), key)
|
||||
}
|
||||
if !bytes.Equal(it.LeafBlob(), val[:]) {
|
||||
t.Fatalf("leaf blob mismatch: got %x, want %x", it.LeafBlob(), val)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("expected to find a leaf")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIteratorEmptyNodeBacktrack is a regression test for the Empty node
|
||||
// backtracking bug. Before the fix, encountering an Empty child during
|
||||
// iteration would terminate the walk prematurely instead of backtracking
|
||||
// to the parent and continuing with the next sibling.
|
||||
func TestIteratorEmptyNodeBacktrack(t *testing.T) {
|
||||
tr := makeTrie(t, [][2]common.Hash{
|
||||
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||
})
|
||||
|
||||
if _, ok := tr.root.(*InternalNode); !ok {
|
||||
t.Fatalf("expected InternalNode root, got %T", tr.root)
|
||||
}
|
||||
if leaves := countLeaves(t, tr); leaves != 2 {
|
||||
t.Fatalf("expected 2 leaves, got %d (Empty backtrack bug?)", leaves)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIteratorHashedNodeNilData is a regression test for the nil-data guard.
|
||||
// When nodeResolver encounters a zero-hash HashedNode, it returns (nil, nil).
|
||||
// The iterator should treat this as Empty and continue rather than panicking.
|
||||
func TestIteratorHashedNodeNilData(t *testing.T) {
|
||||
tr := makeTrie(t, [][2]common.Hash{
|
||||
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||
})
|
||||
|
||||
root, ok := tr.root.(*InternalNode)
|
||||
if !ok {
|
||||
t.Fatalf("expected InternalNode root, got %T", tr.root)
|
||||
}
|
||||
|
||||
// Replace right child with a zero-hash HashedNode. nodeResolver
|
||||
// short-circuits on common.Hash{} and returns (nil, nil), which
|
||||
// triggers the nil-data guard in the iterator.
|
||||
root.right = HashedNode(common.Hash{})
|
||||
|
||||
// Should not panic; the zero-hash right child should be treated as Empty.
|
||||
if leaves := countLeaves(t, tr); leaves != 1 {
|
||||
t.Fatalf("expected 1 leaf (zero-hash right node skipped), got %d", leaves)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIteratorManyStems verifies iteration correctness with many stems,
|
||||
// producing a deep tree structure.
|
||||
func TestIteratorManyStems(t *testing.T) {
|
||||
entries := make([][2]common.Hash, 16)
|
||||
for i := range entries {
|
||||
var key common.Hash
|
||||
key[0] = byte(i << 4)
|
||||
key[31] = 1
|
||||
entries[i] = [2]common.Hash{key, oneKey}
|
||||
}
|
||||
tr := makeTrie(t, entries)
|
||||
if leaves := countLeaves(t, tr); leaves != 16 {
|
||||
t.Fatalf("expected 16 leaves, got %d", leaves)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIteratorDeepTree verifies iteration over a trie with stems that share
|
||||
// a long common prefix, producing many intermediate InternalNodes.
|
||||
func TestIteratorDeepTree(t *testing.T) {
|
||||
tr := makeTrie(t, [][2]common.Hash{
|
||||
{common.HexToHash("0000000000C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0"), oneKey},
|
||||
{common.HexToHash("0000000000E00000000000000000000000000000000000000000000000000000"), twoKey},
|
||||
})
|
||||
if leaves := countLeaves(t, tr); leaves != 2 {
|
||||
t.Fatalf("expected 2 leaves in deep tree, got %d", leaves)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIteratorNodeCount verifies the total number of Next(true) calls
|
||||
// for a known tree structure.
|
||||
func TestIteratorNodeCount(t *testing.T) {
|
||||
tr := makeTrie(t, [][2]common.Hash{
|
||||
{common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||
{common.HexToHash("8000000000000000000000000000000000000000000000000000000000000001"), oneKey},
|
||||
})
|
||||
|
||||
it, err := newBinaryNodeIterator(tr, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
total := 0
|
||||
leaves := 0
|
||||
for it.Next(true) {
|
||||
total++
|
||||
if it.Leaf() {
|
||||
leaves++
|
||||
}
|
||||
}
|
||||
if leaves != 2 {
|
||||
t.Fatalf("expected 2 leaves, got %d", leaves)
|
||||
}
|
||||
// Root(InternalNode) + leaf1 (from left StemNode) + leaf2 (from right StemNode) = 3
|
||||
// StemNodes are not returned as separate steps; the iterator advances
|
||||
// directly to the first non-nil value within the stem.
|
||||
if total != 3 {
|
||||
t.Fatalf("expected 3 total nodes, got %d", total)
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,18 @@ func NewLevelStats() *LevelStats {
|
|||
return &LevelStats{}
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the statistics.
|
||||
func (s *LevelStats) Copy() *LevelStats {
|
||||
cpy := NewLevelStats()
|
||||
for i := range s.level {
|
||||
cpy.level[i].short.Store(s.level[i].short.Load())
|
||||
cpy.level[i].full.Store(s.level[i].full.Load())
|
||||
cpy.level[i].value.Store(s.level[i].value.Load())
|
||||
cpy.level[i].size.Store(s.level[i].size.Load())
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// MaxDepth iterates each level and finds the deepest level with at least one
|
||||
// trie node.
|
||||
func (s *LevelStats) MaxDepth() int {
|
||||
|
|
|
|||
Loading…
Reference in a new issue