Merge branch 'ethereum:master' into master

This commit is contained in:
Dr. Q and Company 2026-04-09 18:50:20 -04:00 committed by GitHub
commit 301bed656d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
179 changed files with 16550 additions and 10707 deletions

View file

@ -438,14 +438,11 @@ func (s *serverWithLimits) fail(desc string) {
// failLocked calculates the dynamic failure delay and applies it. // failLocked calculates the dynamic failure delay and applies it.
func (s *serverWithLimits) failLocked(desc string) { func (s *serverWithLimits) failLocked(desc string) {
log.Debug("Server error", "description", desc) log.Debug("Server error", "description", desc)
s.failureDelay *= 2
now := s.clock.Now() now := s.clock.Now()
if now > s.failureDelayEnd { if now > s.failureDelayEnd {
s.failureDelay *= math.Pow(2, -float64(now-s.failureDelayEnd)/float64(maxFailureDelay)) s.failureDelay *= math.Pow(2, -float64(now-s.failureDelayEnd)/float64(maxFailureDelay))
} }
if s.failureDelay < float64(minFailureDelay) { s.failureDelay = max(min(s.failureDelay*2, float64(maxFailureDelay)), float64(minFailureDelay))
s.failureDelay = float64(minFailureDelay)
}
s.failureDelayEnd = now + mclock.AbsTime(s.failureDelay) s.failureDelayEnd = now + mclock.AbsTime(s.failureDelay)
s.delay(time.Duration(s.failureDelay)) s.delay(time.Duration(s.failureDelay))
} }

View file

@ -62,7 +62,6 @@ const (
ssNeedParent // cp header slot %32 != 0, need parent to check epoch boundary ssNeedParent // cp header slot %32 != 0, need parent to check epoch boundary
ssParentRequested // cp parent header requested ssParentRequested // cp parent header requested
ssPrintStatus // has all necessary info, print log message if init still not successful ssPrintStatus // has all necessary info, print log message if init still not successful
ssDone // log message printed, no more action required
) )
type serverState struct { type serverState struct {
@ -180,7 +179,8 @@ func (s *CheckpointInit) Process(requester request.Requester, events []request.E
default: default:
log.Error("blsync: checkpoint not available, but reported as finalized; specified checkpoint hash might be too old", "server", server.Name()) log.Error("blsync: checkpoint not available, but reported as finalized; specified checkpoint hash might be too old", "server", server.Name())
} }
s.serverState[server] = serverState{state: ssDone} s.serverState[server] = serverState{state: ssDefault}
requester.Fail(server, "checkpoint init failed")
} }
} }

View file

@ -108,17 +108,21 @@ var (
Tags: "ziren", Tags: "ziren",
Env: map[string]string{"GOMIPS": "softfloat", "CGO_ENABLED": "0"}, Env: map[string]string{"GOMIPS": "softfloat", "CGO_ENABLED": "0"},
}, },
{
Name: "womir",
GOOS: "wasip1",
GOARCH: "wasm",
Tags: "womir",
},
{ {
Name: "wasm-js", Name: "wasm-js",
GOOS: "js", GOOS: "js",
GOARCH: "wasm", GOARCH: "wasm",
Tags: "example",
}, },
{ {
Name: "wasm-wasi", Name: "wasm-wasi",
GOOS: "wasip1", GOOS: "wasip1",
GOARCH: "wasm", GOARCH: "wasm",
Tags: "example",
}, },
{ {
Name: "example", Name: "example",
@ -168,11 +172,11 @@ var (
// Distros for which packages are created // Distros for which packages are created
debDistros = []string{ debDistros = []string{
"xenial", // 16.04, EOL: 04/2026 "xenial", // 16.04, EOL: 04/2026
"bionic", // 18.04, EOL: 04/2028 "bionic", // 18.04, EOL: 04/2028
"focal", // 20.04, EOL: 04/2030 "focal", // 20.04, EOL: 04/2030
"jammy", // 22.04, EOL: 04/2032 "jammy", // 22.04, EOL: 04/2032
"noble", // 24.04, EOL: 04/2034 "noble", // 24.04, EOL: 04/2034
} }
// This is where the tests should be unpacked. // This is where the tests should be unpacked.
@ -310,7 +314,7 @@ func doInstallKeeper(cmdline []string) {
args := slices.Clone(gobuild.Args) args := slices.Clone(gobuild.Args)
args = append(args, "-o", executablePath(outputName)) args = append(args, "-o", executablePath(outputName))
args = append(args, ".") args = append(args, ".")
build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env}) build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env, Dir: gobuild.Dir})
} }
} }
@ -1201,7 +1205,7 @@ func doWindowsInstaller(cmdline []string) {
var ( var (
arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging")
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`)
signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`) signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`)
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
) )

View file

@ -51,6 +51,12 @@ type Chain struct {
state map[common.Address]state.DumpAccount // state of head block state map[common.Address]state.DumpAccount // state of head block
senders map[common.Address]*senderInfo senders map[common.Address]*senderInfo
config *params.ChainConfig config *params.ChainConfig
txInfo txInfo
}
type txInfo struct {
LargeReceiptBlock *uint64 `json:"tx-largereceipt"`
} }
// NewChain takes the given chain.rlp file, and decodes and returns // NewChain takes the given chain.rlp file, and decodes and returns
@ -74,12 +80,20 @@ func NewChain(dir string) (*Chain, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var txInfo txInfo
err = common.LoadJSON(filepath.Join(dir, "txinfo.json"), &txInfo)
if err != nil {
return nil, err
}
return &Chain{ return &Chain{
genesis: gen, genesis: gen,
blocks: blocks, blocks: blocks,
state: state, state: state,
senders: accounts, senders: accounts,
config: gen.Config, config: gen.Config,
txInfo: txInfo,
}, nil }, nil
} }

View file

@ -66,9 +66,10 @@ func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) {
return nil, err return nil, err
} }
conn.caps = []p2p.Cap{ conn.caps = []p2p.Cap{
{Name: "eth", Version: 70},
{Name: "eth", Version: 69}, {Name: "eth", Version: 69},
} }
conn.ourHighestProtoVersion = 69 conn.ourHighestProtoVersion = 70
return &conn, nil return &conn, nil
} }
@ -335,10 +336,12 @@ loop:
if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) {
return fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want) return fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want)
} }
if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) { for _, cap := range c.caps {
return fmt.Errorf("wrong protocol version: have %v, want %v", have, want) if cap.Name == "eth" && cap.Version == uint(msg.ProtocolVersion) {
break loop
}
} }
break loop return fmt.Errorf("wrong protocol version: have %v, want %v", msg.ProtocolVersion, c.caps)
case discMsg: case discMsg:
var msg []p2p.DiscReason var msg []p2p.DiscReason
if rlp.DecodeBytes(data, &msg); len(msg) == 0 { if rlp.DecodeBytes(data, &msg); len(msg) == 0 {

View file

@ -87,9 +87,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
root: root, root: root,
startingHash: zero, startingHash: zero,
limitHash: ffHash, limitHash: ffHash,
expAccounts: 67, expAccounts: 68,
expFirst: firstKey, expFirst: firstKey,
expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"), expLast: common.HexToHash("0x59312f89c13e9e24c1cb8b103aa39a9b2800348d97a92c2c9e2a78fa02b70025"),
desc: "In this test, we request the entire state range, but limit the response to 4000 bytes.", desc: "In this test, we request the entire state range, but limit the response to 4000 bytes.",
}, },
{ {
@ -97,9 +97,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
root: root, root: root,
startingHash: zero, startingHash: zero,
limitHash: ffHash, limitHash: ffHash,
expAccounts: 49, expAccounts: 50,
expFirst: firstKey, expFirst: firstKey,
expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"), expLast: common.HexToHash("0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa"),
desc: "In this test, we request the entire state range, but limit the response to 3000 bytes.", desc: "In this test, we request the entire state range, but limit the response to 3000 bytes.",
}, },
{ {
@ -107,9 +107,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
root: root, root: root,
startingHash: zero, startingHash: zero,
limitHash: ffHash, limitHash: ffHash,
expAccounts: 34, expAccounts: 35,
expFirst: firstKey, expFirst: firstKey,
expLast: common.HexToHash("0x2ef46ebd2073cecde499c2e8df028ad79a26d57bfaa812c4c6f7eb4c9617b913"), expLast: common.HexToHash("0x2de4bdbddcfbb9c3e195dae6b45f9c38daff897e926764bf34887fb0db5c3284"),
desc: "In this test, we request the entire state range, but limit the response to 2000 bytes.", desc: "In this test, we request the entire state range, but limit the response to 2000 bytes.",
}, },
{ {
@ -178,9 +178,9 @@ The server should return the first available account.`,
root: root, root: root,
startingHash: firstKey, startingHash: firstKey,
limitHash: ffHash, limitHash: ffHash,
expAccounts: 67, expAccounts: 68,
expFirst: firstKey, expFirst: firstKey,
expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"), expLast: common.HexToHash("0x59312f89c13e9e24c1cb8b103aa39a9b2800348d97a92c2c9e2a78fa02b70025"),
desc: `In this test, startingHash is exactly the first available account key. desc: `In this test, startingHash is exactly the first available account key.
The server should return the first available account of the state as the first item.`, The server should return the first available account of the state as the first item.`,
}, },
@ -189,9 +189,9 @@ The server should return the first available account of the state as the first i
root: root, root: root,
startingHash: hashAdd(firstKey, 1), startingHash: hashAdd(firstKey, 1),
limitHash: ffHash, limitHash: ffHash,
expAccounts: 67, expAccounts: 68,
expFirst: secondKey, expFirst: secondKey,
expLast: common.HexToHash("0x66192e4c757fba1cdc776e6737008f42d50370d3cd801db3624274283bf7cd63"), expLast: common.HexToHash("0x59a7c8818f1c16b298a054020dc7c3f403a970d1d1db33f9478b1c36e3a2e509"),
desc: `In this test, startingHash is after the first available key. desc: `In this test, startingHash is after the first available key.
The server should return the second account of the state as the first item.`, The server should return the second account of the state as the first item.`,
}, },
@ -227,9 +227,9 @@ server to return no data because genesis is older than 127 blocks.`,
root: s.chain.RootAt(int(s.chain.Head().Number().Uint64()) - 127), root: s.chain.RootAt(int(s.chain.Head().Number().Uint64()) - 127),
startingHash: zero, startingHash: zero,
limitHash: ffHash, limitHash: ffHash,
expAccounts: 66, expAccounts: 68,
expFirst: firstKey, expFirst: firstKey,
expLast: common.HexToHash("0x729953a43ed6c913df957172680a17e5735143ad767bda8f58ac84ec62fbec5e"), expLast: common.HexToHash("0x683b6c03cc32afe5db8cb96050f711fdaff8f8ff44c7587a9a848f921d02815e"),
desc: `This test requests data at a state root that is 127 blocks old. desc: `This test requests data at a state root that is 127 blocks old.
We expect the server to have this state available.`, We expect the server to have this state available.`,
}, },
@ -658,8 +658,8 @@ The server should reject the request.`,
// It's a bit unfortunate these are hard-coded, but the result depends on // It's a bit unfortunate these are hard-coded, but the result depends on
// a lot of aspects of the state trie and can't be guessed in a simple // a lot of aspects of the state trie and can't be guessed in a simple
// way. So you'll have to update this when the test chain is changed. // way. So you'll have to update this when the test chain is changed.
common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"), common.HexToHash("0x4bdecec09691ad38113eebee2df94fadefdff5841c0f182bae1be3c8a6d60bf3"),
common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"), common.HexToHash("0x4178696465d4514ff5924ef8c28ce64d41a669634b63184c2c093e252d6b4bc4"),
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
@ -679,8 +679,8 @@ The server should reject the request.`,
// be updated when the test chain is changed. // be updated when the test chain is changed.
expHashes: []common.Hash{ expHashes: []common.Hash{
empty, empty,
common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"), common.HexToHash("0x4178696465d4514ff5924ef8c28ce64d41a669634b63184c2c093e252d6b4bc4"),
common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"), common.HexToHash("0x4bdecec09691ad38113eebee2df94fadefdff5841c0f182bae1be3c8a6d60bf3"),
}, },
}, },

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -83,6 +84,7 @@ func (s *Suite) EthTests() []utesting.Test {
// get history // get history
{Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies},
{Name: "GetReceipts", Fn: s.TestGetReceipts}, {Name: "GetReceipts", Fn: s.TestGetReceipts},
{Name: "GetLargeReceipts", Fn: s.TestGetLargeReceipts},
// test transactions // test transactions
{Name: "LargeTxRequest", Fn: s.TestLargeTxRequest, Slow: true}, {Name: "LargeTxRequest", Fn: s.TestLargeTxRequest, Slow: true},
{Name: "Transaction", Fn: s.TestTransaction}, {Name: "Transaction", Fn: s.TestTransaction},
@ -429,6 +431,9 @@ func (s *Suite) TestGetReceipts(t *utesting.T) {
// Find some blocks containing receipts. // Find some blocks containing receipts.
var hashes = make([]common.Hash, 0, 3) var hashes = make([]common.Hash, 0, 3)
for i := range s.chain.Len() { for i := range s.chain.Len() {
if s.chain.txInfo.LargeReceiptBlock != nil && uint64(i) == *s.chain.txInfo.LargeReceiptBlock {
continue
}
block := s.chain.GetBlock(i) block := s.chain.GetBlock(i)
if len(block.Transactions()) > 0 { if len(block.Transactions()) > 0 {
hashes = append(hashes, block.Hash()) hashes = append(hashes, block.Hash())
@ -437,25 +442,121 @@ func (s *Suite) TestGetReceipts(t *utesting.T) {
break break
} }
} }
if conn.negotiatedProtoVersion < eth.ETH70 {
// Create block bodies request.
req := &eth.GetReceiptsPacket69{
RequestId: 66,
GetReceiptsRequest: (eth.GetReceiptsRequest)(hashes),
}
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil {
t.Fatalf("could not write to connection: %v", err)
}
// Wait for response.
resp := new(eth.ReceiptsPacket69)
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
t.Fatalf("error reading block receipts msg: %v", err)
}
if got, want := resp.RequestId, req.RequestId; got != want {
t.Fatalf("unexpected request id in respond", got, want)
}
if resp.List.Len() != len(req.GetReceiptsRequest) {
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len())
}
} else {
// Create block bodies request.
req := &eth.GetReceiptsPacket70{
RequestId: 66,
FirstBlockReceiptIndex: 0,
GetReceiptsRequest: (eth.GetReceiptsRequest)(hashes),
}
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil {
t.Fatalf("could not write to connection: %v", err)
}
// Wait for response.
resp := new(eth.ReceiptsPacket70)
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
t.Fatalf("error reading block receipts msg: %v", err)
}
if got, want := resp.RequestId, req.RequestId; got != want {
t.Fatalf("unexpected request id in respond", got, want)
}
if resp.List.Len() != len(req.GetReceiptsRequest) {
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len())
}
}
}
// Create receipts request. func (s *Suite) TestGetLargeReceipts(t *utesting.T) {
req := &eth.GetReceiptsPacket{ t.Log(`This test sends GetReceipts requests to the node for large receipt (>10MiB) in the test chain.
RequestId: 66, This test is meaningful only if the client supports protocol version ETH70 or higher
GetReceiptsRequest: (eth.GetReceiptsRequest)(hashes), and LargeReceiptBlock is configured in txInfo.json.`)
conn, err := s.dialAndPeer(nil)
if err != nil {
t.Fatalf("peering failed: %v", err)
} }
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil { defer conn.Close()
t.Fatalf("could not write to connection: %v", err)
if conn.negotiatedProtoVersion < eth.ETH70 || s.chain.txInfo.LargeReceiptBlock == nil {
return
} }
// Wait for response.
resp := new(eth.ReceiptsPacket) // Find block with large receipt.
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil { // Place the large receipt block hash in the middle of the query
t.Fatalf("error reading block bodies msg: %v", err) start := max(int(*s.chain.txInfo.LargeReceiptBlock)-2, 0)
end := min(*s.chain.txInfo.LargeReceiptBlock+2, uint64(len(s.chain.blocks)))
var blocks []common.Hash
var receiptHashes []common.Hash
var receipts []*eth.ReceiptList
for i := uint64(start); i < end; i++ {
block := s.chain.GetBlock(int(i))
blocks = append(blocks, block.Hash())
receiptHashes = append(receiptHashes, block.Header().ReceiptHash)
receipts = append(receipts, &eth.ReceiptList{})
} }
if got, want := resp.RequestId, req.RequestId; got != want {
t.Fatalf("unexpected request id in respond", got, want) incomplete := false
lastBlock := 0
for incomplete || lastBlock != len(blocks)-1 {
// Create get receipt request.
req := &eth.GetReceiptsPacket70{
RequestId: 66,
FirstBlockReceiptIndex: uint64(receipts[lastBlock].Derivable().Len()),
GetReceiptsRequest: blocks[lastBlock:],
}
if err := conn.Write(ethProto, eth.GetReceiptsMsg, req); err != nil {
t.Fatalf("could not write to connection: %v", err)
}
// Wait for response.
resp := new(eth.ReceiptsPacket70)
if err := conn.ReadMsg(ethProto, eth.ReceiptsMsg, &resp); err != nil {
t.Fatalf("error reading block receipts msg: %v", err)
}
if got, want := resp.RequestId, req.RequestId; got != want {
t.Fatalf("unexpected request id in respond, want: %d, got: %d", got, want)
}
receiptLists, _ := resp.List.Items()
for i, rc := range receiptLists {
receipts[lastBlock+i].Append(rc)
}
lastBlock += len(receiptLists) - 1
incomplete = resp.LastBlockIncomplete
} }
if resp.List.Len() != len(req.GetReceiptsRequest) {
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len()) hasher := trie.NewStackTrie(nil)
hashes := make([]common.Hash, len(receipts))
for i := range receipts {
hashes[i] = types.DeriveSha(receipts[i].Derivable(), hasher)
}
for i, hash := range hashes {
if receiptHashes[i] != hash {
t.Fatalf("wrong receipt root: want %x, got %x", receiptHashes[i], hash)
}
} }
} }

Binary file not shown.

View file

@ -37,7 +37,7 @@
"nonce": "0x0", "nonce": "0x0",
"timestamp": "0x0", "timestamp": "0x0",
"extraData": "0x68697665636861696e", "extraData": "0x68697665636861696e",
"gasLimit": "0x23f3e20", "gasLimit": "0x11e1a300",
"difficulty": "0x20000", "difficulty": "0x20000",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000",
@ -119,6 +119,10 @@
"balance": "0x1", "balance": "0x1",
"nonce": "0x1" "nonce": "0x1"
}, },
"8dcd17433742f4c0ca53122ab541d0ba67fc27ff": {
"code": "0x6202e6306000a0",
"balance": "0x0"
},
"c7b99a164efd027a93f147376cc7da7c67c6bbe0": { "c7b99a164efd027a93f147376cc7da7c67c6bbe0": {
"balance": "0xc097ce7bc90715b34b9f1000000000" "balance": "0xc097ce7bc90715b34b9f1000000000"
}, },

View file

@ -1,24 +1,24 @@
{ {
"parentHash": "0x65151b101682b54cd08ba226f640c14c86176865ff9bfc57e0147dadaeac34bb", "parentHash": "0x7e80093a491eba0e5b2c1895837902f64f514100221801318fe391e1e09c96a6",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"miner": "0x0000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000",
"stateRoot": "0xce423ebc60fc7764a43f09f1fe3ae61eef25e3eb8d09b1108f7e7eb77dfff5e6", "stateRoot": "0x8fcfb02cfca007773bd55bc1c3e50a3c8612a59c87ce057e5957e8bf17c1728b",
"transactionsRoot": "0x7ec1ae3989efa75d7bcc766e5e2443afa8a89a5fda42ebba90050e7e702980f7", "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"receiptsRoot": "0xfe160832b1ca85f38c6674cb0aae3a24693bc49be56e2ecdf3698b71a794de86", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x0", "difficulty": "0x0",
"number": "0x258", "number": "0x258",
"gasLimit": "0x23f3e20", "gasLimit": "0x11e1a300",
"gasUsed": "0x19d36", "gasUsed": "0x0",
"timestamp": "0x1770", "timestamp": "0x1770",
"extraData": "0x", "extraData": "0x",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0000000000000000", "nonce": "0x0000000000000000",
"baseFeePerGas": "0x7", "baseFeePerGas": "0x7",
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "withdrawalsRoot": "0x92abfda39de7df7d705c5a8f30386802ad59d31e782a06d5c5b0f9a260056cf0",
"blobGasUsed": "0x0", "blobGasUsed": "0x0",
"excessBlobGas": "0x0", "excessBlobGas": "0x0",
"parentBeaconBlockRoot": "0xf5003fc8f92358e790a114bce93ce1d9c283c85e1787f8d7d56714d3489b49e6", "parentBeaconBlockRoot": "0xf5003fc8f92358e790a114bce93ce1d9c283c85e1787f8d7d56714d3489b49e6",
"requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"hash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0" "hash": "0x44e3809c9a3cda717f00aea3a9da336d149612c8d5657fbc0028176ef8d94d2a"
} }

View file

@ -4,9 +4,9 @@
"method": "engine_forkchoiceUpdatedV3", "method": "engine_forkchoiceUpdatedV3",
"params": [ "params": [
{ {
"headBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0", "headBlockHash": "0x44e3809c9a3cda717f00aea3a9da336d149612c8d5657fbc0028176ef8d94d2a",
"safeBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0", "safeBlockHash": "0x44e3809c9a3cda717f00aea3a9da336d149612c8d5657fbc0028176ef8d94d2a",
"finalizedBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0" "finalizedBlockHash": "0x44e3809c9a3cda717f00aea3a9da336d149612c8d5657fbc0028176ef8d94d2a"
}, },
null null
] ]

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -218,11 +218,15 @@ func (tc *conn) read(c net.PacketConn) v5wire.Packet {
if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil {
return &readError{err} return &readError{err}
} }
n, fromAddr, err := c.ReadFrom(buf) n, _, err := c.ReadFrom(buf)
if err != nil { if err != nil {
return &readError{err} 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 { if err != nil {
return &readError{err} return &readError{err}
} }

View file

@ -365,10 +365,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
// Set requestsHash on block. // Set requestsHash on block.
h := types.CalcRequestsHash(requests) h := types.CalcRequestsHash(requests)
execRs.RequestsHash = &h execRs.RequestsHash = &h
for i := range requests {
// remove prefix
requests[i] = requests[i][1:]
}
execRs.Requests = requests execRs.Requests = requests
} }

408
cmd/geth/bintrie_convert.go Normal file
View file

@ -0,0 +1,408 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"errors"
"fmt"
"runtime"
"runtime/debug"
"slices"
"time"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/urfave/cli/v2"
)
var (
deleteSourceFlag = &cli.BoolFlag{
Name: "delete-source",
Usage: "Delete MPT trie nodes after conversion",
}
memoryLimitFlag = &cli.Uint64Flag{
Name: "memory-limit",
Usage: "Max heap allocation in MB before forcing a commit cycle",
Value: 16384,
}
bintrieCommand = &cli.Command{
Name: "bintrie",
Usage: "A set of commands for binary trie operations",
Description: "",
Subcommands: []*cli.Command{
{
Name: "convert",
Usage: "Convert MPT state to binary trie",
ArgsUsage: "[state-root]",
Action: convertToBinaryTrie,
Flags: slices.Concat([]cli.Flag{
deleteSourceFlag,
memoryLimitFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: `
geth bintrie convert [--delete-source] [--memory-limit MB] [state-root]
Reads all state from the Merkle Patricia Trie and writes it into a Binary Trie,
operating offline. Memory-safe via periodic commit-and-reload cycles.
The optional state-root argument specifies which state root to convert.
If omitted, the head block's state root is used.
Flags:
--delete-source Delete MPT trie nodes after successful conversion
--memory-limit Max heap allocation in MB before forcing a commit (default: 16384)
`,
},
},
}
)
type conversionStats struct {
accounts uint64
slots uint64
codes uint64
commits uint64
start time.Time
lastReport time.Time
lastMemChk time.Time
}
func (s *conversionStats) report(force bool) {
if !force && time.Since(s.lastReport) < 8*time.Second {
return
}
elapsed := time.Since(s.start).Seconds()
acctRate := float64(0)
if elapsed > 0 {
acctRate = float64(s.accounts) / elapsed
}
log.Info("Conversion progress",
"accounts", s.accounts,
"slots", s.slots,
"codes", s.codes,
"commits", s.commits,
"accounts/sec", fmt.Sprintf("%.0f", acctRate),
"elapsed", common.PrettyDuration(time.Since(s.start)),
)
s.lastReport = time.Now()
}
func convertToBinaryTrie(ctx *cli.Context) error {
if ctx.NArg() > 1 {
return errors.New("too many arguments")
}
stack, _ := makeConfigNode(ctx)
defer stack.Close()
chaindb := utils.MakeChainDatabase(ctx, stack, false)
defer chaindb.Close()
headBlock := rawdb.ReadHeadBlock(chaindb)
if headBlock == nil {
return errors.New("no head block found")
}
var (
root common.Hash
err error
)
if ctx.NArg() == 1 {
root, err = parseRoot(ctx.Args().First())
if err != nil {
return fmt.Errorf("invalid state root: %w", err)
}
} else {
root = headBlock.Root()
}
log.Info("Starting MPT to binary trie conversion", "root", root, "block", headBlock.NumberU64())
srcTriedb := utils.MakeTrieDatabase(ctx, stack, chaindb, true, true, false)
defer srcTriedb.Close()
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
IsVerkle: true,
PathDB: &pathdb.Config{
JournalDirectory: stack.ResolvePath("triedb-bintrie"),
},
})
defer destTriedb.Close()
binTrie, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
if err != nil {
return fmt.Errorf("failed to create binary trie: %w", err)
}
memLimit := ctx.Uint64(memoryLimitFlag.Name) * 1024 * 1024
currentRoot, err := runConversionLoop(chaindb, srcTriedb, destTriedb, binTrie, root, memLimit)
if err != nil {
return err
}
log.Info("Conversion complete", "binaryRoot", currentRoot)
if ctx.Bool(deleteSourceFlag.Name) {
log.Info("Deleting source MPT data")
if err := deleteMPTData(chaindb, srcTriedb, root); err != nil {
return fmt.Errorf("MPT deletion failed: %w", err)
}
log.Info("Source MPT data deleted")
}
return nil
}
func runConversionLoop(chaindb ethdb.Database, srcTriedb *triedb.Database, destTriedb *triedb.Database, binTrie *bintrie.BinaryTrie, root common.Hash, memLimit uint64) (common.Hash, error) {
currentRoot := types.EmptyBinaryHash
stats := &conversionStats{
start: time.Now(),
lastReport: time.Now(),
lastMemChk: time.Now(),
}
srcTrie, err := trie.NewStateTrie(trie.StateTrieID(root), srcTriedb)
if err != nil {
return common.Hash{}, fmt.Errorf("failed to open source trie: %w", err)
}
acctIt, err := srcTrie.NodeIterator(nil)
if err != nil {
return common.Hash{}, fmt.Errorf("failed to create account iterator: %w", err)
}
accIter := trie.NewIterator(acctIt)
for accIter.Next() {
var acc types.StateAccount
if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil {
return common.Hash{}, fmt.Errorf("invalid account RLP: %w", err)
}
addrBytes := srcTrie.GetKey(accIter.Key)
if addrBytes == nil {
return common.Hash{}, fmt.Errorf("missing preimage for account hash %x (run with --cache.preimages)", accIter.Key)
}
addr := common.BytesToAddress(addrBytes)
var code []byte
codeHash := common.BytesToHash(acc.CodeHash)
if codeHash != types.EmptyCodeHash {
code = rawdb.ReadCode(chaindb, codeHash)
if code == nil {
return common.Hash{}, fmt.Errorf("missing code for hash %x (account %x)", codeHash, addr)
}
stats.codes++
}
if err := binTrie.UpdateAccount(addr, &acc, len(code)); err != nil {
return common.Hash{}, fmt.Errorf("failed to update account %x: %w", addr, err)
}
if len(code) > 0 {
if err := binTrie.UpdateContractCode(addr, codeHash, code); err != nil {
return common.Hash{}, fmt.Errorf("failed to update code for %x: %w", addr, err)
}
}
if acc.Root != types.EmptyRootHash {
addrHash := common.BytesToHash(accIter.Key)
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(root, addrHash, acc.Root), srcTriedb)
if err != nil {
return common.Hash{}, fmt.Errorf("failed to open storage trie for %x: %w", addr, err)
}
storageNodeIt, err := storageTrie.NodeIterator(nil)
if err != nil {
return common.Hash{}, fmt.Errorf("failed to create storage iterator for %x: %w", addr, err)
}
storageIter := trie.NewIterator(storageNodeIt)
slotCount := uint64(0)
for storageIter.Next() {
slotKey := storageTrie.GetKey(storageIter.Key)
if slotKey == nil {
return common.Hash{}, fmt.Errorf("missing preimage for storage key %x (account %x)", storageIter.Key, addr)
}
_, content, _, err := rlp.Split(storageIter.Value)
if err != nil {
return common.Hash{}, fmt.Errorf("invalid storage RLP for key %x (account %x): %w", slotKey, addr, err)
}
if err := binTrie.UpdateStorage(addr, slotKey, content); err != nil {
return common.Hash{}, fmt.Errorf("failed to update storage %x/%x: %w", addr, slotKey, err)
}
stats.slots++
slotCount++
if slotCount%10000 == 0 {
binTrie, currentRoot, err = maybeCommit(binTrie, currentRoot, destTriedb, memLimit, stats)
if err != nil {
return common.Hash{}, err
}
}
}
if storageIter.Err != nil {
return common.Hash{}, fmt.Errorf("storage iteration error for %x: %w", addr, storageIter.Err)
}
}
stats.accounts++
stats.report(false)
if stats.accounts%1000 == 0 {
binTrie, currentRoot, err = maybeCommit(binTrie, currentRoot, destTriedb, memLimit, stats)
if err != nil {
return common.Hash{}, err
}
}
}
if accIter.Err != nil {
return common.Hash{}, fmt.Errorf("account iteration error: %w", accIter.Err)
}
_, currentRoot, err = commitBinaryTrie(binTrie, currentRoot, destTriedb)
if err != nil {
return common.Hash{}, fmt.Errorf("final commit failed: %w", err)
}
stats.commits++
stats.report(true)
return currentRoot, nil
}
func maybeCommit(bt *bintrie.BinaryTrie, currentRoot common.Hash, destDB *triedb.Database, memLimit uint64, stats *conversionStats) (*bintrie.BinaryTrie, common.Hash, error) {
if time.Since(stats.lastMemChk) < 5*time.Second {
return bt, currentRoot, nil
}
stats.lastMemChk = time.Now()
var m runtime.MemStats
runtime.ReadMemStats(&m)
if m.Alloc < memLimit {
return bt, currentRoot, nil
}
log.Info("Memory limit reached, committing", "alloc", common.StorageSize(m.Alloc), "limit", common.StorageSize(memLimit))
bt, currentRoot, err := commitBinaryTrie(bt, currentRoot, destDB)
if err != nil {
return nil, common.Hash{}, err
}
stats.commits++
stats.report(true)
return bt, currentRoot, nil
}
func commitBinaryTrie(bt *bintrie.BinaryTrie, currentRoot common.Hash, destDB *triedb.Database) (*bintrie.BinaryTrie, common.Hash, error) {
newRoot, nodeSet := bt.Commit(false)
if nodeSet != nil {
merged := trienode.NewWithNodeSet(nodeSet)
if err := destDB.Update(newRoot, currentRoot, 0, merged, triedb.NewStateSet()); err != nil {
return nil, common.Hash{}, fmt.Errorf("triedb update failed: %w", err)
}
if err := destDB.Commit(newRoot, false); err != nil {
return nil, common.Hash{}, fmt.Errorf("triedb commit failed: %w", err)
}
}
runtime.GC()
debug.FreeOSMemory()
bt, err := bintrie.NewBinaryTrie(newRoot, destDB)
if err != nil {
return nil, common.Hash{}, fmt.Errorf("failed to reload binary trie: %w", err)
}
return bt, newRoot, nil
}
func deleteMPTData(chaindb ethdb.Database, srcTriedb *triedb.Database, root common.Hash) error {
isPathDB := srcTriedb.Scheme() == rawdb.PathScheme
srcTrie, err := trie.NewStateTrie(trie.StateTrieID(root), srcTriedb)
if err != nil {
return fmt.Errorf("failed to open source trie for deletion: %w", err)
}
acctIt, err := srcTrie.NodeIterator(nil)
if err != nil {
return fmt.Errorf("failed to create account iterator for deletion: %w", err)
}
batch := chaindb.NewBatch()
deleted := 0
for acctIt.Next(true) {
if isPathDB {
rawdb.DeleteAccountTrieNode(batch, acctIt.Path())
} else {
node := acctIt.Hash()
if node != (common.Hash{}) {
rawdb.DeleteLegacyTrieNode(batch, node)
}
}
deleted++
if acctIt.Leaf() {
var acc types.StateAccount
if err := rlp.DecodeBytes(acctIt.LeafBlob(), &acc); err != nil {
return fmt.Errorf("invalid account during deletion: %w", err)
}
if acc.Root != types.EmptyRootHash {
addrHash := common.BytesToHash(acctIt.LeafKey())
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(root, addrHash, acc.Root), srcTriedb)
if err != nil {
return fmt.Errorf("failed to open storage trie for deletion: %w", err)
}
storageIt, err := storageTrie.NodeIterator(nil)
if err != nil {
return fmt.Errorf("failed to create storage iterator for deletion: %w", err)
}
for storageIt.Next(true) {
if isPathDB {
rawdb.DeleteStorageTrieNode(batch, addrHash, storageIt.Path())
} else {
node := storageIt.Hash()
if node != (common.Hash{}) {
rawdb.DeleteLegacyTrieNode(batch, node)
}
}
deleted++
if batch.ValueSize() >= ethdb.IdealBatchSize {
if err := batch.Write(); err != nil {
return fmt.Errorf("batch write failed: %w", err)
}
batch.Reset()
}
}
if storageIt.Error() != nil {
return fmt.Errorf("storage deletion iterator error: %w", storageIt.Error())
}
}
}
if batch.ValueSize() >= ethdb.IdealBatchSize {
if err := batch.Write(); err != nil {
return fmt.Errorf("batch write failed: %w", err)
}
batch.Reset()
}
}
if acctIt.Error() != nil {
return fmt.Errorf("account deletion iterator error: %w", acctIt.Error())
}
if batch.ValueSize() > 0 {
if err := batch.Write(); err != nil {
return fmt.Errorf("final batch write failed: %w", err)
}
}
log.Info("MPT deletion complete", "nodesDeleted", deleted)
return nil
}

View file

@ -0,0 +1,229 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"math"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/holiman/uint256"
)
func TestBintrieConvert(t *testing.T) {
var (
addr1 = common.HexToAddress("0x1111111111111111111111111111111111111111")
addr2 = common.HexToAddress("0x2222222222222222222222222222222222222222")
slotKey1 = common.HexToHash("0x01")
slotKey2 = common.HexToHash("0x02")
slotVal1 = common.HexToHash("0xdeadbeef")
slotVal2 = common.HexToHash("0xcafebabe")
code = []byte{0x60, 0x42, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3}
)
chaindb := rawdb.NewMemoryDatabase()
srcTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
Preimages: true,
PathDB: pathdb.Defaults,
})
gspec := &core.Genesis{
Config: params.TestChainConfig,
BaseFee: big.NewInt(params.InitialBaseFee),
Alloc: types.GenesisAlloc{
addr1: {
Balance: big.NewInt(1000000),
Nonce: 5,
},
addr2: {
Balance: big.NewInt(2000000),
Nonce: 10,
Code: code,
Storage: map[common.Hash]common.Hash{
slotKey1: slotVal1,
slotKey2: slotVal2,
},
},
},
}
genesisBlock := gspec.MustCommit(chaindb, srcTriedb)
root := genesisBlock.Root()
t.Logf("Genesis root: %x", root)
srcTriedb.Close()
srcTriedb2 := triedb.NewDatabase(chaindb, &triedb.Config{
Preimages: true,
PathDB: &pathdb.Config{ReadOnly: true},
})
defer srcTriedb2.Close()
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
IsVerkle: true,
PathDB: pathdb.Defaults,
})
defer destTriedb.Close()
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
if err != nil {
t.Fatalf("failed to create binary trie: %v", err)
}
currentRoot, err := runConversionLoop(chaindb, srcTriedb2, destTriedb, bt, root, math.MaxUint64)
if err != nil {
t.Fatalf("conversion failed: %v", err)
}
t.Logf("Binary trie root: %x", currentRoot)
bt2, err := bintrie.NewBinaryTrie(currentRoot, destTriedb)
if err != nil {
t.Fatalf("failed to reload binary trie: %v", err)
}
acc1, err := bt2.GetAccount(addr1)
if err != nil {
t.Fatalf("failed to get account1: %v", err)
}
if acc1 == nil {
t.Fatal("account1 not found in binary trie")
}
if acc1.Nonce != 5 {
t.Errorf("account1 nonce: got %d, want 5", acc1.Nonce)
}
wantBal1 := uint256.NewInt(1000000)
if acc1.Balance.Cmp(wantBal1) != 0 {
t.Errorf("account1 balance: got %s, want %s", acc1.Balance, wantBal1)
}
acc2, err := bt2.GetAccount(addr2)
if err != nil {
t.Fatalf("failed to get account2: %v", err)
}
if acc2 == nil {
t.Fatal("account2 not found in binary trie")
}
if acc2.Nonce != 10 {
t.Errorf("account2 nonce: got %d, want 10", acc2.Nonce)
}
wantBal2 := uint256.NewInt(2000000)
if acc2.Balance.Cmp(wantBal2) != 0 {
t.Errorf("account2 balance: got %s, want %s", acc2.Balance, wantBal2)
}
treeKey1 := bintrie.GetBinaryTreeKeyStorageSlot(addr2, slotKey1[:])
val1, err := bt2.GetWithHashedKey(treeKey1)
if err != nil {
t.Fatalf("failed to get storage slot1: %v", err)
}
if len(val1) == 0 {
t.Fatal("storage slot1 not found")
}
got1 := common.BytesToHash(val1)
if got1 != slotVal1 {
t.Errorf("storage slot1: got %x, want %x", got1, slotVal1)
}
treeKey2 := bintrie.GetBinaryTreeKeyStorageSlot(addr2, slotKey2[:])
val2, err := bt2.GetWithHashedKey(treeKey2)
if err != nil {
t.Fatalf("failed to get storage slot2: %v", err)
}
if len(val2) == 0 {
t.Fatal("storage slot2 not found")
}
got2 := common.BytesToHash(val2)
if got2 != slotVal2 {
t.Errorf("storage slot2: got %x, want %x", got2, slotVal2)
}
}
func TestBintrieConvertDeleteSource(t *testing.T) {
addr1 := common.HexToAddress("0x3333333333333333333333333333333333333333")
chaindb := rawdb.NewMemoryDatabase()
srcTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
Preimages: true,
PathDB: pathdb.Defaults,
})
gspec := &core.Genesis{
Config: params.TestChainConfig,
BaseFee: big.NewInt(params.InitialBaseFee),
Alloc: types.GenesisAlloc{
addr1: {
Balance: big.NewInt(1000000),
},
},
}
genesisBlock := gspec.MustCommit(chaindb, srcTriedb)
root := genesisBlock.Root()
srcTriedb.Close()
srcTriedb2 := triedb.NewDatabase(chaindb, &triedb.Config{
Preimages: true,
PathDB: &pathdb.Config{ReadOnly: true},
})
destTriedb := triedb.NewDatabase(chaindb, &triedb.Config{
IsVerkle: true,
PathDB: pathdb.Defaults,
})
bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, destTriedb)
if err != nil {
t.Fatalf("failed to create binary trie: %v", err)
}
newRoot, err := runConversionLoop(chaindb, srcTriedb2, destTriedb, bt, root, math.MaxUint64)
if err != nil {
t.Fatalf("conversion failed: %v", err)
}
if err := deleteMPTData(chaindb, srcTriedb2, root); err != nil {
t.Fatalf("deletion failed: %v", err)
}
srcTriedb2.Close()
bt2, err := bintrie.NewBinaryTrie(newRoot, destTriedb)
if err != nil {
t.Fatalf("failed to reload binary trie after deletion: %v", err)
}
acc, err := bt2.GetAccount(addr1)
if err != nil {
t.Fatalf("failed to get account after deletion: %v", err)
}
if acc == nil {
t.Fatal("account not found after MPT deletion")
}
wantBal := uint256.NewInt(1000000)
if acc.Balance.Cmp(wantBal) != 0 {
t.Errorf("balance after deletion: got %s, want %s", acc.Balance, wantBal)
}
destTriedb.Close()
}

View file

@ -731,13 +731,16 @@ func pruneHistory(ctx *cli.Context) error {
// Determine the prune point based on the history mode. // Determine the prune point based on the history mode.
genesisHash := chain.Genesis().Hash() genesisHash := chain.Genesis().Hash()
prunePoint := history.GetPrunePoint(genesisHash, mode) policy, err := history.NewPolicy(mode, genesisHash)
if prunePoint == nil { if err != nil {
return err
}
if policy.Target == nil {
return fmt.Errorf("prune point for %q not found for this network", mode.String()) return fmt.Errorf("prune point for %q not found for this network", mode.String())
} }
var ( var (
targetBlock = prunePoint.BlockNumber targetBlock = policy.Target.BlockNumber
targetBlockHash = prunePoint.BlockHash targetBlockHash = policy.Target.BlockHash
) )
// Check the current freezer tail to see if pruning is needed/possible. // Check the current freezer tail to see if pruning is needed/possible.

View file

@ -260,6 +260,8 @@ func init() {
utils.ShowDeprecated, utils.ShowDeprecated,
// See snapshot.go // See snapshot.go
snapshotCommand, snapshotCommand,
// See bintrie_convert.go
bintrieCommand,
} }
if logTestCommand != nil { if logTestCommand != nil {
app.Commands = append(app.Commands, logTestCommand) app.Commands = append(app.Commands, logTestCommand)

View file

@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
"github.com/urfave/cli/v2" "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", Usage: "Traverse the state with given root hash and perform quick verification",
ArgsUsage: "<root>", ArgsUsage: "<root>",
Action: traverseState, Action: traverseState,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags), Flags: slices.Concat([]cli.Flag{
utils.AccountFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: ` Description: `
geth snapshot traverse-state <state-root> geth snapshot traverse-state <state-root>
will traverse the whole state from the given state root and will abort if any 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. state integrity verification. The default checking target is the HEAD state.
It's also usable without snapshot enabled. 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", Usage: "Traverse the state with given root hash and perform detailed verification",
ArgsUsage: "<root>", ArgsUsage: "<root>",
Action: traverseRawState, Action: traverseRawState,
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags), Flags: slices.Concat([]cli.Flag{
utils.AccountFlag,
}, utils.NetworkFlags, utils.DatabaseFlags),
Description: ` Description: `
geth snapshot traverse-rawstate <state-root> geth snapshot traverse-rawstate <state-root>
will traverse the whole state from the given root and will abort if any referenced 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. to traverse-state, but the check granularity is smaller.
It's also usable without snapshot enabled. 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) 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. // traverseState is a helper function used for pruning verification.
// Basically it just iterates the trie, ensure all nodes and associated // Basically it just iterates the trie, ensure all nodes and associated
// contract codes are present. // contract codes are present.
@ -309,6 +432,30 @@ func traverseState(ctx *cli.Context) error {
root = headBlock.Root() root = headBlock.Root()
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 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) t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
if err != nil { if err != nil {
log.Error("Failed to open trie", "root", root, "err", err) log.Error("Failed to open trie", "root", root, "err", err)
@ -335,30 +482,10 @@ func traverseState(ctx *cli.Context) error {
return err return err
} }
if acc.Root != types.EmptyRootHash { if acc.Root != types.EmptyRootHash {
id := trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root) err := traverseStorage(trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root), triedb, false, false)
storageTrie, err := trie.NewStateTrie(id, triedb)
if err != nil { if err != nil {
log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
return 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 !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) { if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
@ -418,6 +545,30 @@ func traverseRawState(ctx *cli.Context) error {
root = headBlock.Root() root = headBlock.Root()
log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) 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) t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
if err != nil { if err != nil {
log.Error("Failed to open trie", "root", root, "err", err) 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") return errors.New("invalid account")
} }
if acc.Root != types.EmptyRootHash { if acc.Root != types.EmptyRootHash {
id := trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root) err := traverseStorage(trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root), triedb, false, true)
storageTrie, err := trie.NewStateTrie(id, triedb)
if err != nil { 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 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 !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) { if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {

View file

@ -15,6 +15,7 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build example //go:build example
// +build example
package main package main

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build wasm //go:build wasm && !womir
// +build wasm // +build wasm,!womir
package main package main

View file

@ -0,0 +1,49 @@
// 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/>.
//go:build womir
package main
import "unsafe"
// These match the WOMIR guest-io imports (env module).
// Protocol: __hint_input prepares next item, __hint_buffer reads words.
// Each item has format: [byte_len_u32_le, ...data_words_padded_to_4bytes]
//
//go:wasmimport env __hint_input
func hintInput()
//go:wasmimport env __hint_buffer
func hintBuffer(ptr unsafe.Pointer, numWords uint32)
func readWord() uint32 {
var buf [4]byte
hintBuffer(unsafe.Pointer(&buf[0]), 1)
return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
}
func readBytes() []byte {
hintInput()
byteLen := readWord()
numWords := (byteLen + 3) / 4
data := make([]byte, numWords*4)
hintBuffer(unsafe.Pointer(&data[0]), numWords)
return data[:byteLen]
}
// getInput reads the RLP-encoded Payload from the WOMIR hint stream.
func getInput() []byte {
return readBytes()
}

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License // 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/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
//go:build !example && !ziren && !wasm //go:build !example && !ziren && !wasm && !womir
// +build !example,!ziren,!wasm // +build !example,!ziren,!wasm,!womir
package main package main

View file

@ -274,40 +274,66 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func
reported = time.Now() reported = time.Now()
imported = 0 imported = 0
h = sha256.New() h = sha256.New()
scratch = bytes.NewBuffer(nil) buf = bytes.NewBuffer(nil)
) )
for i, file := range entries { for i, file := range entries {
err := func() error { err := func() error {
path := filepath.Join(dir, file) path := filepath.Join(dir, file)
// validate against checksum file in directory // Validate against checksum file in directory.
f, err := os.Open(path) f, err := os.Open(path)
if err != nil { if err != nil {
return fmt.Errorf("open %s: %w", path, err) return fmt.Errorf("open %s: %w", path, err)
} }
defer f.Close() defer f.Close()
if _, err := io.Copy(h, f); err != nil { if _, err := io.Copy(h, f); err != nil {
return fmt.Errorf("checksum %s: %w", path, err) return fmt.Errorf("checksum %s: %w", path, err)
} }
got := common.BytesToHash(h.Sum(scratch.Bytes()[:])).Hex() got := common.BytesToHash(h.Sum(buf.Bytes()[:])).Hex()
want := checksums[i]
h.Reset() h.Reset()
scratch.Reset() buf.Reset()
if got != checksums[i] {
if got != want { return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, checksums[i])
return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, want)
} }
// Import all block data from Era1. // Import all block data from Era1.
e, err := from(f) e, err := from(f)
if err != nil { if err != nil {
return fmt.Errorf("error opening era: %w", err) return fmt.Errorf("error opening era: %w", err)
} }
defer e.Close()
it, err := e.Iterator() it, err := e.Iterator()
if err != nil { if err != nil {
return fmt.Errorf("error creating iterator: %w", err) 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)
}
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() { for it.Next() {
block, err := it.Block() block, err := it.Block()
if err != nil { if err != nil {
@ -320,23 +346,18 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func
if err != nil { if err != nil {
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err) return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
} }
enc := types.EncodeBlockReceiptLists([]types.Receipts{receipts}) blocks = append(blocks, block)
if _, err := chain.InsertReceiptChain([]*types.Block{block}, enc, math.MaxUint64); err != nil { receiptsList = append(receiptsList, receipts)
return fmt.Errorf("error inserting body %d: %w", it.Number(), err) if len(blocks) == importBatchSize {
} if err := flush(); err != nil {
imported++ return err
}
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()
} }
} }
if err := it.Error(); err != nil { if err := it.Error(); err != nil {
return err return err
} }
return nil return flush()
}() }()
if err != nil { if err != nil {
return err return err

View file

@ -218,6 +218,10 @@ var (
Usage: "Max number of elements (0 = no limit)", Usage: "Max number of elements (0 = no limit)",
Value: 0, Value: 0,
} }
AccountFlag = &cli.StringFlag{
Name: "account",
Usage: "Specifies the account address or hash to traverse a single storage trie",
}
OutputFileFlag = &cli.StringFlag{ OutputFileFlag = &cli.StringFlag{
Name: "output", Name: "output",
Usage: "Writes the result in json to the 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) { if ctx.IsSet(RPCTelemetryTagsFlag.Name) {
tcfg.Tags = ctx.String(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 { if tcfg.Endpoint != "" && !tcfg.Enabled {
log.Warn(fmt.Sprintf("OpenTelemetry endpoint configured but telemetry is not enabled, use --%s to enable.", RPCTelemetryFlag.Name)) log.Warn(fmt.Sprintf("OpenTelemetry endpoint configured but telemetry is not enabled, use --%s to enable.", RPCTelemetryFlag.Name))

View file

@ -155,7 +155,9 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
} }
cfg.historyPruneBlock = new(uint64) 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): case ctx.Bool(testSepoliaFlag.Name):
cfg.fsys = builtinTestFiles cfg.fsys = builtinTestFiles
if ctx.IsSet(filterQueryFileFlag.Name) { if ctx.IsSet(filterQueryFileFlag.Name) {
@ -180,7 +182,9 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
} }
cfg.historyPruneBlock = new(uint64) 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: default:
cfg.fsys = os.DirFS(".") cfg.fsys = os.DirFS(".")
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name) cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)

View file

@ -17,6 +17,7 @@
package beacon package beacon
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -29,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -273,12 +275,22 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
} }
} }
// Verify the existence / non-existence of Amsterdam-specific header fields
amsterdam := chain.Config().IsAmsterdam(header.Number, header.Time) amsterdam := chain.Config().IsAmsterdam(header.Number, header.Time)
if amsterdam && header.SlotNumber == nil { if amsterdam {
return errors.New("header is missing slotNumber") if header.BlockAccessListHash == nil {
} return errors.New("header is missing block access list hash")
if !amsterdam && header.SlotNumber != nil { }
return fmt.Errorf("invalid slotNumber: have %d, expected nil", *header.SlotNumber) if header.SlotNumber == nil {
return errors.New("header is missing slotNumber")
}
} else {
if header.BlockAccessListHash != nil {
return fmt.Errorf("invalid block access list hash: have %x, expected nil", *header.BlockAccessListHash)
}
if header.SlotNumber != nil {
return fmt.Errorf("invalid slotNumber: have %d, expected nil", *header.SlotNumber)
}
} }
return nil return nil
} }
@ -351,9 +363,17 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
// FinalizeAndAssemble implements consensus.Engine, setting the final state and // FinalizeAndAssemble implements consensus.Engine, setting the final state and
// assembling the block. // assembling the block.
func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { func (beacon *Beacon) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (result *types.Block, err error) {
ctx, _, spanEnd := telemetry.StartSpan(ctx, "consensus.beacon.FinalizeAndAssemble",
telemetry.Int64Attribute("block.number", int64(header.Number.Uint64())),
telemetry.Int64Attribute("txs.count", int64(len(body.Transactions))),
telemetry.Int64Attribute("withdrawals.count", int64(len(body.Withdrawals))),
)
defer spanEnd(&err)
if !beacon.IsPoSHeader(header) { if !beacon.IsPoSHeader(header) {
return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts) block, delegateErr := beacon.ethone.FinalizeAndAssemble(ctx, chain, header, state, body, receipts)
return block, delegateErr
} }
shanghai := chain.Config().IsShanghai(header.Number, header.Time) shanghai := chain.Config().IsShanghai(header.Number, header.Time)
if shanghai { if shanghai {
@ -367,13 +387,20 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
} }
} }
// Finalize and assemble the block. // Finalize and assemble the block.
_, _, finalizeSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.Finalize")
beacon.Finalize(chain, header, state, body) beacon.Finalize(chain, header, state, body)
finalizeSpanEnd(nil)
// Assign the final state root to header. // Assign the final state root to header.
_, _, rootSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.IntermediateRoot")
header.Root = state.IntermediateRoot(true) header.Root = state.IntermediateRoot(true)
rootSpanEnd(nil)
// Assemble the final block. // Assemble the final block.
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil _, _, blockSpanEnd := telemetry.StartSpan(ctx, "consensus.beacon.NewBlock")
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
blockSpanEnd(nil)
return block, nil
} }
// Seal generates a new sealing request for the given input block and pushes // Seal generates a new sealing request for the given input block and pushes

View file

@ -19,6 +19,7 @@ package clique
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -581,7 +582,7 @@ func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Heade
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
// nor block rewards given, and returns the final block. // nor block rewards given, and returns the final block.
func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { func (c *Clique) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
if len(body.Withdrawals) > 0 { if len(body.Withdrawals) > 0 {
return nil, errors.New("clique does not support withdrawals") return nil, errors.New("clique does not support withdrawals")
} }

View file

@ -18,6 +18,7 @@
package consensus package consensus
import ( import (
"context"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -92,7 +93,7 @@ type Engine interface {
// //
// Note: The block header and state database might be updated to reflect any // Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards). // consensus rules that happen at finalization (e.g. block rewards).
FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) FinalizeAndAssemble(ctx context.Context, chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error)
// Seal generates a new sealing request for the given input block and pushes // Seal generates a new sealing request for the given input block and pushes
// the result into the given channel. // the result into the given channel.

View file

@ -17,6 +17,7 @@
package ethash package ethash
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -513,7 +514,7 @@ func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.
// FinalizeAndAssemble implements consensus.Engine, accumulating the block and // FinalizeAndAssemble implements consensus.Engine, accumulating the block and
// uncle rewards, setting the final state and assembling the block. // uncle rewards, setting the final state and assembling the block.
func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { func (ethash *Ethash) FinalizeAndAssemble(ctx context.Context, chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) {
if len(body.Withdrawals) > 0 { if len(body.Withdrawals) > 0 {
return nil, errors.New("ethash does not support withdrawals") return nil, errors.New("ethash does not support withdrawals")
} }

View file

@ -282,7 +282,7 @@ func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, str
for ; start > 0; start-- { for ; start > 0; start-- {
// Skip all methods and namespaces (i.e. including the dot) // Skip all methods and namespaces (i.e. including the dot)
c := line[start] c := line[start]
if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '1' && c <= '9') { if c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
continue continue
} }
// We've hit an unexpected character, autocomplete form here // We've hit an unexpected character, autocomplete form here

View file

@ -194,9 +194,8 @@ type BlockChainConfig struct {
SnapshotNoBuild bool // Whether the background generation is allowed 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 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. // HistoryPolicy defines the chain history pruning intent.
// Blocks before this number may be unavailable in the chain database. HistoryPolicy history.HistoryPolicy
ChainHistoryMode history.HistoryMode
// Misc options // Misc options
NoPrefetch bool // Whether to disable heuristic state prefetching when processing blocks 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! // Note the returned object is safe to modify!
func DefaultConfig() *BlockChainConfig { func DefaultConfig() *BlockChainConfig {
return &BlockChainConfig{ return &BlockChainConfig{
TrieCleanLimit: 256, TrieCleanLimit: 256,
TrieDirtyLimit: 256, TrieDirtyLimit: 256,
TrieTimeLimit: 5 * time.Minute, TrieTimeLimit: 5 * time.Minute,
StateScheme: rawdb.HashScheme, StateScheme: rawdb.HashScheme,
SnapshotLimit: 256, SnapshotLimit: 256,
SnapshotWait: true, SnapshotWait: true,
ChainHistoryMode: history.KeepAll, HistoryPolicy: history.HistoryPolicy{Mode: history.KeepAll},
// Transaction indexing is disabled by default. // Transaction indexing is disabled by default.
// This is appropriate for most unit tests. // This is appropriate for most unit tests.
TxLookupLimit: -1, TxLookupLimit: -1,
@ -715,82 +714,44 @@ func (bc *BlockChain) loadLastState() error {
// initializeHistoryPruning sets bc.historyPrunePoint. // initializeHistoryPruning sets bc.historyPrunePoint.
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error { func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
var ( freezerTail, _ := bc.db.Tail()
freezerTail, _ = bc.db.Tail() policy := bc.cfg.HistoryPolicy
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")
case history.KeepPostMerge: switch policy.Mode {
if mergePoint == nil { case history.KeepAll:
return errors.New("history pruning requested for unknown network") 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 return nil
case history.KeepPostPrague: case history.KeepPostMerge, history.KeepPostPrague:
if praguePoint == nil { target := policy.Target
return errors.New("history pruning requested for unknown network") // Already at the target.
} if freezerTail == target.BlockNumber {
// Check if already at the prague prune point. bc.historyPrunePoint.Store(target)
if freezerTail == praguePoint.BlockNumber {
bc.historyPrunePoint.Store(praguePoint)
return nil return nil
} }
// Check if database needs pruning. // Database is pruned beyond the target.
if latest != 0 { if freezerTail > target.BlockNumber {
if freezerTail == 0 { return fmt.Errorf("database pruned beyond requested history (tail=%d, target=%d)", freezerTail, target.BlockNumber)
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")
} }
// Fresh database (latest == 0), will sync from prague point. // Database needs pruning (freezerTail < target).
bc.historyPrunePoint.Store(praguePoint) 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 return nil
default: 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 // If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly // while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction. // useless due to the intermediate root hashing after each transaction.
var ( var witness *stateless.Witness
witness *stateless.Witness
witnessStats *stateless.WitnessStats
)
if bc.chainConfig.IsByzantium(block.Number()) { if bc.chainConfig.IsByzantium(block.Number()) {
// Generate witnesses either if we're self-testing, or if it's the // Generate witnesses either if we're self-testing, or if it's the
// only block being inserted. A bit crude, but witnesses are huge, // only block being inserted. A bit crude, but witnesses are huge,
// so we refuse to make an entire chain of them. // so we refuse to make an entire chain of them.
if config.StatelessSelfValidation || config.MakeWitness { if config.StatelessSelfValidation || config.MakeWitness {
witness, err = stateless.NewWitness(block.Header(), bc) witness, err = stateless.NewWitness(block.Header(), bc, config.EnableWitnessStats)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if config.EnableWitnessStats {
witnessStats = stateless.NewWitnessStats()
}
} }
statedb.StartPrefetcher("chain", witness, witnessStats) statedb.StartPrefetcher("chain", witness)
defer statedb.StopPrefetcher() 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 stats.BlockWrite = time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.DatabaseCommits
} }
// Report the collected witness statistics // Report the collected witness statistics
if witnessStats != nil { if witness != nil {
witnessStats.ReportMetrics(block.NumberU64()) witness.ReportMetrics(block.NumberU64())
} }
elapsed := time.Since(startTime) + 1 // prevent zero division elapsed := time.Since(startTime) + 1 // prevent zero division
stats.TotalTime = elapsed stats.TotalTime = elapsed
@ -2628,6 +2583,7 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
// as the txlookups should be changed atomically, and all subsequent // as the txlookups should be changed atomically, and all subsequent
// reads should be blocked until the mutation is complete. // reads should be blocked until the mutation is complete.
bc.txLookupLock.Lock() bc.txLookupLock.Lock()
defer bc.txLookupLock.Unlock()
// Reorg can be executed, start reducing the chain's old blocks and appending // Reorg can be executed, start reducing the chain's old blocks and appending
// the new blocks // the new blocks
@ -2730,9 +2686,6 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
// Reset the tx lookup cache to clear stale txlookup cache. // Reset the tx lookup cache to clear stale txlookup cache.
bc.txLookupCache.Purge() bc.txLookupCache.Purge()
// Release the tx-lookup lock after mutation.
bc.txLookupLock.Unlock()
return nil return nil
} }

View file

@ -296,6 +296,14 @@ func (bc *BlockChain) GetReceiptsRLP(hash common.Hash) rlp.RawValue {
return rawdb.ReadReceiptsRLP(bc.db, hash, number) return rawdb.ReadReceiptsRLP(bc.db, hash, number)
} }
func (bc *BlockChain) GetAccessListRLP(hash common.Hash) rlp.RawValue {
number, ok := rawdb.ReadHeaderNumber(bc.db, hash)
if !ok {
return nil
}
return rawdb.ReadAccessListRLP(bc.db, hash, number)
}
// GetUnclesInChain retrieves all the uncles from a given block backwards until // GetUnclesInChain retrieves all the uncles from a given block backwards until
// a specific distance is reached. // a specific distance is reached.
func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header { func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header {
@ -468,7 +476,7 @@ func (bc *BlockChain) TxIndexProgress() (TxIndexProgress, error) {
} }
// StateIndexProgress returns the historical state indexing progress. // StateIndexProgress returns the historical state indexing progress.
func (bc *BlockChain) StateIndexProgress() (uint64, error) { func (bc *BlockChain) StateIndexProgress() (uint64, uint64, error) {
return bc.triedb.IndexProgress() return bc.triedb.IndexProgress()
} }

View file

@ -36,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash" "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/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "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) { 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))) // 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() ghash := genesis.ToBlock().Hash()
cutoffBlock := blocks[cutoff-1] 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{}) db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer db.Close() defer db.Close()
options := DefaultConfig().WithStateScheme(rawdb.PathScheme) chain, _ := NewBlockChain(db, genesis, beacon.New(ethash.NewFaker()), DefaultConfig().WithStateScheme(rawdb.PathScheme))
chain, _ := NewBlockChain(db, genesis, beacon.New(ethash.NewFaker()), options)
defer chain.Stop() defer chain.Stop()
var ( var (

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"context"
"fmt" "fmt"
"math/big" "math/big"
@ -411,7 +412,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
} }
body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals} body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals}
block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts) block, err := b.engine.FinalizeAndAssemble(context.Background(), cm, b.header, statedb, &body, b.receipts)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -66,10 +66,6 @@ var (
// have enough funds for transfer(topmost call only). // have enough funds for transfer(topmost call only).
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer") 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 // ErrInsufficientBalanceWitness is returned if the transaction sender has enough
// funds to cover the transfer, but not enough to pay for witness access/modification // funds to cover the transfer, but not enough to pay for witness access/modification
// costs for the transaction // costs for the transaction

View 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)
}
}
}

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "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 // 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.SubBalance(sender, amount, tracing.BalanceChangeTransfer)
db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer) db.AddBalance(recipient, amount, tracing.BalanceChangeTransfer)
if rules.IsAmsterdam && !amount.IsZero() && sender != recipient {
db.AddLog(types.EthTransferLog(sender, recipient, amount))
}
} }

View file

@ -77,57 +77,62 @@ func (m *HistoryMode) UnmarshalText(text []byte) error {
return nil return nil
} }
// PrunePoint identifies a specific block for history pruning.
type PrunePoint struct { type PrunePoint struct {
BlockNumber uint64 BlockNumber uint64
BlockHash common.Hash BlockHash common.Hash
} }
// MergePrunePoints contains the pre-defined history pruning cutoff blocks for known networks. // staticPrunePoints contains the pre-defined history pruning cutoff blocks for
// They point to the first post-merge block. Any pruning should truncate *up to* but excluding // known networks, keyed by history mode and genesis hash. They point to the first
// the given block. // block after the respective fork. Any pruning should truncate *up to* but
var MergePrunePoints = map[common.Hash]*PrunePoint{ // excluding the given block.
// mainnet var staticPrunePoints = map[HistoryMode]map[common.Hash]*PrunePoint{
params.MainnetGenesisHash: { KeepPostMerge: {
BlockNumber: 15537393, params.MainnetGenesisHash: {
BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"), BlockNumber: 15537393,
BlockHash: common.HexToHash("0x55b11b918355b1ef9c5db810302ebad0bf2544255b530cdce90674d5887bb286"),
},
params.SepoliaGenesisHash: {
BlockNumber: 1450409,
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"),
},
}, },
// sepolia KeepPostPrague: {
params.SepoliaGenesisHash: { params.MainnetGenesisHash: {
BlockNumber: 1450409, BlockNumber: 22431084,
BlockHash: common.HexToHash("0x229f6b18ca1552f1d5146deceb5387333f40dc6275aebee3f2c5c4ece07d02db"), BlockHash: common.HexToHash("0x50c8cab760b2948349c590461b166773c45d8f4858cccf5a43025ab2960152e8"),
},
params.SepoliaGenesisHash: {
BlockNumber: 7836331,
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
},
}, },
} }
// PraguePrunePoints contains the pre-defined history pruning cutoff blocks for the Prague // HistoryPolicy describes the configured history pruning strategy. It captures
// (Pectra) upgrade. They point to the first post-Prague block. Any pruning should truncate // user intent as opposed to the actual DB state.
// *up to* but excluding the given block. type HistoryPolicy struct {
var PraguePrunePoints = map[common.Hash]*PrunePoint{ Mode HistoryMode
// mainnet - first Prague block (May 7, 2025) // Static prune point for PostMerge/PostPrague, nil otherwise.
params.MainnetGenesisHash: { Target *PrunePoint
BlockNumber: 22431084,
BlockHash: common.HexToHash("0x50c8cab760b2948349c590461b166773c45d8f4858cccf5a43025ab2960152e8"),
},
// sepolia - first Prague block (March 5, 2025)
params.SepoliaGenesisHash: {
BlockNumber: 7836331,
BlockHash: common.HexToHash("0xe6571beb68bf24dbd8a6ba354518996920c55a3f8d8fdca423e391b8ad071f22"),
},
} }
// PrunePoints is an alias for MergePrunePoints for backward compatibility. // NewPolicy constructs a HistoryPolicy from the given mode and genesis hash.
// Deprecated: Use GetPrunePoint or MergePrunePoints directly. func NewPolicy(mode HistoryMode, genesisHash common.Hash) (HistoryPolicy, error) {
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 {
switch mode { switch mode {
case KeepPostMerge: case KeepAll:
return MergePrunePoints[genesisHash] return HistoryPolicy{Mode: KeepAll}, nil
case KeepPostPrague:
return PraguePrunePoints[genesisHash] 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: default:
return nil return HistoryPolicy{}, fmt.Errorf("invalid history mode: %d", mode)
} }
} }

View 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")
}
}

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -605,6 +606,55 @@ func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
} }
} }
// HasAccessList verifies the existence of a block access list for a block.
func HasAccessList(db ethdb.Reader, hash common.Hash, number uint64) bool {
has, _ := db.Has(accessListKey(number, hash))
return has
}
// ReadAccessListRLP retrieves the RLP-encoded block access list for a block from KV.
func ReadAccessListRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
data, _ := db.Get(accessListKey(number, hash))
return data
}
// ReadAccessList retrieves and decodes the block access list for a block.
func ReadAccessList(db ethdb.Reader, hash common.Hash, number uint64) *bal.BlockAccessList {
data := ReadAccessListRLP(db, hash, number)
if len(data) == 0 {
return nil
}
b := new(bal.BlockAccessList)
if err := rlp.DecodeBytes(data, b); err != nil {
log.Error("Invalid BAL RLP", "hash", hash, "err", err)
return nil
}
return b
}
// WriteAccessList RLP-encodes and stores a block access list in the active KV store.
func WriteAccessList(db ethdb.KeyValueWriter, hash common.Hash, number uint64, b *bal.BlockAccessList) {
bytes, err := rlp.EncodeToBytes(b)
if err != nil {
log.Crit("Failed to encode BAL", "err", err)
}
WriteAccessListRLP(db, hash, number, bytes)
}
// WriteAccessListRLP stores a pre-encoded block access list in the active KV store.
func WriteAccessListRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, encoded rlp.RawValue) {
if err := db.Put(accessListKey(number, hash), encoded); err != nil {
log.Crit("Failed to store BAL", "err", err)
}
}
// DeleteAccessList removes a block access list from the active KV store.
func DeleteAccessList(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
if err := db.Delete(accessListKey(number, hash)); err != nil {
log.Crit("Failed to delete BAL", "err", err)
}
}
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps // ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
// the list of logs. When decoding a stored receipt into this object we // the list of logs. When decoding a stored receipt into this object we
// avoid creating the bloom filter. // avoid creating the bloom filter.
@ -659,13 +709,25 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
if body == nil { if body == nil {
return nil return nil
} }
return types.NewBlockWithHeader(header).WithBody(*body) block := types.NewBlockWithHeader(header).WithBody(*body)
// Best-effort assembly of the block access list from the database.
if header.BlockAccessListHash != nil {
al := ReadAccessList(db, hash, number)
block = block.WithAccessListUnsafe(al)
}
return block
} }
// WriteBlock serializes a block into the database, header and body separately. // WriteBlock serializes a block into the database, header and body separately.
func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) { func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) {
WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) hash, number := block.Hash(), block.NumberU64()
WriteBody(db, hash, number, block.Body())
WriteHeader(db, block.Header()) WriteHeader(db, block.Header())
if accessList := block.AccessList(); accessList != nil {
WriteAccessList(db, hash, number, accessList)
}
} }
// WriteAncientBlocks writes entire block data into ancient store and returns the total written size. // WriteAncientBlocks writes entire block data into ancient store and returns the total written size.

View file

@ -27,10 +27,12 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/keccak" "github.com/ethereum/go-ethereum/crypto/keccak"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
) )
// Tests block header storage and retrieval operations. // Tests block header storage and retrieval operations.
@ -899,3 +901,78 @@ func TestHeadersRLPStorage(t *testing.T) {
checkSequence(1, 1) // Only block 1 checkSequence(1, 1) // Only block 1
checkSequence(1, 2) // Genesis + block 1 checkSequence(1, 2) // Genesis + block 1
} }
func makeTestBAL(t *testing.T) (rlp.RawValue, *bal.BlockAccessList) {
t.Helper()
cb := bal.NewConstructionBlockAccessList()
addr := common.HexToAddress("0x1111111111111111111111111111111111111111")
cb.AccountRead(addr)
cb.StorageRead(addr, common.BytesToHash([]byte{0x01}))
cb.StorageWrite(0, addr, common.BytesToHash([]byte{0x02}), common.BytesToHash([]byte{0xaa}))
cb.BalanceChange(0, addr, uint256.NewInt(100))
cb.NonceChange(addr, 0, 1)
var buf bytes.Buffer
if err := cb.EncodeRLP(&buf); err != nil {
t.Fatalf("failed to encode BAL: %v", err)
}
encoded := buf.Bytes()
var decoded bal.BlockAccessList
if err := rlp.DecodeBytes(encoded, &decoded); err != nil {
t.Fatalf("failed to decode BAL: %v", err)
}
return encoded, &decoded
}
// TestBALStorage tests write/read/delete of BALs in the KV store.
func TestBALStorage(t *testing.T) {
db := NewMemoryDatabase()
hash := common.BytesToHash([]byte{0x03, 0x14})
number := uint64(42)
// Check that no BAL exists in a new database.
if HasAccessList(db, hash, number) {
t.Fatal("BAL found in new database")
}
if b := ReadAccessList(db, hash, number); b != nil {
t.Fatalf("non existent BAL returned: %v", b)
}
// Write a BAL and verify it can be read back.
encoded, testBAL := makeTestBAL(t)
WriteAccessList(db, hash, number, testBAL)
if !HasAccessList(db, hash, number) {
t.Fatal("HasAccessList returned false after write")
}
if blob := ReadAccessListRLP(db, hash, number); len(blob) == 0 {
t.Fatal("ReadAccessListRLP returned empty after write")
}
if b := ReadAccessList(db, hash, number); b == nil {
t.Fatal("ReadAccessList returned nil after write")
} else if b.Hash() != testBAL.Hash() {
t.Fatalf("BAL hash mismatch: got %x, want %x", b.Hash(), testBAL.Hash())
}
// Also test WriteAccessListRLP with pre-encoded data.
hash2 := common.BytesToHash([]byte{0x03, 0x15})
WriteAccessListRLP(db, hash2, number, encoded)
if b := ReadAccessList(db, hash2, number); b == nil {
t.Fatal("ReadAccessList returned nil after WriteAccessListRLP")
} else if b.Hash() != testBAL.Hash() {
t.Fatalf("BAL hash mismatch after WriteAccessListRLP: got %x, want %x", b.Hash(), testBAL.Hash())
}
// Delete the BAL and verify it's gone.
DeleteAccessList(db, hash, number)
if HasAccessList(db, hash, number) {
t.Fatal("HasAccessList returned true after delete")
}
if b := ReadAccessList(db, hash, number); b != nil {
t.Fatalf("deleted BAL returned: %v", b)
}
}

View file

@ -413,6 +413,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
tds stat tds stat
numHashPairings stat numHashPairings stat
hashNumPairings stat hashNumPairings stat
blockAccessList stat
legacyTries stat legacyTries stat
stateLookups stat stateLookups stat
accountTries stat accountTries stat
@ -478,12 +479,15 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
bodies.add(size) bodies.add(size)
case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength): case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
receipts.add(size) receipts.add(size)
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix): case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix) && len(key) == (len(headerPrefix)+8+common.HashLength+len(headerTDSuffix)):
tds.add(size) tds.add(size)
case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix): case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix) && len(key) == (len(headerPrefix)+8+len(headerHashSuffix)):
numHashPairings.add(size) numHashPairings.add(size)
case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
hashNumPairings.add(size) hashNumPairings.add(size)
case bytes.HasPrefix(key, accessListPrefix) && len(key) == len(accessListPrefix)+8+common.HashLength:
blockAccessList.add(size)
case IsLegacyTrieNode(key, it.Value()): case IsLegacyTrieNode(key, it.Value()):
legacyTries.add(size) legacyTries.add(size)
case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength: case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength:
@ -625,6 +629,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
{"Key-Value store", "Difficulties (deprecated)", tds.sizeString(), tds.countString()}, {"Key-Value store", "Difficulties (deprecated)", tds.sizeString(), tds.countString()},
{"Key-Value store", "Block number->hash", numHashPairings.sizeString(), numHashPairings.countString()}, {"Key-Value store", "Block number->hash", numHashPairings.sizeString(), numHashPairings.countString()},
{"Key-Value store", "Block hash->number", hashNumPairings.sizeString(), hashNumPairings.countString()}, {"Key-Value store", "Block hash->number", hashNumPairings.sizeString(), hashNumPairings.countString()},
{"Key-Value store", "Block accessList", blockAccessList.sizeString(), blockAccessList.countString()},
{"Key-Value store", "Transaction index", txLookups.sizeString(), txLookups.countString()}, {"Key-Value store", "Transaction index", txLookups.sizeString(), txLookups.countString()},
{"Key-Value store", "Log index filter-map rows", filterMapRows.sizeString(), filterMapRows.countString()}, {"Key-Value store", "Log index filter-map rows", filterMapRows.sizeString(), filterMapRows.countString()},
{"Key-Value store", "Log index last-block-of-map", filterMapLastBlock.sizeString(), filterMapLastBlock.countString()}, {"Key-Value store", "Log index last-block-of-map", filterMapLastBlock.sizeString(), filterMapLastBlock.countString()},

View file

@ -26,13 +26,7 @@ func atomicRename(src, dest string) error {
if err := os.Rename(src, dest); err != nil { if err := os.Rename(src, dest); err != nil {
return err return err
} }
dir, err := os.Open(filepath.Dir(src)) return syncDir(filepath.Dir(src))
if err != nil {
return err
}
defer dir.Close()
return dir.Sync()
} }
// copyFrom copies data from 'srcPath' at offset 'offset' into 'destPath'. // copyFrom copies data from 'srcPath' at offset 'offset' into 'destPath'.

View file

@ -0,0 +1,49 @@
// Copyright 2022 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/>.
//go:build !windows
// +build !windows
package rawdb
import (
"errors"
"os"
"syscall"
)
// syncDir ensures that the directory metadata (e.g. newly renamed files)
// is flushed to durable storage.
func syncDir(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
// Some file systems do not support fsyncing directories (e.g. some FUSE
// mounts). Ignore EINVAL in those cases.
if err := f.Sync(); err != nil {
if errors.Is(err, os.ErrInvalid) {
return nil
}
if patherr, ok := err.(*os.PathError); ok && patherr.Err == syscall.EINVAL {
return nil
}
return err
}
return nil
}

View file

@ -0,0 +1,26 @@
// Copyright 2022 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/>.
//go:build windows
// +build windows
package rawdb
// syncDir is a no-op on Windows. Fsyncing a directory handle is not
// supported and returns "Access is denied".
func syncDir(name string) error {
return nil
}

View file

@ -112,6 +112,7 @@ var (
blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
accessListPrefix = []byte("j") // accessListPrefix + num (uint64 big endian) + hash -> block access list
txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
@ -214,6 +215,11 @@ func blockReceiptsKey(number uint64, hash common.Hash) []byte {
return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...) return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
} }
// accessListKey = accessListPrefix + num (uint64 big endian) + hash
func accessListKey(number uint64, hash common.Hash) []byte {
return append(append(accessListPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
}
// txLookupKey = txLookupPrefix + hash // txLookupKey = txLookupPrefix + hash
func txLookupKey(hash common.Hash) []byte { func txLookupKey(hash common.Hash) []byte {
return append(txLookupPrefix, hash.Bytes()...) return append(txLookupPrefix, hash.Bytes()...)

View file

@ -39,6 +39,10 @@ type Database interface {
// Reader returns a state reader associated with the specified state root. // Reader returns a state reader associated with the specified state root.
Reader(root common.Hash) (Reader, error) Reader(root common.Hash) (Reader, error)
// Iteratee returns a state iteratee associated with the specified state root,
// through which the account iterator and storage iterator can be created.
Iteratee(root common.Hash) (Iteratee, error)
// OpenTrie opens the main account trie. // OpenTrie opens the main account trie.
OpenTrie(root common.Hash) (Trie, error) OpenTrie(root common.Hash) (Trie, error)
@ -48,9 +52,6 @@ type Database interface {
// TrieDB returns the underlying trie database for managing trie nodes. // TrieDB returns the underlying trie database for managing trie nodes.
TrieDB() *triedb.Database TrieDB() *triedb.Database
// Snapshot returns the underlying state snapshot.
Snapshot() *snapshot.Tree
// Commit flushes all pending writes and finalizes the state transition, // Commit flushes all pending writes and finalizes the state transition,
// committing the changes to the underlying storage. It returns an error // committing the changes to the underlying storage. It returns an error
// if the commit fails. // if the commit fails.
@ -310,6 +311,12 @@ func (db *CachingDB) Commit(update *stateUpdate) error {
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet()) return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
} }
// Iteratee returns a state iteratee associated with the specified state root,
// through which the account iterator and storage iterator can be created.
func (db *CachingDB) Iteratee(root common.Hash) (Iteratee, error) {
return newStateIteratee(!db.triedb.IsVerkle(), root, db.triedb, db.snap)
}
// mustCopyTrie returns a deep-copied trie. // mustCopyTrie returns a deep-copied trie.
func mustCopyTrie(t Trie) Trie { func mustCopyTrie(t Trie) Trie {
switch t := t.(type) { switch t := t.(type) {

View file

@ -22,7 +22,6 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
@ -289,14 +288,15 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
return db.triedb return db.triedb
} }
// Snapshot returns the underlying state snapshot.
func (db *HistoricDB) Snapshot() *snapshot.Tree {
return nil
}
// Commit flushes all pending writes and finalizes the state transition, // Commit flushes all pending writes and finalizes the state transition,
// committing the changes to the underlying storage. It returns an error // committing the changes to the underlying storage. It returns an error
// if the commit fails. // if the commit fails.
func (db *HistoricDB) Commit(update *stateUpdate) error { func (db *HistoricDB) Commit(update *stateUpdate) error {
return errors.New("not implemented") return errors.New("not implemented")
} }
// Iteratee returns a state iteratee associated with the specified state root,
// through which the account iterator and storage iterator can be created.
func (db *HistoricDB) Iteratee(root common.Hash) (Iteratee, error) {
return nil, errors.New("not implemented")
}

View file

@ -0,0 +1,435 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package state
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
)
// Iterator is an iterator to step over all the accounts or the specific
// storage in the specific state.
type Iterator interface {
// Next steps the iterator forward one element. It returns false if the iterator
// is exhausted or if an error occurs. Any error encountered is retained and
// can be retrieved via Error().
Next() bool
// Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit.
Error() error
// Hash returns the hash of the account or storage slot the iterator is
// currently at.
Hash() common.Hash
// Release releases associated resources. Release should always succeed and
// can be called multiple times without causing error.
Release()
}
// AccountIterator is an iterator to step over all the accounts in the
// specific state.
type AccountIterator interface {
Iterator
// Address returns the raw account address the iterator is currently at.
// An error will be returned if the preimage is not available.
Address() (common.Address, error)
// Account returns the RLP encoded account the iterator is currently at.
// An error will be retained if the iterator becomes invalid.
Account() []byte
}
// StorageIterator is an iterator to step over the specific storage in the
// specific state.
type StorageIterator interface {
Iterator
// Key returns the raw storage slot key the iterator is currently at.
// An error will be returned if the preimage is not available.
Key() (common.Hash, error)
// Slot returns the storage slot the iterator is currently at. An error will
// be retained if the iterator becomes invalid.
Slot() []byte
}
// Iteratee wraps the NewIterator methods for traversing the accounts and
// storages of the specific state.
type Iteratee interface {
// NewAccountIterator creates an account iterator for the state specified by
// the given root. It begins at a specified starting position, corresponding
// to a particular initial key (or the next key if the specified one does
// not exist).
//
// The starting position here refers to the hash of the account address.
NewAccountIterator(start common.Hash) (AccountIterator, error)
// NewStorageIterator creates a storage iterator for the state specified by
// the address hash. It begins at a specified starting position, corresponding
// to a particular initial key (or the next key if the specified one does
// not exist).
//
// The starting position here refers to the hash of the slot key.
NewStorageIterator(addressHash common.Hash, start common.Hash) (StorageIterator, error)
}
// PreimageReader wraps the function Preimage for accessing the preimage of
// a given hash.
type PreimageReader interface {
// Preimage returns the preimage of associated hash.
Preimage(hash common.Hash) []byte
}
// flatAccountIterator is a wrapper around the underlying flat state iterator.
// Before returning data from the iterator, it performs an additional conversion
// to bridge the slim encoding with the full encoding format.
type flatAccountIterator struct {
err error
it snapshot.AccountIterator
preimage PreimageReader
}
// newFlatAccountIterator constructs the account iterator with the provided
// flat state iterator.
func newFlatAccountIterator(it snapshot.AccountIterator, preimage PreimageReader) *flatAccountIterator {
return &flatAccountIterator{it: it, preimage: preimage}
}
// Next steps the iterator forward one element. It returns false if the iterator
// is exhausted or if an error occurs. Any error encountered is retained and
// can be retrieved via Error().
func (ai *flatAccountIterator) Next() bool {
if ai.err != nil {
return false
}
return ai.it.Next()
}
// Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit.
func (ai *flatAccountIterator) Error() error {
if ai.err != nil {
return ai.err
}
return ai.it.Error()
}
// Hash returns the hash of the account or storage slot the iterator is
// currently at.
func (ai *flatAccountIterator) Hash() common.Hash {
return ai.it.Hash()
}
// Release releases associated resources. Release should always succeed and
// can be called multiple times without causing error.
func (ai *flatAccountIterator) Release() {
ai.it.Release()
}
// Address returns the raw account address the iterator is currently at.
// An error will be returned if the preimage is not available.
func (ai *flatAccountIterator) Address() (common.Address, error) {
if ai.preimage == nil {
return common.Address{}, errors.New("account address is not available")
}
preimage := ai.preimage.Preimage(ai.Hash())
if preimage == nil {
return common.Address{}, errors.New("account address is not available")
}
return common.BytesToAddress(preimage), nil
}
// Account returns the account data the iterator is currently at. The account
// data is encoded as slim format from the underlying iterator, the conversion
// is required.
func (ai *flatAccountIterator) Account() []byte {
data, err := types.FullAccountRLP(ai.it.Account())
if err != nil {
ai.err = err
return nil
}
return data
}
// flatStorageIterator is a wrapper around the underlying flat state iterator.
type flatStorageIterator struct {
it snapshot.StorageIterator
preimage PreimageReader
}
// newFlatStorageIterator constructs the storage iterator with the provided
// flat state iterator.
func newFlatStorageIterator(it snapshot.StorageIterator, preimage PreimageReader) *flatStorageIterator {
return &flatStorageIterator{it: it, preimage: preimage}
}
// Next steps the iterator forward one element. It returns false if the iterator
// is exhausted or if an error occurs. Any error encountered is retained and
// can be retrieved via Error().
func (si *flatStorageIterator) Next() bool {
return si.it.Next()
}
// Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit.
func (si *flatStorageIterator) Error() error {
return si.it.Error()
}
// Hash returns the hash of the account or storage slot the iterator is
// currently at.
func (si *flatStorageIterator) Hash() common.Hash {
return si.it.Hash()
}
// Release releases associated resources. Release should always succeed and
// can be called multiple times without causing error.
func (si *flatStorageIterator) Release() {
si.it.Release()
}
// Key returns the raw storage slot key the iterator is currently at.
// An error will be returned if the preimage is not available.
func (si *flatStorageIterator) Key() (common.Hash, error) {
if si.preimage == nil {
return common.Hash{}, errors.New("slot key is not available")
}
preimage := si.preimage.Preimage(si.Hash())
if preimage == nil {
return common.Hash{}, errors.New("slot key is not available")
}
return common.BytesToHash(preimage), nil
}
// Slot returns the storage slot data the iterator is currently at.
func (si *flatStorageIterator) Slot() []byte {
return si.it.Slot()
}
// merkleIterator implements the Iterator interface, providing functions to traverse
// the accounts or storages with the manner of Merkle-Patricia-Trie.
type merkleIterator struct {
tr Trie
it *trie.Iterator
account bool
}
// newMerkleTrieIterator constructs the iterator with the given trie and starting position.
func newMerkleTrieIterator(tr Trie, start common.Hash, account bool) (*merkleIterator, error) {
it, err := tr.NodeIterator(start.Bytes())
if err != nil {
return nil, err
}
return &merkleIterator{
tr: tr,
it: trie.NewIterator(it),
account: account,
}, nil
}
// Next steps the iterator forward one element. It returns false if the iterator
// is exhausted or if an error occurs. Any error encountered is retained and
// can be retrieved via Error().
func (ti *merkleIterator) Next() bool {
return ti.it.Next()
}
// Error returns any failure that occurred during iteration, which might have
// caused a premature iteration exit.
func (ti *merkleIterator) Error() error {
return ti.it.Err
}
// Hash returns the hash of the account or storage slot the iterator is
// currently at.
func (ti *merkleIterator) Hash() common.Hash {
return common.BytesToHash(ti.it.Key)
}
// Release releases associated resources. Release should always succeed and
// can be called multiple times without causing error.
func (ti *merkleIterator) Release() {}
// Address returns the raw account address the iterator is currently at.
// An error will be returned if the preimage is not available.
func (ti *merkleIterator) Address() (common.Address, error) {
if !ti.account {
return common.Address{}, errors.New("account address is not available")
}
preimage := ti.tr.GetKey(ti.it.Key)
if preimage == nil {
return common.Address{}, errors.New("account address is not available")
}
return common.BytesToAddress(preimage), nil
}
// Account returns the account data the iterator is currently at.
func (ti *merkleIterator) Account() []byte {
if !ti.account {
return nil
}
return ti.it.Value
}
// Key returns the raw storage slot key the iterator is currently at.
// An error will be returned if the preimage is not available.
func (ti *merkleIterator) Key() (common.Hash, error) {
if ti.account {
return common.Hash{}, errors.New("slot key is not available")
}
preimage := ti.tr.GetKey(ti.it.Key)
if preimage == nil {
return common.Hash{}, errors.New("slot key is not available")
}
return common.BytesToHash(preimage), nil
}
// Slot returns the storage slot the iterator is currently at.
func (ti *merkleIterator) Slot() []byte {
if ti.account {
return nil
}
return ti.it.Value
}
// stateIteratee implements Iteratee interface, providing the state traversal
// functionalities of a specific state.
type stateIteratee struct {
merkle bool
root common.Hash
triedb *triedb.Database
snap *snapshot.Tree
}
func newStateIteratee(merkle bool, root common.Hash, triedb *triedb.Database, snap *snapshot.Tree) (*stateIteratee, error) {
return &stateIteratee{
merkle: merkle,
root: root,
triedb: triedb,
snap: snap,
}, nil
}
// NewAccountIterator creates an account iterator for the state specified by
// the given root. It begins at a specified starting position, corresponding
// to a particular initial key (or the next key if the specified one does
// not exist).
//
// The starting position here refers to the hash of the account address.
func (si *stateIteratee) NewAccountIterator(start common.Hash) (AccountIterator, error) {
// If the external snapshot is available (hash scheme), try to initialize
// the account iterator from there first.
if si.snap != nil {
it, err := si.snap.AccountIterator(si.root, start)
if err == nil {
return newFlatAccountIterator(it, si.triedb), nil
}
}
// If the external snapshot is not available, try to initialize the
// account iterator from the trie database (path scheme)
it, err := si.triedb.AccountIterator(si.root, start)
if err == nil {
return newFlatAccountIterator(it, si.triedb), nil
}
if !si.merkle {
return nil, fmt.Errorf("state %x is not available for account traversal", si.root)
}
// The snapshot is not usable so far, construct the account iterator from
// the trie as the fallback. It's not as efficient as the flat state iterator.
tr, err := trie.NewStateTrie(trie.StateTrieID(si.root), si.triedb)
if err != nil {
return nil, err
}
return newMerkleTrieIterator(tr, start, true)
}
// NewStorageIterator creates a storage iterator for the state specified by
// the address hash. It begins at a specified starting position, corresponding
// to a particular initial key (or the next key if the specified one does not exist).
//
// The starting position here refers to the hash of the slot key.
func (si *stateIteratee) NewStorageIterator(addressHash common.Hash, start common.Hash) (StorageIterator, error) {
// If the external snapshot is available (hash scheme), try to initialize
// the storage iterator from there first.
if si.snap != nil {
it, err := si.snap.StorageIterator(si.root, addressHash, start)
if err == nil {
return newFlatStorageIterator(it, si.triedb), nil
}
}
// If the external snapshot is not available, try to initialize the
// storage iterator from the trie database (path scheme)
it, err := si.triedb.StorageIterator(si.root, addressHash, start)
if err == nil {
return newFlatStorageIterator(it, si.triedb), nil
}
if !si.merkle {
return nil, fmt.Errorf("state %x is not available for storage traversal", si.root)
}
// The snapshot is not usable so far, construct the storage iterator from
// the trie as the fallback. It's not as efficient as the flat state iterator.
tr, err := trie.NewStateTrie(trie.StateTrieID(si.root), si.triedb)
if err != nil {
return nil, err
}
acct, err := tr.GetAccountByHash(addressHash)
if err != nil {
return nil, err
}
if acct == nil || acct.Root == types.EmptyRootHash {
return &exhaustedIterator{}, nil
}
storageTr, err := trie.NewStateTrie(trie.StorageTrieID(si.root, addressHash, acct.Root), si.triedb)
if err != nil {
return nil, err
}
return newMerkleTrieIterator(storageTr, start, false)
}
type exhaustedIterator struct{}
func (e exhaustedIterator) Next() bool {
return false
}
func (e exhaustedIterator) Error() error {
return nil
}
func (e exhaustedIterator) Hash() common.Hash {
return common.Hash{}
}
func (e exhaustedIterator) Release() {
}
func (e exhaustedIterator) Key() (common.Hash, error) {
return common.Hash{}, nil
}
func (e exhaustedIterator) Slot() []byte {
return nil
}

View file

@ -0,0 +1,262 @@
// Copyright 2026 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package state
import (
"bytes"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
// TestExhaustedIterator verifies the exhaustedIterator sentinel: Next is false,
// Error is nil, Hash/Key are zero, Slot is nil, and double Release is safe.
func TestExhaustedIterator(t *testing.T) {
var it exhaustedIterator
if it.Next() {
t.Fatal("Next() returned true")
}
if err := it.Error(); err != nil {
t.Fatalf("Error() = %v, want nil", err)
}
if hash := it.Hash(); hash != (common.Hash{}) {
t.Fatalf("Hash() = %x, want zero", hash)
}
if key, err := it.Key(); key != (common.Hash{}) || err != nil {
t.Fatalf("Key() = %x, %v; want zero, nil", key, err)
}
if slot := it.Slot(); slot != nil {
t.Fatalf("Slot() = %x, want nil", slot)
}
it.Release()
it.Release()
}
// TestAccountIterator tests the account iterator: correct count, ascending
// hash order, valid full-format RLP, data integrity, address preimage
// resolution, and seek behavior.
func TestAccountIterator(t *testing.T) {
testAccountIterator(t, rawdb.HashScheme)
testAccountIterator(t, rawdb.PathScheme)
}
func testAccountIterator(t *testing.T, scheme string) {
_, sdb, ndb, root, accounts := makeTestState(scheme)
ndb.Commit(root, false)
iteratee, err := sdb.Iteratee(root)
if err != nil {
t.Fatalf("(%s) failed to create iteratee: %v", scheme, err)
}
// Build lookups from address hash.
addrByHash := make(map[common.Hash]*testAccount)
for _, acc := range accounts {
addrByHash[crypto.Keccak256Hash(acc.address.Bytes())] = acc
}
// --- Full iteration: count, ordering, RLP validity, data integrity, address resolution ---
acctIt, err := iteratee.NewAccountIterator(common.Hash{})
if err != nil {
t.Fatalf("(%s) failed to create account iterator: %v", scheme, err)
}
var (
hashes []common.Hash
prevHash common.Hash
)
for acctIt.Next() {
hash := acctIt.Hash()
if hash == (common.Hash{}) {
t.Fatalf("(%s) zero hash at position %d", scheme, len(hashes))
}
if len(hashes) > 0 && bytes.Compare(prevHash.Bytes(), hash.Bytes()) >= 0 {
t.Fatalf("(%s) hashes not ascending: %x >= %x", scheme, prevHash, hash)
}
prevHash = hash
hashes = append(hashes, hash)
// Decode and verify account data.
blob := acctIt.Account()
if blob == nil {
t.Fatalf("(%s) nil account at %x", scheme, hash)
}
var decoded types.StateAccount
if err := rlp.DecodeBytes(blob, &decoded); err != nil {
t.Fatalf("(%s) bad RLP at %x: %v", scheme, hash, err)
}
acc := addrByHash[hash]
if decoded.Nonce != acc.nonce {
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, decoded.Nonce, acc.nonce)
}
if decoded.Balance.Cmp(acc.balance) != 0 {
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, decoded.Balance, acc.balance)
}
// Verify address preimage resolution.
addr, err := acctIt.Address()
if err != nil {
t.Fatalf("(%s) failed to address: %v", scheme, err)
}
if addr != acc.address {
t.Fatalf("(%s) Address() = %x, want %x", scheme, addr, acc.address)
}
}
acctIt.Release()
if err := acctIt.Error(); err != nil {
t.Fatalf("(%s) iteration error: %v", scheme, err)
}
if len(hashes) != len(accounts) {
t.Fatalf("(%s) iterated %d accounts, want %d", scheme, len(hashes), len(accounts))
}
// --- Seek: starting from midpoint should skip earlier entries ---
mid := hashes[len(hashes)/2]
seekIt, err := iteratee.NewAccountIterator(mid)
if err != nil {
t.Fatalf("(%s) failed to create seeked iterator: %v", scheme, err)
}
seekCount := 0
for seekIt.Next() {
if bytes.Compare(seekIt.Hash().Bytes(), mid.Bytes()) < 0 {
t.Fatalf("(%s) seeked iterator returned hash before start", scheme)
}
seekCount++
}
seekIt.Release()
if seekCount != len(hashes)/2 {
t.Fatalf("(%s) unexpected seeked count, %d != %d", scheme, seekCount, len(hashes)/2)
}
}
// TestStorageIterator tests the storage iterator: correct slot counts against
// the trie, ascending hash order, non-nil slot data, key preimage resolution,
// seek behavior, and empty-storage accounts.
func TestStorageIterator(t *testing.T) {
testStorageIterator(t, rawdb.HashScheme)
testStorageIterator(t, rawdb.PathScheme)
}
func testStorageIterator(t *testing.T, scheme string) {
_, sdb, ndb, root, accounts := makeTestState(scheme)
ndb.Commit(root, false)
iteratee, err := sdb.Iteratee(root)
if err != nil {
t.Fatalf("(%s) failed to create iteratee: %v", scheme, err)
}
// --- Slot count and ordering for every account ---
var withStorage common.Hash // remember an account that has storage for seek test
for _, acc := range accounts {
addrHash := crypto.Keccak256Hash(acc.address.Bytes())
expected := countStorageSlots(t, scheme, sdb, root, addrHash)
storageIt, err := iteratee.NewStorageIterator(addrHash, common.Hash{})
if err != nil {
t.Fatalf("(%s) failed to create storage iterator for %x: %v", scheme, acc.address, err)
}
count := 0
var prevHash common.Hash
for storageIt.Next() {
hash := storageIt.Hash()
if count > 0 && bytes.Compare(prevHash.Bytes(), hash.Bytes()) >= 0 {
t.Fatalf("(%s) storage hashes not ascending for %x", scheme, acc.address)
}
prevHash = hash
if storageIt.Slot() == nil {
t.Fatalf("(%s) nil slot at %x", scheme, hash)
}
// Check key preimage resolution on first slot.
if _, err := storageIt.Key(); err != nil {
t.Fatalf("(%s) Key() failed to resolve", scheme)
}
count++
}
if err := storageIt.Error(); err != nil {
t.Fatalf("(%s) storage iteration error for %x: %v", scheme, acc.address, err)
}
storageIt.Release()
if count != expected {
t.Fatalf("(%s) account %x: %d slots, want %d", scheme, acc.address, count, expected)
}
if count > 0 {
withStorage = addrHash
}
}
// --- Seek: starting from second slot should skip the first ---
if withStorage == (common.Hash{}) {
t.Fatalf("(%s) no account with storage found", scheme)
}
fullIt, err := iteratee.NewStorageIterator(withStorage, common.Hash{})
if err != nil {
t.Fatalf("(%s) failed to create full storage iterator: %v", scheme, err)
}
var slotHashes []common.Hash
for fullIt.Next() {
slotHashes = append(slotHashes, fullIt.Hash())
}
fullIt.Release()
seekIt, err := iteratee.NewStorageIterator(withStorage, slotHashes[1])
if err != nil {
t.Fatalf("(%s) failed to create seeked storage iterator: %v", scheme, err)
}
seekCount := 0
for seekIt.Next() {
if bytes.Compare(seekIt.Hash().Bytes(), slotHashes[1].Bytes()) < 0 {
t.Fatalf("(%s) seeked storage iterator returned hash before start", scheme)
}
seekCount++
}
seekIt.Release()
if seekCount != len(slotHashes)-1 {
t.Fatalf("(%s) unexpected seeked storage count %d != %d", scheme, seekCount, len(slotHashes)-1)
}
}
// countStorageSlots counts storage slots for an account by opening the
// storage trie directly.
func countStorageSlots(t *testing.T, scheme string, sdb Database, root common.Hash, addrHash common.Hash) int {
t.Helper()
accTrie, err := trie.NewStateTrie(trie.StateTrieID(root), sdb.TrieDB())
if err != nil {
t.Fatalf("(%s) failed to open account trie: %v", scheme, err)
}
acct, err := accTrie.GetAccountByHash(addrHash)
if err != nil || acct == nil || acct.Root == types.EmptyRootHash {
return 0
}
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(root, addrHash, acct.Root), sdb.TrieDB())
if err != nil {
t.Fatalf("(%s) failed to open storage trie for %x: %v", scheme, addrHash, err)
}
it := trie.NewIterator(storageTrie.MustNodeIterator(nil))
count := 0
for it.Next() {
count++
}
return count
}

View file

@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/trie/bintrie"
) )
@ -45,6 +44,7 @@ type DumpConfig struct {
type DumpCollector interface { type DumpCollector interface {
// OnRoot is called with the state root // OnRoot is called with the state root
OnRoot(common.Hash) OnRoot(common.Hash)
// OnAccount is called once for each account in the trie // OnAccount is called once for each account in the trie
OnAccount(*common.Address, DumpAccount) OnAccount(*common.Address, DumpAccount)
} }
@ -65,9 +65,10 @@ type DumpAccount struct {
type Dump struct { type Dump struct {
Root string `json:"root"` Root string `json:"root"`
Accounts map[string]DumpAccount `json:"accounts"` Accounts map[string]DumpAccount `json:"accounts"`
// Next can be set to represent that this dump is only partial, and Next // Next can be set to represent that this dump is only partial, and Next
// is where an iterator should be positioned in order to continue the dump. // is where an iterator should be positioned in order to continue the dump.
Next []byte `json:"next,omitempty"` // nil if no more accounts Next hexutil.Bytes `json:"next,omitempty"` // nil if no more accounts
} }
// OnRoot implements DumpCollector interface // OnRoot implements DumpCollector interface
@ -114,9 +115,6 @@ func (d iterativeDump) OnRoot(root common.Hash) {
// DumpToCollector iterates the state according to the given options and inserts // DumpToCollector iterates the state according to the given options and inserts
// the items into a collector for aggregation or serialization. // the items into a collector for aggregation or serialization.
//
// The state iterator is still trie-based and can be converted to snapshot-based
// once the state snapshot is fully integrated into database. TODO(rjl493456442).
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) { func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
// Sanitize the input to allow nil configs // Sanitize the input to allow nil configs
if conf == nil { if conf == nil {
@ -131,20 +129,23 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
log.Info("Trie dumping started", "root", s.originalRoot) log.Info("Trie dumping started", "root", s.originalRoot)
c.OnRoot(s.originalRoot) c.OnRoot(s.originalRoot)
tr, err := s.db.OpenTrie(s.originalRoot) iteratee, err := s.db.Iteratee(s.originalRoot)
if err != nil { if err != nil {
return nil return nil
} }
trieIt, err := tr.NodeIterator(conf.Start) var startHash common.Hash
if conf.Start != nil {
startHash = common.BytesToHash(conf.Start)
}
acctIt, err := iteratee.NewAccountIterator(startHash)
if err != nil { if err != nil {
log.Error("Trie dumping error", "err", err)
return nil return nil
} }
it := trie.NewIterator(trieIt) defer acctIt.Release()
for it.Next() { for acctIt.Next() {
var data types.StateAccount var data types.StateAccount
if err := rlp.DecodeBytes(it.Value, &data); err != nil { if err := rlp.DecodeBytes(acctIt.Account(), &data); err != nil {
panic(err) panic(err)
} }
var ( var (
@ -153,63 +154,55 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
Nonce: data.Nonce, Nonce: data.Nonce,
Root: data.Root[:], Root: data.Root[:],
CodeHash: data.CodeHash, CodeHash: data.CodeHash,
AddressHash: it.Key, AddressHash: acctIt.Hash().Bytes(),
} }
address *common.Address address *common.Address
addr common.Address
addrBytes = tr.GetKey(it.Key)
) )
if addrBytes == nil { addrBytes, err := acctIt.Address()
if err != nil {
missingPreimages++ missingPreimages++
if conf.OnlyWithAddresses { if conf.OnlyWithAddresses {
continue continue
} }
} else { } else {
addr = common.BytesToAddress(addrBytes) address = &addrBytes
address = &addr
account.Address = address account.Address = address
} }
obj := newObject(s, addr, &data) obj := newObject(s, addrBytes, &data)
if !conf.SkipCode { if !conf.SkipCode {
account.Code = obj.Code() account.Code = obj.Code()
} }
if !conf.SkipStorage { if !conf.SkipStorage {
account.Storage = make(map[common.Hash]string) account.Storage = make(map[common.Hash]string)
storageTr, err := s.db.OpenStorageTrie(s.originalRoot, addr, obj.Root(), tr) storageIt, err := iteratee.NewStorageIterator(acctIt.Hash(), common.Hash{})
if err != nil { if err != nil {
log.Error("Failed to load storage trie", "err", err) log.Error("Failed to load storage trie", "err", err)
continue continue
} }
trieIt, err := storageTr.NodeIterator(nil)
if err != nil {
log.Error("Failed to create trie iterator", "err", err)
continue
}
storageIt := trie.NewIterator(trieIt)
for storageIt.Next() { for storageIt.Next() {
_, content, _, err := rlp.Split(storageIt.Value) _, content, _, err := rlp.Split(storageIt.Slot())
if err != nil { if err != nil {
log.Error("Failed to decode the value returned by iterator", "error", err) log.Error("Failed to decode the value returned by iterator", "error", err)
continue continue
} }
key := storageTr.GetKey(storageIt.Key) key, err := storageIt.Key()
if key == nil { if err != nil {
continue continue
} }
account.Storage[common.BytesToHash(key)] = common.Bytes2Hex(content) account.Storage[key] = common.Bytes2Hex(content)
} }
storageIt.Release()
} }
c.OnAccount(address, account) c.OnAccount(address, account)
accounts++ accounts++
if time.Since(logged) > 8*time.Second { if time.Since(logged) > 8*time.Second {
log.Info("Trie dumping in progress", "at", common.Bytes2Hex(it.Key), "accounts", accounts, log.Info("Trie dumping in progress", "at", acctIt.Hash().Hex(), "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
"elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now() logged = time.Now()
} }
if conf.Max > 0 && accounts >= conf.Max { if conf.Max > 0 && accounts >= conf.Max {
if it.Next() { if acctIt.Next() {
nextKey = it.Key nextKey = acctIt.Hash().Bytes()
} }
break break
} }
@ -217,9 +210,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
if missingPreimages > 0 { if missingPreimages > 0 {
log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages) log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
} }
log.Info("Trie dumping complete", "accounts", accounts, log.Info("Trie dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
"elapsed", common.PrettyDuration(time.Since(start)))
return nextKey return nextKey
} }

View file

@ -98,7 +98,7 @@ func newFlatReader(reader database.StateReader) *flatReader {
// //
// The returned account might be nil if it's not existent. // The returned account might be nil if it's not existent.
func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) { func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
account, err := r.reader.Account(crypto.Keccak256Hash(addr.Bytes())) account, err := r.reader.Account(crypto.Keccak256Hash(addr[:]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -128,8 +128,8 @@ func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
// //
// The returned storage slot might be empty if it's not existent. // The returned storage slot might be empty if it's not existent.
func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) { func (r *flatReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) {
addrHash := crypto.Keccak256Hash(addr.Bytes()) addrHash := crypto.Keccak256Hash(addr[:])
slotHash := crypto.Keccak256Hash(key.Bytes()) slotHash := crypto.Keccak256Hash(key[:])
ret, err := r.reader.Storage(addrHash, slotHash) ret, err := r.reader.Storage(addrHash, slotHash)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err

View file

@ -474,6 +474,14 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
s.origin = s.data.Copy() s.origin = s.data.Copy()
return op, nil, nil return op, nil, nil
} }
// In Verkle/binary trie mode, all state objects share one unified trie.
// The main account trie commit in stateDB.commit() already calls
// CollectNodes on this trie, so calling Commit here again would
// redundantly traverse and serialize the entire tree per dirty account.
if s.db.GetTrie().IsVerkle() {
s.origin = s.data.Copy()
return op, nil, nil
}
root, nodes := s.trie.Commit(false) root, nodes := s.trie.Commit(false)
s.data.Root = root s.data.Root = root
s.origin = s.data.Copy() s.origin = s.data.Copy()

View file

@ -28,7 +28,6 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -135,8 +134,7 @@ type StateDB struct {
journal *journal journal *journal
// State witness if cross validation is needed // State witness if cross validation is needed
witness *stateless.Witness witness *stateless.Witness
witnessStats *stateless.WitnessStats
// Measurements gathered during execution for debugging purposes // Measurements gathered during execution for debugging purposes
AccountReads time.Duration AccountReads time.Duration
@ -201,13 +199,12 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // 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 // state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot. // 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 // Terminate any previously running prefetcher
s.StopPrefetcher() s.StopPrefetcher()
// Enable witness collection if requested // Enable witness collection if requested
s.witness = witness s.witness = witness
s.witnessStats = witnessStats
// With the switch to the Proof-of-Stake consensus algorithm, block production // With the switch to the Proof-of-Stake consensus algorithm, block production
// rewards are now handled at the consensus layer. Consequently, a block may // rewards are now handled at the consensus layer. Consequently, a block may
@ -743,6 +740,44 @@ func (s *StateDB) GetRefund() uint64 {
return s.refund return s.refund
} }
type removedAccountWithBalance struct {
address common.Address
balance *uint256.Int
}
// LogsForBurnAccounts returns 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) LogsForBurnAccounts() []*types.Log {
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 {
return nil
}
sort.Slice(list, func(i, j int) bool {
return list[i].address.Cmp(list[j].address) < 0
})
logs := make([]*types.Log, len(list))
for i, acct := range list {
logs[i] = types.EthBurnLog(acct.address, acct.balance)
}
return logs
}
// Finalise finalises the state by removing the destructed objects and clears // 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 // 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. // into the tries just yet. Only IntermediateRoot or Commit will do that.
@ -824,32 +859,67 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
workers errgroup.Group workers errgroup.Group
) )
if s.db.TrieDB().IsVerkle() { if s.db.TrieDB().IsVerkle() {
// Whilst MPT storage tries are independent, Verkle has one single trie // Bypass per-account updateTrie() for binary trie. In binary trie mode
// for all the accounts and all the storage slots merged together. The // there is only one unified trie (OpenStorageTrie returns self), so the
// former can thus be simply parallelized, but updating the latter will // per-account trie setup in updateTrie() (getPrefetchedTrie, getTrie,
// need concurrency support within the trie itself. That's a TODO for a // prefetcher.used) is redundant overhead. Apply all storage updates
// later time. // directly in a single pass.
workers.SetLimit(1) for addr, op := range s.mutations {
} if op.applied || op.isDelete() {
for addr, op := range s.mutations { continue
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)
}
s.StorageUpdated.Add(1)
} else {
if err := s.trie.DeleteStorage(addr, key[:]); err != nil {
s.setError(err)
}
s.StorageDeleted.Add(1)
}
}
} }
obj := s.stateObjects[addr] // closure for the task runner below // Clear uncommittedStorage and assign trie on each touched object.
workers.Go(func() error { // obj.trie must be set because this path bypasses updateTrie(), which
if s.db.TrieDB().IsVerkle() { // is where obj.trie normally gets lazily loaded via getTrie().
obj.updateTrie() for addr, op := range s.mutations {
} else { 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() obj.updateRoot()
// If witness building is enabled and the state object has a trie, // If witness building is enabled and the state object has a trie,
// gather the witnesses for its specific storage trie // gather the witnesses for its specific storage trie
if s.witness != nil && obj.trie != nil { 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. // If witness building is enabled, gather all the read-only accesses.
// Skip witness collection in Verkle mode, they will be gathered // Skip witness collection in Verkle mode, they will be gathered
@ -862,17 +932,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
continue continue
} }
if trie := obj.getPrefetchedTrie(); trie != nil { if trie := obj.getPrefetchedTrie(); trie != nil {
witness := trie.Witness() s.witness.AddState(trie.Witness(), obj.addrHash())
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash())
}
} else if obj.trie != nil { } else if obj.trie != nil {
witness := obj.trie.Witness() s.witness.AddState(obj.trie.Witness(), obj.addrHash())
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash())
}
} }
} }
// Pull in only-read and non-destructed trie witnesses // Pull in only-read and non-destructed trie witnesses
@ -886,17 +948,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
continue continue
} }
if trie := obj.getPrefetchedTrie(); trie != nil { if trie := obj.getPrefetchedTrie(); trie != nil {
witness := trie.Witness() s.witness.AddState(trie.Witness(), obj.addrHash())
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash())
}
} else if obj.trie != nil { } else if obj.trie != nil {
witness := obj.trie.Witness() s.witness.AddState(obj.trie.Witness(), obj.addrHash())
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, obj.addrHash())
}
} }
} }
} }
@ -911,7 +965,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// only a single trie is used for state hashing. Replacing a non-nil verkle tree // only a single trie is used for state hashing. Replacing a non-nil verkle tree
// here could result in losing uncommitted changes from storage. // here could result in losing uncommitted changes from storage.
start = time.Now() 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 { if trie := s.prefetcher.trie(common.Hash{}, s.originalRoot); trie == nil {
log.Error("Failed to retrieve account pre-fetcher trie") log.Error("Failed to retrieve account pre-fetcher trie")
} else { } else {
@ -969,11 +1023,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// If witness building is enabled, gather the account trie witness // If witness building is enabled, gather the account trie witness
if s.witness != nil { if s.witness != nil {
witness := s.trie.Witness() s.witness.AddState(s.trie.Witness(), common.Hash{})
s.witness.AddState(witness)
if s.witnessStats != nil {
s.witnessStats.Add(witness, common.Hash{})
}
} }
return hash return hash
} }
@ -991,31 +1041,32 @@ func (s *StateDB) clearJournalAndRefund() {
s.refund = 0 s.refund = 0
} }
// fastDeleteStorage is the function that efficiently deletes the storage trie // deleteStorage is designed to delete the storage trie of a designated account.
// of a specific account. It leverages the associated state snapshot for fast func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
// storage iteration and constructs trie node deletion markers by creating
// stack trie with iterated slots.
func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
iter, err := snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{})
if err != nil {
return nil, nil, nil, err
}
defer iter.Release()
var ( var (
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil) nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil) storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
) )
iteratee, err := s.db.Iteratee(s.originalRoot)
if err != nil {
return nil, nil, nil, err
}
it, err := iteratee.NewStorageIterator(addrHash, common.Hash{})
if err != nil {
return nil, nil, nil, err
}
defer it.Release()
stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob)) nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
}) })
for iter.Next() { for it.Next() {
slot := common.CopyBytes(iter.Slot()) slot := common.CopyBytes(it.Slot())
if err := iter.Error(); err != nil { // error might occur after Slot function if err := it.Error(); err != nil { // error might occur after Slot function
return nil, nil, nil, err return nil, nil, nil, err
} }
key := iter.Hash() key := it.Hash()
storages[key] = nil storages[key] = nil
storageOrigins[key] = slot storageOrigins[key] = slot
@ -1023,7 +1074,7 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
return nil, nil, nil, err return nil, nil, nil, err
} }
} }
if err := iter.Error(); err != nil { // error might occur during iteration if err := it.Error(); err != nil { // error might occur during iteration
return nil, nil, nil, err return nil, nil, nil, err
} }
if stack.Hash() != root { if stack.Hash() != root {
@ -1032,68 +1083,6 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
return storages, storageOrigins, nodes, nil return storages, storageOrigins, nodes, nil
} }
// slowDeleteStorage serves as a less-efficient alternative to "fastDeleteStorage,"
// employed when the associated state snapshot is not available. It iterates the
// storage slots along with all internal trie nodes via trie directly.
func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root, s.trie)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err)
}
it, err := tr.NodeIterator(nil)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err)
}
var (
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
)
for it.Next(true) {
if it.Leaf() {
key := common.BytesToHash(it.LeafKey())
storages[key] = nil
storageOrigins[key] = common.CopyBytes(it.LeafBlob())
continue
}
if it.Hash() == (common.Hash{}) {
continue
}
nodes.AddNode(it.Path(), trienode.NewDeletedWithPrev(it.NodeBlob()))
}
if err := it.Error(); err != nil {
return nil, nil, nil, err
}
return storages, storageOrigins, nodes, nil
}
// deleteStorage is designed to delete the storage trie of a designated account.
// The function will make an attempt to utilize an efficient strategy if the
// associated state snapshot is reachable; otherwise, it will resort to a less
// efficient approach.
func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
var (
err error
nodes *trienode.NodeSet // the set for trie node mutations (value is nil)
storages map[common.Hash][]byte // the set for storage mutations (value is nil)
storageOrigins map[common.Hash][]byte // the set for tracking the original value of slot
)
// The fast approach can be failed if the snapshot is not fully
// generated, or it's internally corrupted. Fallback to the slow
// one just in case.
snaps := s.db.Snapshot()
if snaps != nil {
storages, storageOrigins, nodes, err = s.fastDeleteStorage(snaps, addrHash, root)
}
if snaps == nil || err != nil {
storages, storageOrigins, nodes, err = s.slowDeleteStorage(addr, addrHash, root)
}
if err != nil {
return nil, nil, nil, err
}
return storages, storageOrigins, nodes, nil
}
// handleDestruction processes all destruction markers and deletes the account // handleDestruction processes all destruction markers and deletes the account
// and associated storage slots if necessary. There are four potential scenarios // and associated storage slots if necessary. There are four potential scenarios
// as following: // as following:
@ -1144,7 +1133,7 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr) return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr)
} }
// Remove storage slots belonging to the account. // Remove storage slots belonging to the account.
storages, storagesOrigin, set, err := s.deleteStorage(addr, addrHash, prev.Root) storages, storagesOrigin, set, err := s.deleteStorage(addrHash, prev.Root)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err) return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
} }

View file

@ -229,6 +229,10 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
} }
} }
func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log {
return s.inner.LogsForBurnAccounts()
}
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { 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 { 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. // Short circuit if no relevant hooks are set.

View file

@ -1296,12 +1296,12 @@ func TestDeleteStorage(t *testing.T) {
obj := fastState.getOrNewStateObject(addr) obj := fastState.getOrNewStateObject(addr)
storageRoot := obj.data.Root storageRoot := obj.data.Root
_, _, fastNodes, err := fastState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot) _, _, fastNodes, err := fastState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, _, slowNodes, err := slowState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot) _, _, slowNodes, err := slowState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -260,6 +260,9 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) {
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress) evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560) _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
}
evm.StateDB.Finalise(true) evm.StateDB.Finalise(true)
} }
@ -323,6 +326,9 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
evm.SetTxContext(NewEVMTxContext(msg)) evm.SetTxContext(NewEVMTxContext(msg))
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560) ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
}
evm.StateDB.Finalise(true) evm.StateDB.Finalise(true)
if err != nil { if err != nil {
return fmt.Errorf("system call failed to execute: %v", err) return fmt.Errorf("system call failed to execute: %v", err)

View file

@ -583,6 +583,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64) st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
} }
} }
if rules.IsAmsterdam {
for _, log := range st.evm.StateDB.LogsForBurnAccounts() {
st.evm.StateDB.AddLog(log)
}
}
return &ExecutionResult{ return &ExecutionResult{
UsedGas: st.gasUsed(), UsedGas: st.gasUsed(),
MaxUsedGas: peakGasUsed, MaxUsedGas: peakGasUsed,

View file

@ -53,7 +53,7 @@ func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig
} }
// Create and populate the state database to serve as the stateless backend // Create and populate the state database to serve as the stateless backend
memdb := witness.MakeHashDB() memdb := witness.MakeHashDB()
db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil)) db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), state.NewCodeDB(memdb)))
if err != nil { if err != nil {
return common.Hash{}, common.Hash{}, err return common.Hash{}, common.Hash{}, err
} }

View file

@ -17,6 +17,7 @@
package stateless package stateless
import ( import (
"errors"
"io" "io"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
@ -42,6 +43,9 @@ func (w *Witness) ToExtWitness() *ExtWitness {
// FromExtWitness converts the consensus witness format into our internal one. // FromExtWitness converts the consensus witness format into our internal one.
func (w *Witness) FromExtWitness(ext *ExtWitness) error { func (w *Witness) FromExtWitness(ext *ExtWitness) error {
if len(ext.Headers) == 0 {
return errors.New("witness must contain at least one header")
}
w.Headers = ext.Headers w.Headers = ext.Headers
w.Codes = make(map[string]struct{}, len(ext.Codes)) w.Codes = make(map[string]struct{}, len(ext.Codes))

View file

@ -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() { func (s *WitnessStats) init() {
if s.accountTrie == nil { if s.accountTrie == nil {
s.accountTrie = trie.NewLevelStats() s.accountTrie = trie.NewLevelStats()

View file

@ -42,12 +42,13 @@ type Witness struct {
Codes map[string]struct{} // Set of bytecodes ran or accessed Codes map[string]struct{} // Set of bytecodes ran or accessed
State map[string]struct{} // Set of MPT state trie nodes (account and storage together) 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 chain HeaderReader // Chain reader to convert block hash ops to header proofs
lock sync.Mutex // Lock to allow concurrent state insertions stats *WitnessStats // Optional statistics collector
lock sync.Mutex // Lock to allow concurrent state insertions
} }
// NewWitness creates an empty witness ready for population. // 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* // When building witnesses, retrieve the parent header, which will *always*
// be included to act as a trustless pre-root hash container // be included to act as a trustless pre-root hash container
var headers []*types.Header var headers []*types.Header
@ -59,13 +60,17 @@ func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
headers = append(headers, parent) headers = append(headers, parent)
} }
// Create the witness with a reconstructed gutted out block // Create the witness with a reconstructed gutted out block
return &Witness{ w := &Witness{
context: context, context: context,
Headers: headers, Headers: headers,
Codes: make(map[string]struct{}), Codes: make(map[string]struct{}),
State: make(map[string]struct{}), State: make(map[string]struct{}),
chain: chain, chain: chain,
}, nil }
if enableStats {
w.stats = NewWitnessStats()
}
return w, nil
} }
// AddBlockHash adds a "blockhash" to the witness with the designated offset from // 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{}{} w.Codes[string(code)] = struct{}{}
} }
// AddState inserts a batch of MPT trie nodes into the witness. // AddState inserts a batch of MPT trie nodes into the witness. The owner
func (w *Witness) AddState(nodes map[string][]byte) { // 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 { if len(nodes) == 0 {
return return
} }
@ -98,6 +106,17 @@ func (w *Witness) AddState(nodes map[string][]byte) {
for _, value := range nodes { for _, value := range nodes {
w.State[string(value)] = struct{}{} 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() { func (w *Witness) AddKey() {
@ -113,6 +132,9 @@ func (w *Witness) Copy() *Witness {
State: maps.Clone(w.State), State: maps.Clone(w.State),
chain: w.chain, chain: w.chain,
} }
if w.stats != nil {
cpy.stats = w.stats.copy()
}
if w.context != nil { if w.context != nil {
cpy.context = types.CopyHeader(w.context) cpy.context = types.CopyHeader(w.context)
} }

View file

@ -426,7 +426,7 @@ const (
// NonceChangeNewContract is the nonce change of a newly created contract. // NonceChangeNewContract is the nonce change of a newly created contract.
NonceChangeNewContract NonceChangeReason = 4 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 NonceChangeAuthorization NonceChangeReason = 5
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure. // NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.

View file

@ -1865,11 +1865,11 @@ func (p *BlobPool) drop() {
// //
// The transactions can also be pre-filtered by the dynamic fee components to // The transactions can also be pre-filtered by the dynamic fee components to
// reduce allocations and load on downstream subsystems. // reduce allocations and load on downstream subsystems.
func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction { func (p *BlobPool) Pending(filter txpool.PendingFilter) (map[common.Address][]*txpool.LazyTransaction, int) {
// If only plain transactions are requested, this pool is unsuitable as it // If only plain transactions are requested, this pool is unsuitable as it
// contains none, don't even bother. // contains none, don't even bother.
if !filter.BlobTxs { if !filter.BlobTxs {
return nil return nil, 0
} }
// Track the amount of time waiting to retrieve the list of pending blob txs // Track the amount of time waiting to retrieve the list of pending blob txs
// from the pool and the amount of time actually spent on assembling the data. // from the pool and the amount of time actually spent on assembling the data.
@ -1885,6 +1885,7 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx
pendtimeHist.Update(time.Since(execStart).Nanoseconds()) pendtimeHist.Update(time.Since(execStart).Nanoseconds())
}() }()
var count int
pending := make(map[common.Address][]*txpool.LazyTransaction, len(p.index)) pending := make(map[common.Address][]*txpool.LazyTransaction, len(p.index))
for addr, txs := range p.index { for addr, txs := range p.index {
lazies := make([]*txpool.LazyTransaction, 0, len(txs)) lazies := make([]*txpool.LazyTransaction, 0, len(txs))
@ -1930,9 +1931,10 @@ func (p *BlobPool) Pending(filter txpool.PendingFilter) map[common.Address][]*tx
} }
if len(lazies) > 0 { if len(lazies) > 0 {
pending[addr] = lazies pending[addr] = lazies
count += len(lazies)
} }
} }
return pending return pending, count
} }
// updateStorageMetrics retrieves a bunch of stats from the data store and pushes // updateStorageMetrics retrieves a bunch of stats from the data store and pushes

View file

@ -2122,7 +2122,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
p := pool.Pending(txpool.PendingFilter{ p, _ := pool.Pending(txpool.PendingFilter{
MinTip: uint256.NewInt(1), MinTip: uint256.NewInt(1),
BaseFee: chain.basefee, BaseFee: chain.basefee,
BlobFee: chain.blobfee, BlobFee: chain.blobfee,

View file

@ -494,15 +494,16 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction,
// //
// The transactions can also be pre-filtered by the dynamic fee components to // The transactions can also be pre-filtered by the dynamic fee components to
// reduce allocations and load on downstream subsystems. // reduce allocations and load on downstream subsystems.
func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction { func (pool *LegacyPool) Pending(filter txpool.PendingFilter) (map[common.Address][]*txpool.LazyTransaction, int) {
// If only blob transactions are requested, this pool is unsuitable as it // If only blob transactions are requested, this pool is unsuitable as it
// contains none, don't even bother. // contains none, don't even bother.
if filter.BlobTxs { if filter.BlobTxs {
return nil return nil, 0
} }
pool.mu.Lock() pool.mu.Lock()
defer pool.mu.Unlock() defer pool.mu.Unlock()
var count int
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
for addr, list := range pool.pending { for addr, list := range pool.pending {
txs := list.Flatten() txs := list.Flatten()
@ -539,9 +540,10 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
} }
} }
pending[addr] = lazies pending[addr] = lazies
count += len(lazies)
} }
} }
return pending return pending, count
} }
// ValidateTxBasics checks whether a transaction is valid according to the consensus // ValidateTxBasics checks whether a transaction is valid according to the consensus
@ -996,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. // Get returns a transaction if it is contained in the pool and nil otherwise.
func (pool *LegacyPool) Get(hash common.Hash) *types.Transaction { func (pool *LegacyPool) Get(hash common.Hash) *types.Transaction {
tx := pool.get(hash) return pool.get(hash)
if tx == nil {
return nil
}
return tx
} }
// get returns a transaction if it is contained in the pool and nil otherwise. // get returns a transaction if it is contained in the pool and nil otherwise.
@ -1406,7 +1404,7 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
// promote all promotable transactions // promote all promotable transactions
promoted := make([]*types.Transaction, 0, len(promotable)) promoted := make([]*types.Transaction, 0, len(promotable))
for _, tx := range promotable { for _, tx := range promotable {
from, _ := pool.signer.Sender(tx) from, _ := types.Sender(pool.signer, tx) // already validated
if pool.promoteTx(from, tx.Hash(), tx) { if pool.promoteTx(from, tx.Hash(), tx) {
promoted = append(promoted, tx) promoted = append(promoted, tx)
} }

View file

@ -154,7 +154,7 @@ type SubPool interface {
// //
// The transactions can also be pre-filtered by the dynamic fee components to // The transactions can also be pre-filtered by the dynamic fee components to
// reduce allocations and load on downstream subsystems. // reduce allocations and load on downstream subsystems.
Pending(filter PendingFilter) map[common.Address][]*LazyTransaction Pending(filter PendingFilter) (map[common.Address][]*LazyTransaction, int)
// SubscribeTransactions subscribes to new transaction events. The subscriber // SubscribeTransactions subscribes to new transaction events. The subscriber
// can decide whether to receive notifications only for newly seen transactions // can decide whether to receive notifications only for newly seen transactions

View file

@ -359,14 +359,17 @@ func (p *TxPool) Add(txs []*types.Transaction, sync bool) []error {
// //
// The transactions can also be pre-filtered by the dynamic fee components to // The transactions can also be pre-filtered by the dynamic fee components to
// reduce allocations and load on downstream subsystems. // reduce allocations and load on downstream subsystems.
func (p *TxPool) Pending(filter PendingFilter) map[common.Address][]*LazyTransaction { func (p *TxPool) Pending(filter PendingFilter) (map[common.Address][]*LazyTransaction, int) {
var count int
txs := make(map[common.Address][]*LazyTransaction) txs := make(map[common.Address][]*LazyTransaction)
for _, subpool := range p.subpools { for _, subpool := range p.subpools {
for addr, set := range subpool.Pending(filter) { set, n := subpool.Pending(filter)
txs[addr] = set for addr, list := range set {
txs[addr] = list
} }
count += n
} }
return txs return txs, count
} }
// SubscribeTransactions registers a subscription for new transaction events, // SubscribeTransactions registers a subscription for new transaction events,

View file

@ -350,9 +350,12 @@ func (e *BlockAccessList) PrettyPrint() string {
} }
// Copy returns a deep copy of the access list // Copy returns a deep copy of the access list
func (e *BlockAccessList) Copy() (res BlockAccessList) { func (e *BlockAccessList) Copy() *BlockAccessList {
for _, accountAccess := range e.Accesses { cpy := &BlockAccessList{
res.Accesses = append(res.Accesses, accountAccess.Copy()) Accesses: make([]AccountAccess, 0, len(e.Accesses)),
} }
return for _, accountAccess := range e.Accesses {
cpy.Accesses = append(cpy.Accesses, accountAccess.Copy())
}
return cpy
} }

View file

@ -190,8 +190,8 @@ func makeTestAccountAccess(sort bool) AccountAccess {
} }
} }
func makeTestBAL(sort bool) BlockAccessList { func makeTestBAL(sort bool) *BlockAccessList {
list := BlockAccessList{} list := &BlockAccessList{}
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
list.Accesses = append(list.Accesses, makeTestAccountAccess(sort)) list.Accesses = append(list.Accesses, makeTestAccountAccess(sort))
} }

View file

@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -99,6 +100,9 @@ type Header struct {
// RequestsHash was added by EIP-7685 and is ignored in legacy headers. // RequestsHash was added by EIP-7685 and is ignored in legacy headers.
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
// BlockAccessListHash was added by EIP-7928 and is ignored in legacy headers.
BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
// SlotNumber was added by EIP-7843 and is ignored in legacy headers. // SlotNumber was added by EIP-7843 and is ignored in legacy headers.
SlotNumber *uint64 `json:"slotNumber" rlp:"optional"` SlotNumber *uint64 `json:"slotNumber" rlp:"optional"`
} }
@ -204,6 +208,7 @@ type Block struct {
uncles []*Header uncles []*Header
transactions Transactions transactions Transactions
withdrawals Withdrawals withdrawals Withdrawals
accessList *bal.BlockAccessList
// caches // caches
hash atomic.Pointer[common.Hash] hash atomic.Pointer[common.Hash]
@ -320,6 +325,10 @@ func CopyHeader(h *Header) *Header {
cpy.RequestsHash = new(common.Hash) cpy.RequestsHash = new(common.Hash)
*cpy.RequestsHash = *h.RequestsHash *cpy.RequestsHash = *h.RequestsHash
} }
if h.BlockAccessListHash != nil {
cpy.BlockAccessListHash = new(common.Hash)
*cpy.BlockAccessListHash = *h.BlockAccessListHash
}
if h.SlotNumber != nil { if h.SlotNumber != nil {
cpy.SlotNumber = new(uint64) cpy.SlotNumber = new(uint64)
*cpy.SlotNumber = *h.SlotNumber *cpy.SlotNumber = *h.SlotNumber
@ -358,9 +367,10 @@ func (b *Block) Body() *Body {
// Accessors for body data. These do not return a copy because the content // Accessors for body data. These do not return a copy because the content
// of the body slices does not affect the cached hash/size in block. // of the body slices does not affect the cached hash/size in block.
func (b *Block) Uncles() []*Header { return b.uncles } func (b *Block) Uncles() []*Header { return b.uncles }
func (b *Block) Transactions() Transactions { return b.transactions } func (b *Block) Transactions() Transactions { return b.transactions }
func (b *Block) Withdrawals() Withdrawals { return b.withdrawals } func (b *Block) Withdrawals() Withdrawals { return b.withdrawals }
func (b *Block) AccessList() *bal.BlockAccessList { return b.accessList }
func (b *Block) Transaction(hash common.Hash) *Transaction { func (b *Block) Transaction(hash common.Hash) *Transaction {
for _, transaction := range b.transactions { for _, transaction := range b.transactions {
@ -495,6 +505,7 @@ func (b *Block) WithSeal(header *Header) *Block {
transactions: b.transactions, transactions: b.transactions,
uncles: b.uncles, uncles: b.uncles,
withdrawals: b.withdrawals, withdrawals: b.withdrawals,
accessList: b.accessList,
} }
} }
@ -506,6 +517,7 @@ func (b *Block) WithBody(body Body) *Block {
transactions: slices.Clone(body.Transactions), transactions: slices.Clone(body.Transactions),
uncles: make([]*Header, len(body.Uncles)), uncles: make([]*Header, len(body.Uncles)),
withdrawals: slices.Clone(body.Withdrawals), withdrawals: slices.Clone(body.Withdrawals),
accessList: b.accessList,
} }
for i := range body.Uncles { for i := range body.Uncles {
block.uncles[i] = CopyHeader(body.Uncles[i]) block.uncles[i] = CopyHeader(body.Uncles[i])
@ -513,6 +525,24 @@ func (b *Block) WithBody(body Body) *Block {
return block return block
} }
// WithAccessList returns a copy of the block with the given access list embedded.
func (b *Block) WithAccessList(accessList *bal.BlockAccessList) *Block {
return b.WithAccessListUnsafe(accessList.Copy())
}
// WithAccessListUnsafe returns a copy of the block with the given access list
// embedded. Note that the access list is not deep-copied; use WithAccessList
// if the provided list may be modified by other actors.
func (b *Block) WithAccessListUnsafe(accessList *bal.BlockAccessList) *Block {
return &Block{
header: b.header,
transactions: b.transactions,
uncles: b.uncles,
withdrawals: b.withdrawals,
accessList: accessList,
}
}
// Hash returns the keccak256 hash of b's header. // Hash returns the keccak256 hash of b's header.
// The hash is computed on the first call and cached thereafter. // The hash is computed on the first call and cached thereafter.
func (b *Block) Hash() common.Hash { func (b *Block) Hash() common.Hash {

View file

@ -16,29 +16,30 @@ var _ = (*headerMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (h Header) MarshalJSON() ([]byte, error) { func (h Header) MarshalJSON() ([]byte, error) {
type Header struct { type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase common.Address `json:"miner"` Coinbase common.Address `json:"miner"`
Root common.Hash `json:"stateRoot" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
Number *hexutil.Big `json:"number" gencodec:"required"` Number *hexutil.Big `json:"number" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Extra hexutil.Bytes `json:"extraData" gencodec:"required"` Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash"` MixDigest common.Hash `json:"mixHash"`
Nonce BlockNonce `json:"nonce"` Nonce BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"` BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
Hash common.Hash `json:"hash"` SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"`
Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
enc.ParentHash = h.ParentHash enc.ParentHash = h.ParentHash
@ -62,6 +63,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.ParentBeaconRoot = h.ParentBeaconRoot enc.ParentBeaconRoot = h.ParentBeaconRoot
enc.RequestsHash = h.RequestsHash enc.RequestsHash = h.RequestsHash
enc.BlockAccessListHash = h.BlockAccessListHash
enc.SlotNumber = (*hexutil.Uint64)(h.SlotNumber) enc.SlotNumber = (*hexutil.Uint64)(h.SlotNumber)
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
@ -70,28 +72,29 @@ func (h Header) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (h *Header) UnmarshalJSON(input []byte) error { func (h *Header) UnmarshalJSON(input []byte) error {
type Header struct { type Header struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"` ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase *common.Address `json:"miner"` Coinbase *common.Address `json:"miner"`
Root *common.Hash `json:"stateRoot" gencodec:"required"` Root *common.Hash `json:"stateRoot" gencodec:"required"`
TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom *Bloom `json:"logsBloom" gencodec:"required"` Bloom *Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
Number *hexutil.Big `json:"number" gencodec:"required"` Number *hexutil.Big `json:"number" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
MixDigest *common.Hash `json:"mixHash"` MixDigest *common.Hash `json:"mixHash"`
Nonce *BlockNonce `json:"nonce"` Nonce *BlockNonce `json:"nonce"`
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"` BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"`
SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -172,6 +175,9 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.RequestsHash != nil { if dec.RequestsHash != nil {
h.RequestsHash = dec.RequestsHash h.RequestsHash = dec.RequestsHash
} }
if dec.BlockAccessListHash != nil {
h.BlockAccessListHash = dec.BlockAccessListHash
}
if dec.SlotNumber != nil { if dec.SlotNumber != nil {
h.SlotNumber = (*uint64)(dec.SlotNumber) h.SlotNumber = (*uint64)(dec.SlotNumber)
} }

View file

@ -43,8 +43,9 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
_tmp4 := obj.ExcessBlobGas != nil _tmp4 := obj.ExcessBlobGas != nil
_tmp5 := obj.ParentBeaconRoot != nil _tmp5 := obj.ParentBeaconRoot != nil
_tmp6 := obj.RequestsHash != nil _tmp6 := obj.RequestsHash != nil
_tmp7 := obj.SlotNumber != nil _tmp7 := obj.BlockAccessListHash != nil
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { _tmp8 := obj.SlotNumber != nil
if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 {
if obj.BaseFee == nil { if obj.BaseFee == nil {
w.Write(rlp.EmptyString) w.Write(rlp.EmptyString)
} else { } else {
@ -54,42 +55,49 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBigInt(obj.BaseFee) w.WriteBigInt(obj.BaseFee)
} }
} }
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 {
if obj.WithdrawalsHash == nil { if obj.WithdrawalsHash == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.WithdrawalsHash[:]) w.WriteBytes(obj.WithdrawalsHash[:])
} }
} }
if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 {
if obj.BlobGasUsed == nil { if obj.BlobGasUsed == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.BlobGasUsed)) w.WriteUint64((*obj.BlobGasUsed))
} }
} }
if _tmp4 || _tmp5 || _tmp6 || _tmp7 { if _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 {
if obj.ExcessBlobGas == nil { if obj.ExcessBlobGas == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.ExcessBlobGas)) w.WriteUint64((*obj.ExcessBlobGas))
} }
} }
if _tmp5 || _tmp6 || _tmp7 { if _tmp5 || _tmp6 || _tmp7 || _tmp8 {
if obj.ParentBeaconRoot == nil { if obj.ParentBeaconRoot == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.ParentBeaconRoot[:]) w.WriteBytes(obj.ParentBeaconRoot[:])
} }
} }
if _tmp6 || _tmp7 { if _tmp6 || _tmp7 || _tmp8 {
if obj.RequestsHash == nil { if obj.RequestsHash == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.RequestsHash[:]) w.WriteBytes(obj.RequestsHash[:])
} }
} }
if _tmp7 { if _tmp7 || _tmp8 {
if obj.BlockAccessListHash == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.BlockAccessListHash[:])
}
}
if _tmp8 {
if obj.SlotNumber == nil { if obj.SlotNumber == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {

View file

@ -19,6 +19,8 @@ package types
import ( import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "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 //go:generate go run ../../rlp/rlpgen -type Log -out gen_log_rlp.go
@ -62,3 +64,32 @@ type logMarshaling struct {
BlockTimestamp hexutil.Uint64 BlockTimestamp hexutil.Uint64
Index hexutil.Uint 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[:],
}
}

View file

@ -35,7 +35,7 @@ type (
// CanTransferFunc is the signature of a transfer guard function // CanTransferFunc is the signature of a transfer guard function
CanTransferFunc func(StateDB, common.Address, *uint256.Int) bool CanTransferFunc func(StateDB, common.Address, *uint256.Int) bool
// TransferFunc is the signature of a transfer function // 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 // GetHashFunc returns the n'th block hash in the blockchain
// and is used by the BLOCKHASH EVM op code. // and is used by the BLOCKHASH EVM op code.
GetHashFunc func(uint64) common.Hash 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, // Calling this is required even for zero-value transfers,
// to ensure the state clearing mechanism is applied. // to ensure the state clearing mechanism is applied.
if !syscall { if !syscall {
evm.Context.Transfer(evm.StateDB, caller, addr, value) evm.Context.Transfer(evm.StateDB, caller, addr, value, &evm.chainRules)
} }
if isPrecompile { if isPrecompile {
var stateDB StateDB var stateDB StateDB
if evm.chainRules.IsAmsterdam { if evm.chainRules.IsAmsterdam {
@ -567,7 +568,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
} }
gas = gas - consumed 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. // 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. // The contract is a scoped environment for this execution context only.

View file

@ -49,6 +49,5 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u
if !callCost.IsUint64() { if !callCost.IsUint64() {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
return callCost.Uint64(), nil return callCost.Uint64(), nil
} }

View file

@ -373,7 +373,32 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
return gas, nil 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 ( var (
gas uint64 gas uint64
transfersValue = !stack.Back(2).IsZero() transfersValue = !stack.Back(2).IsZero()
@ -382,38 +407,40 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if evm.readOnly && transfersValue { if evm.readOnly && transfersValue {
return 0, ErrWriteProtection return 0, ErrWriteProtection
} }
// Stateless check
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
}
memoryGas, err := memoryGasCost(mem, memorySize) memoryGas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
return 0, err return 0, err
} }
var transferGas uint64
if transfersValue && !evm.chainRules.IsEIP4762 {
transferGas = params.CallValueTransferGas
}
var overflow bool var overflow bool
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
// Terminate the gas measurement if the leftover gas is not sufficient,
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) // it can effectively prevent accessing the states in the following steps.
if err != nil { if contract.Gas < gas {
return 0, err 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 0, ErrGasUintOverflow
} }
return gas, nil 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) memoryGas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
return 0, err 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 { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow 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 return gas, nil
} }
func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize) return 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 gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize) return 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 gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {

View file

@ -94,7 +94,7 @@ func TestEIP2200(t *testing.T) {
vmctx := BlockContext{ vmctx := BlockContext{
CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true }, 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}}) evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
@ -144,7 +144,7 @@ func TestCreateGas(t *testing.T) {
statedb.Finalise(true) statedb.Finalise(true)
vmctx := BlockContext{ vmctx := BlockContext{
CanTransfer: func(StateDB, common.Address, *uint256.Int) bool { return true }, 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), BlockNumber: big.NewInt(0),
} }
config := Config{} config := Config{}

View file

@ -934,6 +934,13 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct) evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct) 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 := evm.Config.Tracer; tracer != nil {
if tracer.OnEnter != nil { if tracer.OnEnter != nil {
@ -1086,9 +1093,6 @@ func makeLog(size int) executionFunc {
Address: scope.Contract.Address(), Address: scope.Contract.Address(),
Topics: topics, Topics: topics,
Data: d, 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 return nil, nil

View file

@ -87,6 +87,7 @@ type StateDB interface {
Snapshot() int Snapshot() int
AddLog(*types.Log) AddLog(*types.Log)
LogsForBurnAccounts() []*types.Log
AddPreimage(common.Hash, []byte) AddPreimage(common.Hash, []byte)
Witness() *stateless.Witness Witness() *stateless.Witness

View file

@ -40,7 +40,7 @@ var loopInterruptTests = []string{
func TestLoopInterrupt(t *testing.T) { func TestLoopInterrupt(t *testing.T) {
address := common.BytesToAddress([]byte("contract")) address := common.BytesToAddress([]byte("contract"))
vmctx := BlockContext{ 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 { for i, tt := range loopInterruptTests {

View file

@ -256,10 +256,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
} }
var ( var (
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall) innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic)
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall) gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall) gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic)
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode) gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic)
) )
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { 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) 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) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var ( var (
total uint64 // total dynamic gas used eip2929Cost uint64
addr = common.Address(stack.Back(1).Bytes20()) eip7702Cost uint64
addr = common.Address(stack.Back(1).Bytes20())
) )
// Perform EIP-2929 checks (stateless), checking address presence
// Check slot presence in the access list // in the accessList and charge the cold access accordingly.
if !evm.StateDB.AddressInAccessList(addr) { if !evm.StateDB.AddressInAccessList(addr) {
evm.StateDB.AddAddressToAccessList(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 // The WarmStorageReadCostEIP2929 (100) is already deducted in the form
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 // of a constant cost, so the cost to charge for cold access, if any,
// Charge the remaining difference here already, to correctly calculate available // is Cold - Warm
// gas for call eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
// 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 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. // Check if code is a delegation and if so, charge for resolution.
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
var cost uint64
if evm.StateDB.AddressInAccessList(target) { if evm.StateDB.AddressInAccessList(target) {
cost = params.WarmStorageReadCostEIP2929 eip7702Cost = params.WarmStorageReadCostEIP2929
} else { } else {
evm.StateDB.AddAddressToAccessList(target) 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 return 0, ErrOutOfGas
} }
total += cost
} }
// Calculate the gas budget for the nested call. The costs defined by
// Now call the old calculator, which takes into account // EIP-2929 and EIP-7702 have already been applied.
// - create new account evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, intrinsicCost, stack.Back(0))
// - transfer value
// - memory expansion
// - 63/64ths rule
old, err := oldCalculator(evm, contract, stack, mem, memorySize)
if err != nil { if err != nil {
return old, err return 0, err
} }
// Temporarily add the gas charge back to the contract and return value. By // 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 // 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 // part of the dynamic gas. This will ensure it is correctly reported to
// tracers. // tracers.
contract.Gas += total contract.Gas += eip2929Cost + eip7702Cost
var overflow bool // Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
if total, overflow = math.SafeAdd(old, total); overflow { // 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 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
} }
} }

View file

@ -347,7 +347,7 @@ func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
} }
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
pending := b.eth.txPool.Pending(txpool.PendingFilter{}) pending, _ := b.eth.txPool.Pending(txpool.PendingFilter{})
var txs types.Transactions var txs types.Transactions
for _, batch := range pending { for _, batch := range pending {
for _, lazy := range batch { for _, lazy := range batch {
@ -414,9 +414,10 @@ func (b *EthAPIBackend) SyncProgress(ctx context.Context) ethereum.SyncProgress
prog.TxIndexFinishedBlocks = txProg.Indexed prog.TxIndexFinishedBlocks = txProg.Indexed
prog.TxIndexRemainingBlocks = txProg.Remaining prog.TxIndexRemainingBlocks = txProg.Remaining
} }
remain, err := b.eth.blockchain.StateIndexProgress() stateRemain, trienodeRemain, err := b.eth.blockchain.StateIndexProgress()
if err == nil { if err == nil {
prog.StateIndexRemaining = remain prog.StateIndexRemaining = stateRemain
prog.TrienodeIndexRemaining = trienodeRemain
} }
return prog return prog
} }
@ -484,12 +485,12 @@ func (b *EthAPIBackend) CurrentHeader() *types.Header {
return b.eth.blockchain.CurrentHeader() return b.eth.blockchain.CurrentHeader()
} }
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) { func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk) return b.eth.stateAtBlock(ctx, block, base, readOnly, preferDisk)
} }
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) return b.eth.stateAtTransaction(ctx, block, txIndex)
} }
func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration { func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration {

View file

@ -222,7 +222,7 @@ func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockNrOrHash rpc.Block
if block == nil { if block == nil {
return StorageRangeResult{}, fmt.Errorf("block %v not found", blockNrOrHash) return StorageRangeResult{}, fmt.Errorf("block %v not found", blockNrOrHash)
} }
_, _, statedb, release, err := api.eth.stateAtTransaction(ctx, block, txIndex, 0) _, _, statedb, release, err := api.eth.stateAtTransaction(ctx, block, txIndex)
if err != nil { if err != nil {
return StorageRangeResult{}, err return StorageRangeResult{}, err
} }
@ -236,6 +236,8 @@ func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Add
if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) { if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) {
return StorageRangeResult{}, nil // empty storage return StorageRangeResult{}, nil // empty storage
} }
// TODO(rjl493456442) it's problematic for traversing the state with in-memory
// state mutations, specifically txIndex != 0.
id := trie.StorageTrieID(root, crypto.Keccak256Hash(address.Bytes()), storageRoot) id := trie.StorageTrieID(root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB()) tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
if err != nil { if err != nil {

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/filtermaps" "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/rawdb"
"github.com/ethereum/go-ethereum/core/state/pruner" "github.com/ethereum/go-ethereum/core/state/pruner"
"github.com/ethereum/go-ethereum/core/txpool" "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. // Here we determine genesis hash and active ChainConfig.
// We need these to figure out the consensus parameters and to set up history pruning. // 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 { if err != nil {
return nil, err return nil, err
} }
@ -220,6 +221,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
} }
} }
histPolicy, err := history.NewPolicy(config.HistoryMode, genesisHash)
if err != nil {
return nil, err
}
var ( var (
options = &core.BlockChainConfig{ options = &core.BlockChainConfig{
TrieCleanLimit: config.TrieCleanCache, TrieCleanLimit: config.TrieCleanCache,
@ -233,7 +238,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
TrienodeHistory: config.TrienodeHistory, TrienodeHistory: config.TrienodeHistory,
NodeFullValueCheckpoint: config.NodeFullValueCheckpoint, NodeFullValueCheckpoint: config.NodeFullValueCheckpoint,
StateScheme: scheme, StateScheme: scheme,
ChainHistoryMode: config.HistoryMode, HistoryPolicy: histPolicy,
TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)), TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)),
VmConfig: vm.Config{ VmConfig: vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording, EnablePreimageRecording: config.EnablePreimageRecording,

View file

@ -162,7 +162,7 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
// //
// If there are payloadAttributes: we try to assemble a block with the payloadAttributes // If there are payloadAttributes: we try to assemble a block with the payloadAttributes
// and return its payloadID. // and return its payloadID.
func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV1(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil { if payloadAttributes != nil {
switch { switch {
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil: case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
@ -171,12 +171,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai") return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
} }
} }
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false) return api.forkchoiceUpdated(ctx, update, payloadAttributes, engine.PayloadV1, false)
} }
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload // ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload
// attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2. // attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2.
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV2(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.BeaconRoot != nil: case params.BeaconRoot != nil:
@ -189,12 +189,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads") return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
} }
} }
return api.forkchoiceUpdated(update, params, engine.PayloadV2, false) return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV2, false)
} }
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root // ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root
// in the payload attributes. It supports only PayloadAttributesV3. // in the payload attributes. It supports only PayloadAttributesV3.
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV3(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.Withdrawals == nil: case params.Withdrawals == nil:
@ -209,12 +209,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
// hash, even if params are wrong. To do this we need to split up // hash, even if params are wrong. To do this we need to split up
// forkchoiceUpdate into a function that only updates the head and then a // forkchoiceUpdate into a function that only updates the head and then a
// function that kicks off block construction. // function that kicks off block construction.
return api.forkchoiceUpdated(update, params, engine.PayloadV3, false) return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV3, false)
} }
// ForkchoiceUpdatedV4 is equivalent to V3 with the addition of slot number // ForkchoiceUpdatedV4 is equivalent to V3 with the addition of slot number
// in the payload attributes. It supports only PayloadAttributesV4. // in the payload attributes. It supports only PayloadAttributesV4.
func (api *ConsensusAPI) ForkchoiceUpdatedV4(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV4(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.Withdrawals == nil: case params.Withdrawals == nil:
@ -231,10 +231,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV4(update engine.ForkchoiceStateV1, pa
// hash, even if params are wrong. To do this we need to split up // hash, even if params are wrong. To do this we need to split up
// forkchoiceUpdate into a function that only updates the head and then a // forkchoiceUpdate into a function that only updates the head and then a
// function that kicks off block construction. // function that kicks off block construction.
return api.forkchoiceUpdated(update, params, engine.PayloadV4, false) return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV4, false)
} }
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (result engine.ForkChoiceResponse, err error) {
ctx, _, spanEnd := telemetry.StartSpan(ctx, "engine.forkchoiceUpdated")
defer spanEnd(&err)
api.forkchoiceLock.Lock() api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock() defer api.forkchoiceLock.Unlock()
@ -375,7 +377,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
if api.localBlocks.has(id) { if api.localBlocks.has(id) {
return valid(&id), nil return valid(&id), nil
} }
payload, err := api.eth.Miner().BuildPayload(args, payloadWitness) payload, err := api.eth.Miner().BuildPayload(ctx, args, payloadWitness)
if err != nil { if err != nil {
log.Error("Failed to build payload", "err", err) log.Error("Failed to build payload", "err", err)
return valid(nil), engine.InvalidPayloadAttributes.With(err) return valid(nil), engine.InvalidPayloadAttributes.With(err)

View file

@ -190,7 +190,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) {
SafeBlockHash: common.Hash{}, SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{},
} }
_, err := api.ForkchoiceUpdatedV1(fcState, &blockParams) _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, &blockParams)
if err != nil { if err != nil {
t.Fatalf("error preparing payload, err=%v", err) t.Fatalf("error preparing payload, err=%v", err)
} }
@ -270,7 +270,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) {
SafeBlockHash: common.Hash{}, SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{},
} }
_, err := api.ForkchoiceUpdatedV1(fcState, &params) _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, &params)
if test.shouldErr && err == nil { if test.shouldErr && err == nil {
t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err) t.Fatalf("expected error preparing payload with invalid timestamp, err=%v", err)
} else if !test.shouldErr && err != nil { } else if !test.shouldErr && err != nil {
@ -329,7 +329,7 @@ func TestEth2NewBlock(t *testing.T) {
SafeBlockHash: block.Hash(), SafeBlockHash: block.Hash(),
FinalizedBlockHash: block.Hash(), FinalizedBlockHash: block.Hash(),
} }
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
t.Fatalf("Failed to insert block: %v", err) t.Fatalf("Failed to insert block: %v", err)
} }
if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want { if have, want := ethservice.BlockChain().CurrentBlock().Number.Uint64(), block.NumberU64(); have != want {
@ -369,7 +369,7 @@ func TestEth2NewBlock(t *testing.T) {
SafeBlockHash: block.Hash(), SafeBlockHash: block.Hash(),
FinalizedBlockHash: block.Hash(), FinalizedBlockHash: block.Hash(),
} }
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
t.Fatalf("Failed to insert block: %v", err) t.Fatalf("Failed to insert block: %v", err)
} }
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() { if ethservice.BlockChain().CurrentBlock().Number.Uint64() != block.NumberU64() {
@ -515,7 +515,7 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He
SafeBlockHash: payload.ParentHash, SafeBlockHash: payload.ParentHash,
FinalizedBlockHash: payload.ParentHash, FinalizedBlockHash: payload.ParentHash,
} }
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
t.Fatalf("Failed to insert block: %v", err) t.Fatalf("Failed to insert block: %v", err)
} }
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number { if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.Number {
@ -629,7 +629,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
err error err error
) )
for i := 0; ; i++ { for i := 0; ; i++ {
if resp, err = api.ForkchoiceUpdatedV1(fcState, &params); err != nil { if resp, err = api.ForkchoiceUpdatedV1(context.Background(), fcState, &params); err != nil {
t.Fatalf("error preparing payload, err=%v", err) t.Fatalf("error preparing payload, err=%v", err)
} }
if resp.PayloadStatus.Status != engine.VALID { if resp.PayloadStatus.Status != engine.VALID {
@ -660,7 +660,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
SafeBlockHash: payload.ExecutionPayload.ParentHash, SafeBlockHash: payload.ExecutionPayload.ParentHash,
FinalizedBlockHash: payload.ExecutionPayload.ParentHash, FinalizedBlockHash: payload.ExecutionPayload.ParentHash,
} }
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
t.Fatalf("Failed to insert block: %v", err) t.Fatalf("Failed to insert block: %v", err)
} }
if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.ExecutionPayload.Number { if ethservice.BlockChain().CurrentBlock().Number.Uint64() != payload.ExecutionPayload.Number {
@ -679,7 +679,7 @@ func assembleEnvelope(api *ConsensusAPI, parentHash common.Hash, params *engine.
Withdrawals: params.Withdrawals, Withdrawals: params.Withdrawals,
BeaconRoot: params.BeaconRoot, BeaconRoot: params.BeaconRoot,
} }
payload, err := api.eth.Miner().BuildPayload(args, false) payload, err := api.eth.Miner().BuildPayload(context.Background(), args, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -867,7 +867,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
t.Error("invalid status: VALID on an invalid chain") t.Error("invalid status: VALID on an invalid chain")
} }
// Now reorg to the head of the invalid chain // Now reorg to the head of the invalid chain
resp, err := apiB.ForkchoiceUpdatedV1(engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil) resp, err := apiB.ForkchoiceUpdatedV1(context.Background(), engine.ForkchoiceStateV1{HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.BlockHash, FinalizedBlockHash: payload.ParentHash}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -970,7 +970,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
for ii := 0; ii < 10; ii++ { for ii := 0; ii < 10; ii++ {
go func() { go func() {
defer wg.Done() defer wg.Done()
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { if _, err := api.ForkchoiceUpdatedV1(context.Background(), fcState, nil); err != nil {
errMu.Lock() errMu.Lock()
testErr = fmt.Errorf("failed to insert block: %w", err) testErr = fmt.Errorf("failed to insert block: %w", err)
errMu.Unlock() errMu.Unlock()
@ -1011,7 +1011,7 @@ func TestWithdrawals(t *testing.T) {
fcState := engine.ForkchoiceStateV1{ fcState := engine.ForkchoiceStateV1{
HeadBlockHash: parent.Hash(), HeadBlockHash: parent.Hash(),
} }
resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams) resp, err := api.ForkchoiceUpdatedV2(context.Background(), fcState, &blockParams)
if err != nil { if err != nil {
t.Fatalf("error preparing payload, err=%v", err) t.Fatalf("error preparing payload, err=%v", err)
} }
@ -1063,7 +1063,7 @@ func TestWithdrawals(t *testing.T) {
}, },
} }
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
_, err = api.ForkchoiceUpdatedV2(fcState, &blockParams) _, err = api.ForkchoiceUpdatedV2(context.Background(), fcState, &blockParams)
if err != nil { if err != nil {
t.Fatalf("error preparing payload, err=%v", err) t.Fatalf("error preparing payload, err=%v", err)
} }
@ -1090,7 +1090,7 @@ func TestWithdrawals(t *testing.T) {
// 11: set block as head. // 11: set block as head.
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
_, err = api.ForkchoiceUpdatedV2(fcState, nil) _, err = api.ForkchoiceUpdatedV2(context.Background(), fcState, nil)
if err != nil { if err != nil {
t.Fatalf("error preparing payload, err=%v", err) t.Fatalf("error preparing payload, err=%v", err)
} }
@ -1196,10 +1196,10 @@ func TestNilWithdrawals(t *testing.T) {
) )
if !shanghai { if !shanghai {
payloadVersion = engine.PayloadV1 payloadVersion = engine.PayloadV1
_, err = api.ForkchoiceUpdatedV1(fcState, &test.blockParams) _, err = api.ForkchoiceUpdatedV1(context.Background(), fcState, &test.blockParams)
} else { } else {
payloadVersion = engine.PayloadV2 payloadVersion = engine.PayloadV2
_, err = api.ForkchoiceUpdatedV2(fcState, &test.blockParams) _, err = api.ForkchoiceUpdatedV2(context.Background(), fcState, &test.blockParams)
} }
if test.wantErr { if test.wantErr {
if err == nil { if err == nil {
@ -1579,7 +1579,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
fcState := engine.ForkchoiceStateV1{ fcState := engine.ForkchoiceStateV1{
HeadBlockHash: parent.Hash(), HeadBlockHash: parent.Hash(),
} }
resp, err := api.ForkchoiceUpdatedV3(fcState, &blockParams) resp, err := api.ForkchoiceUpdatedV3(context.Background(), fcState, &blockParams)
if err != nil { if err != nil {
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData()) t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
} }
@ -1610,7 +1610,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
} }
fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash
resp, err = api.ForkchoiceUpdatedV3(fcState, nil) resp, err = api.ForkchoiceUpdatedV3(context.Background(), fcState, nil)
if err != nil { if err != nil {
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData()) t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
} }
@ -1666,7 +1666,7 @@ func TestWitnessCreationAndConsumption(t *testing.T) {
SafeBlockHash: common.Hash{}, SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{},
} }
_, err := api.ForkchoiceUpdatedWithWitnessV3(fcState, &blockParams) _, err := api.ForkchoiceUpdatedWithWitnessV3(context.Background(), fcState, &blockParams)
if err != nil { if err != nil {
t.Fatalf("error preparing payload, err=%v", err) t.Fatalf("error preparing payload, err=%v", err)
} }

View file

@ -126,7 +126,7 @@ func NewSimulatedBeacon(period uint64, feeRecipient common.Address, eth *eth.Eth
// if genesis block, send forkchoiceUpdated to trigger transition to PoS // if genesis block, send forkchoiceUpdated to trigger transition to PoS
if block.Number.Sign() == 0 { if block.Number.Sign() == 0 {
version := payloadVersion(eth.BlockChain().Config(), block.Time) version := payloadVersion(eth.BlockChain().Config(), block.Time)
if _, err := engineAPI.forkchoiceUpdated(current, nil, version, false); err != nil { if _, err := engineAPI.forkchoiceUpdated(context.Background(), current, nil, version, false); err != nil {
return nil, err return nil, err
} }
} }
@ -212,7 +212,16 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
slotNumber := uint64(0) slotNumber := uint64(0)
attribute.SlotNumber = &slotNumber attribute.SlotNumber = &slotNumber
} }
fcResponse, err := c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, attribute, version, false)
// Create a server span for forkchoiceUpdated with payload attributes,
// simulating an incoming engine API request from a real consensus client.
fcCtx, fcSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
System: "jsonrpc",
Service: "engine",
Method: "forkchoiceUpdatedV" + fmt.Sprintf("%d", version),
})
fcResponse, err := c.engineAPI.forkchoiceUpdated(fcCtx, c.curForkchoiceState, attribute, version, false)
fcSpanEnd(&err)
if err != nil { if err != nil {
return err return err
} }
@ -226,7 +235,15 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
return nil return nil
} }
// Create a server span for getPayload, simulating the consensus client
// coming back to retrieve the built payload.
_, gpSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
System: "jsonrpc",
Service: "engine",
Method: "getPayloadV" + fmt.Sprintf("%d", version),
})
envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true, nil, nil) envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true, nil, nil)
gpSpanEnd(&err)
if err != nil { if err != nil {
return err return err
} }
@ -274,6 +291,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
Service: "engine", Service: "engine",
Method: "newPayloadV" + fmt.Sprintf("%d", version), Method: "newPayloadV" + fmt.Sprintf("%d", version),
}) })
// Mark the payload as canon // Mark the payload as canon
_, err = c.engineAPI.newPayload(npCtx, *payload, blobHashes, beaconRoot, requests, false) _, err = c.engineAPI.newPayload(npCtx, *payload, blobHashes, beaconRoot, requests, false)
npSpanEnd(&err) npSpanEnd(&err)
@ -282,8 +300,16 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
} }
c.setCurrentState(payload.BlockHash, finalizedHash) c.setCurrentState(payload.BlockHash, finalizedHash)
// Mark the block containing the payload as canonical // Create a server span for the final forkchoiceUpdated (no payload attributes),
if _, err = c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, nil, version, false); err != nil { // which sets the new block as the canonical chain head.
fcuCtx, fcuSpanEnd := telemetry.StartServerSpan(context.Background(), tracer, telemetry.RPCInfo{
System: "jsonrpc",
Service: "engine",
Method: "forkchoiceUpdatedV" + fmt.Sprintf("%d", version),
})
_, err = c.engineAPI.forkchoiceUpdated(fcuCtx, c.curForkchoiceState, nil, version, false)
fcuSpanEnd(&err)
if err != nil {
return err return err
} }
c.lastBlockTime = payload.Timestamp c.lastBlockTime = payload.Timestamp
@ -349,7 +375,7 @@ func (c *SimulatedBeacon) Rollback() {
func (c *SimulatedBeacon) Fork(parentHash common.Hash) error { func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
// Ensure no pending transactions. // Ensure no pending transactions.
c.eth.TxPool().Sync() c.eth.TxPool().Sync()
if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 { if pending, _ := c.eth.TxPool().Pending(txpool.PendingFilter{}); len(pending) != 0 {
return errors.New("pending block dirty") return errors.New("pending block dirty")
} }
@ -363,7 +389,7 @@ func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
// AdjustTime creates a new block with an adjusted timestamp. // AdjustTime creates a new block with an adjusted timestamp.
func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error { func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
if len(c.eth.TxPool().Pending(txpool.PendingFilter{})) != 0 { if pending, _ := c.eth.TxPool().Pending(txpool.PendingFilter{}); len(pending) != 0 {
return errors.New("could not adjust time on non-empty block") return errors.New("could not adjust time on non-empty block")
} }
parent := c.eth.BlockChain().CurrentBlock() parent := c.eth.BlockChain().CurrentBlock()

View file

@ -35,7 +35,7 @@ import (
// ForkchoiceUpdatedWithWitnessV1 is analogous to ForkchoiceUpdatedV1, only it // ForkchoiceUpdatedWithWitnessV1 is analogous to ForkchoiceUpdatedV1, only it
// generates an execution witness too if block building was requested. // generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(ctx context.Context, update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil { if payloadAttributes != nil {
switch { switch {
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil: case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
@ -44,12 +44,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.Forkchoice
return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai") return engine.STATUS_INVALID, paramsErr("fcuV1 called post-shanghai")
} }
} }
return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, true) return api.forkchoiceUpdated(ctx, update, payloadAttributes, engine.PayloadV1, true)
} }
// ForkchoiceUpdatedWithWitnessV2 is analogous to ForkchoiceUpdatedV2, only it // ForkchoiceUpdatedWithWitnessV2 is analogous to ForkchoiceUpdatedV2, only it
// generates an execution witness too if block building was requested. // generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.BeaconRoot != nil: case params.BeaconRoot != nil:
@ -62,12 +62,12 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.Forkchoice
return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads") return engine.STATUS_INVALID, unsupportedForkErr("fcuV2 must only be called with paris or shanghai payloads")
} }
} }
return api.forkchoiceUpdated(update, params, engine.PayloadV2, true) return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV2, true)
} }
// ForkchoiceUpdatedWithWitnessV3 is analogous to ForkchoiceUpdatedV3, only it // ForkchoiceUpdatedWithWitnessV3 is analogous to ForkchoiceUpdatedV3, only it
// generates an execution witness too if block building was requested. // generates an execution witness too if block building was requested.
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(ctx context.Context, update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.Withdrawals == nil: case params.Withdrawals == nil:
@ -82,7 +82,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice
// hash, even if params are wrong. To do this we need to split up // hash, even if params are wrong. To do this we need to split up
// forkchoiceUpdate into a function that only updates the head and then a // forkchoiceUpdate into a function that only updates the head and then a
// function that kicks off block construction. // function that kicks off block construction.
return api.forkchoiceUpdated(update, params, engine.PayloadV3, true) return api.forkchoiceUpdated(ctx, update, params, engine.PayloadV3, true)
} }
// NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates // NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates

View file

@ -81,9 +81,10 @@ func (api *DownloaderAPI) eventLoop() {
prog.TxIndexFinishedBlocks = txProg.Indexed prog.TxIndexFinishedBlocks = txProg.Indexed
prog.TxIndexRemainingBlocks = txProg.Remaining prog.TxIndexRemainingBlocks = txProg.Remaining
} }
remain, err := api.chain.StateIndexProgress() stateRemain, trienodeRemain, err := api.chain.StateIndexProgress()
if err == nil { if err == nil {
prog.StateIndexRemaining = remain prog.StateIndexRemaining = stateRemain
prog.TrienodeIndexRemaining = trienodeRemain
} }
return prog return prog
} }

View file

@ -259,8 +259,8 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et
// RequestReceipts constructs a getReceipts method associated with a particular // RequestReceipts constructs a getReceipts method associated with a particular
// peer in the download tester. The returned function can be used to retrieve // peer in the download tester. The returned function can be used to retrieve
// batches of block receipts from the particularly requested peer. // batches of block receipts from the particularly requested peer.
func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) { func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, gasUsed []uint64, timestamps []uint64, sink chan *eth.Response) (*eth.Request, error) {
blobs := eth.ServiceGetReceiptsQuery(dlp.chain, hashes) blobs := eth.ServiceGetReceiptsQuery69(dlp.chain, hashes)
receipts := make([]types.Receipts, blobs.Len()) receipts := make([]types.Receipts, blobs.Len())
// compute hashes // compute hashes
@ -370,7 +370,7 @@ func (dlp *downloadTesterPeer) RequestTrieNodes(id uint64, root common.Hash, cou
Paths: encPaths, Paths: encPaths,
Bytes: uint64(bytes), Bytes: uint64(bytes),
} }
nodes, _ := snap.ServiceGetTrieNodesQuery(dlp.chain, req, time.Now()) nodes, _ := snap.ServiceGetTrieNodesQuery(dlp.chain, req)
go dlp.dl.downloader.SnapSyncer.OnTrieNodes(dlp, id, nodes) go dlp.dl.downloader.SnapSyncer.OnTrieNodes(dlp, id, nodes)
return nil return nil
} }

View file

@ -78,11 +78,17 @@ func (q *receiptQueue) request(peer *peerConnection, req *fetchRequest, resCh ch
if q.receiptFetchHook != nil { if q.receiptFetchHook != nil {
q.receiptFetchHook(req.Headers) q.receiptFetchHook(req.Headers)
} }
hashes := make([]common.Hash, 0, len(req.Headers)) var (
gasUsed = make([]uint64, 0, len(req.Headers))
timestamps = make([]uint64, 0, len(req.Headers))
hashes = make([]common.Hash, 0, len(req.Headers))
)
for _, header := range req.Headers { for _, header := range req.Headers {
hashes = append(hashes, header.Hash()) hashes = append(hashes, header.Hash())
gasUsed = append(gasUsed, header.GasUsed)
timestamps = append(timestamps, header.Time)
} }
return peer.peer.RequestReceipts(hashes, resCh) return peer.peer.RequestReceipts(hashes, gasUsed, timestamps, resCh)
} }
// deliver is responsible for taking a generic response packet from the concurrent // deliver is responsible for taking a generic response packet from the concurrent

View file

@ -60,7 +60,7 @@ type Peer interface {
RequestHeadersByNumber(uint64, int, int, bool, chan *eth.Response) (*eth.Request, error) RequestHeadersByNumber(uint64, int, int, bool, chan *eth.Response) (*eth.Request, error)
RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error) RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error)
RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error) RequestReceipts([]common.Hash, []uint64, []uint64, chan *eth.Response) (*eth.Request, error)
} }
// newPeerConnection creates a new downloader peer. // newPeerConnection creates a new downloader peer.

View file

@ -208,7 +208,7 @@ func (p *skeletonTestPeer) RequestBodies([]common.Hash, chan *eth.Response) (*et
panic("skeleton sync must not request block bodies") panic("skeleton sync must not request block bodies")
} }
func (p *skeletonTestPeer) RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error) { func (p *skeletonTestPeer) RequestReceipts([]common.Hash, []uint64, []uint64, chan *eth.Response) (*eth.Request, error) {
panic("skeleton sync must not request receipts") panic("skeleton sync must not request receipts")
} }

View file

@ -536,6 +536,9 @@ func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Lo
if f.crit.ToBlock != nil { if f.crit.ToBlock != nil {
end = f.crit.ToBlock.Int64() end = f.crit.ToBlock.Int64()
} }
if begin >= 0 && begin < int64(api.events.backend.HistoryPruningCutoff()) {
return nil, &history.PrunedHistoryError{}
}
// Construct the range filter // Construct the range filter
filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics, api.rangeLimit) filter = api.sys.NewRangeFilter(begin, end, f.crit.Addresses, f.crit.Topics, api.rangeLimit)
} }

View file

@ -19,7 +19,6 @@ package filters
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math" "math"
"math/big" "math/big"
"slices" "slices"
@ -147,7 +146,7 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
return nil, err return nil, err
} }
if f.rangeLimit != 0 && (end-begin) > f.rangeLimit { if f.rangeLimit != 0 && (end-begin) > f.rangeLimit {
return nil, fmt.Errorf("exceed maximum block range: %d", f.rangeLimit) return nil, invalidParamsErr("exceed maximum block range %d", f.rangeLimit)
} }
return f.rangeLogs(ctx, begin, end) return f.rangeLogs(ctx, begin, end)
} }
@ -390,7 +389,7 @@ func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([
} }
if firstBlock > lastBlock { if firstBlock > lastBlock {
return nil, nil return nil, errInvalidBlockRange
} }
mb := f.sys.backend.NewMatcherBackend() mb := f.sys.backend.NewMatcherBackend()
defer mb.Close() defer mb.Close()

View file

@ -19,6 +19,7 @@ package filters
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"math/big" "math/big"
"strings" "strings"
"testing" "testing"
@ -357,7 +358,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}]`, 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), f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil, 0),
@ -633,7 +635,19 @@ func TestRangeLimit(t *testing.T) {
// Set rangeLimit to 5, but request a range of 9 (end - begin = 9, from 0 to 9) // Set rangeLimit to 5, but request a range of 9 (end - begin = 9, from 0 to 9)
filter := sys.NewRangeFilter(0, 9, nil, nil, 5) filter := sys.NewRangeFilter(0, 9, nil, nil, 5)
_, err = filter.Logs(context.Background()) _, err = filter.Logs(context.Background())
if err == nil || !strings.Contains(err.Error(), "exceed maximum block range") { if err == nil {
t.Fatalf("expected range limit error, got %v", err) t.Fatal("expected range limit error, got nil")
}
var re rpc.Error
if errors.As(err, &re) {
if re.ErrorCode() != -32602 {
t.Fatalf("expected error code -32602, got %d", re.ErrorCode())
}
if re.Error() != "exceed maximum block range 5" {
t.Fatalf("expected error message 'exceed maximum block range 5', got %q", re.Error())
}
} else {
t.Fatalf("expected rpc error, got %v", err)
} }
} }

Some files were not shown because too many files have changed in this diff Show more