From 6e64b11da474128e2e839bae7892428634e97d22 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 29 Aug 2025 14:23:45 +0200 Subject: [PATCH 001/228] p2p/discover: add waitForNodes --- p2p/discover/lookup.go | 17 +++-------------- p2p/discover/table.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index 09808b71e079..db1bbe32f927 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -19,7 +19,6 @@ package discover import ( "context" "errors" - "time" "github.com/ethereum/go-ethereum/p2p/enode" ) @@ -106,11 +105,10 @@ func (it *lookup) startQueries() bool { // The first query returns nodes from the local table. if it.queries == -1 { closest := it.tab.findnodeByID(it.result.target, bucketSize, false) - // Avoid finishing the lookup too quickly if table is empty. It'd be better to wait - // for the table to fill in this case, but there is no good mechanism for that - // yet. + // Avoid finishing the lookup too quickly if table is empty. + // Wait for the table to fill. if len(closest.entries) == 0 { - it.slowdown() + it.tab.waitForNodes(1) } it.queries = 1 it.replyCh <- closest.entries @@ -130,15 +128,6 @@ func (it *lookup) startQueries() bool { return it.queries > 0 } -func (it *lookup) slowdown() { - sleep := time.NewTimer(1 * time.Second) - defer sleep.Stop() - select { - case <-sleep.C: - case <-it.tab.closeReq: - } -} - func (it *lookup) query(n *enode.Node, reply chan<- []*enode.Node) { r, err := it.queryfunc(n) if !errors.Is(err, errClosed) { // avoid recording failures on shutdown. diff --git a/p2p/discover/table.go b/p2p/discover/table.go index b6c35aaaa941..63aa655256fa 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p/enode" @@ -84,6 +85,7 @@ type Table struct { closeReq chan struct{} closed chan struct{} + nodeFeed event.FeedOf[*enode.Node] nodeAddedHook func(*bucket, *tableNode) nodeRemovedHook func(*bucket, *tableNode) } @@ -567,6 +569,8 @@ func (tab *Table) nodeAdded(b *bucket, n *tableNode) { } n.addedToBucket = time.Now() tab.revalidation.nodeAdded(tab, n) + + tab.nodeFeed.Send(n.Node) if tab.nodeAddedHook != nil { tab.nodeAddedHook(b, n) } @@ -702,3 +706,36 @@ func (tab *Table) deleteNode(n *enode.Node) { b := tab.bucket(n.ID()) tab.deleteInBucket(b, n.ID()) } + +// waitForNodes blocks until the table contains at least n nodes. +func (tab *Table) waitForNodes(n int) bool { + getlength := func() (count int) { + for _, b := range &tab.buckets { + count += len(b.entries) + } + return count + } + + var ch chan *enode.Node + for { + tab.mutex.Lock() + if getlength() >= n { + tab.mutex.Unlock() + return true + } + if ch == nil { + // Init subscription. + ch = make(chan *enode.Node) + sub := tab.nodeFeed.Subscribe(ch) + defer sub.Unsubscribe() + } + tab.mutex.Unlock() + + // Wait for a node add event. + select { + case <-ch: + case <-tab.closeReq: + return false + } + } +} From 29faeee2c90489343703445993050324d3e67caf Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 29 Aug 2025 15:47:09 +0200 Subject: [PATCH 002/228] p2p/discover: add context in waitForNodes --- p2p/discover/table.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 63aa655256fa..6a1c7494eeb6 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -708,7 +708,7 @@ func (tab *Table) deleteNode(n *enode.Node) { } // waitForNodes blocks until the table contains at least n nodes. -func (tab *Table) waitForNodes(n int) bool { +func (tab *Table) waitForNodes(ctx context.Context, n int) error { getlength := func() (count int) { for _, b := range &tab.buckets { count += len(b.entries) @@ -721,7 +721,7 @@ func (tab *Table) waitForNodes(n int) bool { tab.mutex.Lock() if getlength() >= n { tab.mutex.Unlock() - return true + return nil } if ch == nil { // Init subscription. @@ -734,8 +734,10 @@ func (tab *Table) waitForNodes(n int) bool { // Wait for a node add event. select { case <-ch: + case <-ctx.Done(): + return ctx.Err() case <-tab.closeReq: - return false + return errClosed } } } From 3c6e404ddb25831762df8225a77dc6a118d02488 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 29 Aug 2025 15:47:28 +0200 Subject: [PATCH 003/228] p2p/discover: move wait condition to lookupIterator --- p2p/discover/lookup.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index db1bbe32f927..de4761d73793 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -19,6 +19,7 @@ package discover import ( "context" "errors" + "time" "github.com/ethereum/go-ethereum/p2p/enode" ) @@ -105,10 +106,8 @@ func (it *lookup) startQueries() bool { // The first query returns nodes from the local table. if it.queries == -1 { closest := it.tab.findnodeByID(it.result.target, bucketSize, false) - // Avoid finishing the lookup too quickly if table is empty. - // Wait for the table to fill. if len(closest.entries) == 0 { - it.tab.waitForNodes(1) + return false } it.queries = 1 it.replyCh <- closest.entries @@ -171,6 +170,7 @@ func (it *lookupIterator) Next() bool { if len(it.buffer) > 0 { it.buffer = it.buffer[1:] } + // Advance the lookup to refill the buffer. for len(it.buffer) == 0 { if it.ctx.Err() != nil { @@ -183,6 +183,7 @@ func (it *lookupIterator) Next() bool { continue } if !it.lookup.advance() { + it.lookupFailed(it.lookup.tab) it.lookup = nil continue } @@ -191,6 +192,16 @@ func (it *lookupIterator) Next() bool { return true } +// lookupFailed handles failed lookup attempts. This can be called when the table has +// exited, or when it runs out of nodes. +func (it *lookupIterator) lookupFailed(tab *Table) { + timeout, cancel := context.WithTimeout(it.ctx, 1*time.Minute) + defer cancel() + tab.waitForNodes(timeout, 1) + + // TODO: here we need to trigger a table refresh somehow +} + // Close ends the iterator. func (it *lookupIterator) Close() { it.cancel() From 1c956a10138ac84b74cd153a70191906268d8f94 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 5 Sep 2025 11:15:09 +0200 Subject: [PATCH 004/228] p2p/discover: improve iterator --- p2p/discover/lookup.go | 48 +++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index de4761d73793..91d4d7a919cc 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -49,11 +49,14 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l result: nodesByDistance{target: target}, replyCh: make(chan []*enode.Node, alpha), cancelCh: ctx.Done(), - queries: -1, } // Don't query further if we hit ourself. // Unlikely to happen often in practice. it.asked[tab.self().ID()] = true + + // Initialize the lookup with nodes from table. + closest := it.tab.findnodeByID(it.result.target, bucketSize, false) + it.addNodes(closest.entries) return it } @@ -64,22 +67,18 @@ func (it *lookup) run() []*enode.Node { return it.result.entries } +func (it *lookup) empty() bool { + return len(it.replyBuffer) == 0 +} + // advance advances the lookup until any new nodes have been found. // It returns false when the lookup has ended. func (it *lookup) advance() bool { for it.startQueries() { select { case nodes := <-it.replyCh: - it.replyBuffer = it.replyBuffer[:0] - for _, n := range nodes { - if n != nil && !it.seen[n.ID()] { - it.seen[n.ID()] = true - it.result.push(n, bucketSize) - it.replyBuffer = append(it.replyBuffer, n) - } - } it.queries-- - if len(it.replyBuffer) > 0 { + if it.addNodes(nodes) { return true } case <-it.cancelCh: @@ -89,6 +88,18 @@ func (it *lookup) advance() bool { return false } +func (it *lookup) addNodes(nodes []*enode.Node) (done bool) { + it.replyBuffer = it.replyBuffer[:0] + for _, n := range nodes { + if n != nil && !it.seen[n.ID()] { + it.seen[n.ID()] = true + it.result.push(n, bucketSize) + it.replyBuffer = append(it.replyBuffer, n) + } + } + return len(it.replyBuffer) == 0 +} + func (it *lookup) shutdown() { for it.queries > 0 { <-it.replyCh @@ -103,17 +114,6 @@ func (it *lookup) startQueries() bool { return false } - // The first query returns nodes from the local table. - if it.queries == -1 { - closest := it.tab.findnodeByID(it.result.target, bucketSize, false) - if len(closest.entries) == 0 { - return false - } - it.queries = 1 - it.replyCh <- closest.entries - return true - } - // Ask the closest nodes that we haven't asked yet. for i := 0; i < len(it.result.entries) && it.queries < alpha; i++ { n := it.result.entries[i] @@ -180,10 +180,14 @@ func (it *lookupIterator) Next() bool { } if it.lookup == nil { it.lookup = it.nextLookup(it.ctx) + if it.lookup.empty() { + // If the lookup is empty right after creation, it means the local table + // is in a degraded state, and we need to wait for it to fill again. + it.lookupFailed(it.lookup.tab) + } continue } if !it.lookup.advance() { - it.lookupFailed(it.lookup.tab) it.lookup = nil continue } From a8fff86d44bb532729934dfa1489a6a9e72b5f88 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 5 Sep 2025 14:15:09 +0200 Subject: [PATCH 005/228] p2p/discover: fix bug in lookup --- p2p/discover/lookup.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index 91d4d7a919cc..c2b39a8c2ba5 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -78,7 +78,8 @@ func (it *lookup) advance() bool { select { case nodes := <-it.replyCh: it.queries-- - if it.addNodes(nodes) { + it.addNodes(nodes) + if !it.empty() { return true } case <-it.cancelCh: @@ -88,7 +89,7 @@ func (it *lookup) advance() bool { return false } -func (it *lookup) addNodes(nodes []*enode.Node) (done bool) { +func (it *lookup) addNodes(nodes []*enode.Node) { it.replyBuffer = it.replyBuffer[:0] for _, n := range nodes { if n != nil && !it.seen[n.ID()] { @@ -97,7 +98,6 @@ func (it *lookup) addNodes(nodes []*enode.Node) (done bool) { it.replyBuffer = append(it.replyBuffer, n) } } - return len(it.replyBuffer) == 0 } func (it *lookup) shutdown() { From af9eb45069c23407eb0872534726db9ac810de13 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 5 Sep 2025 14:16:17 +0200 Subject: [PATCH 006/228] p2p/discover: trigger refresh in lookupIterator --- p2p/discover/lookup.go | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index c2b39a8c2ba5..d15cdccc4817 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -142,11 +142,12 @@ func (it *lookup) query(n *enode.Node, reply chan<- []*enode.Node) { // lookupIterator performs lookup operations and iterates over all seen nodes. // When a lookup finishes, a new one is created through nextLookup. type lookupIterator struct { - buffer []*enode.Node - nextLookup lookupFunc - ctx context.Context - cancel func() - lookup *lookup + buffer []*enode.Node + nextLookup lookupFunc + ctx context.Context + cancel func() + lookup *lookup + tabRefreshing <-chan struct{} } type lookupFunc func(ctx context.Context) *lookup @@ -187,11 +188,11 @@ func (it *lookupIterator) Next() bool { } continue } - if !it.lookup.advance() { + newNodes := it.lookup.advance() + it.buffer = it.lookup.replyBuffer + if !newNodes { it.lookup = nil - continue } - it.buffer = it.lookup.replyBuffer } return true } @@ -201,9 +202,27 @@ func (it *lookupIterator) Next() bool { func (it *lookupIterator) lookupFailed(tab *Table) { timeout, cancel := context.WithTimeout(it.ctx, 1*time.Minute) defer cancel() - tab.waitForNodes(timeout, 1) - // TODO: here we need to trigger a table refresh somehow + // Wait for Table initialization to complete, in case it is still in progress. + select { + case <-tab.initDone: + case <-timeout.Done(): + return + } + + // Wait for ongoing refresh operation, or trigger one. + if it.tabRefreshing == nil { + it.tabRefreshing = tab.refresh() + } + select { + case <-it.tabRefreshing: + it.tabRefreshing = nil + case <-timeout.Done(): + return + } + + // Wait for the table to fill. + tab.waitForNodes(timeout, 1) } // Close ends the iterator. From e848f373285975ddeb0762580ffe3464a1d0598d Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 5 Sep 2025 14:16:51 +0200 Subject: [PATCH 007/228] p2p/discover: track missing nodes in test --- p2p/discover/v4_udp_test.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 1af31f4f1b9b..b1363c73b004 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -509,18 +509,27 @@ func TestUDPv4_smallNetConvergence(t *testing.T) { // they have all found each other. status := make(chan error, len(nodes)) for i := range nodes { - node := nodes[i] + self := nodes[i] go func() { - found := make(map[enode.ID]bool, len(nodes)) - it := node.RandomNodes() + missing := make(map[enode.ID]bool, len(nodes)) + for _, n := range nodes { + if n.Self().ID() == self.Self().ID() { + continue // skip self + } + missing[n.Self().ID()] = true + } + + it := self.RandomNodes() for it.Next() { - found[it.Node().ID()] = true - if len(found) == len(nodes) { + fmt.Println(self.Self().ID(), "found:", it.Node().ID()) + delete(missing, it.Node().ID()) + if len(missing) == 0 { status <- nil return } } - status <- fmt.Errorf("node %s didn't find all nodes", node.Self().ID().TerminalString()) + missingIDs := slices.Collect(maps.Keys(missing)) + status <- fmt.Errorf("node %s didn't find all nodes, missing %v", self.Self().ID().TerminalString(), missingIDs) }() } @@ -537,7 +546,6 @@ func TestUDPv4_smallNetConvergence(t *testing.T) { received++ if err != nil { t.Error("ERROR:", err) - return } } } From 4d9ed321dbd76dc64c9a64ce3a4b14126a7ee1d2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 9 Sep 2025 12:05:04 +0200 Subject: [PATCH 008/228] p2p/discover: fix two bugs in lookup iterator The lookup would add self into the replyBuffer if returned by another node. Avoid doing that by marking self as seen. With the changed initialization behavior of lookup, the lookupIterator needs to yield the buffer right after creation. This fixes the smallNetConvergence test, where all results are straight out of the local table. --- p2p/discover/lookup.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index d15cdccc4817..88c93a57cfee 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -53,6 +53,7 @@ func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *l // Don't query further if we hit ourself. // Unlikely to happen often in practice. it.asked[tab.self().ID()] = true + it.seen[tab.self().ID()] = true // Initialize the lookup with nodes from table. closest := it.tab.findnodeByID(it.result.target, bucketSize, false) @@ -186,8 +187,12 @@ func (it *lookupIterator) Next() bool { // is in a degraded state, and we need to wait for it to fill again. it.lookupFailed(it.lookup.tab) } + // Otherwise, yield the initial nodes from the iterator before advancing + // the lookup. + it.buffer = it.lookup.replyBuffer continue } + newNodes := it.lookup.advance() it.buffer = it.lookup.replyBuffer if !newNodes { From eda3cfbc510c8ff68e568e9c4e4bb47bdec14a8e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 9 Sep 2025 12:07:54 +0200 Subject: [PATCH 009/228] p2p/discover: remove print in test --- p2p/discover/v4_udp_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index b1363c73b004..21c4daada3b1 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -521,7 +521,6 @@ func TestUDPv4_smallNetConvergence(t *testing.T) { it := self.RandomNodes() for it.Next() { - fmt.Println(self.Self().ID(), "found:", it.Node().ID()) delete(missing, it.Node().ID()) if len(missing) == 0 { status <- nil From 6092898506ec81667504f06a9281744458be3a95 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 9 Sep 2025 12:08:44 +0200 Subject: [PATCH 010/228] p2p/discover: add imports in test --- p2p/discover/v4_udp_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 21c4daada3b1..44863183fa92 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -24,10 +24,12 @@ import ( "errors" "fmt" "io" + "maps" "math/rand" "net" "net/netip" "reflect" + "slices" "sync" "testing" "time" From f34a9a910ad3ba9a3ce3aa7104842d72bb6ad60a Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Fri, 12 Sep 2025 10:52:53 +0200 Subject: [PATCH 011/228] p2p/discover: clarify lookup behavior on empty table We have changed this behavior, better clarify in comment. Signed-off-by: Csaba Kiraly --- p2p/discover/lookup.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index 88c93a57cfee..7382bbe41a34 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -27,6 +27,7 @@ import ( // lookup performs a network search for nodes close to the given target. It approaches the // target by querying nodes that are closer to it on each iteration. The given target does // not need to be an actual node identifier. +// lookup on an empty table will return immediately with no nodes. type lookup struct { tab *Table queryfunc queryFunc @@ -142,6 +143,9 @@ func (it *lookup) query(n *enode.Node, reply chan<- []*enode.Node) { // lookupIterator performs lookup operations and iterates over all seen nodes. // When a lookup finishes, a new one is created through nextLookup. +// LookupIterator waits for table initialization and triggers a table refresh +// when necessary. + type lookupIterator struct { buffer []*enode.Node nextLookup lookupFunc From a974d74100eff43eb668f06a7e4ed070753f882c Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Fri, 12 Sep 2025 10:59:29 +0200 Subject: [PATCH 012/228] p2p/discover: add test for lookup returning immediately Signed-off-by: Csaba Kiraly --- p2p/discover/v4_lookup_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go index 29a9dd6645e0..88ca82039e87 100644 --- a/p2p/discover/v4_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -23,6 +23,7 @@ import ( "slices" "sync" "testing" + "time" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/discover/v4wire" @@ -34,11 +35,15 @@ func TestUDPv4_Lookup(t *testing.T) { t.Parallel() test := newUDPTest(t) - // Lookup on empty table returns no nodes. + // Lookup on empty table returns immediately with no nodes. targetKey, _ := v4wire.DecodePubkey(crypto.S256(), lookupTestnet.target) + start := time.Now() if results := test.udp.LookupPubkey(targetKey); len(results) > 0 { t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) } + if time.Since(start) > 100*time.Millisecond { + t.Fatalf("lookup on empty table took too long: %s", time.Since(start)) + } // Seed table with initial node. fillTable(test.table, []*enode.Node{lookupTestnet.node(256, 0)}, true) From 2b25b2b79e6509498f8009fc162a8863c9f1f3cc Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Fri, 12 Sep 2025 11:29:43 +0200 Subject: [PATCH 013/228] Revert "p2p/discover: add test for lookup returning immediately" This reverts commit 3eab4616a69bc313a94f073e24cd5093b0dc922b. --- p2p/discover/v4_lookup_test.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go index 88ca82039e87..29a9dd6645e0 100644 --- a/p2p/discover/v4_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -23,7 +23,6 @@ import ( "slices" "sync" "testing" - "time" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/discover/v4wire" @@ -35,15 +34,11 @@ func TestUDPv4_Lookup(t *testing.T) { t.Parallel() test := newUDPTest(t) - // Lookup on empty table returns immediately with no nodes. + // Lookup on empty table returns no nodes. targetKey, _ := v4wire.DecodePubkey(crypto.S256(), lookupTestnet.target) - start := time.Now() if results := test.udp.LookupPubkey(targetKey); len(results) > 0 { t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) } - if time.Since(start) > 100*time.Millisecond { - t.Fatalf("lookup on empty table took too long: %s", time.Since(start)) - } // Seed table with initial node. fillTable(test.table, []*enode.Node{lookupTestnet.node(256, 0)}, true) From 60a6847680d71e45d164b9a6aa483fdbfdd4d235 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 12 Sep 2025 11:34:44 +0200 Subject: [PATCH 014/228] Update lookup.go --- p2p/discover/lookup.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index 7382bbe41a34..efa8191ae09d 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -191,8 +191,7 @@ func (it *lookupIterator) Next() bool { // is in a degraded state, and we need to wait for it to fill again. it.lookupFailed(it.lookup.tab) } - // Otherwise, yield the initial nodes from the iterator before advancing - // the lookup. + // Yield the initial nodes from the iterator before advancing the lookup. it.buffer = it.lookup.replyBuffer continue } From c3f0e7a5886b0c9b5e5347d7a3635ded410c9da8 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 12 Sep 2025 12:50:07 +0200 Subject: [PATCH 015/228] p2p/discover: continue --- p2p/discover/lookup.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index efa8191ae09d..ff2dc907cd1d 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -190,6 +190,8 @@ func (it *lookupIterator) Next() bool { // If the lookup is empty right after creation, it means the local table // is in a degraded state, and we need to wait for it to fill again. it.lookupFailed(it.lookup.tab) + it.lookup = nil + continue } // Yield the initial nodes from the iterator before advancing the lookup. it.buffer = it.lookup.replyBuffer From 1acd3e82affd4e7a4990b6c7cee3871360255686 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Tue, 16 Sep 2025 14:03:11 +0200 Subject: [PATCH 016/228] p2p/discover: expose timeout in lookupFailed Signed-off-by: Csaba Kiraly # Conflicts: # p2p/discover/lookup.go --- p2p/discover/lookup.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index ff2dc907cd1d..684067a61995 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -189,7 +189,7 @@ func (it *lookupIterator) Next() bool { if it.lookup.empty() { // If the lookup is empty right after creation, it means the local table // is in a degraded state, and we need to wait for it to fill again. - it.lookupFailed(it.lookup.tab) + it.lookupFailed(1 * time.Minute) it.lookup = nil continue } @@ -209,30 +209,30 @@ func (it *lookupIterator) Next() bool { // lookupFailed handles failed lookup attempts. This can be called when the table has // exited, or when it runs out of nodes. -func (it *lookupIterator) lookupFailed(tab *Table) { - timeout, cancel := context.WithTimeout(it.ctx, 1*time.Minute) +func (it *lookupIterator) lookupFailed(timeout time.Duration) { + ctx, cancel := context.WithTimeout(it.ctx, timeout) defer cancel() // Wait for Table initialization to complete, in case it is still in progress. select { - case <-tab.initDone: - case <-timeout.Done(): + case <-it.lookup.tab.initDone: + case <-ctx.Done(): return } // Wait for ongoing refresh operation, or trigger one. if it.tabRefreshing == nil { - it.tabRefreshing = tab.refresh() + it.tabRefreshing = it.lookup.tab.refresh() } select { case <-it.tabRefreshing: it.tabRefreshing = nil - case <-timeout.Done(): + case <-ctx.Done(): return } // Wait for the table to fill. - tab.waitForNodes(timeout, 1) + it.lookup.tab.waitForNodes(ctx, 1) } // Close ends the iterator. From 9ee23be59269f1d9d24f9838d4ecb832afc8540e Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Wed, 17 Sep 2025 09:02:38 +0200 Subject: [PATCH 017/228] revert to using table parameter using it.lookup.tab inside is unsafe Signed-off-by: Csaba Kiraly --- p2p/discover/lookup.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index 684067a61995..9cca0118ac65 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -189,7 +189,7 @@ func (it *lookupIterator) Next() bool { if it.lookup.empty() { // If the lookup is empty right after creation, it means the local table // is in a degraded state, and we need to wait for it to fill again. - it.lookupFailed(1 * time.Minute) + it.lookupFailed(it.lookup.tab, 1*time.Minute) it.lookup = nil continue } @@ -209,30 +209,30 @@ func (it *lookupIterator) Next() bool { // lookupFailed handles failed lookup attempts. This can be called when the table has // exited, or when it runs out of nodes. -func (it *lookupIterator) lookupFailed(timeout time.Duration) { - ctx, cancel := context.WithTimeout(it.ctx, timeout) +func (it *lookupIterator) lookupFailed(tab *Table, timeout time.Duration) { + tout, cancel := context.WithTimeout(it.ctx, timeout) defer cancel() // Wait for Table initialization to complete, in case it is still in progress. select { - case <-it.lookup.tab.initDone: - case <-ctx.Done(): + case <-tab.initDone: + case <-tout.Done(): return } // Wait for ongoing refresh operation, or trigger one. if it.tabRefreshing == nil { - it.tabRefreshing = it.lookup.tab.refresh() + it.tabRefreshing = tab.refresh() } select { case <-it.tabRefreshing: it.tabRefreshing = nil - case <-ctx.Done(): + case <-tout.Done(): return } // Wait for the table to fill. - it.lookup.tab.waitForNodes(ctx, 1) + tab.waitForNodes(tout, 1) } // Close ends the iterator. From 015658511cd2b81d4ca09fa8b294c1f7f2708901 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 17 Sep 2025 11:41:14 +0200 Subject: [PATCH 018/228] go.mod: update c-kzg (#32639) Updates c-kzg to the latest release: https://github.com/ethereum/c-kzg-4844/releases/tag/v2.1.3 --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 15a920c5cac2..97673cc7ed9c 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 - github.com/ethereum/c-kzg-4844/v2 v2.1.0 + github.com/ethereum/c-kzg-4844/v2 v2.1.3 github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab github.com/ethereum/go-verkle v0.2.2 github.com/fatih/color v1.16.0 @@ -60,7 +60,7 @@ require ( github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible github.com/status-im/keycard-go v0.2.0 github.com/stretchr/testify v1.10.0 - github.com/supranational/blst v0.3.14 + github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/urfave/cli/v2 v2.27.5 go.uber.org/automaxprocs v1.5.2 diff --git a/go.sum b/go.sum index 6ea07ab007ae..7107e4f5ec2e 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,8 @@ github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w= github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= +github.com/ethereum/c-kzg-4844/v2 v2.1.3 h1:DQ21UU0VSsuGy8+pcMJHDS0CV1bKmJmxsJYK8l3MiLU= +github.com/ethereum/c-kzg-4844/v2 v2.1.3/go.mod h1:fyNcYI/yAuLWJxf4uzVtS8VDKeoAaRM8G/+ADz/pRdA= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= @@ -353,6 +355,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= +github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= From 7b9b0e7e28ba48e443324b061f1dd7af4b31a414 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 17 Sep 2025 12:33:44 +0200 Subject: [PATCH 019/228] go.mod: update go-eth-kzg (#32640) Updates go-eth-kzg: https://github.com/crate-crypto/go-eth-kzg/releases/tag/v1.4.0 --- go.mod | 2 +- go.sum | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 97673cc7ed9c..03cdf3bb2daf 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/cloudflare/cloudflare-go v0.114.0 github.com/cockroachdb/pebble v1.1.5 github.com/consensys/gnark-crypto v0.18.0 - github.com/crate-crypto/go-eth-kzg v1.3.0 + github.com/crate-crypto/go-eth-kzg v1.4.0 github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a github.com/davecgh/go-spew v1.1.1 github.com/dchest/siphash v1.2.3 diff --git a/go.sum b/go.sum index 7107e4f5ec2e..764cfdb668ca 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEf github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI= -github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= +github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= +github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -112,8 +112,6 @@ github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= -github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w= -github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= github.com/ethereum/c-kzg-4844/v2 v2.1.3 h1:DQ21UU0VSsuGy8+pcMJHDS0CV1bKmJmxsJYK8l3MiLU= github.com/ethereum/c-kzg-4844/v2 v2.1.3/go.mod h1:fyNcYI/yAuLWJxf4uzVtS8VDKeoAaRM8G/+ADz/pRdA= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= @@ -353,8 +351,6 @@ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= -github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= From 6ca13c6e4906a43c28f2be3615fcbf0083111ee3 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:06:39 +0200 Subject: [PATCH 020/228] core/stateless: add vmwitnessstats cli flag to report leaf stats + log to console (#32619) The format that is currently reported by the chain isn't very useful, as it gives an average for ALL the nodes, and not only the leaves, which skews the results. Also, until now there was no way to activate the reporting of errors. We also decided that metrics weren't the right tool to report this data, so we decided to dump it to the console if the flag is enabled. A better system should be built, but for now, printing to the logs does the job. --- cmd/geth/main.go | 2 ++ cmd/utils/flags.go | 22 ++++++++++++++ core/blockchain.go | 2 +- core/stateless/stats.go | 16 +++++++++- core/stateless/stats_test.go | 58 ++++++++++++++++++++++++++++++++++++ eth/backend.go | 2 ++ eth/ethconfig/config.go | 6 ++++ eth/ethconfig/gen_config.go | 12 ++++++++ 8 files changed, 118 insertions(+), 2 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 750bf55927cc..b661228681f0 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -133,6 +133,8 @@ var ( utils.VMEnableDebugFlag, utils.VMTraceFlag, utils.VMTraceJsonConfigFlag, + utils.VMWitnessStatsFlag, + utils.VMStatelessSelfValidationFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, utils.GpoBlocksFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a134ea430832..83d1c8bda536 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -571,6 +571,16 @@ var ( Value: "{}", Category: flags.VMCategory, } + VMWitnessStatsFlag = &cli.BoolFlag{ + Name: "vmwitnessstats", + Usage: "Enable collection of witness trie access statistics (automatically enables witness generation)", + Category: flags.VMCategory, + } + VMStatelessSelfValidationFlag = &cli.BoolFlag{ + Name: "stateless-self-validation", + Usage: "Generate execution witnesses and self-check against them (testing purpose)", + Category: flags.VMCategory, + } // API options. RPCGlobalGasCapFlag = &cli.Uint64Flag{ Name: "rpc.gascap", @@ -1707,6 +1717,16 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(VMEnableDebugFlag.Name) { cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) } + if ctx.IsSet(VMWitnessStatsFlag.Name) { + cfg.EnableWitnessStats = ctx.Bool(VMWitnessStatsFlag.Name) + } + if ctx.IsSet(VMStatelessSelfValidationFlag.Name) { + cfg.StatelessSelfValidation = ctx.Bool(VMStatelessSelfValidationFlag.Name) + } + // Auto-enable StatelessSelfValidation when witness stats are enabled + if ctx.Bool(VMWitnessStatsFlag.Name) { + cfg.StatelessSelfValidation = true + } if ctx.IsSet(RPCGlobalGasCapFlag.Name) { cfg.RPCGasCap = ctx.Uint64(RPCGlobalGasCapFlag.Name) @@ -2243,6 +2263,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh } vmcfg := vm.Config{ EnablePreimageRecording: ctx.Bool(VMEnableDebugFlag.Name), + EnableWitnessStats: ctx.Bool(VMWitnessStatsFlag.Name), + StatelessSelfValidation: ctx.Bool(VMStatelessSelfValidationFlag.Name) || ctx.Bool(VMWitnessStatsFlag.Name), } if ctx.IsSet(VMTraceFlag.Name) { if name := ctx.String(VMTraceFlag.Name); name != "" { diff --git a/core/blockchain.go b/core/blockchain.go index 939b46634f8c..346692364861 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2157,7 +2157,7 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s } // Report the collected witness statistics if witnessStats != nil { - witnessStats.ReportMetrics() + witnessStats.ReportMetrics(block.NumberU64()) } // Update the metrics touched during block commit diff --git a/core/stateless/stats.go b/core/stateless/stats.go index 1a6389284ceb..94f5587f9997 100644 --- a/core/stateless/stats.go +++ b/core/stateless/stats.go @@ -17,6 +17,7 @@ package stateless import ( + "encoding/json" "maps" "slices" "sort" @@ -24,6 +25,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" ) @@ -70,7 +72,19 @@ func (s *WitnessStats) Add(nodes map[string][]byte, owner common.Hash) { } // ReportMetrics reports the collected statistics to the global metrics registry. -func (s *WitnessStats) ReportMetrics() { +func (s *WitnessStats) ReportMetrics(blockNumber uint64) { + // Encode the metrics as JSON for easier consumption + accountLeavesJson, _ := json.Marshal(s.accountTrieLeaves) + storageLeavesJson, _ := json.Marshal(s.storageTrieLeaves) + + // Log account trie depth statistics + log.Info("Account trie depth stats", + "block", blockNumber, + "leavesAtDepth", string(accountLeavesJson)) + log.Info("Storage trie depth stats", + "block", blockNumber, + "leavesAtDepth", string(storageLeavesJson)) + for i := 0; i < 16; i++ { accountTrieLeavesAtDepth[i].Inc(s.accountTrieLeaves[i]) storageTrieLeavesAtDepth[i].Inc(s.storageTrieLeaves[i]) diff --git a/core/stateless/stats_test.go b/core/stateless/stats_test.go index 6219e622f5bb..e77084df5dcb 100644 --- a/core/stateless/stats_test.go +++ b/core/stateless/stats_test.go @@ -140,6 +140,64 @@ func TestWitnessStatsAdd(t *testing.T) { } } +func TestWitnessStatsMinMax(t *testing.T) { + stats := NewWitnessStats() + + // Add some account trie nodes with varying depths + stats.Add(map[string][]byte{ + "a": []byte("data1"), + "ab": []byte("data2"), + "abc": []byte("data3"), + "abcd": []byte("data4"), + "abcde": []byte("data5"), + }, common.Hash{}) + + // Only "abcde" is a leaf (depth 5) + for i, v := range stats.accountTrieLeaves { + if v != 0 && i != 5 { + t.Errorf("leaf found at invalid depth %d", i) + } + } + + // Add more leaves with different depths + stats.Add(map[string][]byte{ + "x": []byte("data6"), + "yz": []byte("data7"), + }, common.Hash{}) + + // Now we have leaves at depths 1, 2, and 5 + for i, v := range stats.accountTrieLeaves { + if v != 0 && (i != 5 && i != 2 && i != 1) { + t.Errorf("leaf found at invalid depth %d", i) + } + } +} + +func TestWitnessStatsAverage(t *testing.T) { + stats := NewWitnessStats() + + // Add nodes that will create leaves at depths 2, 3, and 4 + stats.Add(map[string][]byte{ + "aa": []byte("data1"), + "bb": []byte("data2"), + "ccc": []byte("data3"), + "dddd": []byte("data4"), + }, common.Hash{}) + + // All are leaves: 2 + 2 + 3 + 4 = 11 total, 4 samples + expectedAvg := int64(11) / int64(4) + var actualAvg, totalSamples int64 + for i, c := range stats.accountTrieLeaves { + actualAvg += c * int64(i) + totalSamples += c + } + actualAvg = actualAvg / totalSamples + + if actualAvg != expectedAvg { + t.Errorf("Account trie average depth = %d, want %d", actualAvg, expectedAvg) + } +} + func BenchmarkWitnessStatsAdd(b *testing.B) { // Create a realistic trie node structure nodes := make(map[string][]byte) diff --git a/eth/backend.go b/eth/backend.go index 435673318915..3bfe0765f4cb 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -235,6 +235,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { TxLookupLimit: int64(min(config.TransactionHistory, math.MaxInt64)), VmConfig: vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, + EnableWitnessStats: config.EnableWitnessStats, + StatelessSelfValidation: config.StatelessSelfValidation, }, // Enables file journaling for the trie database. The journal files will be stored // within the data directory. The corresponding paths will be either: diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index dc7714108168..ba0a7762c726 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -144,6 +144,12 @@ type Config struct { // Enables tracking of SHA3 preimages in the VM EnablePreimageRecording bool + // Enables collection of witness trie access statistics + EnableWitnessStats bool + + // Generate execution witnesses and self-check against them (testing purpose) + StatelessSelfValidation bool + // Enables tracking of state size EnableStateSizeTracking bool diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 2fdd219dee8e..b54ba14d6889 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -49,6 +49,8 @@ func (c Config) MarshalTOML() (interface{}, error) { BlobPool blobpool.Config GPO gasprice.Config EnablePreimageRecording bool + EnableWitnessStats bool + StatelessSelfValidation bool EnableStateSizeTracking bool VMTrace string VMTraceJsonConfig string @@ -91,6 +93,8 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.BlobPool = c.BlobPool enc.GPO = c.GPO enc.EnablePreimageRecording = c.EnablePreimageRecording + enc.EnableWitnessStats = c.EnableWitnessStats + enc.StatelessSelfValidation = c.StatelessSelfValidation enc.EnableStateSizeTracking = c.EnableStateSizeTracking enc.VMTrace = c.VMTrace enc.VMTraceJsonConfig = c.VMTraceJsonConfig @@ -137,6 +141,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { BlobPool *blobpool.Config GPO *gasprice.Config EnablePreimageRecording *bool + EnableWitnessStats *bool + StatelessSelfValidation *bool EnableStateSizeTracking *bool VMTrace *string VMTraceJsonConfig *string @@ -246,6 +252,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.EnablePreimageRecording != nil { c.EnablePreimageRecording = *dec.EnablePreimageRecording } + if dec.EnableWitnessStats != nil { + c.EnableWitnessStats = *dec.EnableWitnessStats + } + if dec.StatelessSelfValidation != nil { + c.StatelessSelfValidation = *dec.StatelessSelfValidation + } if dec.EnableStateSizeTracking != nil { c.EnableStateSizeTracking = *dec.EnableStateSizeTracking } From 6db8bf6c4c80aaa5763753bb42a27323a6d589cc Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 17 Sep 2025 21:57:16 +0800 Subject: [PATCH 021/228] triedb/pathdb: generalize the history indexer (#32523) This pull request is based on #32306 , is the second part for shipping trienode history. Specifically, this pull request generalize the existing index mechanism, making is usable by both state history and trienode history in the near future. --- triedb/pathdb/database.go | 8 +- triedb/pathdb/disklayer.go | 2 +- triedb/pathdb/history.go | 146 +++++++++++- triedb/pathdb/history_index.go | 179 +++++++------- triedb/pathdb/history_index_test.go | 10 +- triedb/pathdb/history_indexer.go | 329 ++++++++++++++------------ triedb/pathdb/history_indexer_test.go | 2 +- triedb/pathdb/history_reader.go | 88 +------ triedb/pathdb/history_reader_test.go | 2 +- triedb/pathdb/history_state.go | 35 ++- triedb/pathdb/history_state_test.go | 12 +- 11 files changed, 468 insertions(+), 345 deletions(-) diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index ae9574963e05..546d2e0301f9 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -189,7 +189,7 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database { } // TODO (rjl493456442) disable the background indexing in read-only mode if db.stateFreezer != nil && db.config.EnableStateIndexing { - db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID()) + db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID(), typeStateHistory) log.Info("Enabled state history indexing") } fields := config.fields() @@ -245,7 +245,7 @@ func (db *Database) repairHistory() error { } // Truncate the extra state histories above in freezer in case it's not // aligned with the disk layer. It might happen after a unclean shutdown. - pruned, err := truncateFromHead(db.stateFreezer, id) + pruned, err := truncateFromHead(db.stateFreezer, typeStateHistory, id) if err != nil { log.Crit("Failed to truncate extra state histories", "err", err) } @@ -448,7 +448,7 @@ func (db *Database) Enable(root common.Hash) error { // 2. Re-initialize the indexer so it starts indexing from the new state root. if db.stateIndexer != nil && db.stateFreezer != nil && db.config.EnableStateIndexing { db.stateIndexer.close() - db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID()) + db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID(), typeStateHistory) log.Info("Re-enabled state history indexing") } log.Info("Rebuilt trie database", "root", root) @@ -502,7 +502,7 @@ func (db *Database) Recover(root common.Hash) error { if err := db.diskdb.SyncKeyValue(); err != nil { return err } - _, err := truncateFromHead(db.stateFreezer, dl.stateID()) + _, err := truncateFromHead(db.stateFreezer, typeStateHistory, dl.stateID()) if err != nil { return err } diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go index 2042e916110c..76f3f5a46eaf 100644 --- a/triedb/pathdb/disklayer.go +++ b/triedb/pathdb/disklayer.go @@ -378,7 +378,7 @@ func (dl *diskLayer) writeStateHistory(diff *diffLayer) (bool, error) { log.Debug("Skip tail truncation", "persistentID", persistentID, "tailID", tail+1, "headID", diff.stateID(), "limit", limit) return true, nil } - pruned, err := truncateFromTail(dl.db.stateFreezer, newFirst-1) + pruned, err := truncateFromTail(dl.db.stateFreezer, typeStateHistory, newFirst-1) if err != nil { return false, err } diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go index bbedd52f344a..ae022236fefb 100644 --- a/triedb/pathdb/history.go +++ b/triedb/pathdb/history.go @@ -19,11 +19,147 @@ package pathdb import ( "errors" "fmt" + "iter" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" ) +// historyType represents the category of historical data. +type historyType uint8 + +const ( + // typeStateHistory indicates history data related to account or storage changes. + typeStateHistory historyType = 0 +) + +// String returns the string format representation. +func (h historyType) String() string { + switch h { + case typeStateHistory: + return "state" + default: + return fmt.Sprintf("unknown type: %d", h) + } +} + +// elementType represents the category of state element. +type elementType uint8 + +const ( + typeAccount elementType = 0 // represents the account data + typeStorage elementType = 1 // represents the storage slot data +) + +// String returns the string format representation. +func (e elementType) String() string { + switch e { + case typeAccount: + return "account" + case typeStorage: + return "storage" + default: + return fmt.Sprintf("unknown element type: %d", e) + } +} + +// toHistoryType maps an element type to its corresponding history type. +func toHistoryType(typ elementType) historyType { + if typ == typeAccount || typ == typeStorage { + return typeStateHistory + } + panic(fmt.Sprintf("unknown element type %v", typ)) +} + +// stateIdent represents the identifier of a state element, which can be +// an account or a storage slot. +type stateIdent struct { + typ elementType + + // The hash of the account address. This is used instead of the raw account + // address is to align the traversal order with the Merkle-Patricia-Trie. + addressHash common.Hash + + // The hash of the storage slot key. This is used instead of the raw slot key + // because, in legacy state histories (prior to the Cancun fork), the slot + // identifier is the hash of the key, and the original key (preimage) cannot + // be recovered. To maintain backward compatibility, the key hash is used. + // + // Meanwhile, using the storage key hash also preserve the traversal order + // with Merkle-Patricia-Trie. + // + // This field is null if the identifier refers to an account or a trie node. + storageHash common.Hash +} + +// String returns the string format state identifier. +func (ident stateIdent) String() string { + if ident.typ == typeAccount { + return ident.addressHash.Hex() + } + return ident.addressHash.Hex() + ident.storageHash.Hex() +} + +// newAccountIdent constructs a state identifier for an account. +func newAccountIdent(addressHash common.Hash) stateIdent { + return stateIdent{ + typ: typeAccount, + addressHash: addressHash, + } +} + +// newStorageIdent constructs a state identifier for a storage slot. +// The address denotes the address hash of the associated account; +// the storageHash denotes the hash of the raw storage slot key; +func newStorageIdent(addressHash common.Hash, storageHash common.Hash) stateIdent { + return stateIdent{ + typ: typeStorage, + addressHash: addressHash, + storageHash: storageHash, + } +} + +// stateIdentQuery is the extension of stateIdent by adding the account address +// and raw storage key. +type stateIdentQuery struct { + stateIdent + + address common.Address + storageKey common.Hash +} + +// newAccountIdentQuery constructs a state identifier for an account. +func newAccountIdentQuery(address common.Address, addressHash common.Hash) stateIdentQuery { + return stateIdentQuery{ + stateIdent: newAccountIdent(addressHash), + address: address, + } +} + +// newStorageIdentQuery constructs a state identifier for a storage slot. +// the address denotes the address of the associated account; +// the addressHash denotes the address hash of the associated account; +// the storageKey denotes the raw storage slot key; +// the storageHash denotes the hash of the raw storage slot key; +func newStorageIdentQuery(address common.Address, addressHash common.Hash, storageKey common.Hash, storageHash common.Hash) stateIdentQuery { + return stateIdentQuery{ + stateIdent: newStorageIdent(addressHash, storageHash), + address: address, + storageKey: storageKey, + } +} + +// history defines the interface of historical data, implemented by stateHistory +// and trienodeHistory (in the near future). +type history interface { + // typ returns the historical data type held in the history. + typ() historyType + + // forEach returns an iterator to traverse the state entries in the history. + forEach() iter.Seq[stateIdent] +} + var ( errHeadTruncationOutOfRange = errors.New("history head truncation out of range") errTailTruncationOutOfRange = errors.New("history tail truncation out of range") @@ -31,7 +167,7 @@ var ( // truncateFromHead removes excess elements from the head of the freezer based // on the given parameters. It returns the number of items that were removed. -func truncateFromHead(store ethdb.AncientStore, nhead uint64) (int, error) { +func truncateFromHead(store ethdb.AncientStore, typ historyType, nhead uint64) (int, error) { ohead, err := store.Ancients() if err != nil { return 0, err @@ -40,11 +176,11 @@ func truncateFromHead(store ethdb.AncientStore, nhead uint64) (int, error) { if err != nil { return 0, err } - log.Info("Truncating from head", "ohead", ohead, "tail", otail, "nhead", nhead) + log.Info("Truncating from head", "type", typ.String(), "ohead", ohead, "tail", otail, "nhead", nhead) // Ensure that the truncation target falls within the valid range. if ohead < nhead || nhead < otail { - return 0, fmt.Errorf("%w, tail: %d, head: %d, target: %d", errHeadTruncationOutOfRange, otail, ohead, nhead) + return 0, fmt.Errorf("%w, %s, tail: %d, head: %d, target: %d", errHeadTruncationOutOfRange, typ, otail, ohead, nhead) } // Short circuit if nothing to truncate. if ohead == nhead { @@ -61,7 +197,7 @@ func truncateFromHead(store ethdb.AncientStore, nhead uint64) (int, error) { // truncateFromTail removes excess elements from the end of the freezer based // on the given parameters. It returns the number of items that were removed. -func truncateFromTail(store ethdb.AncientStore, ntail uint64) (int, error) { +func truncateFromTail(store ethdb.AncientStore, typ historyType, ntail uint64) (int, error) { ohead, err := store.Ancients() if err != nil { return 0, err @@ -72,7 +208,7 @@ func truncateFromTail(store ethdb.AncientStore, ntail uint64) (int, error) { } // Ensure that the truncation target falls within the valid range. if otail > ntail || ntail > ohead { - return 0, fmt.Errorf("%w, tail: %d, head: %d, target: %d", errTailTruncationOutOfRange, otail, ohead, ntail) + return 0, fmt.Errorf("%w, %s, tail: %d, head: %d, target: %d", errTailTruncationOutOfRange, typ, otail, ohead, ntail) } // Short circuit if nothing to truncate. if otail == ntail { diff --git a/triedb/pathdb/history_index.go b/triedb/pathdb/history_index.go index e781a898e1a4..47cee9820dd2 100644 --- a/triedb/pathdb/history_index.go +++ b/triedb/pathdb/history_index.go @@ -78,12 +78,7 @@ type indexReader struct { // loadIndexData loads the index data associated with the specified state. func loadIndexData(db ethdb.KeyValueReader, state stateIdent) ([]*indexBlockDesc, error) { - var blob []byte - if state.account { - blob = rawdb.ReadAccountHistoryIndex(db, state.addressHash) - } else { - blob = rawdb.ReadStorageHistoryIndex(db, state.addressHash, state.storageHash) - } + blob := readStateIndex(state, db) if len(blob) == 0 { return nil, nil } @@ -137,15 +132,8 @@ func (r *indexReader) readGreaterThan(id uint64) (uint64, error) { br, ok := r.readers[desc.id] if !ok { - var ( - err error - blob []byte - ) - if r.state.account { - blob = rawdb.ReadAccountHistoryIndexBlock(r.db, r.state.addressHash, desc.id) - } else { - blob = rawdb.ReadStorageHistoryIndexBlock(r.db, r.state.addressHash, r.state.storageHash, desc.id) - } + var err error + blob := readStateIndexBlock(r.state, r.db, desc.id) br, err = newBlockReader(blob) if err != nil { return 0, err @@ -174,12 +162,7 @@ type indexWriter struct { // newIndexWriter constructs the index writer for the specified state. func newIndexWriter(db ethdb.KeyValueReader, state stateIdent) (*indexWriter, error) { - var blob []byte - if state.account { - blob = rawdb.ReadAccountHistoryIndex(db, state.addressHash) - } else { - blob = rawdb.ReadStorageHistoryIndex(db, state.addressHash, state.storageHash) - } + blob := readStateIndex(state, db) if len(blob) == 0 { desc := newIndexBlockDesc(0) bw, _ := newBlockWriter(nil, desc) @@ -194,15 +177,8 @@ func newIndexWriter(db ethdb.KeyValueReader, state stateIdent) (*indexWriter, er if err != nil { return nil, err } - var ( - indexBlock []byte - lastDesc = descList[len(descList)-1] - ) - if state.account { - indexBlock = rawdb.ReadAccountHistoryIndexBlock(db, state.addressHash, lastDesc.id) - } else { - indexBlock = rawdb.ReadStorageHistoryIndexBlock(db, state.addressHash, state.storageHash, lastDesc.id) - } + lastDesc := descList[len(descList)-1] + indexBlock := readStateIndexBlock(state, db, lastDesc.id) bw, err := newBlockWriter(indexBlock, lastDesc) if err != nil { return nil, err @@ -270,11 +246,7 @@ func (w *indexWriter) finish(batch ethdb.Batch) { return // nothing to commit } for _, bw := range writers { - if w.state.account { - rawdb.WriteAccountHistoryIndexBlock(batch, w.state.addressHash, bw.desc.id, bw.finish()) - } else { - rawdb.WriteStorageHistoryIndexBlock(batch, w.state.addressHash, w.state.storageHash, bw.desc.id, bw.finish()) - } + writeStateIndexBlock(w.state, batch, bw.desc.id, bw.finish()) } w.frozen = nil // release all the frozen writers @@ -282,11 +254,7 @@ func (w *indexWriter) finish(batch ethdb.Batch) { for _, desc := range descList { buf = append(buf, desc.encode()...) } - if w.state.account { - rawdb.WriteAccountHistoryIndex(batch, w.state.addressHash, buf) - } else { - rawdb.WriteStorageHistoryIndex(batch, w.state.addressHash, w.state.storageHash, buf) - } + writeStateIndex(w.state, batch, buf) } // indexDeleter is responsible for deleting index data for a specific state. @@ -301,12 +269,7 @@ type indexDeleter struct { // newIndexDeleter constructs the index deleter for the specified state. func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent) (*indexDeleter, error) { - var blob []byte - if state.account { - blob = rawdb.ReadAccountHistoryIndex(db, state.addressHash) - } else { - blob = rawdb.ReadStorageHistoryIndex(db, state.addressHash, state.storageHash) - } + blob := readStateIndex(state, db) if len(blob) == 0 { // TODO(rjl493456442) we can probably return an error here, // deleter with no data is meaningless. @@ -323,15 +286,8 @@ func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent) (*indexDeleter, if err != nil { return nil, err } - var ( - indexBlock []byte - lastDesc = descList[len(descList)-1] - ) - if state.account { - indexBlock = rawdb.ReadAccountHistoryIndexBlock(db, state.addressHash, lastDesc.id) - } else { - indexBlock = rawdb.ReadStorageHistoryIndexBlock(db, state.addressHash, state.storageHash, lastDesc.id) - } + lastDesc := descList[len(descList)-1] + indexBlock := readStateIndexBlock(state, db, lastDesc.id) bw, err := newBlockWriter(indexBlock, lastDesc) if err != nil { return nil, err @@ -376,15 +332,8 @@ func (d *indexDeleter) pop(id uint64) error { d.descList = d.descList[:len(d.descList)-1] // Open the previous block writer for deleting - var ( - indexBlock []byte - lastDesc = d.descList[len(d.descList)-1] - ) - if d.state.account { - indexBlock = rawdb.ReadAccountHistoryIndexBlock(d.db, d.state.addressHash, lastDesc.id) - } else { - indexBlock = rawdb.ReadStorageHistoryIndexBlock(d.db, d.state.addressHash, d.state.storageHash, lastDesc.id) - } + lastDesc := d.descList[len(d.descList)-1] + indexBlock := readStateIndexBlock(d.state, d.db, lastDesc.id) bw, err := newBlockWriter(indexBlock, lastDesc) if err != nil { return err @@ -399,38 +348,100 @@ func (d *indexDeleter) pop(id uint64) error { // This function is safe to be called multiple times. func (d *indexDeleter) finish(batch ethdb.Batch) { for _, id := range d.dropped { - if d.state.account { - rawdb.DeleteAccountHistoryIndexBlock(batch, d.state.addressHash, id) - } else { - rawdb.DeleteStorageHistoryIndexBlock(batch, d.state.addressHash, d.state.storageHash, id) - } + deleteStateIndexBlock(d.state, batch, id) } d.dropped = nil // Flush the content of last block writer, regardless it's dirty or not if !d.bw.empty() { - if d.state.account { - rawdb.WriteAccountHistoryIndexBlock(batch, d.state.addressHash, d.bw.desc.id, d.bw.finish()) - } else { - rawdb.WriteStorageHistoryIndexBlock(batch, d.state.addressHash, d.state.storageHash, d.bw.desc.id, d.bw.finish()) - } + writeStateIndexBlock(d.state, batch, d.bw.desc.id, d.bw.finish()) } // Flush the index metadata into the supplied batch if d.empty() { - if d.state.account { - rawdb.DeleteAccountHistoryIndex(batch, d.state.addressHash) - } else { - rawdb.DeleteStorageHistoryIndex(batch, d.state.addressHash, d.state.storageHash) - } + deleteStateIndex(d.state, batch) } else { buf := make([]byte, 0, indexBlockDescSize*len(d.descList)) for _, desc := range d.descList { buf = append(buf, desc.encode()...) } - if d.state.account { - rawdb.WriteAccountHistoryIndex(batch, d.state.addressHash, buf) - } else { - rawdb.WriteStorageHistoryIndex(batch, d.state.addressHash, d.state.storageHash, buf) - } + writeStateIndex(d.state, batch, buf) + } +} + +// readStateIndex retrieves the index metadata for the given state identifier. +// This function is shared by accounts and storage slots. +func readStateIndex(ident stateIdent, db ethdb.KeyValueReader) []byte { + switch ident.typ { + case typeAccount: + return rawdb.ReadAccountHistoryIndex(db, ident.addressHash) + case typeStorage: + return rawdb.ReadStorageHistoryIndex(db, ident.addressHash, ident.storageHash) + default: + panic(fmt.Errorf("unknown type: %v", ident.typ)) + } +} + +// writeStateIndex writes the provided index metadata into database with the +// given state identifier. This function is shared by accounts and storage slots. +func writeStateIndex(ident stateIdent, db ethdb.KeyValueWriter, data []byte) { + switch ident.typ { + case typeAccount: + rawdb.WriteAccountHistoryIndex(db, ident.addressHash, data) + case typeStorage: + rawdb.WriteStorageHistoryIndex(db, ident.addressHash, ident.storageHash, data) + default: + panic(fmt.Errorf("unknown type: %v", ident.typ)) + } +} + +// deleteStateIndex removes the index metadata for the given state identifier. +// This function is shared by accounts and storage slots. +func deleteStateIndex(ident stateIdent, db ethdb.KeyValueWriter) { + switch ident.typ { + case typeAccount: + rawdb.DeleteAccountHistoryIndex(db, ident.addressHash) + case typeStorage: + rawdb.DeleteStorageHistoryIndex(db, ident.addressHash, ident.storageHash) + default: + panic(fmt.Errorf("unknown type: %v", ident.typ)) + } +} + +// readStateIndexBlock retrieves the index block for the given state identifier +// and block ID. This function is shared by accounts and storage slots. +func readStateIndexBlock(ident stateIdent, db ethdb.KeyValueReader, id uint32) []byte { + switch ident.typ { + case typeAccount: + return rawdb.ReadAccountHistoryIndexBlock(db, ident.addressHash, id) + case typeStorage: + return rawdb.ReadStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id) + default: + panic(fmt.Errorf("unknown type: %v", ident.typ)) + } +} + +// writeStateIndexBlock writes the provided index block into database with the +// given state identifier. This function is shared by accounts and storage slots. +func writeStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32, data []byte) { + switch ident.typ { + case typeAccount: + rawdb.WriteAccountHistoryIndexBlock(db, ident.addressHash, id, data) + case typeStorage: + rawdb.WriteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id, data) + default: + panic(fmt.Errorf("unknown type: %v", ident.typ)) + } +} + +// deleteStateIndexBlock removes the index block from database with the given +// state identifier. This function is shared by accounts and storage slots. +func deleteStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32) { + switch ident.typ { + case typeAccount: + rawdb.DeleteAccountHistoryIndexBlock(db, ident.addressHash, id) + case typeStorage: + rawdb.DeleteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id) + default: + panic(fmt.Errorf("unknown type: %v", ident.typ)) } } diff --git a/triedb/pathdb/history_index_test.go b/triedb/pathdb/history_index_test.go index c83c33ffbde1..be9b7c40491c 100644 --- a/triedb/pathdb/history_index_test.go +++ b/triedb/pathdb/history_index_test.go @@ -179,7 +179,7 @@ func TestIndexWriterDelete(t *testing.T) { func TestBatchIndexerWrite(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - batch = newBatchIndexer(db, false) + batch = newBatchIndexer(db, false, typeStateHistory) histories = makeStateHistories(10) ) for i, h := range histories { @@ -190,7 +190,7 @@ func TestBatchIndexerWrite(t *testing.T) { if err := batch.finish(true); err != nil { t.Fatalf("Failed to finish batch indexer, %v", err) } - metadata := loadIndexMetadata(db) + metadata := loadIndexMetadata(db, typeStateHistory) if metadata == nil || metadata.Last != uint64(10) { t.Fatal("Unexpected index position") } @@ -256,7 +256,7 @@ func TestBatchIndexerWrite(t *testing.T) { func TestBatchIndexerDelete(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - bw = newBatchIndexer(db, false) + bw = newBatchIndexer(db, false, typeStateHistory) histories = makeStateHistories(10) ) // Index histories @@ -270,7 +270,7 @@ func TestBatchIndexerDelete(t *testing.T) { } // Unindex histories - bd := newBatchIndexer(db, true) + bd := newBatchIndexer(db, true, typeStateHistory) for i := len(histories) - 1; i >= 0; i-- { if err := bd.process(histories[i], uint64(i+1)); err != nil { t.Fatalf("Failed to process history, %v", err) @@ -280,7 +280,7 @@ func TestBatchIndexerDelete(t *testing.T) { t.Fatalf("Failed to finish batch indexer, %v", err) } - metadata := loadIndexMetadata(db) + metadata := loadIndexMetadata(db, typeStateHistory) if metadata != nil { t.Fatal("Unexpected index position") } diff --git a/triedb/pathdb/history_indexer.go b/triedb/pathdb/history_indexer.go index b4e89c3f17b4..d6185859291f 100644 --- a/triedb/pathdb/history_indexer.go +++ b/triedb/pathdb/history_indexer.go @@ -26,7 +26,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -41,13 +40,32 @@ const ( stateIndexVersion = stateIndexV0 // the current state index version ) +// indexVersion returns the latest index version for the given history type. +// It panics if the history type is unknown. +func indexVersion(typ historyType) uint8 { + switch typ { + case typeStateHistory: + return stateIndexVersion + default: + panic(fmt.Errorf("unknown history type: %d", typ)) + } +} + +// indexMetadata describes the metadata of the historical data index. type indexMetadata struct { Version uint8 Last uint64 } -func loadIndexMetadata(db ethdb.KeyValueReader) *indexMetadata { - blob := rawdb.ReadStateHistoryIndexMetadata(db) +// loadIndexMetadata reads the metadata of the specific history index. +func loadIndexMetadata(db ethdb.KeyValueReader, typ historyType) *indexMetadata { + var blob []byte + switch typ { + case typeStateHistory: + blob = rawdb.ReadStateHistoryIndexMetadata(db) + default: + panic(fmt.Errorf("unknown history type %d", typ)) + } if len(blob) == 0 { return nil } @@ -59,91 +77,94 @@ func loadIndexMetadata(db ethdb.KeyValueReader) *indexMetadata { return &m } -func storeIndexMetadata(db ethdb.KeyValueWriter, last uint64) { - var m indexMetadata - m.Version = stateIndexVersion - m.Last = last +// storeIndexMetadata stores the metadata of the specific history index. +func storeIndexMetadata(db ethdb.KeyValueWriter, typ historyType, last uint64) { + m := indexMetadata{ + Version: indexVersion(typ), + Last: last, + } blob, err := rlp.EncodeToBytes(m) if err != nil { - log.Crit("Failed to encode index metadata", "err", err) + panic(fmt.Errorf("fail to encode index metadata, %v", err)) } - rawdb.WriteStateHistoryIndexMetadata(db, blob) + switch typ { + case typeStateHistory: + rawdb.WriteStateHistoryIndexMetadata(db, blob) + default: + panic(fmt.Errorf("unknown history type %d", typ)) + } + log.Debug("Written index metadata", "type", typ, "last", last) } -// batchIndexer is a structure designed to perform batch indexing or unindexing -// of state histories atomically. +// deleteIndexMetadata deletes the metadata of the specific history index. +func deleteIndexMetadata(db ethdb.KeyValueWriter, typ historyType) { + switch typ { + case typeStateHistory: + rawdb.DeleteStateHistoryIndexMetadata(db) + default: + panic(fmt.Errorf("unknown history type %d", typ)) + } + log.Debug("Deleted index metadata", "type", typ) +} + +// batchIndexer is responsible for performing batch indexing or unindexing +// of historical data (e.g., state or trie node changes) atomically. type batchIndexer struct { - accounts map[common.Hash][]uint64 // History ID list, Keyed by the hash of account address - storages map[common.Hash]map[common.Hash][]uint64 // History ID list, Keyed by the hash of account address and the hash of raw storage key - counter int // The counter of processed states - delete bool // Index or unindex mode - lastID uint64 // The ID of latest processed history - db ethdb.KeyValueStore + index map[stateIdent][]uint64 // List of history IDs for tracked state entry + pending int // Number of entries processed in the current batch. + delete bool // Operation mode: true for unindex, false for index. + lastID uint64 // ID of the most recently processed history. + typ historyType // Type of history being processed (e.g., state or trienode). + db ethdb.KeyValueStore // Key-value database used to store or delete index data. } // newBatchIndexer constructs the batch indexer with the supplied mode. -func newBatchIndexer(db ethdb.KeyValueStore, delete bool) *batchIndexer { +func newBatchIndexer(db ethdb.KeyValueStore, delete bool, typ historyType) *batchIndexer { return &batchIndexer{ - accounts: make(map[common.Hash][]uint64), - storages: make(map[common.Hash]map[common.Hash][]uint64), - delete: delete, - db: db, + index: make(map[stateIdent][]uint64), + delete: delete, + typ: typ, + db: db, } } -// process iterates through the accounts and their associated storage slots in the -// state history, tracking the mapping between state and history IDs. -func (b *batchIndexer) process(h *stateHistory, historyID uint64) error { - for _, address := range h.accountList { - addrHash := crypto.Keccak256Hash(address.Bytes()) - b.counter += 1 - b.accounts[addrHash] = append(b.accounts[addrHash], historyID) - - for _, slotKey := range h.storageList[address] { - b.counter += 1 - if _, ok := b.storages[addrHash]; !ok { - b.storages[addrHash] = make(map[common.Hash][]uint64) - } - // The hash of the storage slot key is used as the identifier because the - // legacy history does not include the raw storage key, therefore, the - // conversion from storage key to hash is necessary for non-v0 histories. - slotHash := slotKey - if h.meta.version != stateHistoryV0 { - slotHash = crypto.Keccak256Hash(slotKey.Bytes()) - } - b.storages[addrHash][slotHash] = append(b.storages[addrHash][slotHash], historyID) - } +// process traverses the state entries within the provided history and tracks the mutation +// records for them. +func (b *batchIndexer) process(h history, id uint64) error { + for ident := range h.forEach() { + b.index[ident] = append(b.index[ident], id) + b.pending++ } - b.lastID = historyID + b.lastID = id + return b.finish(false) } // finish writes the accumulated state indexes into the disk if either the // memory limitation is reached or it's requested forcibly. func (b *batchIndexer) finish(force bool) error { - if b.counter == 0 { + if b.pending == 0 { return nil } - if !force && b.counter < historyIndexBatch { + if !force && b.pending < historyIndexBatch { return nil } var ( - batch = b.db.NewBatch() - batchMu sync.RWMutex - storages int - start = time.Now() - eg errgroup.Group + batch = b.db.NewBatch() + batchMu sync.RWMutex + start = time.Now() + eg errgroup.Group ) eg.SetLimit(runtime.NumCPU()) - for addrHash, idList := range b.accounts { + for ident, list := range b.index { eg.Go(func() error { if !b.delete { - iw, err := newIndexWriter(b.db, newAccountIdent(addrHash)) + iw, err := newIndexWriter(b.db, ident) if err != nil { return err } - for _, n := range idList { + for _, n := range list { if err := iw.append(n); err != nil { return err } @@ -152,11 +173,11 @@ func (b *batchIndexer) finish(force bool) error { iw.finish(batch) batchMu.Unlock() } else { - id, err := newIndexDeleter(b.db, newAccountIdent(addrHash)) + id, err := newIndexDeleter(b.db, ident) if err != nil { return err } - for _, n := range idList { + for _, n := range list { if err := id.pop(n); err != nil { return err } @@ -168,72 +189,36 @@ func (b *batchIndexer) finish(force bool) error { return nil }) } - for addrHash, slots := range b.storages { - storages += len(slots) - for storageHash, idList := range slots { - eg.Go(func() error { - if !b.delete { - iw, err := newIndexWriter(b.db, newStorageIdent(addrHash, storageHash)) - if err != nil { - return err - } - for _, n := range idList { - if err := iw.append(n); err != nil { - return err - } - } - batchMu.Lock() - iw.finish(batch) - batchMu.Unlock() - } else { - id, err := newIndexDeleter(b.db, newStorageIdent(addrHash, storageHash)) - if err != nil { - return err - } - for _, n := range idList { - if err := id.pop(n); err != nil { - return err - } - } - batchMu.Lock() - id.finish(batch) - batchMu.Unlock() - } - return nil - }) - } - } if err := eg.Wait(); err != nil { return err } // Update the position of last indexed state history if !b.delete { - storeIndexMetadata(batch, b.lastID) + storeIndexMetadata(batch, b.typ, b.lastID) } else { if b.lastID == 1 { - rawdb.DeleteStateHistoryIndexMetadata(batch) + deleteIndexMetadata(batch, b.typ) } else { - storeIndexMetadata(batch, b.lastID-1) + storeIndexMetadata(batch, b.typ, b.lastID-1) } } if err := batch.Write(); err != nil { return err } - log.Debug("Committed batch indexer", "accounts", len(b.accounts), "storages", storages, "records", b.counter, "elapsed", common.PrettyDuration(time.Since(start))) - b.counter = 0 - b.accounts = make(map[common.Hash][]uint64) - b.storages = make(map[common.Hash]map[common.Hash][]uint64) + log.Debug("Committed batch indexer", "type", b.typ, "entries", len(b.index), "records", b.pending, "elapsed", common.PrettyDuration(time.Since(start))) + b.pending = 0 + b.index = make(map[stateIdent][]uint64) return nil } // indexSingle processes the state history with the specified ID for indexing. -func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader) error { +func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader, typ historyType) error { start := time.Now() defer func() { indexHistoryTimer.UpdateSince(start) }() - metadata := loadIndexMetadata(db) + metadata := loadIndexMetadata(db, typ) if metadata == nil || metadata.Last+1 != historyID { last := "null" if metadata != nil { @@ -241,29 +226,37 @@ func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancient } return fmt.Errorf("history indexing is out of order, last: %s, requested: %d", last, historyID) } - h, err := readStateHistory(freezer, historyID) + var ( + err error + h history + b = newBatchIndexer(db, false, typ) + ) + if typ == typeStateHistory { + h, err = readStateHistory(freezer, historyID) + } else { + // h, err = readTrienodeHistory(freezer, historyID) + } if err != nil { return err } - b := newBatchIndexer(db, false) if err := b.process(h, historyID); err != nil { return err } if err := b.finish(true); err != nil { return err } - log.Debug("Indexed state history", "id", historyID, "elapsed", common.PrettyDuration(time.Since(start))) + log.Debug("Indexed history", "type", typ, "id", historyID, "elapsed", common.PrettyDuration(time.Since(start))) return nil } // unindexSingle processes the state history with the specified ID for unindexing. -func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader) error { +func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader, typ historyType) error { start := time.Now() defer func() { unindexHistoryTimer.UpdateSince(start) }() - metadata := loadIndexMetadata(db) + metadata := loadIndexMetadata(db, typ) if metadata == nil || metadata.Last != historyID { last := "null" if metadata != nil { @@ -271,18 +264,26 @@ func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancie } return fmt.Errorf("history unindexing is out of order, last: %s, requested: %d", last, historyID) } - h, err := readStateHistory(freezer, historyID) + var ( + err error + h history + ) + b := newBatchIndexer(db, true, typ) + if typ == typeStateHistory { + h, err = readStateHistory(freezer, historyID) + } else { + // h, err = readTrienodeHistory(freezer, historyID) + } if err != nil { return err } - b := newBatchIndexer(db, true) if err := b.process(h, historyID); err != nil { return err } if err := b.finish(true); err != nil { return err } - log.Debug("Unindexed state history", "id", historyID, "elapsed", common.PrettyDuration(time.Since(start))) + log.Debug("Unindexed history", "type", typ, "id", historyID, "elapsed", common.PrettyDuration(time.Since(start))) return nil } @@ -305,6 +306,8 @@ type indexIniter struct { interrupt chan *interruptSignal done chan struct{} closed chan struct{} + typ historyType + log log.Logger // Contextual logger with the history type injected // indexing progress indexed atomic.Uint64 // the id of latest indexed state @@ -313,18 +316,20 @@ type indexIniter struct { wg sync.WaitGroup } -func newIndexIniter(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, lastID uint64) *indexIniter { +func newIndexIniter(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, typ historyType, lastID uint64) *indexIniter { initer := &indexIniter{ disk: disk, freezer: freezer, interrupt: make(chan *interruptSignal), done: make(chan struct{}), closed: make(chan struct{}), + typ: typ, + log: log.New("type", typ.String()), } // Load indexing progress var recover bool initer.last.Store(lastID) - metadata := loadIndexMetadata(disk) + metadata := loadIndexMetadata(disk, typ) if metadata != nil { initer.indexed.Store(metadata.Last) recover = metadata.Last > lastID @@ -371,7 +376,7 @@ func (i *indexIniter) remain() uint64 { default: last, indexed := i.last.Load(), i.indexed.Load() if last < indexed { - log.Warn("State indexer is in recovery", "indexed", indexed, "last", last) + i.log.Warn("State indexer is in recovery", "indexed", indexed, "last", last) return indexed - last } return last - indexed @@ -389,7 +394,7 @@ func (i *indexIniter) run(lastID uint64) { // checkDone indicates whether all requested state histories // have been fully indexed. checkDone = func() bool { - metadata := loadIndexMetadata(i.disk) + metadata := loadIndexMetadata(i.disk, i.typ) return metadata != nil && metadata.Last == lastID } ) @@ -411,7 +416,7 @@ func (i *indexIniter) run(lastID uint64) { if newLastID == lastID+1 { lastID = newLastID signal.result <- nil - log.Debug("Extended state history range", "last", lastID) + i.log.Debug("Extended history range", "last", lastID) continue } // The index limit is shortened by one, interrupt the current background @@ -422,14 +427,14 @@ func (i *indexIniter) run(lastID uint64) { // If all state histories, including the one to be reverted, have // been fully indexed, unindex it here and shut down the initializer. if checkDone() { - log.Info("Truncate the extra history", "id", lastID) - if err := unindexSingle(lastID, i.disk, i.freezer); err != nil { + i.log.Info("Truncate the extra history", "id", lastID) + if err := unindexSingle(lastID, i.disk, i.freezer, i.typ); err != nil { signal.result <- err return } close(i.done) signal.result <- nil - log.Info("State histories have been fully indexed", "last", lastID-1) + i.log.Info("Histories have been fully indexed", "last", lastID-1) return } // Adjust the indexing target and relaunch the process @@ -438,12 +443,12 @@ func (i *indexIniter) run(lastID uint64) { done, interrupt = make(chan struct{}), new(atomic.Int32) go i.index(done, interrupt, lastID) - log.Debug("Shortened state history range", "last", lastID) + i.log.Debug("Shortened history range", "last", lastID) case <-done: if checkDone() { close(i.done) - log.Info("State histories have been fully indexed", "last", lastID) + i.log.Info("Histories have been fully indexed", "last", lastID) return } // Relaunch the background runner if some tasks are left @@ -452,7 +457,7 @@ func (i *indexIniter) run(lastID uint64) { case <-i.closed: interrupt.Store(1) - log.Info("Waiting background history index initer to exit") + i.log.Info("Waiting background history index initer to exit") <-done if checkDone() { @@ -472,14 +477,14 @@ func (i *indexIniter) next() (uint64, error) { tailID := tail + 1 // compute the id of the oldest history // Start indexing from scratch if nothing has been indexed - metadata := loadIndexMetadata(i.disk) + metadata := loadIndexMetadata(i.disk, i.typ) if metadata == nil { - log.Debug("Initialize state history indexing from scratch", "id", tailID) + i.log.Debug("Initialize history indexing from scratch", "id", tailID) return tailID, nil } // Resume indexing from the last interrupted position if metadata.Last+1 >= tailID { - log.Debug("Resume state history indexing", "id", metadata.Last+1, "tail", tailID) + i.log.Debug("Resume history indexing", "id", metadata.Last+1, "tail", tailID) return metadata.Last + 1, nil } // History has been shortened without indexing. Discard the gapped segment @@ -487,7 +492,7 @@ func (i *indexIniter) next() (uint64, error) { // // The missing indexes corresponding to the gapped histories won't be visible. // It's fine to leave them unindexed. - log.Info("History gap detected, discard old segment", "oldHead", metadata.Last, "newHead", tailID) + i.log.Info("History gap detected, discard old segment", "oldHead", metadata.Last, "newHead", tailID) return tailID, nil } @@ -496,7 +501,7 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID beginID, err := i.next() if err != nil { - log.Error("Failed to find next state history for indexing", "err", err) + i.log.Error("Failed to find next history for indexing", "err", err) return } // All available state histories have been indexed, and the last indexed one @@ -511,36 +516,47 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID // // This step is essential to avoid spinning up indexing thread // endlessly until a history object is produced. - storeIndexMetadata(i.disk, 0) - log.Info("Initialized history indexing flag") + storeIndexMetadata(i.disk, i.typ, 0) + i.log.Info("Initialized history indexing flag") } else { - log.Debug("State history is fully indexed", "last", lastID) + i.log.Debug("History is fully indexed", "last", lastID) } return } - log.Info("Start history indexing", "beginID", beginID, "lastID", lastID) + i.log.Info("Start history indexing", "beginID", beginID, "lastID", lastID) var ( current = beginID start = time.Now() logged = time.Now() - batch = newBatchIndexer(i.disk, false) + batch = newBatchIndexer(i.disk, false, i.typ) ) for current <= lastID { count := lastID - current + 1 if count > historyReadBatch { count = historyReadBatch } - histories, err := readStateHistories(i.freezer, current, count) - if err != nil { - // The history read might fall if the history is truncated from - // head due to revert operation. - log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err) - return + var histories []history + if i.typ == typeStateHistory { + histories, err = readStateHistories(i.freezer, current, count) + if err != nil { + // The history read might fall if the history is truncated from + // head due to revert operation. + i.log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err) + return + } + } else { + // histories, err = readTrienodeHistories(i.freezer, current, count) + // if err != nil { + // // The history read might fall if the history is truncated from + // // head due to revert operation. + // i.log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err) + // return + // } } for _, h := range histories { if err := batch.process(h, current); err != nil { - log.Error("Failed to index history", "err", err) + i.log.Error("Failed to index history", "err", err) return } current += 1 @@ -554,7 +570,7 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID done = current - beginID ) eta := common.CalculateETA(done, left, time.Since(start)) - log.Info("Indexing state history", "processed", done, "left", left, "elapsed", common.PrettyDuration(time.Since(start)), "eta", common.PrettyDuration(eta)) + i.log.Info("Indexing state history", "processed", done, "left", left, "elapsed", common.PrettyDuration(time.Since(start)), "eta", common.PrettyDuration(eta)) } } i.indexed.Store(current - 1) // update indexing progress @@ -563,7 +579,7 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID if interrupt != nil { if signal := interrupt.Load(); signal != 0 { if err := batch.finish(true); err != nil { - log.Error("Failed to flush index", "err", err) + i.log.Error("Failed to flush index", "err", err) } log.Info("State indexing interrupted") return @@ -571,9 +587,9 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID } } if err := batch.finish(true); err != nil { - log.Error("Failed to flush index", "err", err) + i.log.Error("Failed to flush index", "err", err) } - log.Info("Indexed state history", "from", beginID, "to", lastID, "elapsed", common.PrettyDuration(time.Since(start))) + i.log.Info("Indexed history", "from", beginID, "to", lastID, "elapsed", common.PrettyDuration(time.Since(start))) } // recover handles unclean shutdown recovery. After an unclean shutdown, any @@ -602,14 +618,14 @@ func (i *indexIniter) recover(lastID uint64) { lastID = newLastID signal.result <- nil i.last.Store(newLastID) - log.Debug("Updated history index flag", "last", lastID) + i.log.Debug("Updated history index flag", "last", lastID) // Terminate the recovery routine once the histories are fully aligned // with the index data, indicating that index initialization is complete. - metadata := loadIndexMetadata(i.disk) + metadata := loadIndexMetadata(i.disk, i.typ) if metadata != nil && metadata.Last == lastID { close(i.done) - log.Info("History indexer is recovered", "last", lastID) + i.log.Info("History indexer is recovered", "last", lastID) return } @@ -631,21 +647,31 @@ func (i *indexIniter) recover(lastID uint64) { // state history. type historyIndexer struct { initer *indexIniter + typ historyType disk ethdb.KeyValueStore freezer ethdb.AncientStore } // checkVersion checks whether the index data in the database matches the version. -func checkVersion(disk ethdb.KeyValueStore) { - blob := rawdb.ReadStateHistoryIndexMetadata(disk) +func checkVersion(disk ethdb.KeyValueStore, typ historyType) { + var blob []byte + if typ == typeStateHistory { + blob = rawdb.ReadStateHistoryIndexMetadata(disk) + } else { + panic(fmt.Errorf("unknown history type: %v", typ)) + } + // Short circuit if metadata is not found, re-index is required + // from scratch. if len(blob) == 0 { return } + // Short circuit if the metadata is found and the version is matched var m indexMetadata err := rlp.DecodeBytes(blob, &m) if err == nil && m.Version == stateIndexVersion { return } + // Version is not matched, prune the existing data and re-index from scratch version := "unknown" if err == nil { version = fmt.Sprintf("%d", m.Version) @@ -662,10 +688,11 @@ func checkVersion(disk ethdb.KeyValueStore) { // newHistoryIndexer constructs the history indexer and launches the background // initer to complete the indexing of any remaining state histories. -func newHistoryIndexer(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, lastHistoryID uint64) *historyIndexer { - checkVersion(disk) +func newHistoryIndexer(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, lastHistoryID uint64, typ historyType) *historyIndexer { + checkVersion(disk, typ) return &historyIndexer{ - initer: newIndexIniter(disk, freezer, lastHistoryID), + initer: newIndexIniter(disk, freezer, typ, lastHistoryID), + typ: typ, disk: disk, freezer: freezer, } @@ -693,7 +720,7 @@ func (i *historyIndexer) extend(historyID uint64) error { case <-i.initer.closed: return errors.New("indexer is closed") case <-i.initer.done: - return indexSingle(historyID, i.disk, i.freezer) + return indexSingle(historyID, i.disk, i.freezer, i.typ) case i.initer.interrupt <- signal: return <-signal.result } @@ -710,7 +737,7 @@ func (i *historyIndexer) shorten(historyID uint64) error { case <-i.initer.closed: return errors.New("indexer is closed") case <-i.initer.done: - return unindexSingle(historyID, i.disk, i.freezer) + return unindexSingle(historyID, i.disk, i.freezer, i.typ) case i.initer.interrupt <- signal: return <-signal.result } diff --git a/triedb/pathdb/history_indexer_test.go b/triedb/pathdb/history_indexer_test.go index 96c87ccb1bde..f333d18d8b53 100644 --- a/triedb/pathdb/history_indexer_test.go +++ b/triedb/pathdb/history_indexer_test.go @@ -38,7 +38,7 @@ func TestHistoryIndexerShortenDeadlock(t *testing.T) { rawdb.WriteStateHistory(freezer, uint64(i+1), h.meta.encode(), accountIndex, storageIndex, accountData, storageData) } // As a workaround, assign a future block to keep the initer running indefinitely - indexer := newHistoryIndexer(db, freezer, 200) + indexer := newHistoryIndexer(db, freezer, 200, typeStateHistory) defer indexer.close() done := make(chan error, 1) diff --git a/triedb/pathdb/history_reader.go b/triedb/pathdb/history_reader.go index a11297b3f61b..ce6aa693d117 100644 --- a/triedb/pathdb/history_reader.go +++ b/triedb/pathdb/history_reader.go @@ -29,88 +29,6 @@ import ( "github.com/ethereum/go-ethereum/ethdb" ) -// stateIdent represents the identifier of a state element, which can be -// either an account or a storage slot. -type stateIdent struct { - account bool - - // The hash of the account address. This is used instead of the raw account - // address is to align the traversal order with the Merkle-Patricia-Trie. - addressHash common.Hash - - // The hash of the storage slot key. This is used instead of the raw slot key - // because, in legacy state histories (prior to the Cancun fork), the slot - // identifier is the hash of the key, and the original key (preimage) cannot - // be recovered. To maintain backward compatibility, the key hash is used. - // - // Meanwhile, using the storage key hash also preserve the traversal order - // with Merkle-Patricia-Trie. - // - // This field is null if the identifier refers to account data. - storageHash common.Hash -} - -// String returns the string format state identifier. -func (ident stateIdent) String() string { - if ident.account { - return ident.addressHash.Hex() - } - return ident.addressHash.Hex() + ident.storageHash.Hex() -} - -// newAccountIdent constructs a state identifier for an account. -func newAccountIdent(addressHash common.Hash) stateIdent { - return stateIdent{ - account: true, - addressHash: addressHash, - } -} - -// newStorageIdent constructs a state identifier for a storage slot. -// The address denotes the address of the associated account; -// the storageHash denotes the hash of the raw storage slot key; -func newStorageIdent(addressHash common.Hash, storageHash common.Hash) stateIdent { - return stateIdent{ - addressHash: addressHash, - storageHash: storageHash, - } -} - -// stateIdentQuery is the extension of stateIdent by adding the raw storage key. -type stateIdentQuery struct { - stateIdent - - address common.Address - storageKey common.Hash -} - -// newAccountIdentQuery constructs a state identifier for an account. -func newAccountIdentQuery(address common.Address, addressHash common.Hash) stateIdentQuery { - return stateIdentQuery{ - stateIdent: stateIdent{ - account: true, - addressHash: addressHash, - }, - address: address, - } -} - -// newStorageIdentQuery constructs a state identifier for a storage slot. -// the address denotes the address of the associated account; -// the addressHash denotes the address hash of the associated account; -// the storageKey denotes the raw storage slot key; -// the storageHash denotes the hash of the raw storage slot key; -func newStorageIdentQuery(address common.Address, addressHash common.Hash, storageKey common.Hash, storageHash common.Hash) stateIdentQuery { - return stateIdentQuery{ - stateIdent: stateIdent{ - addressHash: addressHash, - storageHash: storageHash, - }, - address: address, - storageKey: storageKey, - } -} - // indexReaderWithLimitTag is a wrapper around indexReader that includes an // additional index position. This position represents the ID of the last // indexed state history at the time the reader was created, implying that @@ -169,7 +87,7 @@ func (r *indexReaderWithLimitTag) readGreaterThan(id uint64, lastID uint64) (uin // Given that it's very unlikely to occur and users try to perform historical // state queries while reverting the states at the same time. Simply returning // an error should be sufficient for now. - metadata := loadIndexMetadata(r.db) + metadata := loadIndexMetadata(r.db, toHistoryType(r.reader.state.typ)) if metadata == nil || metadata.Last < lastID { return 0, errors.New("state history hasn't been indexed yet") } @@ -331,7 +249,7 @@ func (r *historyReader) read(state stateIdentQuery, stateID uint64, lastID uint6 // To serve the request, all state histories from stateID+1 to lastID // must be indexed. It's not supposed to happen unless system is very // wrong. - metadata := loadIndexMetadata(r.disk) + metadata := loadIndexMetadata(r.disk, toHistoryType(state.typ)) if metadata == nil || metadata.Last < lastID { indexed := "null" if metadata != nil { @@ -364,7 +282,7 @@ func (r *historyReader) read(state stateIdentQuery, stateID uint64, lastID uint6 // that the associated state histories are no longer available due to a rollback. // Such truncation should be captured by the state resolver below, rather than returning // invalid data. - if state.account { + if state.typ == typeAccount { return r.readAccount(state.address, historyID) } return r.readStorage(state.address, state.storageKey, state.storageHash, historyID) diff --git a/triedb/pathdb/history_reader_test.go b/triedb/pathdb/history_reader_test.go index 75c5f701f964..3e1a545ff324 100644 --- a/triedb/pathdb/history_reader_test.go +++ b/triedb/pathdb/history_reader_test.go @@ -29,7 +29,7 @@ import ( func waitIndexing(db *Database) { for { - metadata := loadIndexMetadata(db.diskdb) + metadata := loadIndexMetadata(db.diskdb, typeStateHistory) if metadata != nil && metadata.Last >= db.tree.bottom().stateID() { return } diff --git a/triedb/pathdb/history_state.go b/triedb/pathdb/history_state.go index 3bb69a7f4d0a..9d1e4dfb0999 100644 --- a/triedb/pathdb/history_state.go +++ b/triedb/pathdb/history_state.go @@ -21,6 +21,7 @@ import ( "encoding/binary" "errors" "fmt" + "iter" "maps" "slices" "time" @@ -275,6 +276,36 @@ func newStateHistory(root common.Hash, parent common.Hash, block uint64, account } } +// typ implements the history interface, returning the historical data type held. +func (h *stateHistory) typ() historyType { + return typeStateHistory +} + +// forEach implements the history interface, returning an iterator to traverse the +// state entries in the history. +func (h *stateHistory) forEach() iter.Seq[stateIdent] { + return func(yield func(stateIdent) bool) { + for _, addr := range h.accountList { + addrHash := crypto.Keccak256Hash(addr.Bytes()) + if !yield(newAccountIdent(addrHash)) { + return + } + for _, slotKey := range h.storageList[addr] { + // The hash of the storage slot key is used as the identifier because the + // legacy history does not include the raw storage key, therefore, the + // conversion from storage key to hash is necessary for non-v0 histories. + slotHash := slotKey + if h.meta.version != stateHistoryV0 { + slotHash = crypto.Keccak256Hash(slotKey.Bytes()) + } + if !yield(newStorageIdent(addrHash, slotHash)) { + return + } + } + } + } +} + // stateSet returns the state set, keyed by the hash of the account address // and the hash of the storage slot key. func (h *stateHistory) stateSet() (map[common.Hash][]byte, map[common.Hash]map[common.Hash][]byte) { @@ -536,8 +567,8 @@ func readStateHistory(reader ethdb.AncientReader, id uint64) (*stateHistory, err } // readStateHistories reads a list of state history records within the specified range. -func readStateHistories(freezer ethdb.AncientReader, start uint64, count uint64) ([]*stateHistory, error) { - var histories []*stateHistory +func readStateHistories(freezer ethdb.AncientReader, start uint64, count uint64) ([]history, error) { + var histories []history metaList, aIndexList, sIndexList, aDataList, sDataList, err := rawdb.ReadStateHistoryList(freezer, start, count) if err != nil { return nil, err diff --git a/triedb/pathdb/history_state_test.go b/triedb/pathdb/history_state_test.go index 4a777111ea81..5718081566c6 100644 --- a/triedb/pathdb/history_state_test.go +++ b/triedb/pathdb/history_state_test.go @@ -137,7 +137,7 @@ func TestTruncateHeadStateHistory(t *testing.T) { rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) } for size := len(hs); size > 0; size-- { - pruned, err := truncateFromHead(freezer, uint64(size-1)) + pruned, err := truncateFromHead(freezer, typeStateHistory, uint64(size-1)) if err != nil { t.Fatalf("Failed to truncate from head %v", err) } @@ -161,7 +161,7 @@ func TestTruncateTailStateHistory(t *testing.T) { rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) } for newTail := 1; newTail < len(hs); newTail++ { - pruned, _ := truncateFromTail(freezer, uint64(newTail)) + pruned, _ := truncateFromTail(freezer, typeStateHistory, uint64(newTail)) if pruned != 1 { t.Error("Unexpected pruned items", "want", 1, "got", pruned) } @@ -209,7 +209,7 @@ func TestTruncateTailStateHistories(t *testing.T) { accountData, storageData, accountIndex, storageIndex := hs[i].encode() rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) } - pruned, _ := truncateFromTail(freezer, uint64(10)-c.limit) + pruned, _ := truncateFromTail(freezer, typeStateHistory, uint64(10)-c.limit) if pruned != c.expPruned { t.Error("Unexpected pruned items", "want", c.expPruned, "got", pruned) } @@ -233,7 +233,7 @@ func TestTruncateOutOfRange(t *testing.T) { accountData, storageData, accountIndex, storageIndex := hs[i].encode() rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) } - truncateFromTail(freezer, uint64(len(hs)/2)) + truncateFromTail(freezer, typeStateHistory, uint64(len(hs)/2)) // Ensure of-out-range truncations are rejected correctly. head, _ := freezer.Ancients() @@ -254,9 +254,9 @@ func TestTruncateOutOfRange(t *testing.T) { for _, c := range cases { var gotErr error if c.mode == 0 { - _, gotErr = truncateFromHead(freezer, c.target) + _, gotErr = truncateFromHead(freezer, typeStateHistory, c.target) } else { - _, gotErr = truncateFromTail(freezer, c.target) + _, gotErr = truncateFromTail(freezer, typeStateHistory, c.target) } if !errors.Is(gotErr, c.expErr) { t.Errorf("Unexpected error, want: %v, got: %v", c.expErr, gotErr) From b4682de5d757911510267ee56c1a15c2ca132c1e Mon Sep 17 00:00:00 2001 From: Samuel Arogbonlo <47984109+samuelarogbonlo@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:07:02 +0100 Subject: [PATCH 022/228] trie: add sub-trie iterator support (#32520) - Adds `NodeIteratorWithPrefix()` method to support iterating only nodes within a specific key prefix - Adds `NodeIteratorWithRange()` method to support iterating only nodes within a specific key range Current `NodeIterator` always traverses the entire remaining trie from a start position. For non-ethereum applications using the trie implementation, there's no way to limit iteration to just a subtree with a specific prefix. **Usage:** ```go // Only iterate nodes with prefix "key1" iter, err := trie.NodeIteratorWithPrefix([]byte("key1")) ``` Testing: Comprehensive test suite covering edge cases and boundary conditions. Closes #32484 --------- Co-authored-by: gballet Co-authored-by: Gary Rong --- trie/iterator.go | 88 +++++++ trie/iterator_test.go | 536 ++++++++++++++++++++++++++++++++++++++++++ trie/trie.go | 28 +++ 3 files changed, 652 insertions(+) diff --git a/trie/iterator.go b/trie/iterator.go index 80298ce48f1d..3d3191ffba87 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -836,3 +836,91 @@ func (it *unionIterator) Error() error { } return nil } + +// subTreeIterator wraps nodeIterator to traverse a trie within a predefined +// start and limit range. +type subtreeIterator struct { + NodeIterator + + stopPath []byte // Precomputed hex path for stopKey (without terminator), nil means no limit + exhausted bool // Flag whether the iterator has been exhausted +} + +// newSubtreeIterator creates an iterator that only traverses nodes within a subtree +// defined by the given startKey and stopKey. This supports general range iteration +// where startKey is inclusive and stopKey is exclusive. +// +// The iterator will only visit nodes whose keys k satisfy: startKey <= k < stopKey, +// where comparisons are performed in lexicographic order of byte keys (internally +// implemented via hex-nibble path comparisons for efficiency). +// +// If startKey is nil, iteration starts from the beginning. If stopKey is nil, +// iteration continues to the end of the trie. +func newSubtreeIterator(trie *Trie, startKey, stopKey []byte) (NodeIterator, error) { + it, err := trie.NodeIterator(startKey) + if err != nil { + return nil, err + } + if startKey == nil && stopKey == nil { + return it, nil + } + // Precompute nibble paths for efficient comparison + var stopPath []byte + if stopKey != nil { + stopPath = keybytesToHex(stopKey) + if hasTerm(stopPath) { + stopPath = stopPath[:len(stopPath)-1] + } + } + return &subtreeIterator{ + NodeIterator: it, + stopPath: stopPath, + }, nil +} + +// nextKey returns the next possible key after the given prefix. +// For example, "abc" -> "abd", "ab\xff" -> "ac", etc. +func nextKey(prefix []byte) []byte { + if len(prefix) == 0 { + return nil + } + // Make a copy to avoid modifying the original + next := make([]byte, len(prefix)) + copy(next, prefix) + + // Increment the last byte that isn't 0xff + for i := len(next) - 1; i >= 0; i-- { + if next[i] < 0xff { + next[i]++ + return next + } + // If it's 0xff, we need to carry over + // Trim trailing 0xff bytes + next = next[:i] + } + // If all bytes were 0xff, return nil (no upper bound) + return nil +} + +// newPrefixIterator creates an iterator that only traverses nodes with the given prefix. +// This ensures that only keys starting with the prefix are visited. +func newPrefixIterator(trie *Trie, prefix []byte) (NodeIterator, error) { + return newSubtreeIterator(trie, prefix, nextKey(prefix)) +} + +// Next moves the iterator to the next node. If the parameter is false, any child +// nodes will be skipped. +func (it *subtreeIterator) Next(descend bool) bool { + if it.exhausted { + return false + } + if !it.NodeIterator.Next(descend) { + it.exhausted = true + return false + } + if it.stopPath != nil && reachedPath(it.NodeIterator.Path(), it.stopPath) { + it.exhausted = true + return false + } + return true +} diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 74a1aa378c65..f1451cef904a 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -19,7 +19,9 @@ package trie import ( "bytes" "fmt" + "maps" "math/rand" + "slices" "testing" "github.com/ethereum/go-ethereum/common" @@ -624,6 +626,540 @@ func isTrieNode(scheme string, key, val []byte) (bool, []byte, common.Hash) { return true, path, hash } +func TestSubtreeIterator(t *testing.T) { + var ( + db = newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + tr = NewEmpty(db) + ) + vals := []struct{ k, v string }{ + {"do", "verb"}, + {"dog", "puppy"}, + {"doge", "coin"}, + {"dog\xff", "value6"}, + {"dog\xff\xff", "value7"}, + {"horse", "stallion"}, + {"house", "building"}, + {"houses", "multiple"}, + {"xyz", "value"}, + {"xyz\xff", "value"}, + {"xyz\xff\xff", "value"}, + } + all := make(map[string]string) + for _, val := range vals { + all[val.k] = val.v + tr.MustUpdate([]byte(val.k), []byte(val.v)) + } + root, nodes := tr.Commit(false) + db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + + allNodes := make(map[string][]byte) + tr, _ = New(TrieID(root), db) + it, err := tr.NodeIterator(nil) + if err != nil { + t.Fatal(err) + } + for it.Next(true) { + allNodes[string(it.Path())] = it.NodeBlob() + } + allKeys := slices.Collect(maps.Keys(all)) + + suites := []struct { + start []byte + end []byte + expected []string + }{ + // entire key range + { + start: nil, + end: nil, + expected: allKeys, + }, + { + start: nil, + end: bytes.Repeat([]byte{0xff}, 32), + expected: allKeys, + }, + { + start: bytes.Repeat([]byte{0x0}, 32), + end: bytes.Repeat([]byte{0xff}, 32), + expected: allKeys, + }, + // key range with start + { + start: []byte("do"), + end: nil, + expected: allKeys, + }, + { + start: []byte("doe"), + end: nil, + expected: allKeys[1:], + }, + { + start: []byte("dog"), + end: nil, + expected: allKeys[1:], + }, + { + start: []byte("doge"), + end: nil, + expected: allKeys[2:], + }, + { + start: []byte("dog\xff"), + end: nil, + expected: allKeys[3:], + }, + { + start: []byte("dog\xff\xff"), + end: nil, + expected: allKeys[4:], + }, + { + start: []byte("dog\xff\xff\xff"), + end: nil, + expected: allKeys[5:], + }, + // key range with limit + { + start: nil, + end: []byte("xyz"), + expected: allKeys[:len(allKeys)-3], + }, + { + start: nil, + end: []byte("xyz\xff"), + expected: allKeys[:len(allKeys)-2], + }, + { + start: nil, + end: []byte("xyz\xff\xff"), + expected: allKeys[:len(allKeys)-1], + }, + { + start: nil, + end: []byte("xyz\xff\xff\xff"), + expected: allKeys, + }, + } + for _, suite := range suites { + // We need to re-open the trie from the committed state + tr, _ = New(TrieID(root), db) + it, err := newSubtreeIterator(tr, suite.start, suite.end) + if err != nil { + t.Fatal(err) + } + + found := make(map[string]string) + for it.Next(true) { + if it.Leaf() { + found[string(it.LeafKey())] = string(it.LeafBlob()) + } + } + if len(found) != len(suite.expected) { + t.Errorf("wrong number of values: got %d, want %d", len(found), len(suite.expected)) + } + for k, v := range found { + if all[k] != v { + t.Errorf("wrong value for %s: got %s, want %s", k, found[k], all[k]) + } + } + + expectedNodes := make(map[string][]byte) + for path, blob := range allNodes { + if suite.start != nil { + hexStart := keybytesToHex(suite.start) + hexStart = hexStart[:len(hexStart)-1] + if !reachedPath([]byte(path), hexStart) { + continue + } + } + if suite.end != nil { + hexEnd := keybytesToHex(suite.end) + hexEnd = hexEnd[:len(hexEnd)-1] + if reachedPath([]byte(path), hexEnd) { + continue + } + } + expectedNodes[path] = bytes.Clone(blob) + } + + // Compare the result yield from the subtree iterator + var ( + subCount int + subIt, _ = newSubtreeIterator(tr, suite.start, suite.end) + ) + for subIt.Next(true) { + blob, ok := expectedNodes[string(subIt.Path())] + if !ok { + t.Errorf("Unexpected node iterated, path: %v", subIt.Path()) + } + subCount++ + + if !bytes.Equal(blob, subIt.NodeBlob()) { + t.Errorf("Unexpected node blob, path: %v, want: %v, got: %v", subIt.Path(), blob, subIt.NodeBlob()) + } + } + if subCount != len(expectedNodes) { + t.Errorf("Unexpected node being iterated, want: %d, got: %d", len(expectedNodes), subCount) + } + } +} + +func TestPrefixIterator(t *testing.T) { + // Create a new trie + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + + // Insert test data + testData := map[string]string{ + "key1": "value1", + "key2": "value2", + "key10": "value10", + "key11": "value11", + "different": "value_different", + } + + for key, value := range testData { + trie.Update([]byte(key), []byte(value)) + } + + // Test prefix iteration for "key1" prefix + prefix := []byte("key1") + iter, err := trie.NodeIteratorWithPrefix(prefix) + if err != nil { + t.Fatalf("Failed to create prefix iterator: %v", err) + } + + var foundKeys [][]byte + for iter.Next(true) { + if iter.Leaf() { + foundKeys = append(foundKeys, iter.LeafKey()) + } + } + + if err := iter.Error(); err != nil { + t.Fatalf("Iterator error: %v", err) + } + + // Verify only keys starting with "key1" were found + expectedCount := 3 // "key1", "key10", "key11" + if len(foundKeys) != expectedCount { + t.Errorf("Expected %d keys, found %d", expectedCount, len(foundKeys)) + } + + for _, key := range foundKeys { + keyStr := string(key) + if !bytes.HasPrefix(key, prefix) { + t.Errorf("Found key %s doesn't have prefix %s", keyStr, string(prefix)) + } + } +} + +func TestPrefixIteratorVsFullIterator(t *testing.T) { + // Create a new trie with more structured data + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + + // Insert structured test data + testData := map[string]string{ + "aaa": "value_aaa", + "aab": "value_aab", + "aba": "value_aba", + "bbb": "value_bbb", + } + + for key, value := range testData { + trie.Update([]byte(key), []byte(value)) + } + + // Test that prefix iterator stops at boundary + prefix := []byte("aa") + prefixIter, err := trie.NodeIteratorWithPrefix(prefix) + if err != nil { + t.Fatalf("Failed to create prefix iterator: %v", err) + } + + var prefixKeys [][]byte + for prefixIter.Next(true) { + if prefixIter.Leaf() { + prefixKeys = append(prefixKeys, prefixIter.LeafKey()) + } + } + + // Should only find "aaa" and "aab", not "aba" or "bbb" + if len(prefixKeys) != 2 { + t.Errorf("Expected 2 keys with prefix 'aa', found %d", len(prefixKeys)) + } + + // Verify no keys outside prefix were found + for _, key := range prefixKeys { + if !bytes.HasPrefix(key, prefix) { + t.Errorf("Prefix iterator returned key %s outside prefix %s", string(key), string(prefix)) + } + } +} + +func TestEmptyPrefixIterator(t *testing.T) { + // Test with empty trie + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + + iter, err := trie.NodeIteratorWithPrefix([]byte("nonexistent")) + if err != nil { + t.Fatalf("Failed to create iterator: %v", err) + } + + if iter.Next(true) { + t.Error("Expected no results from empty trie") + } +} + +// TestPrefixIteratorEdgeCases tests various edge cases for prefix iteration +func TestPrefixIteratorEdgeCases(t *testing.T) { + // Create a trie with test data + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + testData := map[string]string{ + "abc": "value1", + "abcd": "value2", + "abce": "value3", + "abd": "value4", + "dog": "value5", + "dog\xff": "value6", // Test with 0xff byte + "dog\xff\xff": "value7", // Multiple 0xff bytes + } + for key, value := range testData { + trie.Update([]byte(key), []byte(value)) + } + + // Test 1: Prefix not present in trie + t.Run("NonexistentPrefix", func(t *testing.T) { + iter, err := trie.NodeIteratorWithPrefix([]byte("xyz")) + if err != nil { + t.Fatalf("Failed to create iterator: %v", err) + } + count := 0 + for iter.Next(true) { + if iter.Leaf() { + count++ + } + } + if count != 0 { + t.Errorf("Expected 0 results for nonexistent prefix, got %d", count) + } + }) + + // Test 2: Prefix exactly equals an existing key + t.Run("ExactKeyPrefix", func(t *testing.T) { + iter, err := trie.NodeIteratorWithPrefix([]byte("abc")) + if err != nil { + t.Fatalf("Failed to create iterator: %v", err) + } + found := make(map[string]bool) + for iter.Next(true) { + if iter.Leaf() { + found[string(iter.LeafKey())] = true + } + } + // Should find "abc", "abcd", "abce" but not "abd" + if !found["abc"] || !found["abcd"] || !found["abce"] { + t.Errorf("Missing expected keys: got %v", found) + } + if found["abd"] { + t.Errorf("Found unexpected key 'abd' with prefix 'abc'") + } + }) + + // Test 3: Prefix with trailing 0xff + t.Run("TrailingFFPrefix", func(t *testing.T) { + iter, err := trie.NodeIteratorWithPrefix([]byte("dog\xff")) + if err != nil { + t.Fatalf("Failed to create iterator: %v", err) + } + found := make(map[string]bool) + for iter.Next(true) { + if iter.Leaf() { + found[string(iter.LeafKey())] = true + } + } + // Should find "dog\xff" and "dog\xff\xff" + if !found["dog\xff"] || !found["dog\xff\xff"] { + t.Errorf("Missing expected keys with 0xff: got %v", found) + } + if found["dog"] { + t.Errorf("Found unexpected key 'dog' with prefix 'dog\\xff'") + } + }) + + // Test 4: All 0xff case (edge case for nextKey) + t.Run("AllFFPrefix", func(t *testing.T) { + // Add a key with all 0xff bytes + allFF := []byte{0xff, 0xff} + trie.Update(allFF, []byte("all_ff_value")) + trie.Update(append(allFF, 0x00), []byte("all_ff_plus")) + + iter, err := trie.NodeIteratorWithPrefix(allFF) + if err != nil { + t.Fatalf("Failed to create iterator: %v", err) + } + count := 0 + for iter.Next(true) { + if iter.Leaf() { + count++ + } + } + // Should find at least the allFF key itself + if count != 2 { + t.Errorf("Expected at least 1 result for all-0xff prefix, got %d", count) + } + }) + + // Test 5: Empty prefix (should iterate entire trie) + t.Run("EmptyPrefix", func(t *testing.T) { + iter, err := trie.NodeIteratorWithPrefix([]byte{}) + if err != nil { + t.Fatalf("Failed to create iterator: %v", err) + } + count := 0 + for iter.Next(true) { + if iter.Leaf() { + count++ + } + } + // Should find all keys in the trie + expectedCount := len(testData) + 2 // +2 for the extra keys added in test 4 + if count != expectedCount { + t.Errorf("Expected %d results for empty prefix, got %d", expectedCount, count) + } + }) +} + +// TestGeneralRangeIteration tests NewSubtreeIterator with arbitrary start/stop ranges +func TestGeneralRangeIteration(t *testing.T) { + // Create a trie with test data + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + testData := map[string]string{ + "apple": "fruit1", + "apricot": "fruit2", + "banana": "fruit3", + "cherry": "fruit4", + "date": "fruit5", + "fig": "fruit6", + "grape": "fruit7", + } + for key, value := range testData { + trie.Update([]byte(key), []byte(value)) + } + + // Test range iteration from "banana" to "fig" (exclusive) + t.Run("RangeIteration", func(t *testing.T) { + iter, _ := newSubtreeIterator(trie, []byte("banana"), []byte("fig")) + found := make(map[string]bool) + for iter.Next(true) { + if iter.Leaf() { + found[string(iter.LeafKey())] = true + } + } + // Should find "banana", "cherry", "date" but not "fig" + if !found["banana"] || !found["cherry"] || !found["date"] { + t.Errorf("Missing expected keys in range: got %v", found) + } + if found["apple"] || found["apricot"] || found["fig"] || found["grape"] { + t.Errorf("Found unexpected keys outside range: got %v", found) + } + }) + + // Test with nil stopKey (iterate to end) + t.Run("NilStopKey", func(t *testing.T) { + iter, _ := newSubtreeIterator(trie, []byte("date"), nil) + found := make(map[string]bool) + for iter.Next(true) { + if iter.Leaf() { + found[string(iter.LeafKey())] = true + } + } + // Should find "date", "fig", "grape" + if !found["date"] || !found["fig"] || !found["grape"] { + t.Errorf("Missing expected keys from 'date' to end: got %v", found) + } + if found["apple"] || found["banana"] || found["cherry"] { + t.Errorf("Found unexpected keys before 'date': got %v", found) + } + }) + + // Test with nil startKey (iterate from beginning) + t.Run("NilStartKey", func(t *testing.T) { + iter, _ := newSubtreeIterator(trie, nil, []byte("cherry")) + found := make(map[string]bool) + for iter.Next(true) { + if iter.Leaf() { + found[string(iter.LeafKey())] = true + } + } + // Should find "apple", "apricot", "banana" but not "cherry" or later + if !found["apple"] || !found["apricot"] || !found["banana"] { + t.Errorf("Missing expected keys before 'cherry': got %v", found) + } + if found["cherry"] || found["date"] || found["fig"] || found["grape"] { + t.Errorf("Found unexpected keys at or after 'cherry': got %v", found) + } + }) +} + +// TestPrefixIteratorWithDescend tests prefix iteration with descend=false +func TestPrefixIteratorWithDescend(t *testing.T) { + // Create a trie with nested structure + trie := NewEmpty(newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme)) + testData := map[string]string{ + "a": "value_a", + "a/b": "value_ab", + "a/b/c": "value_abc", + "a/b/d": "value_abd", + "a/e": "value_ae", + "b": "value_b", + } + for key, value := range testData { + trie.Update([]byte(key), []byte(value)) + } + + // Test skipping subtrees with descend=false + t.Run("SkipSubtrees", func(t *testing.T) { + iter, err := trie.NodeIteratorWithPrefix([]byte("a")) + if err != nil { + t.Fatalf("Failed to create iterator: %v", err) + } + + // Count nodes at each level + nodesVisited := 0 + leafsFound := make(map[string]bool) + + // First call with descend=true to enter the "a" subtree + if !iter.Next(true) { + t.Fatal("Expected to find at least one node") + } + nodesVisited++ + + // Continue iteration, sometimes with descend=false + descendPattern := []bool{false, true, false, true, true} + for i := 0; iter.Next(descendPattern[i%len(descendPattern)]); i++ { + nodesVisited++ + if iter.Leaf() { + leafsFound[string(iter.LeafKey())] = true + } + } + + // We should still respect the prefix boundary even when skipping + prefix := []byte("a") + for key := range leafsFound { + if !bytes.HasPrefix([]byte(key), prefix) { + t.Errorf("Found key outside prefix when using descend=false: %s", key) + } + } + + // Should not have found "b" even if we skip some subtrees + if leafsFound["b"] { + t.Error("Iterator leaked outside prefix boundary with descend=false") + } + }) +} + func BenchmarkIterator(b *testing.B) { diskDb, srcDb, tr, _ := makeTestTrie(rawdb.HashScheme) root := tr.Hash() diff --git a/trie/trie.go b/trie/trie.go index 630462f8ca52..36cc732ee850 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -134,6 +134,34 @@ func (t *Trie) NodeIterator(start []byte) (NodeIterator, error) { return newNodeIterator(t, start), nil } +// NodeIteratorWithPrefix returns an iterator that returns nodes of the trie +// whose leaf keys start with the given prefix. Iteration includes all keys +// where prefix <= k < nextKey(prefix), effectively returning only keys that +// have the prefix. The iteration stops once it would encounter a key that +// doesn't start with the prefix. +// +// For example, with prefix "dog", the iterator will return "dog", "dogcat", +// "dogfish" but not "dot" or "fog". An empty prefix iterates the entire trie. +func (t *Trie) NodeIteratorWithPrefix(prefix []byte) (NodeIterator, error) { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return nil, ErrCommitted + } + // Use the dedicated prefix iterator which handles prefix checking correctly + return newPrefixIterator(t, prefix) +} + +// NodeIteratorWithRange returns an iterator over trie nodes whose leaf keys +// fall within the specified range. It includes all keys where start <= k < end. +// Iteration stops once a key beyond the end boundary is encountered. +func (t *Trie) NodeIteratorWithRange(start, end []byte) (NodeIterator, error) { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return nil, ErrCommitted + } + return newSubtreeIterator(t, start, end) +} + // MustGet is a wrapper of Get and will omit any encountered error but just // print out an error message. func (t *Trie) MustGet(key []byte) []byte { From 59c2815cfadf07ac36aa30ead83f73fd1d748c6f Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Wed, 17 Sep 2025 16:12:16 +0200 Subject: [PATCH 023/228] cmd/keeper: disable GC for zkvm execution (#32638) ZKVMs are constrained environments that liberally allocate memory and never release it. In this context, using the GC is only going to cause issues down the road, and slow things down in any case. --- cmd/keeper/main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/keeper/main.go b/cmd/keeper/main.go index cfb06f0da00a..9b459f6f36df 100644 --- a/cmd/keeper/main.go +++ b/cmd/keeper/main.go @@ -19,6 +19,7 @@ package main import ( "fmt" "os" + "runtime/debug" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/stateless" @@ -35,6 +36,10 @@ type Payload struct { Witness *stateless.Witness } +func init() { + debug.SetGCPercent(-1) // Disable garbage collection +} + func main() { input := getInput() var payload Payload From 7dd6477444aa6e1f7f18d9ca2ca6a5622abd4212 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Thu, 18 Sep 2025 06:28:03 +0200 Subject: [PATCH 024/228] core: fix fork readiness log (#32623) When I implemented in #31340 I didn't expect multiple forks to be configured at once, but this is exactly how BPOs are defined. This updates the method to determine the next scheduled fork rather than the last fork. --- core/blockchain.go | 13 +++++-------- params/forks/forks.go | 5 +++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 346692364861..30f3da3004af 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "io" - "math" "math/big" "runtime" "slices" @@ -2663,13 +2662,11 @@ func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err er // logForkReadiness will write a log when a future fork is scheduled, but not // active. This is useful so operators know their client is ready for the fork. func (bc *BlockChain) logForkReadiness(block *types.Block) { - config := bc.Config() - current, last := config.LatestFork(block.Time()), config.LatestFork(math.MaxUint64) + current := bc.Config().LatestFork(block.Time()) - // Short circuit if the timestamp of the last fork is undefined, - // or if the network has already passed the last configured fork. - t := config.Timestamp(last) - if t == nil || current >= last { + // Short circuit if the timestamp of the last fork is undefined. + t := bc.Config().Timestamp(current + 1) + if t == nil { return } at := time.Unix(int64(*t), 0) @@ -2679,7 +2676,7 @@ func (bc *BlockChain) logForkReadiness(block *types.Block) { // - Enough time has passed since last alert now := time.Now() if now.Before(at) && now.After(bc.lastForkReadyAlert.Add(forkReadyInterval)) { - log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822), + log.Info("Ready for fork activation", "fork", current+1, "date", at.Format(time.RFC822), "remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix()) bc.lastForkReadyAlert = time.Now() } diff --git a/params/forks/forks.go b/params/forks/forks.go index aab0a54ab783..adb65c862419 100644 --- a/params/forks/forks.go +++ b/params/forks/forks.go @@ -77,4 +77,9 @@ var forkToString = map[Fork]string{ Cancun: "Cancun", Prague: "Prague", Osaka: "Osaka", + BPO1: "BPO1", + BPO2: "BPO2", + BPO3: "BPO3", + BPO4: "BPO4", + BPO5: "BPO5", } From 98a42fa176a76669a09192eb484827cbff33815b Mon Sep 17 00:00:00 2001 From: GarmashAlex Date: Thu, 18 Sep 2025 09:46:51 +0300 Subject: [PATCH 025/228] core/rawdb: report truncateErr in concurrent truncate failure (#32651) --- core/rawdb/freezer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go index b8a3d4a6d2fd..fab3319a2a36 100644 --- a/core/rawdb/freezer_test.go +++ b/core/rawdb/freezer_test.go @@ -239,7 +239,7 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) { // fails, otherwise it succeeds. In either case, the freezer should be positioned // at 10 after both operations are done. if truncateErr != nil { - t.Fatal("concurrent truncate failed:", err) + t.Fatal("concurrent truncate failed:", truncateErr) } if !(errors.Is(modifyErr, nil) || errors.Is(modifyErr, errOutOrderInsertion)) { t.Fatal("wrong error from concurrent modify:", modifyErr) From 43cd79c28c7c6f9848acaab2e9cb6cfbb2f836ae Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 18 Sep 2025 22:58:40 +0200 Subject: [PATCH 026/228] build: update to execution-spec-tests v5.0.0 (#32592) https://github.com/ethereum/execution-spec-tests/releases/tag/v5.0.0 As of this release, execution-spec-tests also contains all state tests that were previously in ethereum/tests. We can probably remove the tests submodule now. However, this would mean we are missing the pre-cancun tests. Still need to figure out how to resolve this. --------- Co-authored-by: MariusVanDerWijden --- build/checksums.txt | 6 +- build/ci.go | 2 +- core/genesis.go | 5 + params/config.go | 24 ++++ tests/block_test.go | 5 +- tests/init.go | 256 ++++++++++++++++++++++++++++++++++++++ tests/transaction_test.go | 2 +- 7 files changed, 293 insertions(+), 7 deletions(-) diff --git a/build/checksums.txt b/build/checksums.txt index 6e65fa47fb06..ca937d115c3b 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,9 +1,9 @@ # This file contains sha256 checksums of optional build dependencies. -# version:spec-tests fusaka-devnet-3%40v1.0.0 +# version:spec-tests v5.0.0 # https://github.com/ethereum/execution-spec-tests/releases -# https://github.com/ethereum/execution-spec-tests/releases/download/fusaka-devnet-3%40v1.0.0 -576261e1280e5300c458aa9b05eccb2fec5ff80a0005940dc52fa03fdd907249 fixtures_fusaka-devnet-3.tar.gz +# https://github.com/ethereum/execution-spec-tests/releases/download/v5.0.0 +a5ed96800ca1af0d86fe2ee894861c24eea079bfb83b924f565bb86ba70021d5 fixtures_develop.tar.gz # version:golang 1.25.1 # https://go.dev/dl/ diff --git a/build/ci.go b/build/ci.go index 6a9848876d56..e145cc1cb563 100644 --- a/build/ci.go +++ b/build/ci.go @@ -333,7 +333,7 @@ func doTest(cmdline []string) { // downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures. func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string { ext := ".tar.gz" - base := "fixtures_fusaka-devnet-3" + base := "fixtures_develop" archivePath := filepath.Join(cachedir, base+ext) if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil { log.Fatal(err) diff --git a/core/genesis.go b/core/genesis.go index 2673334e9e00..13d4addd7e7b 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -514,6 +514,11 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block { if head.BlobGasUsed == nil { head.BlobGasUsed = new(uint64) } + } else { + if g.ExcessBlobGas != nil { + log.Warn("Invalid genesis, unexpected ExcessBlobGas set before Cancun, allowing it for testing purposes") + head.ExcessBlobGas = g.ExcessBlobGas + } } if conf.IsPrague(num, g.Timestamp) { head.RequestsHash = &types.EmptyRequestsHash diff --git a/params/config.go b/params/config.go index b1297144c3a4..f8fc35454cb0 100644 --- a/params/config.go +++ b/params/config.go @@ -359,6 +359,30 @@ var ( Max: 9, UpdateFraction: 5007716, } + // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + DefaultBPO1BlobConfig = &BlobConfig{ + Target: 9, + Max: 14, + UpdateFraction: 8832827, + } + // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + DefaultBPO2BlobConfig = &BlobConfig{ + Target: 14, + Max: 21, + UpdateFraction: 13739630, + } + // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + DefaultBPO3BlobConfig = &BlobConfig{ + Target: 21, + Max: 32, + UpdateFraction: 20609697, + } + // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + DefaultBPO4BlobConfig = &BlobConfig{ + Target: 14, + Max: 21, + UpdateFraction: 13739630, + } // DefaultBlobSchedule is the latest configured blob schedule for Ethereum mainnet. DefaultBlobSchedule = &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, diff --git a/tests/block_test.go b/tests/block_test.go index 91d9f2e653a0..c718b304b6f5 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -81,8 +81,9 @@ func TestExecutionSpecBlocktests(t *testing.T) { } bt := new(testMatcher) - bt.skipLoad(".*prague/eip7251_consolidations/contract_deployment/system_contract_deployment.json") - bt.skipLoad(".*prague/eip7002_el_triggerable_withdrawals/contract_deployment/system_contract_deployment.json") + // These tests require us to handle scenarios where a system contract is not deployed at a fork + bt.skipLoad(".*prague/eip7251_consolidations/test_system_contract_deployment.json") + bt.skipLoad(".*prague/eip7002_el_triggerable_withdrawals/test_system_contract_deployment.json") bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) { execBlockTest(t, bt, test) diff --git a/tests/init.go b/tests/init.go index a8bc424fa2c8..71072ac275d7 100644 --- a/tests/init.go +++ b/tests/init.go @@ -464,6 +464,262 @@ var Forks = map[string]*params.ChainConfig{ Osaka: params.DefaultOsakaBlobConfig, }, }, + "BPO1": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(0), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: params.DefaultBPO1BlobConfig, + }, + }, + "OsakaToBPO1AtTime15k": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(15_000), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: params.DefaultBPO1BlobConfig, + }, + }, + "BPO2": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(0), + BPO2Time: u64(0), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: params.DefaultBPO1BlobConfig, + BPO2: params.DefaultBPO2BlobConfig, + }, + }, + "BPO1ToBPO2AtTime15k": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(0), + BPO2Time: u64(15_000), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: params.DefaultBPO1BlobConfig, + BPO2: params.DefaultBPO2BlobConfig, + }, + }, + "BPO3": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(0), + BPO2Time: u64(0), + BPO3Time: u64(0), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: params.DefaultBPO1BlobConfig, + BPO2: params.DefaultBPO2BlobConfig, + BPO3: params.DefaultBPO3BlobConfig, + }, + }, + "BPO2ToBPO3AtTime15k": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(0), + BPO2Time: u64(0), + BPO3Time: u64(15_000), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: params.DefaultBPO1BlobConfig, + BPO2: params.DefaultBPO2BlobConfig, + BPO3: params.DefaultBPO3BlobConfig, + }, + }, + "BPO4": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(0), + BPO2Time: u64(0), + BPO3Time: u64(0), + BPO4Time: u64(0), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: params.DefaultBPO1BlobConfig, + BPO2: params.DefaultBPO2BlobConfig, + BPO3: params.DefaultBPO3BlobConfig, + BPO4: params.DefaultBPO4BlobConfig, + }, + }, + "BPO3ToBPO4AtTime15k": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(0), + BPO2Time: u64(0), + BPO3Time: u64(0), + BPO4Time: u64(15_000), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: params.DefaultBPO1BlobConfig, + BPO2: params.DefaultBPO2BlobConfig, + BPO3: params.DefaultBPO3BlobConfig, + BPO4: params.DefaultBPO4BlobConfig, + }, + }, } // AvailableForks returns the set of defined fork names diff --git a/tests/transaction_test.go b/tests/transaction_test.go index 6260df0f3f80..73ee3aa16a14 100644 --- a/tests/transaction_test.go +++ b/tests/transaction_test.go @@ -70,7 +70,7 @@ func TestExecutionSpecTransaction(t *testing.T) { st := new(testMatcher) // Emptiness of authorization list is only validated during the tx precheck - st.skipLoad("^prague/eip7702_set_code_tx/invalid_tx/empty_authorization_list.json") + st.skipLoad("^prague/eip7702_set_code_tx/test_empty_authorization_list.json") st.walk(t, executionSpecTransactionTestDir, func(t *testing.T, name string, test *TransactionTest) { if err := st.checkFailure(t, test.Run()); err != nil { From 375b1103732494a000f065299b3aba6dde60472d Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 19 Sep 2025 06:16:01 +0800 Subject: [PATCH 027/228] eth/catalyst, beacon/engine: enable BPO and Osaka on stateless APIs (#32636) Addresses https://github.com/ethereum/go-ethereum/issues/32630 This pull request enables the stateless engine APIs for Osaka and the following BPOs. Apart from that, a few more descriptions have been added in the engine APIs, making it easier to follow the spec change. --- beacon/engine/gen_epe.go | 4 ++-- beacon/engine/types.go | 42 +++++++++++++++++++++++++++++++++------- eth/catalyst/api.go | 21 ++++++++++++++++---- eth/catalyst/witness.go | 12 ++++++------ 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/beacon/engine/gen_epe.go b/beacon/engine/gen_epe.go index deada06166c5..cf7bd9ee3fa5 100644 --- a/beacon/engine/gen_epe.go +++ b/beacon/engine/gen_epe.go @@ -17,7 +17,7 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) { type ExecutionPayloadEnvelope struct { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` - BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` + BlobsBundle *BlobsBundle `json:"blobsBundle"` Requests []hexutil.Bytes `json:"executionRequests"` Override bool `json:"shouldOverrideBuilder"` Witness *hexutil.Bytes `json:"witness,omitempty"` @@ -42,7 +42,7 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error { type ExecutionPayloadEnvelope struct { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` - BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` + BlobsBundle *BlobsBundle `json:"blobsBundle"` Requests []hexutil.Bytes `json:"executionRequests"` Override *bool `json:"shouldOverrideBuilder"` Witness *hexutil.Bytes `json:"witness,omitempty"` diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 76bfd22a2367..ddb276ab0914 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -33,8 +33,22 @@ import ( type PayloadVersion byte var ( + // PayloadV1 is the identifier of ExecutionPayloadV1 introduced in paris fork. + // https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#executionpayloadv1 PayloadV1 PayloadVersion = 0x1 + + // PayloadV2 is the identifier of ExecutionPayloadV2 introduced in shanghai fork. + // + // https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#executionpayloadv2 + // ExecutionPayloadV2 has the syntax of ExecutionPayloadV1 and appends a + // single field: withdrawals. PayloadV2 PayloadVersion = 0x2 + + // PayloadV3 is the identifier of ExecutionPayloadV3 introduced in cancun fork. + // + // https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#executionpayloadv3 + // ExecutionPayloadV3 has the syntax of ExecutionPayloadV2 and appends the new + // fields: blobGasUsed and excessBlobGas. PayloadV3 PayloadVersion = 0x3 ) @@ -106,13 +120,18 @@ type StatelessPayloadStatusV1 struct { type ExecutionPayloadEnvelope struct { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *big.Int `json:"blockValue" gencodec:"required"` - BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` + BlobsBundle *BlobsBundle `json:"blobsBundle"` Requests [][]byte `json:"executionRequests"` Override bool `json:"shouldOverrideBuilder"` Witness *hexutil.Bytes `json:"witness,omitempty"` } -type BlobsBundleV1 struct { +// BlobsBundle includes the marshalled sidecar data. Note this structure is +// shared by BlobsBundleV1 and BlobsBundleV2 for the sake of simplicity. +// +// - BlobsBundleV1: proofs contain exactly len(blobs) kzg proofs. +// - BlobsBundleV2: proofs contain exactly CELLS_PER_EXT_BLOB * len(blobs) cell proofs. +type BlobsBundle struct { Commitments []hexutil.Bytes `json:"commitments"` Proofs []hexutil.Bytes `json:"proofs"` Blobs []hexutil.Bytes `json:"blobs"` @@ -125,7 +144,7 @@ type BlobAndProofV1 struct { type BlobAndProofV2 struct { Blob hexutil.Bytes `json:"blob"` - CellProofs []hexutil.Bytes `json:"proofs"` + CellProofs []hexutil.Bytes `json:"proofs"` // proofs MUST contain exactly CELLS_PER_EXT_BLOB cell proofs. } // JSON type overrides for ExecutionPayloadEnvelope. @@ -327,18 +346,27 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. } // Add blobs. - bundle := BlobsBundleV1{ + bundle := BlobsBundle{ Commitments: make([]hexutil.Bytes, 0), Blobs: make([]hexutil.Bytes, 0), Proofs: make([]hexutil.Bytes, 0), } for _, sidecar := range sidecars { for j := range sidecar.Blobs { - bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:])) - bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:])) + bundle.Blobs = append(bundle.Blobs, sidecar.Blobs[j][:]) + bundle.Commitments = append(bundle.Commitments, sidecar.Commitments[j][:]) } + // - Before the Osaka fork, only version-0 blob transactions should be packed, + // with the proof length equal to len(blobs). + // + // - After the Osaka fork, only version-1 blob transactions should be packed, + // with the proof length equal to CELLS_PER_EXT_BLOB * len(blobs). + // + // Ideally, length validation should be performed based on the bundle version. + // In practice, this is unnecessary because blob transaction filtering is + // already done during payload construction. for _, proof := range sidecar.Proofs { - bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(proof[:])) + bundle.Proofs = append(bundle.Proofs, proof[:]) } } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index b222470228a2..17299d92963d 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -418,13 +418,21 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.Execu // GetPayloadV2 returns a cached payload by id. func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { + // executionPayload: ExecutionPayloadV1 | ExecutionPayloadV2 where: + // + // - ExecutionPayloadV1 MUST be returned if the payload timestamp is lower + // than the Shanghai timestamp + // + // - ExecutionPayloadV2 MUST be returned if the payload timestamp is greater + // or equal to the Shanghai timestamp if !payloadID.Is(engine.PayloadV1, engine.PayloadV2) { return nil, engine.UnsupportedFork } return api.getPayload(payloadID, false) } -// GetPayloadV3 returns a cached payload by id. +// GetPayloadV3 returns a cached payload by id. This endpoint should only +// be used for the Cancun fork. func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { if !payloadID.Is(engine.PayloadV3) { return nil, engine.UnsupportedFork @@ -432,7 +440,8 @@ func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.Execu return api.getPayload(payloadID, false) } -// GetPayloadV4 returns a cached payload by id. +// GetPayloadV4 returns a cached payload by id. This endpoint should only +// be used for the Prague fork. func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { if !payloadID.Is(engine.PayloadV3) { return nil, engine.UnsupportedFork @@ -440,7 +449,11 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu return api.getPayload(payloadID, false) } -// GetPayloadV5 returns a cached payload by id. +// GetPayloadV5 returns a cached payload by id. This endpoint should only +// be used after the Osaka fork. +// +// This method follows the same specification as engine_getPayloadV4 with +// changes of returning BlobsBundleV2 with BlobSidecar version 1. func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { if !payloadID.Is(engine.PayloadV3) { return nil, engine.UnsupportedFork @@ -637,7 +650,7 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas case executionRequests == nil: return invalidStatus, paramsErr("nil executionRequests post-prague") case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): - return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for Prague payloads") + return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague/osaka payloads") } requests := convertRequests(executionRequests) if err := validateRequests(requests); err != nil { diff --git a/eth/catalyst/witness.go b/eth/catalyst/witness.go index 703f1b0881be..0df612a69550 100644 --- a/eth/catalyst/witness.go +++ b/eth/catalyst/witness.go @@ -73,8 +73,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice return engine.STATUS_INVALID, attributesErr("missing withdrawals") case params.BeaconRoot == nil: return engine.STATUS_INVALID, attributesErr("missing beacon root") - case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague): - return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads") + case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): + return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka payloads") } } // TODO(matt): the spec requires that fcu is applied when called on a valid @@ -151,8 +151,8 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, v return invalidStatus, paramsErr("nil beaconRoot post-cancun") case executionRequests == nil: return invalidStatus, paramsErr("nil executionRequests post-prague") - case !api.checkFork(params.Timestamp, forks.Prague): - return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague payloads") + case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): + return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague/osaka payloads") } requests := convertRequests(executionRequests) if err := validateRequests(requests); err != nil { @@ -228,8 +228,8 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData, return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun") case executionRequests == nil: return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague") - case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka): - return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV4 must only be called for prague payloads") + case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): + return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV4 must only be called for prague/osaka payloads") } requests := convertRequests(executionRequests) if err := validateRequests(requests); err != nil { From f17095752b6e0dadd4280d8753a752deb7dc54b3 Mon Sep 17 00:00:00 2001 From: Long Vu Date: Fri, 19 Sep 2025 05:53:40 +0700 Subject: [PATCH 028/228] eth/filters, cmd: add config of eth_getLogs address limit (#32327) Add cli configurable limit for the number of addresses allowed in eth_getLogs filter criteria: https://github.com/ethereum/go-ethereum/issues/32264 Key changes: - Added --rpc.getlogmaxaddrs CLI flag (default: 1000) to configure the maximum number of addresses - Updated ethconfig.Config with FilterMaxAddresses field for configuration management - Modified filter system to use the configurable limit instead of the hardcoded maxAddresses constant - Enhanced test coverage with new test cases for address limit validation - Removed hardcoded validation from JSON unmarshaling, moving it to runtime validation Please notice that I remove the check at FilterCriteria UnmarshalJSON because the runtime config can not pass into this validation. Please help review this change! --------- Co-authored-by: zsfelfoldi Co-authored-by: rjl493456442 --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 12 +++- eth/ethconfig/config.go | 5 ++ eth/ethconfig/gen_config.go | 6 ++ eth/filters/api.go | 38 ++++++------ eth/filters/api_test.go | 12 ---- eth/filters/filter_system.go | 16 +++-- eth/filters/filter_system_test.go | 97 +++++++++++++++++++++++++++++-- 8 files changed, 148 insertions(+), 39 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b661228681f0..f380c9f2d448 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -182,6 +182,7 @@ var ( utils.RPCGlobalGasCapFlag, utils.RPCGlobalEVMTimeoutFlag, utils.RPCGlobalTxFeeCapFlag, + utils.RPCGlobalLogQueryLimit, utils.AllowUnprotectedTxs, utils.BatchRequestLimit, utils.BatchResponseMaxSize, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 83d1c8bda536..325f8250f8e4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -600,6 +600,12 @@ var ( Value: ethconfig.Defaults.RPCTxFeeCap, Category: flags.APICategory, } + RPCGlobalLogQueryLimit = &cli.IntFlag{ + Name: "rpc.logquerylimit", + Usage: "Maximum number of alternative addresses or topics allowed per search position in eth_getLogs filter criteria (0 = no cap)", + Value: ethconfig.Defaults.LogQueryLimit, + Category: flags.APICategory, + } // Authenticated RPC HTTP settings AuthListenFlag = &cli.StringFlag{ Name: "authrpc.addr", @@ -1699,6 +1705,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(CacheLogSizeFlag.Name) { cfg.FilterLogCacheSize = ctx.Int(CacheLogSizeFlag.Name) } + if ctx.IsSet(RPCGlobalLogQueryLimit.Name) { + cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name) + } if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 { // If snap-sync is requested, this flag is also required if cfg.SyncMode == ethconfig.SnapSync { @@ -2017,7 +2026,8 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSyst // RegisterFilterAPI adds the eth log filtering RPC API to the node. func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem { filterSystem := filters.NewFilterSystem(backend, filters.Config{ - LogCacheSize: ethcfg.FilterLogCacheSize, + LogCacheSize: ethcfg.FilterLogCacheSize, + LogQueryLimit: ethcfg.LogQueryLimit, }) stack.RegisterAPIs([]rpc.API{{ Namespace: "eth", diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index ba0a7762c726..4fe24c0fe077 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -62,6 +62,7 @@ var Defaults = Config{ TrieTimeout: 60 * time.Minute, SnapshotCache: 102, FilterLogCacheSize: 32, + LogQueryLimit: 1000, Miner: miner.DefaultConfig, TxPool: legacypool.DefaultConfig, BlobPool: blobpool.DefaultConfig, @@ -131,6 +132,10 @@ type Config struct { // This is the number of blocks for which logs will be cached in the filter system. FilterLogCacheSize int + // This is the maximum number of addresses or topics allowed in filter criteria + // for eth_getLogs. + LogQueryLimit int + // Mining options Miner miner.Config diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index b54ba14d6889..50eb5c4161f3 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -44,6 +44,7 @@ func (c Config) MarshalTOML() (interface{}, error) { SnapshotCache int Preimages bool FilterLogCacheSize int + LogQueryLimit int Miner miner.Config TxPool legacypool.Config BlobPool blobpool.Config @@ -88,6 +89,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.SnapshotCache = c.SnapshotCache enc.Preimages = c.Preimages enc.FilterLogCacheSize = c.FilterLogCacheSize + enc.LogQueryLimit = c.LogQueryLimit enc.Miner = c.Miner enc.TxPool = c.TxPool enc.BlobPool = c.BlobPool @@ -136,6 +138,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { SnapshotCache *int Preimages *bool FilterLogCacheSize *int + LogQueryLimit *int Miner *miner.Config TxPool *legacypool.Config BlobPool *blobpool.Config @@ -237,6 +240,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.FilterLogCacheSize != nil { c.FilterLogCacheSize = *dec.FilterLogCacheSize } + if dec.LogQueryLimit != nil { + c.LogQueryLimit = *dec.LogQueryLimit + } if dec.Miner != nil { c.Miner = *dec.Miner } diff --git a/eth/filters/api.go b/eth/filters/api.go index c929810a12b3..d678c403894d 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -42,12 +42,10 @@ var ( errBlockHashWithRange = errors.New("can't specify fromBlock/toBlock with blockHash") errPendingLogsUnsupported = errors.New("pending logs are not supported") errExceedMaxTopics = errors.New("exceed max topics") - errExceedMaxAddresses = errors.New("exceed max addresses") + errExceedLogQueryLimit = errors.New("exceed max addresses or topics per search position") ) const ( - // The maximum number of addresses allowed in a filter criteria - maxAddresses = 1000 // The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0 maxTopics = 4 // The maximum number of allowed topics within a topic criteria @@ -70,20 +68,22 @@ type filter struct { // FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various // information related to the Ethereum protocol such as blocks, transactions and logs. type FilterAPI struct { - sys *FilterSystem - events *EventSystem - filtersMu sync.Mutex - filters map[rpc.ID]*filter - timeout time.Duration + sys *FilterSystem + events *EventSystem + filtersMu sync.Mutex + filters map[rpc.ID]*filter + timeout time.Duration + logQueryLimit int } // NewFilterAPI returns a new FilterAPI instance. func NewFilterAPI(system *FilterSystem) *FilterAPI { api := &FilterAPI{ - sys: system, - events: NewEventSystem(system), - filters: make(map[rpc.ID]*filter), - timeout: system.cfg.Timeout, + sys: system, + events: NewEventSystem(system), + filters: make(map[rpc.ID]*filter), + timeout: system.cfg.Timeout, + logQueryLimit: system.cfg.LogQueryLimit, } go api.timeoutLoop(system.cfg.Timeout) @@ -347,8 +347,15 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type if len(crit.Topics) > maxTopics { return nil, errExceedMaxTopics } - if len(crit.Addresses) > maxAddresses { - return nil, errExceedMaxAddresses + if api.logQueryLimit != 0 { + if len(crit.Addresses) > api.logQueryLimit { + return nil, errExceedLogQueryLimit + } + for _, topics := range crit.Topics { + if len(topics) > api.logQueryLimit { + return nil, errExceedLogQueryLimit + } + } } var filter *Filter @@ -545,9 +552,6 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { // raw.Address can contain a single address or an array of addresses switch rawAddr := raw.Addresses.(type) { case []interface{}: - if len(rawAddr) > maxAddresses { - return errExceedMaxAddresses - } for i, addr := range rawAddr { if strAddr, ok := addr.(string); ok { addr, err := decodeAddress(strAddr) diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go index 2eb3ee97b35a..822bc826f6b0 100644 --- a/eth/filters/api_test.go +++ b/eth/filters/api_test.go @@ -19,7 +19,6 @@ package filters import ( "encoding/json" "fmt" - "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -183,15 +182,4 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) { if len(test7.Topics[2]) != 0 { t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2])) } - - // multiple address exceeding max - var test8 FilterCriteria - addresses := make([]string, maxAddresses+1) - for i := 0; i < maxAddresses+1; i++ { - addresses[i] = fmt.Sprintf(`"%s"`, common.HexToAddress(fmt.Sprintf("0x%x", i)).Hex()) - } - vector = fmt.Sprintf(`{"address": [%s]}`, strings.Join(addresses, ", ")) - if err := json.Unmarshal([]byte(vector), &test8); err != errExceedMaxAddresses { - t.Fatal("expected errExceedMaxAddresses, got", err) - } } diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 751cd417e8f1..ecf1c870c13f 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -41,8 +41,9 @@ import ( // Config represents the configuration of the filter system. type Config struct { - LogCacheSize int // maximum number of cached blocks (default: 32) - Timeout time.Duration // how long filters stay active (default: 5min) + LogCacheSize int // maximum number of cached blocks (default: 32) + Timeout time.Duration // how long filters stay active (default: 5min) + LogQueryLimit int // maximum number of addresses allowed in filter criteria (default: 1000) } func (cfg Config) withDefaults() Config { @@ -291,8 +292,15 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ if len(crit.Topics) > maxTopics { return nil, errExceedMaxTopics } - if len(crit.Addresses) > maxAddresses { - return nil, errExceedMaxAddresses + if es.sys.cfg.LogQueryLimit != 0 { + if len(crit.Addresses) > es.sys.cfg.LogQueryLimit { + return nil, errExceedLogQueryLimit + } + for _, topics := range crit.Topics { + if len(topics) > es.sys.cfg.LogQueryLimit { + return nil, errExceedLogQueryLimit + } + } } var from, to rpc.BlockNumber if crit.FromBlock == nil { diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 013c1ae527e2..0048e74995fc 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/triedb" ) type testBackend struct { @@ -424,7 +425,7 @@ func TestInvalidLogFilterCreation(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() - _, sys = newTestFilterSystem(db, Config{}) + _, sys = newTestFilterSystem(db, Config{LogQueryLimit: 1000}) api = NewFilterAPI(sys) ) @@ -435,7 +436,7 @@ func TestInvalidLogFilterCreation(t *testing.T) { 1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, - 4: {Addresses: make([]common.Address, maxAddresses+1)}, + 4: {Addresses: make([]common.Address, api.logQueryLimit+1)}, } for i, test := range testCases { @@ -455,7 +456,7 @@ func TestInvalidGetLogsRequest(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), } db, blocks, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 10, func(i int, gen *core.BlockGen) {}) - _, sys = newTestFilterSystem(db, Config{}) + _, sys = newTestFilterSystem(db, Config{LogQueryLimit: 10}) api = NewFilterAPI(sys) blockHash = blocks[0].Hash() unknownBlockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") @@ -500,8 +501,8 @@ func TestInvalidGetLogsRequest(t *testing.T) { err: errExceedMaxTopics, }, { - f: FilterCriteria{BlockHash: &blockHash, Addresses: make([]common.Address, maxAddresses+1)}, - err: errExceedMaxAddresses, + f: FilterCriteria{BlockHash: &blockHash, Addresses: make([]common.Address, api.logQueryLimit+1)}, + err: errExceedLogQueryLimit, }, } @@ -528,6 +529,92 @@ func TestInvalidGetRangeLogsRequest(t *testing.T) { } } +// TestExceedLogQueryLimit tests getLogs with too many addresses or topics +func TestExceedLogQueryLimit(t *testing.T) { + t.Parallel() + + // Test with custom config (LogQueryLimit = 5 for easier testing) + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(db, Config{LogQueryLimit: 5}) + api = NewFilterAPI(sys) + gspec = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + ) + + _, err := gspec.Commit(db, triedb.NewDatabase(db, nil)) + if err != nil { + t.Fatal(err) + } + chain, _ := core.GenerateChain(gspec.Config, gspec.ToBlock(), ethash.NewFaker(), db, 1000, func(i int, gen *core.BlockGen) {}) + + options := core.DefaultConfig().WithStateScheme(rawdb.HashScheme) + options.TxLookupLimit = 0 // index all txs + bc, err := core.NewBlockChain(db, gspec, ethash.NewFaker(), options) + if err != nil { + t.Fatal(err) + } + _, err = bc.InsertChain(chain[:600]) + if err != nil { + t.Fatal(err) + } + + backend.startFilterMaps(200, false, filtermaps.RangeTestParams) + defer backend.stopFilterMaps() + + addresses := make([]common.Address, 6) + for i := range addresses { + addresses[i] = common.HexToAddress("0x1234567890123456789012345678901234567890") + } + + topics := make([]common.Hash, 6) + for i := range topics { + topics[i] = common.HexToHash("0x123456789012345678901234567890123456789001234567890012345678901234") + } + + // Test that 5 addresses do not result in error + // Add FromBlock and ToBlock to make it similar to other invalid tests + if _, err := api.GetLogs(context.Background(), FilterCriteria{ + FromBlock: big.NewInt(0), + ToBlock: big.NewInt(100), + Addresses: addresses[:5], + }); err != nil { + t.Errorf("Expected GetLogs with 5 addresses to return with no error, got: %v", err) + } + + // Test that 6 addresses fails with correct error + if _, err := api.GetLogs(context.Background(), FilterCriteria{ + FromBlock: big.NewInt(0), + ToBlock: big.NewInt(100), + Addresses: addresses, + }); err != errExceedLogQueryLimit { + t.Errorf("Expected GetLogs with 6 addresses to return errExceedLogQueryLimit, got: %v", err) + } + + // Test that 5 topics at one position do not result in error + if _, err := api.GetLogs(context.Background(), FilterCriteria{ + FromBlock: big.NewInt(0), + ToBlock: big.NewInt(100), + Addresses: addresses[:1], + Topics: [][]common.Hash{topics[:5]}, + }); err != nil { + t.Errorf("Expected GetLogs with 5 topics at one position to return with no error, got: %v", err) + } + + // Test that 6 topics at one position fails with correct error + if _, err := api.GetLogs(context.Background(), FilterCriteria{ + FromBlock: big.NewInt(0), + ToBlock: big.NewInt(100), + Addresses: addresses[:1], + Topics: [][]common.Hash{topics}, + }); err != errExceedLogQueryLimit { + t.Errorf("Expected GetLogs with 6 topics at one position to return errExceedLogQueryLimit, got: %v", err) + } +} + // TestLogFilter tests whether log filters match the correct logs that are posted to the event feed. func TestLogFilter(t *testing.T) { t.Parallel() From b253959a11d71e90468ce8feb2db83cc8dc91c5b Mon Sep 17 00:00:00 2001 From: Klimov Sergei Date: Fri, 19 Sep 2025 07:30:00 +0800 Subject: [PATCH 029/228] beacon/config: fix LoadForks with non-string values (#32609) Fixes a crash when loading the beacon chain config if new fields like `BLOB_SCHEDULE: []` are present. Previously, the config loader assumed all values were strings, causing errors such as: ``` Fatal: Could not load beacon chain config '/network-configs/config.yaml': failed to parse beacon chain config file: yaml: unmarshal errors: line 242: cannot unmarshal !!seq into string ``` This PR updates the parsing logic to handle non-string values correctly and adds explicit validation for fork fields. --- beacon/params/config.go | 39 +++++++++++++++++++++++++----------- beacon/params/config_test.go | 34 +++++++++++++++++++++++++++++++ cmd/utils/flags.go | 12 +++++++---- 3 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 beacon/params/config_test.go diff --git a/beacon/params/config.go b/beacon/params/config.go index 2f6ba082c51e..492ee53308fd 100644 --- a/beacon/params/config.go +++ b/beacon/params/config.go @@ -20,6 +20,7 @@ import ( "crypto/sha256" "fmt" "math" + "math/big" "os" "slices" "sort" @@ -90,12 +91,8 @@ func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainC // LoadForks parses the beacon chain configuration file (config.yaml) and extracts // the list of forks. -func (c *ChainConfig) LoadForks(path string) error { - file, err := os.ReadFile(path) - if err != nil { - return fmt.Errorf("failed to read beacon chain config file: %v", err) - } - config := make(map[string]string) +func (c *ChainConfig) LoadForks(file []byte) error { + config := make(map[string]any) if err := yaml.Unmarshal(file, &config); err != nil { return fmt.Errorf("failed to parse beacon chain config file: %v", err) } @@ -108,18 +105,36 @@ func (c *ChainConfig) LoadForks(path string) error { for key, value := range config { if strings.HasSuffix(key, "_FORK_VERSION") { name := key[:len(key)-len("_FORK_VERSION")] - if v, err := hexutil.Decode(value); err == nil { + switch version := value.(type) { + case int: + versions[name] = new(big.Int).SetUint64(uint64(version)).FillBytes(make([]byte, 4)) + case uint64: + versions[name] = new(big.Int).SetUint64(version).FillBytes(make([]byte, 4)) + case string: + v, err := hexutil.Decode(version) + if err != nil { + return fmt.Errorf("failed to decode hex fork id %q in beacon chain config file: %v", version, err) + } versions[name] = v - } else { - return fmt.Errorf("failed to decode hex fork id %q in beacon chain config file: %v", value, err) + default: + return fmt.Errorf("invalid fork version %q in beacon chain config file", version) } } if strings.HasSuffix(key, "_FORK_EPOCH") { name := key[:len(key)-len("_FORK_EPOCH")] - if v, err := strconv.ParseUint(value, 10, 64); err == nil { + switch epoch := value.(type) { + case int: + epochs[name] = uint64(epoch) + case uint64: + epochs[name] = epoch + case string: + v, err := strconv.ParseUint(epoch, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse epoch number %q in beacon chain config file: %v", epoch, err) + } epochs[name] = v - } else { - return fmt.Errorf("failed to parse epoch number %q in beacon chain config file: %v", value, err) + default: + return fmt.Errorf("invalid fork epoch %q in beacon chain config file", epoch) } } } diff --git a/beacon/params/config_test.go b/beacon/params/config_test.go new file mode 100644 index 000000000000..41e120469be5 --- /dev/null +++ b/beacon/params/config_test.go @@ -0,0 +1,34 @@ +package params + +import ( + "bytes" + "testing" +) + +func TestChainConfig_LoadForks(t *testing.T) { + const config = ` +GENESIS_FORK_VERSION: 0x00000000 + +ALTAIR_FORK_VERSION: 0x00000001 +ALTAIR_FORK_EPOCH: 1 + +EIP7928_FORK_VERSION: 0xb0000038 +EIP7928_FORK_EPOCH: 18446744073709551615 + +BLOB_SCHEDULE: [] +` + c := &ChainConfig{} + err := c.LoadForks([]byte(config)) + if err != nil { + t.Fatal(err) + } + + for _, fork := range c.Forks { + if fork.Name == "GENESIS" && (fork.Epoch != 0) { + t.Errorf("unexpected genesis fork epoch %d", fork.Epoch) + } + if fork.Name == "ALTAIR" && (fork.Epoch != 1 || !bytes.Equal(fork.Version, []byte{0, 0, 0, 1})) { + t.Errorf("unexpected altair fork epoch %d version %x", fork.Epoch, fork.Version) + } + } +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 325f8250f8e4..5e96185dbd03 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1931,11 +1931,15 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig { } else { Fatalf("Could not parse --%s: %v", BeaconGenesisRootFlag.Name, err) } - configFile := ctx.String(BeaconConfigFlag.Name) - if err := config.ChainConfig.LoadForks(configFile); err != nil { - Fatalf("Could not load beacon chain config '%s': %v", configFile, err) + configPath := ctx.String(BeaconConfigFlag.Name) + file, err := os.ReadFile(configPath) + if err != nil { + Fatalf("failed to read beacon chain config file '%s': %v", configPath, err) + } + if err := config.ChainConfig.LoadForks(file); err != nil { + Fatalf("Could not load beacon chain config '%s': %v", configPath, err) } - log.Info("Using custom beacon chain config", "file", configFile) + log.Info("Using custom beacon chain config", "file", configPath) } else { if ctx.IsSet(BeaconGenesisRootFlag.Name) { Fatalf("Genesis root is specified but custom beacon chain config is missing") From 7f3995d2fa23c82b2376deeb79c5f00c152ab300 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 19 Sep 2025 13:29:17 +0200 Subject: [PATCH 030/228] internal/ethapi: skip tx gas limit check for calls (#32641) This disables the tx gaslimit cap for eth_call and related RPC operations. I don't like how this fix works. Ideally we'd be checking the tx gaslimit somewhere else, like in the block validator, or any other place that considers block transactions. Doing the check in StateTransition means it affects all possible ways of executing a message. The challenge is finding a place for this check that also triggers correctly in tests where it is wanted. So for now, we are just combining this with the EOA sender check for transactions. Both are disabled for call-type messages. --- core/state_transition.go | 22 +++++++++++++--------- eth/tracers/api.go | 2 +- internal/ethapi/api.go | 10 +++++----- internal/ethapi/simulate.go | 2 +- internal/ethapi/transaction_args.go | 4 ++-- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 2cafe4865f56..bf5ac07636d5 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -164,8 +164,12 @@ type Message struct { // or the state prefetching. SkipNonceChecks bool - // When SkipFromEOACheck is true, the message sender is not checked to be an EOA. - SkipFromEOACheck bool + // When set, the message is not treated as a transaction, and certain + // transaction-specific checks are skipped: + // + // - From is not verified to be an EOA + // - GasLimit is not checked against the protocol defined tx gaslimit + SkipTransactionChecks bool } // TransactionToMessage converts a transaction into a Message. @@ -182,7 +186,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In AccessList: tx.AccessList(), SetCodeAuthorizations: tx.SetCodeAuthorizations(), SkipNonceChecks: false, - SkipFromEOACheck: false, + SkipTransactionChecks: false, BlobHashes: tx.BlobHashes(), BlobGasFeeCap: tx.BlobGasFeeCap(), } @@ -320,7 +324,12 @@ func (st *stateTransition) preCheck() error { msg.From.Hex(), stNonce) } } - if !msg.SkipFromEOACheck { + isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time) + if !msg.SkipTransactionChecks { + // Verify tx gas limit does not exceed EIP-7825 cap. + if isOsaka && msg.GasLimit > params.MaxTxGas { + return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit) + } // Make sure the sender is an EOA code := st.state.GetCode(msg.From) _, delegated := types.ParseDelegation(code) @@ -354,7 +363,6 @@ func (st *stateTransition) preCheck() error { } } // Check the blob version validity - isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time) if msg.BlobHashes != nil { // The to field of a blob tx type is mandatory, and a `BlobTx` transaction internally // has it as a non-nillable value, so any msg derived from blob transaction has it non-nil. @@ -398,10 +406,6 @@ func (st *stateTransition) preCheck() error { return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From) } } - // Verify tx gas limit does not exceed EIP-7825 cap. - if isOsaka && msg.GasLimit > params.MaxTxGas { - return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit) - } return st.buyGas() } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index b50a532b6071..a05b7a7a4a79 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -984,7 +984,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc return nil, err } var ( - msg = args.ToMessage(blockContext.BaseFee, true, true) + msg = args.ToMessage(blockContext.BaseFee, true) tx = args.ToTransaction(types.LegacyTxType) traceConfig *TraceConfig ) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 554f5252903c..ebb8ece73017 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -699,15 +699,15 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S } else { gp.AddGas(globalGasCap) } - return applyMessage(ctx, b, args, state, header, timeout, gp, &blockCtx, &vm.Config{NoBaseFee: true}, precompiles, true) + return applyMessage(ctx, b, args, state, header, timeout, gp, &blockCtx, &vm.Config{NoBaseFee: true}, precompiles) } -func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, gp *core.GasPool, blockContext *vm.BlockContext, vmConfig *vm.Config, precompiles vm.PrecompiledContracts, skipChecks bool) (*core.ExecutionResult, error) { +func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, gp *core.GasPool, blockContext *vm.BlockContext, vmConfig *vm.Config, precompiles vm.PrecompiledContracts) (*core.ExecutionResult, error) { // Get a new instance of the EVM. if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil { return nil, err } - msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks) + msg := args.ToMessage(header.BaseFee, true) // Lower the basefee to 0 to avoid breaking EVM // invariants (basefee < feecap). if msg.GasPrice.Sign() == 0 { @@ -858,7 +858,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil { return 0, err } - call := args.ToMessage(header.BaseFee, true, true) + call := args.ToMessage(header.BaseFee, true) // Run the gas estimation and wrap any revertals into a custom return estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap) @@ -1301,7 +1301,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH statedb := db.Copy() // Set the accesslist to the last al args.AccessList = &accessList - msg := args.ToMessage(header.BaseFee, true, true) + msg := args.ToMessage(header.BaseFee, true) // Apply the transaction with the access list tracer tracer := logger.NewAccessListTracer(accessList, addressesToExclude) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 362536bcb564..e26e5bd0e92c 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -287,7 +287,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, tracer.reset(txHash, uint(i)) sim.state.SetTxContext(txHash, i) // EoA check is always skipped, even in validation mode. - msg := call.ToMessage(header.BaseFee, !sim.validate, true) + msg := call.ToMessage(header.BaseFee, !sim.validate) result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp) if err != nil { txErr := txValidationError(err) diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index f80ef6d08039..23aa8e5947a6 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -443,7 +443,7 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, // core evm. This method is used in calls and traces that do not require a real // live transaction. // Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called. -func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoACheck bool) *core.Message { +func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck bool) *core.Message { var ( gasPrice *big.Int gasFeeCap *big.Int @@ -491,7 +491,7 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoA BlobHashes: args.BlobHashes, SetCodeAuthorizations: args.AuthorizationList, SkipNonceChecks: skipNonceCheck, - SkipFromEOACheck: skipEoACheck, + SkipTransactionChecks: true, } } From cfa209bb987602b6645959e2d7195934dd0bf61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Fri, 19 Sep 2025 14:07:10 +0200 Subject: [PATCH 031/228] beacon/blsync: test validated finality (#32633) This PR improves `TestBlockSync` so that it also tests the finality update validation. Note: to this date four long and complex (at least partly AI generated) PRs arrived that did something related to testing finality but honestly we do not need a bloated "comprehensive" test to test a trivial feature because maintaining these tests can also be a pain over the long term. This PR adds some sufficient sanity checks to detect if finality ever gets broken by a future change. --- beacon/blsync/block_sync_test.go | 57 +++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/beacon/blsync/block_sync_test.go b/beacon/blsync/block_sync_test.go index e7c2c4d16311..e4717667381a 100644 --- a/beacon/blsync/block_sync_test.go +++ b/beacon/blsync/block_sync_test.go @@ -32,7 +32,7 @@ var ( testServer2 = testServer("testServer2") testBlock1 = types.NewBeaconBlock(&deneb.BeaconBlock{ - Slot: 123, + Slot: 127, Body: deneb.BeaconBlockBody{ ExecutionPayload: deneb.ExecutionPayload{ BlockNumber: 456, @@ -41,7 +41,7 @@ var ( }, }) testBlock2 = types.NewBeaconBlock(&deneb.BeaconBlock{ - Slot: 124, + Slot: 128, Body: deneb.BeaconBlockBody{ ExecutionPayload: deneb.ExecutionPayload{ BlockNumber: 457, @@ -49,6 +49,14 @@ var ( }, }, }) + testFinal1 = types.NewExecutionHeader(&deneb.ExecutionPayloadHeader{ + BlockNumber: 395, + BlockHash: zrntcommon.Hash32(common.HexToHash("abbe7625624bf8ddd84723709e2758956289465dd23475f02387e0854942666")), + }) + testFinal2 = types.NewExecutionHeader(&deneb.ExecutionPayloadHeader{ + BlockNumber: 420, + BlockHash: zrntcommon.Hash32(common.HexToHash("9182a6ef8723654de174283750932ccc092378549836bf4873657eeec474598")), + }) ) type testServer string @@ -66,9 +74,10 @@ func TestBlockSync(t *testing.T) { ts.AddServer(testServer1, 1) ts.AddServer(testServer2, 1) - expHeadBlock := func(expHead *types.BeaconBlock) { + expHeadEvent := func(expHead *types.BeaconBlock, expFinal *types.ExecutionHeader) { t.Helper() var expNumber, headNumber uint64 + var expFinalHash, finalHash common.Hash if expHead != nil { p, err := expHead.ExecutionPayload() if err != nil { @@ -76,19 +85,26 @@ func TestBlockSync(t *testing.T) { } expNumber = p.NumberU64() } + if expFinal != nil { + expFinalHash = expFinal.BlockHash() + } select { case event := <-headCh: headNumber = event.Block.NumberU64() + finalHash = event.Finalized default: } if headNumber != expNumber { t.Errorf("Wrong head block, expected block number %d, got %d)", expNumber, headNumber) } + if finalHash != expFinalHash { + t.Errorf("Wrong finalized block, expected block hash %064x, got %064x)", expFinalHash[:], finalHash[:]) + } } // no block requests expected until head tracker knows about a head ts.Run(1) - expHeadBlock(nil) + expHeadEvent(nil, nil) // set block 1 as prefetch head, announced by server 2 head1 := blockHeadInfo(testBlock1) @@ -103,12 +119,13 @@ func TestBlockSync(t *testing.T) { ts.AddAllowance(testServer2, 1) ts.Run(3) // head block still not expected as the fetched block is not the validated head yet - expHeadBlock(nil) + expHeadEvent(nil, nil) // set as validated head, expect no further requests but block 1 set as head block ht.validated.Header = testBlock1.Header() + ht.finalized, ht.finalizedPayload = testBlock1.Header(), testFinal1 ts.Run(4) - expHeadBlock(testBlock1) + expHeadEvent(testBlock1, testFinal1) // set block 2 as prefetch head, announced by server 1 head2 := blockHeadInfo(testBlock2) @@ -126,17 +143,26 @@ func TestBlockSync(t *testing.T) { // expect req2 retry to server 2 ts.Run(7, testServer2, sync.ReqBeaconBlock(head2.BlockRoot)) // now head block should be unavailable again - expHeadBlock(nil) + expHeadEvent(nil, nil) // valid response, now head block should be block 2 immediately as it is already validated + // but head event is still not expected because an epoch boundary was crossed and the + // expected finality update has not arrived yet ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testBlock2) ts.Run(8) - expHeadBlock(testBlock2) + expHeadEvent(nil, nil) + + // expected finality update arrived, now a head event is expected + ht.finalized, ht.finalizedPayload = testBlock2.Header(), testFinal2 + ts.Run(9) + expHeadEvent(testBlock2, testFinal2) } type testHeadTracker struct { - prefetch types.HeadInfo - validated types.SignedHeader + prefetch types.HeadInfo + validated types.SignedHeader + finalized types.Header + finalizedPayload *types.ExecutionHeader } func (h *testHeadTracker) PrefetchHead() types.HeadInfo { @@ -151,13 +177,14 @@ func (h *testHeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) { }, h.validated.Header != (types.Header{}) } -// TODO add test case for finality func (h *testHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) { - finalized := types.NewExecutionHeader(new(deneb.ExecutionPayloadHeader)) + if h.validated.Header == (types.Header{}) || h.finalizedPayload == nil { + return types.FinalityUpdate{}, false + } return types.FinalityUpdate{ - Attested: types.HeaderWithExecProof{Header: h.validated.Header}, - Finalized: types.HeaderWithExecProof{PayloadHeader: finalized}, + Attested: types.HeaderWithExecProof{Header: h.finalized}, + Finalized: types.HeaderWithExecProof{Header: h.finalized, PayloadHeader: h.finalizedPayload}, Signature: h.validated.Signature, SignatureSlot: h.validated.SignatureSlot, - }, h.validated.Header != (types.Header{}) + }, true } From 0a946fa30400815d5db050edfa9725c6c591a869 Mon Sep 17 00:00:00 2001 From: Galoretka Date: Fri, 19 Sep 2025 16:13:32 +0300 Subject: [PATCH 032/228] core/state: add missing address key in state_object log (#32676) --- core/state/state_object.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 767f469bfde5..293875050313 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -333,7 +333,7 @@ func (s *stateObject) updateTrie() (Trie, error) { continue } if !exist { - log.Error("Storage slot is not found in pending area", s.address, "slot", key) + log.Error("Storage slot is not found in pending area", "address", s.address, "slot", key) continue } if (value != common.Hash{}) { From 1c01fae4392dd92ef22412db56209e6980061068 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 19 Sep 2025 15:18:42 +0200 Subject: [PATCH 033/228] crypto/bn256: switch to gnark again (#32659) We recently update our default implementation to gnark in https://github.com/ethereum/go-ethereum/pull/32024 Then we found a consensus issue and reverted it in https://github.com/ethereum/go-ethereum/commit/65d77c51295c3b5c237a082af856a6926766a51c We fixed the consensus issue and have been fuzzing it more since then in https://github.com/ethereum/go-ethereum/pull/32055/files https://github.com/ethereum/go-ethereum/pull/32065 https://github.com/ethereum/go-ethereum/pull/32055/files So I think now is the time to update it back to gnark --- crypto/bn256/bn256_fast.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crypto/bn256/bn256_fast.go b/crypto/bn256/bn256_fast.go index e3c9b605184f..93a2eef8798b 100644 --- a/crypto/bn256/bn256_fast.go +++ b/crypto/bn256/bn256_fast.go @@ -9,18 +9,18 @@ package bn256 import ( - bn256cf "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare" + gnark "github.com/ethereum/go-ethereum/crypto/bn256/gnark" ) // G1 is an abstract cyclic group. The zero value is suitable for use as the // output of an operation, but cannot be used as an input. -type G1 = bn256cf.G1 +type G1 = gnark.G1 // G2 is an abstract cyclic group. The zero value is suitable for use as the // output of an operation, but cannot be used as an input. -type G2 = bn256cf.G2 +type G2 = gnark.G2 // PairingCheck calculates the Optimal Ate pairing for a set of points. func PairingCheck(a []*G1, b []*G2) bool { - return bn256cf.PairingCheck(a, b) + return gnark.PairingCheck(a, b) } From fae2e9493dc55aa726470bd65ab628b0a9bf0513 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Fri, 19 Sep 2025 16:21:15 +0200 Subject: [PATCH 034/228] core/txpool/blobpool: remove conversion in GetBlobs (#32578) This disables blob proof conversion in `GetBlobs` by default, making it conditional. --------- Co-authored-by: Felix Lange --- core/txpool/blobpool/blobpool.go | 22 +++++++++++---- core/txpool/blobpool/blobpool_test.go | 40 ++++++++++++++++----------- eth/catalyst/api.go | 4 +-- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 55d24c7a9372..2229f1544ccc 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1331,7 +1331,11 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata { // // This is a utility method for the engine API, enabling consensus clients to // retrieve blobs from the pools directly instead of the network. -func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) { +// +// The version argument specifies the type of proofs to return, either the +// blob proofs (version 0) or the cell proofs (version 1). Proofs conversion is +// CPU intensive, so only done if explicitly requested with the convert flag. +func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte, convert bool) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) { var ( blobs = make([]*kzg4844.Blob, len(vhashes)) commitments = make([]kzg4844.Commitment, len(vhashes)) @@ -1343,13 +1347,14 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo for i, h := range vhashes { indices[h] = append(indices[h], i) } + for _, vhash := range vhashes { - // Skip duplicate vhash that was already resolved in a previous iteration if _, ok := filled[vhash]; ok { + // Skip vhash that was already resolved in a previous iteration continue } - // Retrieve the corresponding blob tx with the vhash, skip blob resolution - // if it's not found locally and place the null instead. + + // Retrieve the corresponding blob tx with the vhash. p.lock.RLock() txID, exists := p.lookup.storeidOfBlob(vhash) p.lock.RUnlock() @@ -1379,6 +1384,14 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo if !ok { continue // non-interesting blob } + // Mark hash as seen. + filled[hash] = struct{}{} + if sidecar.Version != version && !convert { + // Skip blobs with incompatible version. Note we still track the blob hash + // in `filled` here, ensuring that we do not resolve this tx another time. + continue + } + // Get or convert the proof. var pf []kzg4844.Proof switch version { case types.BlobSidecarVersion0: @@ -1411,7 +1424,6 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo commitments[index] = sidecar.Commitments[i] proofs[index] = pf } - filled[hash] = struct{}{} } } return blobs, commitments, proofs, nil diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index e46529a2415d..57d27962ce56 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -423,11 +423,11 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) { hashes = append(hashes, tx.vhashes...) } } - blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0) + blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0, false) if err != nil { t.Fatal(err) } - blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1) + blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1, false) if err != nil { t.Fatal(err) } @@ -441,22 +441,18 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) { return } for i, hash := range hashes { - // If an item is missing, but shouldn't, error - if blobs1[i] == nil || proofs1[i] == nil { - t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash) - continue - } - if blobs2[i] == nil || proofs2[i] == nil { + // If an item is missing from both, but shouldn't, error + if (blobs1[i] == nil || proofs1[i] == nil) && (blobs2[i] == nil || proofs2[i] == nil) { t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash) continue } // Item retrieved, make sure it matches the expectation index := testBlobIndices[hash] - if *blobs1[i] != *testBlobs[index] || proofs1[i][0] != testBlobProofs[index] { + if blobs1[i] != nil && (*blobs1[i] != *testBlobs[index] || proofs1[i][0] != testBlobProofs[index]) { t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash) continue } - if *blobs2[i] != *testBlobs[index] || !slices.Equal(proofs2[i], testBlobCellProofs[index]) { + if blobs2[i] != nil && (*blobs2[i] != *testBlobs[index] || !slices.Equal(proofs2[i], testBlobCellProofs[index])) { t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash) continue } @@ -1926,8 +1922,9 @@ func TestGetBlobs(t *testing.T) { cases := []struct { start int limit int - fillRandom bool - version byte + fillRandom bool // Whether to randomly fill some of the requested blobs with unknowns + version byte // Blob sidecar version to request + convert bool // Whether to convert version on retrieval }{ { start: 0, limit: 6, @@ -1993,6 +1990,11 @@ func TestGetBlobs(t *testing.T) { start: 0, limit: 18, fillRandom: true, version: types.BlobSidecarVersion1, }, + { + start: 0, limit: 18, fillRandom: true, + version: types.BlobSidecarVersion1, + convert: true, // Convert some version 0 blobs to version 1 while retrieving + }, } for i, c := range cases { var ( @@ -2014,7 +2016,7 @@ func TestGetBlobs(t *testing.T) { filled[len(vhashes)] = struct{}{} vhashes = append(vhashes, testrand.Hash()) } - blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version) + blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version, c.convert) if err != nil { t.Errorf("Unexpected error for case %d, %v", i, err) } @@ -2029,6 +2031,7 @@ func TestGetBlobs(t *testing.T) { var unknown int for j := 0; j < len(blobs); j++ { + testBlobIndex := c.start + j - unknown if _, exist := filled[j]; exist { if blobs[j] != nil || proofs[j] != nil { t.Errorf("Unexpected blob and proof, item %d", j) @@ -2038,17 +2041,22 @@ func TestGetBlobs(t *testing.T) { } // If an item is missing, but shouldn't, error if blobs[j] == nil || proofs[j] == nil { - t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j]) + // This is only an error if there was no version mismatch + if c.convert || + (c.version == types.BlobSidecarVersion1 && 6 <= testBlobIndex && testBlobIndex < 12) || + (c.version == types.BlobSidecarVersion0 && (testBlobIndex < 6 || 12 <= testBlobIndex)) { + t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j]) + } continue } // Item retrieved, make sure the blob matches the expectation - if *blobs[j] != *testBlobs[c.start+j-unknown] { + if *blobs[j] != *testBlobs[testBlobIndex] { t.Errorf("retrieved blob mismatch: item %d, hash %x", j, vhashes[j]) continue } // Item retrieved, make sure the proof matches the expectation if c.version == types.BlobSidecarVersion0 { - if proofs[j][0] != testBlobProofs[c.start+j-unknown] { + if proofs[j][0] != testBlobProofs[testBlobIndex] { t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j]) } } else { diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 17299d92963d..b37c26149f64 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -495,7 +495,7 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo if len(hashes) > 128 { return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) } - blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion0) + blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion0, false) if err != nil { return nil, engine.InvalidParams.With(err) } @@ -555,7 +555,7 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo return nil, nil } - blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1) + blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1, false) if err != nil { return nil, engine.InvalidParams.With(err) } From d08a2a67fe7bf41b7dcff2c1e824a89fe6a76a50 Mon Sep 17 00:00:00 2001 From: cui Date: Sat, 20 Sep 2025 06:38:36 +0800 Subject: [PATCH 035/228] p2p: using testing.B.Loop (#32664) --- p2p/discover/v5wire/encoding_test.go | 6 ++---- p2p/netutil/net_test.go | 2 +- p2p/rlpx/rlpx_test.go | 5 ++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/p2p/discover/v5wire/encoding_test.go b/p2p/discover/v5wire/encoding_test.go index 2304d0f13279..5774cb3d8c4c 100644 --- a/p2p/discover/v5wire/encoding_test.go +++ b/p2p/discover/v5wire/encoding_test.go @@ -477,10 +477,9 @@ func BenchmarkV5_DecodeHandshakePingSecp256k1(b *testing.B) { b.Fatal("can't encode handshake packet") } challenge.Node = nil // force ENR signature verification in decoder - b.ResetTimer() input := make([]byte, len(enc)) - for i := 0; i < b.N; i++ { + for b.Loop() { copy(input, enc) net.nodeB.c.sc.storeSentHandshake(idA, "", challenge) _, _, _, err := net.nodeB.c.Decode(input, "") @@ -507,10 +506,9 @@ func BenchmarkV5_DecodePing(b *testing.B) { if err != nil { b.Fatalf("can't encode: %v", err) } - b.ResetTimer() input := make([]byte, len(enc)) - for i := 0; i < b.N; i++ { + for b.Loop() { copy(input, enc) _, _, packet, _ := net.nodeB.c.Decode(input, addrB) if _, ok := packet.(*Ping); !ok { diff --git a/p2p/netutil/net_test.go b/p2p/netutil/net_test.go index 569c7ac45428..8c810f494f35 100644 --- a/p2p/netutil/net_test.go +++ b/p2p/netutil/net_test.go @@ -187,7 +187,7 @@ func TestCheckRelayIP(t *testing.T) { func BenchmarkCheckRelayIP(b *testing.B) { sender := parseIP("23.55.1.242") addr := parseIP("23.55.1.2") - for i := 0; i < b.N; i++ { + for b.Loop() { CheckRelayIP(sender, addr) } } diff --git a/p2p/rlpx/rlpx_test.go b/p2p/rlpx/rlpx_test.go index 27d51546e79c..a02c1dc2cca5 100644 --- a/p2p/rlpx/rlpx_test.go +++ b/p2p/rlpx/rlpx_test.go @@ -369,8 +369,7 @@ func TestHandshakeForwardCompatibility(t *testing.T) { func BenchmarkHandshakeRead(b *testing.B) { var input = unhex(eip8HandshakeAuthTests[0].input) - - for i := 0; i < b.N; i++ { + for b.Loop() { var ( h handshakeState r = bytes.NewReader(input) @@ -427,7 +426,7 @@ func BenchmarkThroughput(b *testing.B) { // Read N messages. b.SetBytes(int64(len(msgdata))) b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { _, _, _, err := conn2.Read() if err != nil { b.Fatal("read error:", err) From e4ce2f5f43f128158fbda003b9ed3658e3878317 Mon Sep 17 00:00:00 2001 From: cui Date: Sat, 20 Sep 2025 06:57:43 +0800 Subject: [PATCH 036/228] core/state: using testing.B.Loop (#32658) before: go test -run=^$ -bench=. ./core/state/... 120.85s user 7.96s system 129% cpu 1:39.13 tota after: go test -run=^$ -bench=. ./core/state/... 21.32s user 2.12s system 97% cpu 24.006 total --- core/state/snapshot/difflayer_test.go | 14 ++++---------- core/state/snapshot/iterator_test.go | 16 ++++++++-------- core/state/state_object_test.go | 6 +++--- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index b212098e758d..2c868b30106a 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -229,8 +229,7 @@ func BenchmarkSearch(b *testing.B) { layer = fill(layer) } key := crypto.Keccak256Hash([]byte{0x13, 0x38}) - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { layer.AccountRLP(key) } } @@ -269,8 +268,7 @@ func BenchmarkSearchSlot(b *testing.B) { for i := 0; i < 128; i++ { layer = fill(layer) } - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { layer.Storage(accountKey, storageKey) } } @@ -300,9 +298,7 @@ func BenchmarkFlatten(b *testing.B) { } return newDiffLayer(parent, common.Hash{}, accounts, storage) } - b.ResetTimer() - for i := 0; i < b.N; i++ { - b.StopTimer() + for b.Loop() { var layer snapshot layer = emptyLayer() for i := 1; i < 128; i++ { @@ -352,9 +348,7 @@ func BenchmarkJournal(b *testing.B) { for i := 1; i < 128; i++ { layer = fill(layer) } - b.ResetTimer() - - for i := 0; i < b.N; i++ { + for b.Loop() { layer.Journal(new(bytes.Buffer)) } } diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 2e882f484ec6..dd6c4cf9688d 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -928,7 +928,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { head.(*diffLayer).newBinaryAccountIterator(common.Hash{}) b.Run("binary iterator keys", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { got := 0 it := head.(*diffLayer).newBinaryAccountIterator(common.Hash{}) for it.Next() { @@ -940,7 +940,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { } }) b.Run("binary iterator values", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { got := 0 it := head.(*diffLayer).newBinaryAccountIterator(common.Hash{}) for it.Next() { @@ -953,7 +953,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { } }) b.Run("fast iterator keys", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) defer it.Release() @@ -967,7 +967,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { } }) b.Run("fast iterator values", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) defer it.Release() @@ -1025,7 +1025,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { head.(*diffLayer).newBinaryAccountIterator(common.Hash{}) b.Run("binary iterator (keys)", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { got := 0 it := head.(*diffLayer).newBinaryAccountIterator(common.Hash{}) for it.Next() { @@ -1037,7 +1037,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { } }) b.Run("binary iterator (values)", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { got := 0 it := head.(*diffLayer).newBinaryAccountIterator(common.Hash{}) for it.Next() { @@ -1051,7 +1051,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { } }) b.Run("fast iterator (keys)", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) defer it.Release() @@ -1065,7 +1065,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { } }) b.Run("fast iterator (values)", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) defer it.Release() diff --git a/core/state/state_object_test.go b/core/state/state_object_test.go index 42fd77802587..0237f0580d6a 100644 --- a/core/state/state_object_test.go +++ b/core/state/state_object_test.go @@ -25,7 +25,7 @@ import ( func BenchmarkCutOriginal(b *testing.B) { value := common.HexToHash("0x01") - for i := 0; i < b.N; i++ { + for b.Loop() { bytes.TrimLeft(value[:], "\x00") } } @@ -33,14 +33,14 @@ func BenchmarkCutOriginal(b *testing.B) { func BenchmarkCutsetterFn(b *testing.B) { value := common.HexToHash("0x01") cutSetFn := func(r rune) bool { return r == 0 } - for i := 0; i < b.N; i++ { + for b.Loop() { bytes.TrimLeftFunc(value[:], cutSetFn) } } func BenchmarkCutCustomTrim(b *testing.B) { value := common.HexToHash("0x01") - for i := 0; i < b.N; i++ { + for b.Loop() { common.TrimLeftZeroes(value[:]) } } From 759a61747a421ef8926f8d86915856f2e09f9623 Mon Sep 17 00:00:00 2001 From: cui Date: Sat, 20 Sep 2025 07:00:29 +0800 Subject: [PATCH 037/228] eth: using testing.B.Loop (#32657) before: go test -run=^$ -bench=. ./eth/... 827.57s user 23.80s system 361% cpu 3:55.49 total after: go test -run=^$ -bench=. ./eth/... 281.62s user 13.62s system 245% cpu 2:00.49 total --- eth/filters/filter_test.go | 5 +---- eth/protocols/snap/sync_test.go | 4 ++-- eth/tracers/internal/tracetest/calltrace_test.go | 4 +--- eth/tracers/internal/tracetest/flat_calltrace_test.go | 2 +- eth/tracers/tracers_test.go | 4 +--- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index e99b2f6caacd..edec3e027f94 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -109,11 +109,8 @@ func benchmarkFilters(b *testing.B, history uint64, noHistory bool) { backend.startFilterMaps(history, noHistory, filtermaps.DefaultParams) defer backend.stopFilterMaps() - b.ResetTimer() - filter := sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{addr1, addr2, addr3, addr4}, nil) - - for i := 0; i < b.N; i++ { + for b.Loop() { filter.begin = 0 logs, _ := filter.Logs(context.Background()) if len(logs) != 4 { diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index d599e7ecc325..89a782496f6f 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -106,13 +106,13 @@ func BenchmarkHashing(b *testing.B) { } b.Run("old", func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { old() } }) b.Run("new", func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { new() } }) diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index b45452297880..ba0706c5986d 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -211,11 +211,9 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { defer state.Close() b.ReportAllocs() - b.ResetTimer() evm := vm.NewEVM(context, state.StateDB, test.Genesis.Config, vm.Config{}) - - for i := 0; i < b.N; i++ { + for b.Loop() { snap := state.StateDB.Snapshot() tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil, test.Genesis.Config) if err != nil { diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index d1fa44e9d843..1882ef315e06 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -201,7 +201,7 @@ func BenchmarkFlatCallTracer(b *testing.B) { for _, file := range files { filename := strings.TrimPrefix(file, "testdata/call_tracer_flat/") b.Run(camel(strings.TrimSuffix(filename, ".json")), func(b *testing.B) { - for n := 0; n < b.N; n++ { + for b.Loop() { err := flatCallTracerTestRunner("flatCallTracer", filename, "call_tracer_flat", b) if err != nil { b.Fatal(err) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index a72dbf6ee6ce..06edeaf6986e 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -84,10 +84,8 @@ func BenchmarkTransactionTraceV2(b *testing.B) { if err != nil { b.Fatalf("failed to prepare transaction for tracing: %v", err) } - b.ResetTimer() b.ReportAllocs() - - for i := 0; i < b.N; i++ { + for b.Loop() { tracer := logger.NewStructLogger(&logger.Config{}).Hooks() tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) evm.Config.Tracer = tracer From bb1ca1ccef3bd0327179defe1bcf71feb0ad7ccf Mon Sep 17 00:00:00 2001 From: cui Date: Sat, 20 Sep 2025 07:05:21 +0800 Subject: [PATCH 038/228] log: using testing.B.Loop (#32663) before: go test -run=^$ -bench=. ./log -timeout=1h 12.19s user 2.19s system 89% cpu 16.025 total after: go test -run=^$ -bench=. ./log -timeout=1h 10.64s user 1.53s system 89% cpu 13.607 total --------- Co-authored-by: lightclient --- log/format_test.go | 4 ++-- log/logger_test.go | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/log/format_test.go b/log/format_test.go index d4c1df4abcf9..bb740ceb8488 100644 --- a/log/format_test.go +++ b/log/format_test.go @@ -10,7 +10,7 @@ var sink []byte func BenchmarkPrettyInt64Logfmt(b *testing.B) { buf := make([]byte, 100) b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { sink = appendInt64(buf, rand.Int63()) } } @@ -18,7 +18,7 @@ func BenchmarkPrettyInt64Logfmt(b *testing.B) { func BenchmarkPrettyUint64Logfmt(b *testing.B) { buf := make([]byte, 100) b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { sink = appendUint64(buf, rand.Uint64(), false) } } diff --git a/log/logger_test.go b/log/logger_test.go index 3ec6d2e19c81..dae84972048a 100644 --- a/log/logger_test.go +++ b/log/logger_test.go @@ -70,9 +70,10 @@ func TestJSONHandler(t *testing.T) { func BenchmarkTraceLogging(b *testing.B) { SetDefault(NewLogger(NewTerminalHandler(io.Discard, true))) - b.ResetTimer() - for i := 0; i < b.N; i++ { + i := 0 + for b.Loop() { Trace("a message", "v", i) + i++ } } @@ -99,8 +100,8 @@ func benchmarkLogger(b *testing.B, l Logger) { err = errors.New("oh nooes it's crap") ) b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { + i := 0 + for b.Loop() { l.Info("This is a message", "foo", int16(i), "bytes", bb, @@ -109,8 +110,8 @@ func benchmarkLogger(b *testing.B, l Logger) { "bigint", bigint, "nilbig", nilbig, "err", err) + i++ } - b.StopTimer() } func TestLoggerOutput(t *testing.T) { @@ -161,18 +162,18 @@ const termTimeFormat = "01-02|15:04:05.000" func BenchmarkAppendFormat(b *testing.B) { var now = time.Now() b.Run("fmt time.Format", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { fmt.Fprintf(io.Discard, "%s", now.Format(termTimeFormat)) } }) b.Run("time.AppendFormat", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { now.AppendFormat(nil, termTimeFormat) } }) var buf = new(bytes.Buffer) b.Run("time.Custom", func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { writeTimeTermFormat(buf, now) buf.Reset() } From d6cb8a90c5593f84e6a65bd492d6ca55484f69a7 Mon Sep 17 00:00:00 2001 From: cui Date: Sat, 20 Sep 2025 07:06:27 +0800 Subject: [PATCH 039/228] core: using testing.B.Loop (#32662) Co-authored-by: lightclient --- core/bench_test.go | 6 ++---- core/rlp_test.go | 9 +++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/core/bench_test.go b/core/bench_test.go index 283002266209..932188f8e203 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -301,7 +301,7 @@ func makeChainForBench(db ethdb.Database, genesis *Genesis, full bool, count uin func benchWriteChain(b *testing.B, full bool, count uint64) { genesis := &Genesis{Config: params.AllEthashProtocolChanges} - for i := 0; i < b.N; i++ { + for b.Loop() { pdb, err := pebble.New(b.TempDir(), 1024, 128, "", false) if err != nil { b.Fatalf("error opening database: %v", err) @@ -326,9 +326,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) { db.Close() options := DefaultConfig().WithArchive(true) b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { + for b.Loop() { pdb, err = pebble.New(dir, 1024, 128, "", false) if err != nil { b.Fatalf("error opening database: %v", err) diff --git a/core/rlp_test.go b/core/rlp_test.go index bc37408537a3..69efa8255183 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -149,8 +149,7 @@ func BenchmarkHashing(b *testing.B) { var got common.Hash var hasher = sha3.NewLegacyKeccak256() b.Run("iteratorhashing", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { var hash common.Hash it, err := rlp.NewListIterator(bodyRlp) if err != nil { @@ -172,8 +171,7 @@ func BenchmarkHashing(b *testing.B) { }) var exp common.Hash b.Run("fullbodyhashing", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { var body types.Body rlp.DecodeBytes(bodyRlp, &body) for _, tx := range body.Transactions { @@ -182,8 +180,7 @@ func BenchmarkHashing(b *testing.B) { } }) b.Run("fullblockhashing", func(b *testing.B) { - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { var block types.Block rlp.DecodeBytes(blockRlp, &block) for _, tx := range block.Transactions() { From b94e1a6213b2ff0d592c3e9834f46ab9a02118c2 Mon Sep 17 00:00:00 2001 From: cui Date: Sat, 20 Sep 2025 07:06:55 +0800 Subject: [PATCH 040/228] core/vm: using testing.B.Loop (#32660) before: go test -run=^$ -bench=. ./core/vm/... -timeout=1h 1841.87s user 40.96s system 124% cpu 25:15.76 total after: go test -run=^$ -bench=. ./core/vm/... -timeout=1h 1588.65s user 33.79s system 123% cpu 21:53.25 total --------- Co-authored-by: lightclient --- core/vm/analysis_legacy_test.go | 11 +++-------- core/vm/contracts_test.go | 4 +--- core/vm/instructions_test.go | 10 +++------- core/vm/interpreter_test.go | 3 +-- core/vm/runtime/runtime_test.go | 15 +++++---------- 5 files changed, 13 insertions(+), 30 deletions(-) diff --git a/core/vm/analysis_legacy_test.go b/core/vm/analysis_legacy_test.go index f84a4abc9262..75d8a495fa11 100644 --- a/core/vm/analysis_legacy_test.go +++ b/core/vm/analysis_legacy_test.go @@ -65,21 +65,17 @@ func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) { // 1.4 ms code := make([]byte, analysisCodeSize) bench.SetBytes(analysisCodeSize) - bench.ResetTimer() - for i := 0; i < bench.N; i++ { + for bench.Loop() { codeBitmap(code) } - bench.StopTimer() } func BenchmarkJumpdestHashing_1200k(bench *testing.B) { // 4 ms code := make([]byte, analysisCodeSize) bench.SetBytes(analysisCodeSize) - bench.ResetTimer() - for i := 0; i < bench.N; i++ { + for bench.Loop() { crypto.Keccak256Hash(code) } - bench.StopTimer() } func BenchmarkJumpdestOpAnalysis(bench *testing.B) { @@ -91,8 +87,7 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) { code[i] = byte(op) } bits := make(BitVec, len(code)/8+1+4) - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { clear(bits) codeBitmapInternal(code, bits) } diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 3200ace5cc27..51bd7101ff4b 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -167,12 +167,10 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) { bench.ReportAllocs() start := time.Now() - bench.ResetTimer() - for i := 0; i < bench.N; i++ { + for bench.Loop() { copy(data, in) res, _, err = RunPrecompiledContract(p, data, reqGas, nil) } - bench.StopTimer() elapsed := uint64(time.Since(start)) if elapsed < 1 { elapsed = 1 diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index cd31829a7ec7..72f561f4bf3f 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -291,15 +291,13 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { intArgs[i] = new(uint256.Int).SetBytes(common.Hex2Bytes(arg)) } pc := uint64(0) - bench.ResetTimer() - for i := 0; i < bench.N; i++ { + for bench.Loop() { for _, arg := range intArgs { stack.push(arg) } op(&pc, evm, scope) stack.pop() } - bench.StopTimer() for i, arg := range args { want := new(uint256.Int).SetBytes(common.Hex2Bytes(arg)) @@ -551,8 +549,7 @@ func BenchmarkOpMstore(bench *testing.B) { memStart := new(uint256.Int) value := new(uint256.Int).SetUint64(0x1337) - bench.ResetTimer() - for i := 0; i < bench.N; i++ { + for bench.Loop() { stack.push(value) stack.push(memStart) opMstore(&pc, evm, &ScopeContext{mem, stack, nil}) @@ -609,8 +606,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { pc := uint64(0) start := new(uint256.Int) - bench.ResetTimer() - for i := 0; i < bench.N; i++ { + for bench.Loop() { stack.push(uint256.NewInt(32)) stack.push(start) opKeccak256(&pc, evm, &ScopeContext{mem, stack, nil}) diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go index 90eeda34e672..79531f78d288 100644 --- a/core/vm/interpreter_test.go +++ b/core/vm/interpreter_test.go @@ -90,8 +90,7 @@ func BenchmarkInterpreter(b *testing.B) { stack.push(uint256.NewInt(123)) stack.push(uint256.NewInt(123)) gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { gasSStoreEIP3529(evm, contract, stack, mem, 1234) } } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index cabc57d1fbab..ddd32df039c3 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -150,8 +150,7 @@ func BenchmarkCall(b *testing.B) { b.Fatal(err) } - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { for j := 0; j < 400; j++ { Execute(code, cpurchase, nil) Execute(code, creceived, nil) @@ -190,11 +189,9 @@ func benchmarkEVM_Create(bench *testing.B, code string) { EVMConfig: vm.Config{}, } // Warm up the intpools and stuff - bench.ResetTimer() - for i := 0; i < bench.N; i++ { + for bench.Loop() { Call(receiver, []byte{}, &runtimeConfig) } - bench.StopTimer() } func BenchmarkEVM_CREATE_500(bench *testing.B) { @@ -233,8 +230,7 @@ func BenchmarkEVM_SWAP1(b *testing.B) { b.Run("10k", func(b *testing.B) { contractCode := swapContract(10_000) state.SetCode(contractAddr, contractCode, tracing.CodeChangeUnspecified) - - for i := 0; i < b.N; i++ { + for b.Loop() { _, _, err := Call(contractAddr, []byte{}, &Config{State: state}) if err != nil { b.Fatal(err) @@ -264,8 +260,7 @@ func BenchmarkEVM_RETURN(b *testing.B) { contractCode := returnContract(n) state.SetCode(contractAddr, contractCode, tracing.CodeChangeUnspecified) - - for i := 0; i < b.N; i++ { + for b.Loop() { ret, _, err := Call(contractAddr, []byte{}, &Config{State: state}) if err != nil { b.Fatal(err) @@ -432,7 +427,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode b.Run(name, func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { Call(destination, nil, cfg) } }) From 5765c31e2131bc02f54d56dbccc8336358a8bd38 Mon Sep 17 00:00:00 2001 From: cui Date: Sat, 20 Sep 2025 07:11:32 +0800 Subject: [PATCH 041/228] core/types: using testing.B.Loop (#32643) before: go test -run=^$ -bench=. ./core/types 47.80s user 2.18s system 102% cpu 48.936 tota after: go test -run=^$ -bench=. ./core/types 42.42s user 2.27s system 112% cpu 39.593 total --- core/types/block_test.go | 3 +-- core/types/bloom9_test.go | 16 ++++++---------- core/types/hashing_test.go | 5 ++--- core/types/transaction_test.go | 8 ++++---- core/types/types_test.go | 4 ++-- 5 files changed, 15 insertions(+), 21 deletions(-) diff --git a/core/types/block_test.go b/core/types/block_test.go index 2130a2fcf3b2..5fa4756a50e0 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -263,9 +263,8 @@ var benchBuffer = bytes.NewBuffer(make([]byte, 0, 32000)) func BenchmarkEncodeBlock(b *testing.B) { block := makeBenchBlock() - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { benchBuffer.Reset() if err := rlp.Encode(benchBuffer, block); err != nil { b.Fatal(err) diff --git a/core/types/bloom9_test.go b/core/types/bloom9_test.go index 07f6446a978a..ceb6797e1feb 100644 --- a/core/types/bloom9_test.go +++ b/core/types/bloom9_test.go @@ -78,7 +78,7 @@ func TestBloomExtensively(t *testing.T) { func BenchmarkBloom9(b *testing.B) { test := []byte("testestestest") - for i := 0; i < b.N; i++ { + for b.Loop() { Bloom9(test) } } @@ -86,7 +86,7 @@ func BenchmarkBloom9(b *testing.B) { func BenchmarkBloom9Lookup(b *testing.B) { toTest := []byte("testtest") bloom := new(Bloom) - for i := 0; i < b.N; i++ { + for b.Loop() { bloom.Test(toTest) } } @@ -128,7 +128,7 @@ func BenchmarkCreateBloom(b *testing.B) { } b.Run("small-createbloom", func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { for _, receipt := range rSmall { receipt.Bloom = CreateBloom(receipt) } @@ -144,7 +144,7 @@ func BenchmarkCreateBloom(b *testing.B) { }) b.Run("large-createbloom", func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { for _, receipt := range rLarge { receipt.Bloom = CreateBloom(receipt) } @@ -163,13 +163,11 @@ func BenchmarkCreateBloom(b *testing.B) { receipt.Bloom = CreateBloom(receipt) } b.ReportAllocs() - b.ResetTimer() var bl Bloom - for i := 0; i < b.N; i++ { + for b.Loop() { bl = MergeBloom(rSmall) } - b.StopTimer() var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") got := crypto.Keccak256Hash(bl.Bytes()) @@ -182,13 +180,11 @@ func BenchmarkCreateBloom(b *testing.B) { receipt.Bloom = CreateBloom(receipt) } b.ReportAllocs() - b.ResetTimer() var bl Bloom - for i := 0; i < b.N; i++ { + for b.Loop() { bl = MergeBloom(rLarge) } - b.StopTimer() var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949") got := crypto.Keccak256Hash(bl.Bytes()) diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go index c846ecd0c53d..54adbc73e8d0 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -84,9 +84,8 @@ func BenchmarkDeriveSha200(b *testing.B) { var exp common.Hash var got common.Hash b.Run("std_trie", func(b *testing.B) { - b.ResetTimer() b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) } }) @@ -94,7 +93,7 @@ func BenchmarkDeriveSha200(b *testing.B) { b.Run("stack_trie", func(b *testing.B) { b.ResetTimer() b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { got = types.DeriveSha(txs, trie.NewStackTrie(nil)) } }) diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index cc41674dfdb1..2d854ae345fe 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -591,7 +591,7 @@ func BenchmarkHash(b *testing.B) { GasTipCap: big.NewInt(500), GasFeeCap: big.NewInt(500), }) - for i := 0; i < b.N; i++ { + for b.Loop() { signer.Hash(tx) } } @@ -614,7 +614,7 @@ func BenchmarkEffectiveGasTip(b *testing.B) { b.Run("Original", func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { _, err := tx.EffectiveGasTip(baseFee.ToBig()) if err != nil { b.Fatal(err) @@ -625,7 +625,7 @@ func BenchmarkEffectiveGasTip(b *testing.B) { b.Run("IntoMethod", func(b *testing.B) { b.ReportAllocs() dst := new(uint256.Int) - for i := 0; i < b.N; i++ { + for b.Loop() { err := tx.calcEffectiveGasTip(dst, baseFee) if err != nil { b.Fatal(err) @@ -729,7 +729,7 @@ func BenchmarkEffectiveGasTipCmp(b *testing.B) { b.Run("Original", func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { tx.EffectiveGasTipCmp(other, baseFee) } }) diff --git a/core/types/types_test.go b/core/types/types_test.go index 1fb386d5deef..96d044499473 100644 --- a/core/types/types_test.go +++ b/core/types/types_test.go @@ -126,7 +126,7 @@ func benchRLP(b *testing.B, encode bool) { b.Run(tc.name, func(b *testing.B) { b.ReportAllocs() var null = &devnull{} - for i := 0; i < b.N; i++ { + for b.Loop() { rlp.Encode(null, tc.obj) } b.SetBytes(int64(null.len / b.N)) @@ -136,7 +136,7 @@ func benchRLP(b *testing.B, encode bool) { // Test decoding b.Run(tc.name, func(b *testing.B) { b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { if err := rlp.DecodeBytes(data, tc.obj); err != nil { b.Fatal(err) } From b6866b775f0d8b0d3fd661237e2c10711afd96ea Mon Sep 17 00:00:00 2001 From: cui Date: Sat, 20 Sep 2025 07:12:41 +0800 Subject: [PATCH 042/228] crypto: using testing.B.Loop (#32645) before: go test -run=^$ -bench=. ./crypto/... 94.83s user 2.68s system 138% cpu 1:10.55 tota after: go test -run=^$ -bench=. ./crypto/... 75.43s user 2.58s system 123% cpu 1:03.01 total --- crypto/blake2b/blake2b_test.go | 6 ++---- crypto/crypto_test.go | 6 +++--- crypto/ecies/ecies_test.go | 8 +++----- crypto/kzg4844/kzg4844_test.go | 15 +++++---------- crypto/secp256k1/secp256_test.go | 8 ++------ crypto/signature_test.go | 6 +++--- 6 files changed, 18 insertions(+), 31 deletions(-) diff --git a/crypto/blake2b/blake2b_test.go b/crypto/blake2b/blake2b_test.go index 9d24444a27b7..8f210445fc0d 100644 --- a/crypto/blake2b/blake2b_test.go +++ b/crypto/blake2b/blake2b_test.go @@ -303,8 +303,7 @@ func benchmarkSum(b *testing.B, size int, sse4, avx, avx2 bool) { data := make([]byte, size) b.SetBytes(int64(size)) - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { Sum512(data) } } @@ -319,8 +318,7 @@ func benchmarkWrite(b *testing.B, size int, sse4, avx, avx2 bool) { data := make([]byte, size) h, _ := New512(nil) b.SetBytes(int64(size)) - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { h.Write(data) } } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index e620d6ee3a12..d803ab27c5d9 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -60,7 +60,7 @@ func TestToECDSAErrors(t *testing.T) { func BenchmarkSha3(b *testing.B) { a := []byte("hello world") - for i := 0; i < b.N; i++ { + for b.Loop() { Keccak256(a) } } @@ -310,7 +310,7 @@ func BenchmarkKeccak256Hash(b *testing.B) { rand.Read(input[:]) b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { Keccak256Hash(input[:]) } } @@ -329,7 +329,7 @@ func BenchmarkHashData(b *testing.B) { rand.Read(input[:]) b.ReportAllocs() - for i := 0; i < b.N; i++ { + for b.Loop() { HashData(buffer, input[:]) } } diff --git a/crypto/ecies/ecies_test.go b/crypto/ecies/ecies_test.go index e3da71010edd..e822db278343 100644 --- a/crypto/ecies/ecies_test.go +++ b/crypto/ecies/ecies_test.go @@ -164,7 +164,7 @@ func TestTooBigSharedKey(t *testing.T) { // Benchmark the generation of P256 keys. func BenchmarkGenerateKeyP256(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { if _, err := GenerateKey(rand.Reader, elliptic.P256(), nil); err != nil { b.Fatal(err) } @@ -177,8 +177,7 @@ func BenchmarkGenSharedKeyP256(b *testing.B) { if err != nil { b.Fatal(err) } - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) if err != nil { b.Fatal(err) @@ -192,8 +191,7 @@ func BenchmarkGenSharedKeyS256(b *testing.B) { if err != nil { b.Fatal(err) } - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { _, err := prv.GenerateShared(&prv.PublicKey, 16, 16) if err != nil { b.Fatal(err) diff --git a/crypto/kzg4844/kzg4844_test.go b/crypto/kzg4844/kzg4844_test.go index 7e73efd85089..d398a0f48b94 100644 --- a/crypto/kzg4844/kzg4844_test.go +++ b/crypto/kzg4844/kzg4844_test.go @@ -105,8 +105,7 @@ func benchmarkBlobToCommitment(b *testing.B, ckzg bool) { blob := randBlob() - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { BlobToCommitment(blob) } } @@ -125,8 +124,7 @@ func benchmarkComputeProof(b *testing.B, ckzg bool) { point = randFieldElement() ) - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { ComputeProof(blob, point) } } @@ -147,8 +145,7 @@ func benchmarkVerifyProof(b *testing.B, ckzg bool) { proof, claim, _ = ComputeProof(blob, point) ) - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { VerifyProof(commitment, point, claim, proof) } } @@ -167,8 +164,7 @@ func benchmarkComputeBlobProof(b *testing.B, ckzg bool) { commitment, _ = BlobToCommitment(blob) ) - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { ComputeBlobProof(blob, commitment) } } @@ -188,8 +184,7 @@ func benchmarkVerifyBlobProof(b *testing.B, ckzg bool) { proof, _ = ComputeBlobProof(blob, commitment) ) - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { VerifyBlobProof(blob, commitment, proof) } } diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index 4827cc5b255c..c7485bca0813 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -221,9 +221,7 @@ func TestRecoverSanity(t *testing.T) { func BenchmarkSign(b *testing.B) { _, seckey := generateKeyPair() msg := csprngEntropy(32) - b.ResetTimer() - - for i := 0; i < b.N; i++ { + for b.Loop() { Sign(msg, seckey) } } @@ -232,9 +230,7 @@ func BenchmarkRecover(b *testing.B) { msg := csprngEntropy(32) _, seckey := generateKeyPair() sig, _ := Sign(msg, seckey) - b.ResetTimer() - - for i := 0; i < b.N; i++ { + for b.Loop() { RecoverPubkey(msg, sig) } } diff --git a/crypto/signature_test.go b/crypto/signature_test.go index 74d683b50758..b1f2960254c2 100644 --- a/crypto/signature_test.go +++ b/crypto/signature_test.go @@ -135,7 +135,7 @@ func TestPubkeyRandom(t *testing.T) { } func BenchmarkEcrecoverSignature(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { if _, err := Ecrecover(testmsg, testsig); err != nil { b.Fatal("ecrecover error", err) } @@ -144,7 +144,7 @@ func BenchmarkEcrecoverSignature(b *testing.B) { func BenchmarkVerifySignature(b *testing.B) { sig := testsig[:len(testsig)-1] // remove recovery id - for i := 0; i < b.N; i++ { + for b.Loop() { if !VerifySignature(testpubkey, testmsg, sig) { b.Fatal("verify error") } @@ -152,7 +152,7 @@ func BenchmarkVerifySignature(b *testing.B) { } func BenchmarkDecompressPubkey(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { if _, err := DecompressPubkey(testpubkeyc); err != nil { b.Fatal(err) } From 7915b7dff942f675d39d6fe8dbad9b4c08b5d8af Mon Sep 17 00:00:00 2001 From: Gengar Date: Sat, 20 Sep 2025 02:13:58 +0300 Subject: [PATCH 043/228] fix: correct typo in TestMustParseUint64Panic error message (#32648) Fix typo in test error message where "MustParseBig" was incorrectly used instead of "MustParseUint64" in the TestMustParseUint64Panic function. The test still functions correctly, but now the error message accurately reflects the function being tested. --- common/math/integer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/math/integer_test.go b/common/math/integer_test.go index 4643a43f20f3..feb761c6dc70 100644 --- a/common/math/integer_test.go +++ b/common/math/integer_test.go @@ -110,7 +110,7 @@ func TestMustParseUint64(t *testing.T) { func TestMustParseUint64Panic(t *testing.T) { defer func() { if recover() == nil { - t.Error("MustParseBig should've panicked") + t.Error("MustParseUint64 should've panicked") } }() MustParseUint64("ggg") From 18bb7b45c871443d2b70c64e41654ca7a56e64fa Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 20 Sep 2025 07:20:44 +0800 Subject: [PATCH 044/228] common, eth: remove duplicate test cases (#32624) Remove redundant duplicate test vectors. The two entries were identical and back-to-back, providing no additional coverage while adding noise. Keeping a single instance maintains test intent and clarity without altering behavior. --- common/hexutil/json_test.go | 1 - eth/protocols/snap/gentrie_test.go | 2 -- eth/protocols/snap/sync_test.go | 1 - 3 files changed, 4 deletions(-) diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index a0144384585b..3dc19680f984 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -408,7 +408,6 @@ func TestUnmarshalFixedUnprefixedText(t *testing.T) { {input: "0x2", wantErr: ErrOddLength}, {input: "2", wantErr: ErrOddLength}, {input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")}, - {input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")}, // check that output is not modified for partially correct input {input: "444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}}, {input: "0x444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}}, diff --git a/eth/protocols/snap/gentrie_test.go b/eth/protocols/snap/gentrie_test.go index 2da4f3c866e6..5998840b5202 100644 --- a/eth/protocols/snap/gentrie_test.go +++ b/eth/protocols/snap/gentrie_test.go @@ -239,7 +239,6 @@ func TestPartialGentree(t *testing.T) { {1, len(entries) - 1}, // no left {2, len(entries) - 1}, // no left {2, len(entries) - 2}, // no left and right - {2, len(entries) - 2}, // no left and right {len(entries) / 2, len(entries) / 2}, // single {0, 0}, // single first {len(entries) - 1, len(entries) - 1}, // single last @@ -348,7 +347,6 @@ func TestGentreeDanglingClearing(t *testing.T) { {1, len(entries) - 1}, // no left {2, len(entries) - 1}, // no left {2, len(entries) - 2}, // no left and right - {2, len(entries) - 2}, // no left and right {len(entries) / 2, len(entries) / 2}, // single {0, 0}, // single first {len(entries) - 1, len(entries) - 1}, // single last diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 89a782496f6f..713b358ff8f9 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -597,7 +597,6 @@ func testSyncBloatedProof(t *testing.T, scheme string) { proof := trienode.NewProofSet() if err := t.accountTrie.Prove(origin[:], proof); err != nil { t.logger.Error("Could not prove origin", "origin", origin, "error", err) - t.logger.Error("Could not prove origin", "origin", origin, "error", err) } // The bloat: add proof of every single element for _, entry := range t.accountValues { From 1c3e5fd1f0266b1b5b5473a4a0aac525e39cdfd2 Mon Sep 17 00:00:00 2001 From: MozirDmitriy Date: Sat, 20 Sep 2025 02:23:50 +0300 Subject: [PATCH 045/228] =?UTF-8?q?core/rawdb:=20fix=20bad=20blocks=20sort?= =?UTF-8?q?ed=20failure=20message=20to=20map=20index=E2=86=92number=20corr?= =?UTF-8?q?ectly=20(#32627)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the t.Fatalf format arguments in TestBadBlockStorage to match the intended #index output. Previously, the left number used i+1 and the right index used the block number, producing misleading diagnostics. Correct mapping improves test failure clarity and debuggability. --- core/rawdb/accessors_chain_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 5ed65b69b54a..819788b4dae2 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -246,7 +246,7 @@ func TestBadBlockStorage(t *testing.T) { } for i := 0; i < len(badBlocks)-1; i++ { if badBlocks[i].NumberU64() < badBlocks[i+1].NumberU64() { - t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, i+1, badBlocks[i].NumberU64(), badBlocks[i+1].NumberU64()) + t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, badBlocks[i].NumberU64(), i+1, badBlocks[i+1].NumberU64()) } } From fde18adced63be2b169c66e0cdff9eed70871213 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:45:11 -0600 Subject: [PATCH 046/228] params: update config description links to new format (#32681) A PR in the specs repo broke our existing links: https://github.com/ethereum/execution-specs/pull/1416 This PR fixes it and adds the Prague and Osaka specs. --- params/config.go | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/params/config.go b/params/config.go index f8fc35454cb0..cf94fb67586c 100644 --- a/params/config.go +++ b/params/config.go @@ -509,33 +509,33 @@ func (c *ChainConfig) Description() string { // makes sense for mainnet should be optional at printing to avoid bloating // the output for testnets and private networks. banner += "Pre-Merge hard forks (block based):\n" - banner += fmt.Sprintf(" - Homestead: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/homestead.md)\n", c.HomesteadBlock) + banner += fmt.Sprintf(" - Homestead: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/homestead/__init__.py.html)\n", c.HomesteadBlock) if c.DAOForkBlock != nil { - banner += fmt.Sprintf(" - DAO Fork: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/dao-fork.md)\n", c.DAOForkBlock) - } - banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/tangerine-whistle.md)\n", c.EIP150Block) - banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/spurious-dragon.md)\n", c.EIP155Block) - banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/spurious-dragon.md)\n", c.EIP155Block) - banner += fmt.Sprintf(" - Byzantium: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/byzantium.md)\n", c.ByzantiumBlock) - banner += fmt.Sprintf(" - Constantinople: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/constantinople.md)\n", c.ConstantinopleBlock) - banner += fmt.Sprintf(" - Petersburg: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/petersburg.md)\n", c.PetersburgBlock) - banner += fmt.Sprintf(" - Istanbul: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/istanbul.md)\n", c.IstanbulBlock) + banner += fmt.Sprintf(" - DAO Fork: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/dao_fork/__init__.py.html)\n", c.DAOForkBlock) + } + banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/tangerine_whistle/__init__.py.html)\n", c.EIP150Block) + banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/spurious_dragon/__init__.py.html)\n", c.EIP155Block) + banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/spurious_dragon/__init__.py.html)\n", c.EIP155Block) + banner += fmt.Sprintf(" - Byzantium: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/byzantium/__init__.py.html)\n", c.ByzantiumBlock) + banner += fmt.Sprintf(" - Constantinople: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/constantinople/__init__.py.html)\n", c.ConstantinopleBlock) + banner += fmt.Sprintf(" - Petersburg: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/constantinople/__init__.py.html)\n", c.PetersburgBlock) + banner += fmt.Sprintf(" - Istanbul: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/istanbul/__init__.py.html)\n", c.IstanbulBlock) if c.MuirGlacierBlock != nil { - banner += fmt.Sprintf(" - Muir Glacier: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/muir-glacier.md)\n", c.MuirGlacierBlock) + banner += fmt.Sprintf(" - Muir Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/muir_glacier/__init__.py.html)\n", c.MuirGlacierBlock) } - banner += fmt.Sprintf(" - Berlin: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/berlin.md)\n", c.BerlinBlock) - banner += fmt.Sprintf(" - London: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/london.md)\n", c.LondonBlock) + banner += fmt.Sprintf(" - Berlin: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/berlin/__init__.py.html)\n", c.BerlinBlock) + banner += fmt.Sprintf(" - London: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/london/__init__.py.html)\n", c.LondonBlock) if c.ArrowGlacierBlock != nil { - banner += fmt.Sprintf(" - Arrow Glacier: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/arrow-glacier.md)\n", c.ArrowGlacierBlock) + banner += fmt.Sprintf(" - Arrow Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/arrow_glacier/__init__.py.html)\n", c.ArrowGlacierBlock) } if c.GrayGlacierBlock != nil { - banner += fmt.Sprintf(" - Gray Glacier: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/gray-glacier.md)\n", c.GrayGlacierBlock) + banner += fmt.Sprintf(" - Gray Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/gray_glacier/__init__.py.html)\n", c.GrayGlacierBlock) } banner += "\n" // Add a special section for the merge as it's non-obvious banner += "Merge configured:\n" - banner += " - Hard-fork specification: https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/paris.md\n" + banner += " - Hard-fork specification: https://ethereum.github.io/execution-specs/src/ethereum/forks/paris/__init__.py.html\n" banner += " - Network known to be merged\n" banner += fmt.Sprintf(" - Total terminal difficulty: %v\n", c.TerminalTotalDifficulty) if c.MergeNetsplitBlock != nil { @@ -546,16 +546,16 @@ func (c *ChainConfig) Description() string { // Create a list of forks post-merge banner += "Post-Merge hard forks (timestamp based):\n" if c.ShanghaiTime != nil { - banner += fmt.Sprintf(" - Shanghai: @%-10v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md)\n", *c.ShanghaiTime) + banner += fmt.Sprintf(" - Shanghai: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/shanghai/__init__.py.html)\n", *c.ShanghaiTime) } if c.CancunTime != nil { - banner += fmt.Sprintf(" - Cancun: @%-10v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md)\n", *c.CancunTime) + banner += fmt.Sprintf(" - Cancun: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/cancun/__init__.py.html)\n", *c.CancunTime) } if c.PragueTime != nil { - banner += fmt.Sprintf(" - Prague: @%-10v\n", *c.PragueTime) + banner += fmt.Sprintf(" - Prague: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/prague/__init__.py.html)\n", *c.PragueTime) } if c.OsakaTime != nil { - banner += fmt.Sprintf(" - Osaka: @%-10v\n", *c.OsakaTime) + banner += fmt.Sprintf(" - Osaka: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/osaka/__init__.py.html)\n", *c.OsakaTime) } if c.VerkleTime != nil { banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime) From 066a79bb783a0910a334d93b93460c0e103c4682 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Sat, 20 Sep 2025 10:19:55 +0800 Subject: [PATCH 047/228] core/txpool/blobpool: introduce sidecar conversion for legacy blob transactions (#32656) This pull request introduces a queue for legacy sidecar conversion to handle transactions that persist after the Osaka fork. Simply dropping these transactions would significantly harm the user experience. To balance usability with system complexity, we have introduced a conversion time window of two hours post Osaka fork. During this period, the system will accept legacy blob transactions and convert them in a background process. After the window, all legacy transactions will be rejected. Notably, all the blob transactions will be validated statically before the conversion, and also all conversion are performed in a single thread, minimize the risk of being DoS. We believe this two hour window provides sufficient time to process in-flight legacy transactions and allows submitters to migrate to the new format. --------- Co-authored-by: Felix Lange --- core/txpool/blobpool/blobpool.go | 123 ++++++++++++++----- core/txpool/blobpool/blobpool_test.go | 42 +++++-- core/txpool/blobpool/conversion.go | 152 ++++++++++++++++++++++++ core/txpool/blobpool/conversion_test.go | 101 ++++++++++++++++ core/txpool/validation.go | 11 +- 5 files changed, 384 insertions(+), 45 deletions(-) create mode 100644 core/txpool/blobpool/conversion.go create mode 100644 core/txpool/blobpool/conversion_test.go diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 2229f1544ccc..1bf48cf949aa 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -27,6 +27,7 @@ import ( "path/filepath" "sort" "sync" + "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" @@ -61,11 +62,11 @@ const ( // small buffer is added to the proof overhead. txBlobOverhead = uint32(kzg4844.CellProofsPerBlob*len(kzg4844.Proof{}) + 64) - // txMaxSize is the maximum size a single transaction can have, outside - // the included blobs. Since blob transactions are pulled instead of pushed, - // and only a small metadata is kept in ram, the rest is on disk, there is - // no critical limit that should be enforced. Still, capping it to some sane - // limit can never hurt. + // txMaxSize is the maximum size a single transaction can have, including the + // blobs. Since blob transactions are pulled instead of pushed, and only a + // small metadata is kept in ram, the rest is on disk, there is no critical + // limit that should be enforced. Still, capping it to some sane limit can + // never hurt, which is aligned with maxBlobsPerTx constraint enforced internally. txMaxSize = 1024 * 1024 // maxBlobsPerTx is the maximum number of blobs that a single transaction can @@ -93,6 +94,11 @@ const ( // storeVersion is the current slotter layout used for the billy.Database // store. storeVersion = 1 + + // conversionTimeWindow defines the period after the Osaka fork during which + // the pool will still accept and convert legacy blob transactions. After this + // window, all legacy blob transactions will be rejected. + conversionTimeWindow = time.Hour * 2 ) // blobTxMeta is the minimal subset of types.BlobTx necessary to validate and @@ -329,12 +335,13 @@ type BlobPool struct { stored uint64 // Useful data size of all transactions on disk limbo *limbo // Persistent data store for the non-finalized blobs - signer types.Signer // Transaction signer to use for sender recovery - chain BlockChain // Chain object to access the state through + signer types.Signer // Transaction signer to use for sender recovery + chain BlockChain // Chain object to access the state through + cQueue *conversionQueue // The queue for performing legacy sidecar conversion - head *types.Header // Current head of the chain - state *state.StateDB // Current state at the head of the chain - gasTip *uint256.Int // Currently accepted minimum gas tip + head atomic.Pointer[types.Header] // Current head of the chain + state *state.StateDB // Current state at the head of the chain + gasTip atomic.Pointer[uint256.Int] // Currently accepted minimum gas tip lookup *lookup // Lookup table mapping blobs to txs and txs to billy entries index map[common.Address][]*blobTxMeta // Blob transactions grouped by accounts, sorted by nonce @@ -359,6 +366,7 @@ func New(config Config, chain BlockChain, hasPendingAuth func(common.Address) bo hasPendingAuth: hasPendingAuth, signer: types.LatestSigner(chain.Config()), chain: chain, + cQueue: newConversionQueue(), // Deprecate it after the osaka fork lookup: newLookup(), index: make(map[common.Address][]*blobTxMeta), spent: make(map[common.Address]*uint256.Int), @@ -400,7 +408,8 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser if err != nil { return err } - p.head, p.state = head, state + p.head.Store(head) + p.state = state // Create new slotter for pre-Osaka blob configuration. slotter := newSlotter(eip4844.LatestMaxBlobsPerBlock(p.chain.Config())) @@ -440,11 +449,11 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser p.recheck(addr, nil) } var ( - basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head)) + basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), head)) blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice) ) - if p.head.ExcessBlobGas != nil { - blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(p.chain.Config(), p.head)) + if head.ExcessBlobGas != nil { + blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(p.chain.Config(), head)) } p.evict = newPriceHeap(basefee, blobfee, p.index) @@ -474,6 +483,9 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser // Close closes down the underlying persistent store. func (p *BlobPool) Close() error { + // Terminate the conversion queue + p.cQueue.close() + var errs []error if p.limbo != nil { // Close might be invoked due to error in constructor, before p,limbo is set if err := p.limbo.Close(); err != nil { @@ -832,7 +844,7 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { log.Error("Failed to reset blobpool state", "err", err) return } - p.head = newHead + p.head.Store(newHead) p.state = statedb // Run the reorg between the old and new head and figure out which accounts @@ -855,7 +867,7 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { } } // Flush out any blobs from limbo that are older than the latest finality - if p.chain.Config().IsCancun(p.head.Number, p.head.Time) { + if p.chain.Config().IsCancun(newHead.Number, newHead.Time) { p.limbo.finalize(p.chain.CurrentFinalBlock()) } // Reset the price heap for the new set of basefee/blobfee pairs @@ -1056,14 +1068,15 @@ func (p *BlobPool) SetGasTip(tip *big.Int) { defer p.lock.Unlock() // Store the new minimum gas tip - old := p.gasTip - p.gasTip = uint256.MustFromBig(tip) + old := p.gasTip.Load() + newTip := uint256.MustFromBig(tip) + p.gasTip.Store(newTip) // If the min miner fee increased, remove transactions below the new threshold - if old == nil || p.gasTip.Cmp(old) > 0 { + if old == nil || newTip.Cmp(old) > 0 { for addr, txs := range p.index { for i, tx := range txs { - if tx.execTipCap.Cmp(p.gasTip) < 0 { + if tx.execTipCap.Cmp(newTip) < 0 { // Drop the offending transaction var ( ids = []uint64{tx.id} @@ -1123,10 +1136,10 @@ func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error { Config: p.chain.Config(), Accept: 1 << types.BlobTxType, MaxSize: txMaxSize, - MinTip: p.gasTip.ToBig(), + MinTip: p.gasTip.Load().ToBig(), MaxBlobCount: maxBlobsPerTx, } - return txpool.ValidateTransaction(tx, p.head, p.signer, opts) + return txpool.ValidateTransaction(tx, p.head.Load(), p.signer, opts) } // checkDelegationLimit determines if the tx sender is delegated or has a @@ -1164,10 +1177,10 @@ func (p *BlobPool) checkDelegationLimit(tx *types.Transaction) error { // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). +// +// This function assumes the static validation has been performed already and +// only runs the stateful checks with lock protection. func (p *BlobPool) validateTx(tx *types.Transaction) error { - if err := p.ValidateTxBasics(tx); err != nil { - return err - } // Ensure the transaction adheres to the stateful pool filters (nonce, balance) stateOpts := &txpool.ValidationOptionsWithState{ State: p.state, @@ -1444,17 +1457,67 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int { return available } +// preCheck performs the static validation upon the provided txs and converts +// the legacy sidecars if Osaka fork has been activated with a short time window. +// +// This function is pure static and lock free. +func (p *BlobPool) preCheck(txs []*types.Transaction) ([]*types.Transaction, []error) { + var ( + head = p.head.Load() + isOsaka = p.chain.Config().IsOsaka(head.Number, head.Time) + deadline time.Time + ) + if isOsaka { + deadline = time.Unix(int64(*p.chain.Config().OsakaTime), 0).Add(conversionTimeWindow) + } + var errs []error + for _, tx := range txs { + // Validate the transaction statically at first to avoid unnecessary + // conversion. This step doesn't require lock protection. + if err := p.ValidateTxBasics(tx); err != nil { + errs = append(errs, err) + continue + } + // Before the Osaka fork, reject the blob txs with cell proofs + if !isOsaka { + if tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 { + errs = append(errs, nil) + } else { + errs = append(errs, errors.New("cell proof is not supported yet")) + } + continue + } + // After the Osaka fork, reject the legacy blob txs if the conversion + // time window is passed. + if tx.BlobTxSidecar().Version == types.BlobSidecarVersion1 { + errs = append(errs, nil) + continue + } + if head.Time > uint64(deadline.Unix()) { + errs = append(errs, errors.New("legacy blob tx is not supported")) + continue + } + // Convert the legacy sidecar after Osaka fork. This could be a long + // procedure which takes a few seconds, even minutes if there is a long + // queue. Fortunately it will only block the routine of the source peer + // announcing the tx, without affecting other parts. + errs = append(errs, p.cQueue.convert(tx)) + } + return txs, errs +} + // Add inserts a set of blob transactions into the pool if they pass validation (both // consensus validity and pool restrictions). -// -// Note, if sync is set the method will block until all internal maintenance -// related to the add is finished. Only use this during tests for determinism. func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error { var ( - errs = make([]error, len(txs)) + errs []error adds = make([]*types.Transaction, 0, len(txs)) ) + txs, errs = p.preCheck(txs) for i, tx := range txs { + if errs[i] != nil { + continue + } errs[i] = p.add(tx) if errs[i] == nil { adds = append(adds, tx.WithoutBlobTxSidecar()) @@ -1949,7 +2012,7 @@ func (p *BlobPool) Clear() { p.spent = make(map[common.Address]*uint256.Int) var ( - basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head)) + basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head.Load())) blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice) ) p.evict = newPriceHeap(basefee, blobfee, p.index) diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 57d27962ce56..75a87940bd9a 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -88,6 +88,12 @@ type testBlockChain struct { statedb *state.StateDB blocks map[uint64]*types.Block + + blockTime *uint64 +} + +func (bc *testBlockChain) setHeadTime(time uint64) { + bc.blockTime = &time } func (bc *testBlockChain) Config() *params.ChainConfig { @@ -105,6 +111,10 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { blockTime = *bc.config.CancunTime + 1 gasLimit = uint64(30_000_000) ) + if bc.blockTime != nil { + blockTime = *bc.blockTime + } + lo := new(big.Int) hi := new(big.Int).Mul(big.NewInt(5714), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) @@ -1748,8 +1758,8 @@ func TestAdd(t *testing.T) { // Add each transaction one by one, verifying the pool internals in between for j, add := range tt.adds { signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(params.MainnetChainConfig), add.tx) - if err := pool.add(signed); !errors.Is(err, add.err) { - t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err) + if errs := pool.Add([]*types.Transaction{signed}, true); !errors.Is(errs[0], add.err) { + t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, errs[0], add.err) } if add.err == nil { size, exist := pool.lookup.sizeOfTx(signed.Hash()) @@ -1796,8 +1806,14 @@ func TestAdd(t *testing.T) { } } -// Tests adding transactions with legacy sidecars are correctly rejected. +// Tests that transactions with legacy sidecars are accepted within the +// conversion window but rejected after it has passed. func TestAddLegacyBlobTx(t *testing.T) { + testAddLegacyBlobTx(t, true) // conversion window has not yet passed + testAddLegacyBlobTx(t, false) // conversion window passed +} + +func testAddLegacyBlobTx(t *testing.T, accept bool) { var ( key1, _ = crypto.GenerateKey() key2, _ = crypto.GenerateKey() @@ -1817,6 +1833,15 @@ func TestAddLegacyBlobTx(t *testing.T) { blobfee: uint256.NewInt(105), statedb: statedb, } + var timeDiff uint64 + if accept { + timeDiff = uint64(conversionTimeWindow.Seconds()) - 1 + } else { + timeDiff = uint64(conversionTimeWindow.Seconds()) + 1 + } + time := *params.MergedTestChainConfig.OsakaTime + timeDiff + chain.setHeadTime(time) + pool := New(Config{Datadir: t.TempDir()}, chain, nil) if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { t.Fatalf("failed to create blob pool: %v", err) @@ -1826,12 +1851,15 @@ func TestAddLegacyBlobTx(t *testing.T) { var ( tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0) tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion0) - tx3 = makeMultiBlobTx(1, 1, 800, 70, 6, 12, key2, types.BlobSidecarVersion1) + txs = []*types.Transaction{tx1, tx2} ) - errs := pool.Add([]*types.Transaction{tx1, tx2, tx3}, true) + errs := pool.Add(txs, true) for _, err := range errs { - if err == nil { - t.Fatalf("expected tx add to fail") + if accept && err != nil { + t.Fatalf("expected tx add to succeed, %v", err) + } + if !accept && err == nil { + t.Fatal("expected tx add to fail") } } verifyPoolInternals(t, pool) diff --git a/core/txpool/blobpool/conversion.go b/core/txpool/blobpool/conversion.go new file mode 100644 index 000000000000..5026892fc8a1 --- /dev/null +++ b/core/txpool/blobpool/conversion.go @@ -0,0 +1,152 @@ +// 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 . + +package blobpool + +import ( + "errors" + "slices" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// maxPendingConversionTasks caps the number of pending conversion tasks. This +// prevents excessive memory usage; the worst-case scenario (2k transactions +// with 6 blobs each) would consume approximately 1.5GB of memory. +const maxPendingConversionTasks = 2048 + +// cTask represents a conversion task with an attached legacy blob transaction. +type cTask struct { + tx *types.Transaction // Legacy blob transaction + done chan error // Channel for signaling back if the conversion succeeds +} + +// conversionQueue is a dedicated queue for converting legacy blob transactions +// received from the network after the Osaka fork. Since conversion is expensive, +// it is performed in the background by a single thread, ensuring the main Geth +// process is not overloaded. +type conversionQueue struct { + tasks chan *cTask + quit chan struct{} + closed chan struct{} +} + +// newConversionQueue constructs the conversion queue. +func newConversionQueue() *conversionQueue { + q := &conversionQueue{ + tasks: make(chan *cTask), + quit: make(chan struct{}), + closed: make(chan struct{}), + } + go q.loop() + return q +} + +// convert accepts a legacy blob transaction with version-0 blobs and queues it +// for conversion. +// +// This function may block for a long time until the transaction is processed. +func (q *conversionQueue) convert(tx *types.Transaction) error { + done := make(chan error, 1) + select { + case q.tasks <- &cTask{tx: tx, done: done}: + return <-done + case <-q.closed: + return errors.New("conversion queue closed") + } +} + +// close terminates the conversion queue. +func (q *conversionQueue) close() { + select { + case <-q.closed: + return + default: + close(q.quit) + <-q.closed + } +} + +// run converts a batch of legacy blob txs to the new cell proof format. +func (q *conversionQueue) run(tasks []*cTask, done chan struct{}, interrupt *atomic.Int32) { + defer close(done) + + for _, t := range tasks { + if interrupt != nil && interrupt.Load() != 0 { + t.done <- errors.New("conversion is interrupted") + continue + } + sidecar := t.tx.BlobTxSidecar() + if sidecar == nil { + t.done <- errors.New("tx without sidecar") + continue + } + // Run the conversion, the original sidecar will be mutated in place + start := time.Now() + err := sidecar.ToV1() + t.done <- err + log.Trace("Converted legacy blob tx", "hash", t.tx.Hash(), "err", err, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +func (q *conversionQueue) loop() { + defer close(q.closed) + + var ( + done chan struct{} // Non-nil if background routine is active + interrupt *atomic.Int32 // Flag to signal conversion interruption + + // The pending tasks for sidecar conversion. We assume the number of legacy + // blob transactions requiring conversion will not be excessive. However, + // a hard cap is applied as a protective measure. + cTasks []*cTask + ) + for { + select { + case t := <-q.tasks: + if len(cTasks) >= maxPendingConversionTasks { + t.done <- errors.New("conversion queue is overloaded") + continue + } + cTasks = append(cTasks, t) + + // Launch the background conversion thread if it's idle + if done == nil { + done, interrupt = make(chan struct{}), new(atomic.Int32) + + tasks := slices.Clone(cTasks) + cTasks = cTasks[:0] + go q.run(tasks, done, interrupt) + } + + case <-done: + done, interrupt = nil, nil + + case <-q.quit: + if done == nil { + return + } + interrupt.Store(1) + log.Debug("Waiting for blob proof conversion to exit") + <-done + return + } + } +} diff --git a/core/txpool/blobpool/conversion_test.go b/core/txpool/blobpool/conversion_test.go new file mode 100644 index 000000000000..a9fd26dbaf6c --- /dev/null +++ b/core/txpool/blobpool/conversion_test.go @@ -0,0 +1,101 @@ +// 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 . + +package blobpool + +import ( + "crypto/ecdsa" + "crypto/sha256" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +// createV1BlobTx creates a blob transaction with version 1 sidecar for testing. +func createV1BlobTx(nonce uint64, key *ecdsa.PrivateKey) *types.Transaction { + blob := &kzg4844.Blob{byte(nonce)} + commitment, _ := kzg4844.BlobToCommitment(blob) + cellProofs, _ := kzg4844.ComputeCellProofs(blob) + + blobtx := &types.BlobTx{ + ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID), + Nonce: nonce, + GasTipCap: uint256.NewInt(1), + GasFeeCap: uint256.NewInt(1000), + Gas: 21000, + BlobFeeCap: uint256.NewInt(100), + BlobHashes: []common.Hash{kzg4844.CalcBlobHashV1(sha256.New(), &commitment)}, + Value: uint256.NewInt(100), + Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion1, []kzg4844.Blob{*blob}, []kzg4844.Commitment{commitment}, cellProofs), + } + return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) +} + +func TestConversionQueueBasic(t *testing.T) { + queue := newConversionQueue() + defer queue.close() + + key, _ := crypto.GenerateKey() + tx := makeTx(0, 1, 1, 1, key) + if err := queue.convert(tx); err != nil { + t.Fatalf("Expected successful conversion, got error: %v", err) + } + if tx.BlobTxSidecar().Version != types.BlobSidecarVersion1 { + t.Errorf("Expected sidecar version to be %d, got %d", types.BlobSidecarVersion1, tx.BlobTxSidecar().Version) + } +} + +func TestConversionQueueV1BlobTx(t *testing.T) { + queue := newConversionQueue() + defer queue.close() + + key, _ := crypto.GenerateKey() + tx := createV1BlobTx(0, key) + version := tx.BlobTxSidecar().Version + + err := queue.convert(tx) + if err != nil { + t.Fatalf("Expected successful conversion, got error: %v", err) + } + if tx.BlobTxSidecar().Version != version { + t.Errorf("Expected sidecar version to remain %d, got %d", version, tx.BlobTxSidecar().Version) + } +} + +func TestConversionQueueClosed(t *testing.T) { + queue := newConversionQueue() + + // Close the queue first + queue.close() + key, _ := crypto.GenerateKey() + tx := makeTx(0, 1, 1, 1, key) + + err := queue.convert(tx) + if err == nil { + t.Fatal("Expected error when converting on closed queue, got nil") + } +} + +func TestConversionQueueDoubleClose(t *testing.T) { + queue := newConversionQueue() + queue.close() + queue.close() // Should not panic +} diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 46974fad3cbe..df53f30a8668 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -176,16 +176,14 @@ func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationO return err } // Fork-specific sidecar checks, including proof verification. - if opts.Config.IsOsaka(head.Number, head.Time) { + if sidecar.Version == types.BlobSidecarVersion1 { return validateBlobSidecarOsaka(sidecar, hashes) + } else { + return validateBlobSidecarLegacy(sidecar, hashes) } - return validateBlobSidecarLegacy(sidecar, hashes) } func validateBlobSidecarLegacy(sidecar *types.BlobTxSidecar, hashes []common.Hash) error { - if sidecar.Version != types.BlobSidecarVersion0 { - return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version) - } if len(sidecar.Proofs) != len(hashes) { return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes)) } @@ -198,9 +196,6 @@ func validateBlobSidecarLegacy(sidecar *types.BlobTxSidecar, hashes []common.Has } func validateBlobSidecarOsaka(sidecar *types.BlobTxSidecar, hashes []common.Hash) error { - if sidecar.Version != types.BlobSidecarVersion1 { - return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version) - } if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob { return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes)*kzg4844.CellProofsPerBlob) } From aa4feba284e71ffdeb8b50751fffbca416355c4f Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Sat, 20 Sep 2025 01:32:21 -0600 Subject: [PATCH 048/228] go.work.sum: add to repo (#32677) --- go.work.sum | 515 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 go.work.sum diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 000000000000..7413bba03fc5 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,515 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= From 3a1c99defa451ef71d78e4d11a27f80db53ff53b Mon Sep 17 00:00:00 2001 From: GarmashAlex Date: Mon, 22 Sep 2025 05:30:29 +0300 Subject: [PATCH 049/228] core/state: fix committed-state expectations in StateDB tests (#32678) --- core/state/statedb_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 147546a3c754..661d17bb7beb 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -772,7 +772,7 @@ func TestCopyCommitCopy(t *testing.T) { t.Fatalf("second copy non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) { - t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, sval) + t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } // Commit state, ensure states can be loaded from disk root, _ := state.Commit(0, false, false) @@ -859,7 +859,7 @@ func TestCopyCopyCommitCopy(t *testing.T) { t.Fatalf("third copy non-committed storage slot mismatch: have %x, want %x", val, sval) } if val := copyThree.GetCommittedState(addr, skey); val != (common.Hash{}) { - t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, sval) + t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } } @@ -912,10 +912,10 @@ func TestCommitCopy(t *testing.T) { } // Slots cached in the stateDB, available after commit if val := copied.GetState(addr, skey2); val != sval2 { - t.Fatalf("unexpected storage slot: have %x", sval1) + t.Fatalf("unexpected storage slot: have %x, want %x", val, sval2) } if val := copied.GetCommittedState(addr, skey2); val != sval2 { - t.Fatalf("unexpected storage slot: have %x", val) + t.Fatalf("unexpected storage slot: have %x, want %x", val, sval2) } } From aa917bb15af3f25c71f1a969506292bf6dd697cf Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 22 Sep 2025 14:45:15 +0800 Subject: [PATCH 050/228] triedb/pathdb: move head truncation log (#32649) Print the `Truncating from head` log only if head truncation is needed. --- triedb/pathdb/history.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go index ae022236fefb..81b843d9f12e 100644 --- a/triedb/pathdb/history.go +++ b/triedb/pathdb/history.go @@ -176,8 +176,6 @@ func truncateFromHead(store ethdb.AncientStore, typ historyType, nhead uint64) ( if err != nil { return 0, err } - log.Info("Truncating from head", "type", typ.String(), "ohead", ohead, "tail", otail, "nhead", nhead) - // Ensure that the truncation target falls within the valid range. if ohead < nhead || nhead < otail { return 0, fmt.Errorf("%w, %s, tail: %d, head: %d, target: %d", errHeadTruncationOutOfRange, typ, otail, ohead, nhead) @@ -186,6 +184,8 @@ func truncateFromHead(store ethdb.AncientStore, typ historyType, nhead uint64) ( if ohead == nhead { return 0, nil } + log.Info("Truncating from head", "type", typ.String(), "ohead", ohead, "tail", otail, "nhead", nhead) + ohead, err = store.TruncateHead(nhead) if err != nil { return 0, err From 758ad4d0427bbb517a16c56000425bbad24941a5 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 22 Sep 2025 14:48:18 +0800 Subject: [PATCH 051/228] cmd, tests: fix snapshot dump and export-preimages (#32650) Address #32646 --- cmd/geth/snapshot.go | 22 ++++---------- cmd/utils/cmd.go | 62 ++++++++++++++++++++++++++++++++++++++-- tests/state_test.go | 4 +-- tests/state_test_util.go | 2 +- 4 files changed, 67 insertions(+), 23 deletions(-) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 994cb149ce47..7621dfa93ca0 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -561,17 +561,11 @@ func dumpState(ctx *cli.Context) error { triedb := utils.MakeTrieDatabase(ctx, stack, db, false, true, false) defer triedb.Close() - snapConfig := snapshot.Config{ - CacheSize: 256, - Recovery: false, - NoBuild: true, - AsyncBuild: false, - } - snaptree, err := snapshot.New(snapConfig, db, triedb, root) + stateIt, err := utils.NewStateIterator(triedb, db, root) if err != nil { return err } - accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start)) + accIt, err := stateIt.AccountIterator(root, common.BytesToHash(conf.Start)) if err != nil { return err } @@ -605,7 +599,7 @@ func dumpState(ctx *cli.Context) error { if !conf.SkipStorage { da.Storage = make(map[common.Hash]string) - stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{}) + stIt, err := stateIt.StorageIterator(root, accIt.Hash(), common.Hash{}) if err != nil { return err } @@ -658,17 +652,11 @@ func snapshotExportPreimages(ctx *cli.Context) error { } root = headBlock.Root() } - snapConfig := snapshot.Config{ - CacheSize: 256, - Recovery: false, - NoBuild: true, - AsyncBuild: false, - } - snaptree, err := snapshot.New(snapConfig, chaindb, triedb, root) + stateIt, err := utils.NewStateIterator(triedb, chaindb, root) if err != nil { return err } - return utils.ExportSnapshotPreimages(chaindb, snaptree, ctx.Args().First(), root) + return utils.ExportSnapshotPreimages(chaindb, stateIt, ctx.Args().First(), root) } // checkAccount iterates the snap data layers, and looks up the given account diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index b332a060deed..db7bd691d8e1 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -48,6 +48,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/triedb" "github.com/urfave/cli/v2" ) @@ -567,9 +568,64 @@ func ExportPreimages(db ethdb.Database, fn string) error { return nil } +// StateIterator is a temporary structure for traversing state in order. It serves +// as an aggregator for both path scheme and hash scheme implementations and should +// be removed once the hash scheme is fully deprecated. +type StateIterator struct { + scheme string + root common.Hash + triedb *triedb.Database + snapshots *snapshot.Tree +} + +// NewStateIterator constructs the state iterator with the specific root. +func NewStateIterator(triedb *triedb.Database, db ethdb.Database, root common.Hash) (*StateIterator, error) { + if triedb.Scheme() == rawdb.PathScheme { + return &StateIterator{ + scheme: rawdb.PathScheme, + root: root, + triedb: triedb, + }, nil + } + config := snapshot.Config{ + CacheSize: 256, + Recovery: false, + NoBuild: true, + AsyncBuild: false, + } + snapshots, err := snapshot.New(config, db, triedb, root) + if err != nil { + return nil, err + } + return &StateIterator{ + scheme: rawdb.HashScheme, + root: root, + triedb: triedb, + snapshots: snapshots, + }, nil +} + +// AccountIterator creates a new account iterator for the specified root hash and +// seeks to a starting account hash. +func (it *StateIterator) AccountIterator(root common.Hash, start common.Hash) (snapshot.AccountIterator, error) { + if it.scheme == rawdb.PathScheme { + return it.triedb.AccountIterator(root, start) + } + return it.snapshots.AccountIterator(root, start) +} + +// StorageIterator creates a new storage iterator for the specified root hash and +// account. The iterator will be moved to the specific start position. +func (it *StateIterator) StorageIterator(root common.Hash, accountHash common.Hash, start common.Hash) (snapshot.StorageIterator, error) { + if it.scheme == rawdb.PathScheme { + return it.triedb.StorageIterator(root, accountHash, start) + } + return it.snapshots.StorageIterator(root, accountHash, start) +} + // ExportSnapshotPreimages exports the preimages corresponding to the enumeration of // the snapshot for a given root. -func ExportSnapshotPreimages(chaindb ethdb.Database, snaptree *snapshot.Tree, fn string, root common.Hash) error { +func ExportSnapshotPreimages(chaindb ethdb.Database, stateIt *StateIterator, fn string, root common.Hash) error { log.Info("Exporting preimages", "file", fn) fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) @@ -602,7 +658,7 @@ func ExportSnapshotPreimages(chaindb ethdb.Database, snaptree *snapshot.Tree, fn ) go func() { defer close(hashCh) - accIt, err := snaptree.AccountIterator(root, common.Hash{}) + accIt, err := stateIt.AccountIterator(root, common.Hash{}) if err != nil { log.Error("Failed to create account iterator", "error", err) return @@ -619,7 +675,7 @@ func ExportSnapshotPreimages(chaindb ethdb.Database, snaptree *snapshot.Tree, fn hashCh <- hashAndPreimageSize{Hash: accIt.Hash(), Size: common.AddressLength} if acc.Root != (common.Hash{}) && acc.Root != types.EmptyRootHash { - stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{}) + stIt, err := stateIt.StorageIterator(root, accIt.Hash(), common.Hash{}) if err != nil { log.Error("Failed to create storage iterator", "error", err) return diff --git a/tests/state_test.go b/tests/state_test.go index 301bc3a7a94f..f80bda4372c8 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -156,8 +156,8 @@ func execStateTest(t *testing.T, st *testMatcher, test *StateTest) { withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { var result error test.Run(subtest, vmconfig, true, rawdb.PathScheme, func(err error, state *StateTestState) { - if state.Snapshots != nil && state.StateDB != nil { - if _, err := state.Snapshots.Journal(state.StateDB.IntermediateRoot(false)); err != nil { + if state.TrieDB != nil && state.StateDB != nil { + if err := state.TrieDB.Journal(state.StateDB.IntermediateRoot(false)); err != nil { result = err return } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index ec7eec1f39f0..b8d3c4fb92e2 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -523,7 +523,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, snapshotter bo // If snapshot is requested, initialize the snapshotter and use it in state. var snaps *snapshot.Tree - if snapshotter { + if snapshotter && scheme == rawdb.HashScheme { snapconfig := snapshot.Config{ CacheSize: 1, Recovery: false, From 062542a56912cd7be24cf2a2c7d215e8596d4e61 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 22 Sep 2025 22:20:47 +0200 Subject: [PATCH 052/228] go.work, build: remove workspace file (#32699) https://go.dev/ref/mod#go-work-file advises against checking `go.work` files because they can interfere with local development. We added the workspace file in order to make `go test` and other tools work across multiple modules. But it seems to cause weird issues with the `go.work.sum` file being modified, etc. So with this PR, we instead run all the `ci.go` commands for all modules in the workspace manually. --- build/ci.go | 131 +++++------ cmd/keeper/go.mod | 9 +- cmd/keeper/go.sum | 18 +- go.work | 6 - go.work.sum | 515 ----------------------------------------- internal/build/util.go | 12 +- 6 files changed, 92 insertions(+), 599 deletions(-) delete mode 100644 go.work delete mode 100644 go.work.sum diff --git a/build/ci.go b/build/ci.go index e145cc1cb563..c5d522c0e621 100644 --- a/build/ci.go +++ b/build/ci.go @@ -64,6 +64,11 @@ import ( ) var ( + goModules = []string{ + ".", + "./cmd/keeper", + } + // Files that end up in the geth*.zip archive. gethArchiveFiles = []string{ "COPYING", @@ -295,6 +300,7 @@ func doTest(cmdline []string) { if *dlgo { tc.Root = build.DownloadGo(csdb) } + gotest := tc.Go("test") // CI needs a bit more time for the statetests (default 45m). @@ -323,11 +329,19 @@ func doTest(cmdline []string) { } packages := flag.CommandLine.Args() - if len(packages) == 0 { - packages = workspacePackagePatterns() + if len(packages) > 0 { + gotest.Args = append(gotest.Args, packages...) + build.MustRun(gotest) + return + } + + // No packages specified, run all tests for all modules. + gotest.Args = append(gotest.Args, "./...") + for _, mod := range goModules { + test := *gotest + test.Dir = mod + build.MustRun(&test) } - gotest.Args = append(gotest.Args, packages...) - build.MustRun(gotest) } // downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures. @@ -351,40 +365,46 @@ func doCheckGenerate() { cachedir = flag.String("cachedir", "./build/cache", "directory for caching binaries.") tc = new(build.GoToolchain) ) - // Compute the origin hashes of all the files - var hashes map[string][32]byte - var err error - hashes, err = build.HashFolder(".", []string{"tests/testdata", "build/cache", ".git"}) - if err != nil { - log.Fatal("Error computing hashes", "err", err) - } // Run any go generate steps we might be missing var ( protocPath = downloadProtoc(*cachedir) protocGenGoPath = downloadProtocGenGo(*cachedir) ) - c := tc.Go("generate", workspacePackagePatterns()...) pathList := []string{filepath.Join(protocPath, "bin"), protocGenGoPath, os.Getenv("PATH")} - c.Env = append(c.Env, "PATH="+strings.Join(pathList, string(os.PathListSeparator))) - build.MustRun(c) - // Check if generate file hashes have changed - generated, err := build.HashFolder(".", []string{"tests/testdata", "build/cache", ".git"}) - if err != nil { - log.Fatalf("Error re-computing hashes: %v", err) - } - updates := build.DiffHashes(hashes, generated) - for _, file := range updates { - log.Printf("File changed: %s", file) - } - if len(updates) != 0 { - log.Fatal("One or more generated files were updated by running 'go generate ./...'") + for _, mod := range goModules { + // Compute the origin hashes of all the files + hashes, err := build.HashFolder(mod, []string{"tests/testdata", "build/cache", ".git"}) + if err != nil { + log.Fatal("Error computing hashes", "err", err) + } + + c := tc.Go("generate", "./...") + c.Env = append(c.Env, "PATH="+strings.Join(pathList, string(os.PathListSeparator))) + c.Dir = mod + build.MustRun(c) + // Check if generate file hashes have changed + generated, err := build.HashFolder(mod, []string{"tests/testdata", "build/cache", ".git"}) + if err != nil { + log.Fatalf("Error re-computing hashes: %v", err) + } + updates := build.DiffHashes(hashes, generated) + for _, file := range updates { + log.Printf("File changed: %s", file) + } + if len(updates) != 0 { + log.Fatal("One or more generated files were updated by running 'go generate ./...'") + } } fmt.Println("No stale files detected.") // Run go mod tidy check. - build.MustRun(tc.Go("mod", "tidy", "-diff")) + for _, mod := range goModules { + tidy := tc.Go("mod", "tidy", "-diff") + tidy.Dir = mod + build.MustRun(tidy) + } fmt.Println("No untidy module files detected.") } @@ -425,20 +445,29 @@ func doLint(cmdline []string) { ) flag.CommandLine.Parse(cmdline) + linter := downloadLinter(*cachedir) + linter, err := filepath.Abs(linter) + if err != nil { + log.Fatal(err) + } + config, err := filepath.Abs(".golangci.yml") + if err != nil { + log.Fatal(err) + } + + lflags := []string{"run", "--config", config} packages := flag.CommandLine.Args() - if len(packages) == 0 { - // Get module directories in workspace. - packages = []string{"./..."} - modules := workspaceModules() - for _, m := range modules[1:] { - dir := strings.TrimPrefix(m, modules[0]) - packages = append(packages, "."+dir+"/...") + if len(packages) > 0 { + build.MustRunCommandWithOutput(linter, append(lflags, packages...)...) + } else { + // Run for all modules in workspace. + for _, mod := range goModules { + args := append(lflags, "./...") + lintcmd := exec.Command(linter, args...) + lintcmd.Dir = mod + build.MustRunWithOutput(lintcmd) } } - - linter := downloadLinter(*cachedir) - lflags := []string{"run", "--config", ".golangci.yml"} - build.MustRunCommandWithOutput(linter, append(lflags, packages...)...) fmt.Println("You have achieved perfection.") } @@ -1176,31 +1205,3 @@ func doSanityCheck() { csdb := download.MustLoadChecksums("build/checksums.txt") csdb.DownloadAndVerifyAll() } - -// workspaceModules lists the module paths in the current work. -func workspaceModules() []string { - listing, err := new(build.GoToolchain).Go("list", "-m").Output() - if err != nil { - log.Fatalf("go list failed:", err) - } - var modules []string - for _, m := range bytes.Split(listing, []byte("\n")) { - m = bytes.TrimSpace(m) - if len(m) > 0 { - modules = append(modules, string(m)) - } - } - if len(modules) == 0 { - panic("no modules found") - } - return modules -} - -func workspacePackagePatterns() []string { - modules := workspaceModules() - patterns := make([]string, len(modules)) - for i, m := range modules { - patterns[i] = m + "/..." - } - return patterns -} diff --git a/cmd/keeper/go.mod b/cmd/keeper/go.mod index 72bec0fef328..d1649da43fc9 100644 --- a/cmd/keeper/go.mod +++ b/cmd/keeper/go.mod @@ -13,12 +13,13 @@ require ( github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/consensys/gnark-crypto v0.18.0 // indirect - github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect + github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/emicklei/dot v1.6.2 // indirect - github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.1.3 // indirect + github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -33,12 +34,12 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect - github.com/supranational/blst v0.3.14 // indirect + github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/sys v0.36.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/cmd/keeper/go.sum b/cmd/keeper/go.sum index 036d5ebd4b17..e3bc204ba83c 100644 --- a/cmd/keeper/go.sum +++ b/cmd/keeper/go.sum @@ -27,8 +27,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAK github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0= github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= -github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI= -github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= +github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= +github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -42,8 +42,10 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= -github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w= -github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= +github.com/ethereum/c-kzg-4844/v2 v2.1.3 h1:DQ21UU0VSsuGy8+pcMJHDS0CV1bKmJmxsJYK8l3MiLU= +github.com/ethereum/c-kzg-4844/v2 v2.1.3/go.mod h1:fyNcYI/yAuLWJxf4uzVtS8VDKeoAaRM8G/+ADz/pRdA= +github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= +github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= @@ -114,8 +116,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= -github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= +github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= @@ -133,8 +135,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= diff --git a/go.work b/go.work deleted file mode 100644 index 54e37dd75a50..000000000000 --- a/go.work +++ /dev/null @@ -1,6 +0,0 @@ -go 1.24.0 - -use ( - . - ./cmd/keeper -) diff --git a/go.work.sum b/go.work.sum deleted file mode 100644 index 7413bba03fc5..000000000000 --- a/go.work.sum +++ /dev/null @@ -1,515 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= -github.com/consensys/bavard v0.1.31-0.20250406004941-2db259e4b582/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= -github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= -github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= -github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/internal/build/util.go b/internal/build/util.go index aee8bf0fc8af..27531d3d9511 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -39,6 +39,9 @@ var DryRunFlag = flag.Bool("n", false, "dry run, don't execute commands") // MustRun executes the given command and exits the host process for // any error. func MustRun(cmd *exec.Cmd) { + if cmd.Dir != "" && cmd.Dir != "." { + fmt.Printf("(in %s) ", cmd.Dir) + } fmt.Println(">>>", printArgs(cmd.Args)) if !*DryRunFlag { cmd.Stderr = os.Stderr @@ -71,6 +74,13 @@ func MustRunCommand(cmd string, args ...string) { // printed while it runs. This is useful for CI builds where the process will be stopped // when there is no output. func MustRunCommandWithOutput(cmd string, args ...string) { + MustRunWithOutput(exec.Command(cmd, args...)) +} + +// MustRunWithOutput runs the given command, and ensures that some output will be printed +// while it runs. This is useful for CI builds where the process will be stopped when +// there is no output. +func MustRunWithOutput(cmd *exec.Cmd) { interval := time.NewTicker(time.Minute) done := make(chan struct{}) defer interval.Stop() @@ -85,7 +95,7 @@ func MustRunCommandWithOutput(cmd string, args ...string) { } } }() - MustRun(exec.Command(cmd, args...)) + MustRun(cmd) } var warnedAboutGit bool From 4cf25a28be917453548f9140a2bc436d878de880 Mon Sep 17 00:00:00 2001 From: MozirDmitriy Date: Mon, 22 Sep 2025 23:27:54 +0300 Subject: [PATCH 053/228] cmd/era: fix iterator error source handling in checkAccumulator (#32698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change replaces wrapping a stale outer err with the iterator’s own error after Next(), and switches the post-BlockAndReceipts() check to use the returned err. According to internal/era iterator contract, Error() should be consulted immediately after Next() to surface iteration errors, while decoding errors from Block/Receipts are returned directly. The previous code could hide the real failure (using nil or unrelated err), leading to misleading diagnostics and missed iteration errors. --------- Co-authored-by: lightclient --- cmd/era/main.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/era/main.go b/cmd/era/main.go index 8b57fd695c53..35a889d4dc6b 100644 --- a/cmd/era/main.go +++ b/cmd/era/main.go @@ -274,10 +274,10 @@ func checkAccumulator(e *era.Era) error { for it.Next() { // 1) next() walks the block index, so we're able to implicitly verify it. if it.Error() != nil { - return fmt.Errorf("error reading block %d: %w", it.Number(), err) + return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error()) } block, receipts, err := it.BlockAndReceipts() - if it.Error() != nil { + if err != nil { return fmt.Errorf("error reading block %d: %w", it.Number(), err) } // 2) recompute tx root and verify against header. @@ -294,6 +294,9 @@ func checkAccumulator(e *era.Era) error { td.Add(td, block.Difficulty()) tds = append(tds, new(big.Int).Set(td)) } + if it.Error() != nil { + return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error()) + } // 4+5) Verify accumulator and total difficulty. got, err := era.ComputeAccumulator(hashes, tds) if err != nil { From a31c6a90f1ea1aeb6709f73e9a9d764109233d28 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Tue, 23 Sep 2025 14:39:44 +0200 Subject: [PATCH 054/228] cmd/evm/internal/t8ntool: fix nil pointer dereference in Osaka blob gas calculation (#32714) The parent header was missing the BaseFee field when calculating the reserve price for EIP-7918 in the Osaka fork, causing a nil pointer dereference. This fix ensures BaseFee is properly set from ParentBaseFee in the environment. Added regression test case 34 to verify Osaka fork blob gas calculation works correctly with parent base fee. --- cmd/evm/internal/t8ntool/execution.go | 1 + cmd/evm/t8n_test.go | 8 ++++++++ cmd/evm/testdata/34/README.md | 6 ++++++ cmd/evm/testdata/34/alloc.json | 6 ++++++ cmd/evm/testdata/34/env.json | 18 ++++++++++++++++++ cmd/evm/testdata/34/exp.json | 23 +++++++++++++++++++++++ cmd/evm/testdata/34/txs.json | 1 + 7 files changed, 63 insertions(+) create mode 100644 cmd/evm/testdata/34/README.md create mode 100644 cmd/evm/testdata/34/alloc.json create mode 100644 cmd/evm/testdata/34/env.json create mode 100644 cmd/evm/testdata/34/exp.json create mode 100644 cmd/evm/testdata/34/txs.json diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index f595a9bf4de0..f1e65afe9c7a 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -191,6 +191,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, Time: pre.Env.ParentTimestamp, ExcessBlobGas: pre.Env.ParentExcessBlobGas, BlobGasUsed: pre.Env.ParentBlobGasUsed, + BaseFee: pre.Env.ParentBaseFee, } header := &types.Header{ Time: pre.Env.Timestamp, diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go index 46146be787c2..3c6fd90b4764 100644 --- a/cmd/evm/t8n_test.go +++ b/cmd/evm/t8n_test.go @@ -296,6 +296,14 @@ func TestT8n(t *testing.T) { output: t8nOutput{alloc: true, result: true}, expOut: "exp.json", }, + { // Osaka test, EIP-7918 blob gas with parent base fee + base: "./testdata/34", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Osaka", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, } { args := []string{"t8n"} args = append(args, tc.output.get()...) diff --git a/cmd/evm/testdata/34/README.md b/cmd/evm/testdata/34/README.md new file mode 100644 index 000000000000..a18f85ca14b6 --- /dev/null +++ b/cmd/evm/testdata/34/README.md @@ -0,0 +1,6 @@ +This test verifies that Osaka fork blob gas calculation works correctly when +parentBaseFee is provided. It tests the EIP-7918 reserve price calculation +which requires parent.BaseFee to be properly set. + +Regression test for: nil pointer dereference when parent.BaseFee was not +included in the parent header during Osaka fork blob gas calculations. \ No newline at end of file diff --git a/cmd/evm/testdata/34/alloc.json b/cmd/evm/testdata/34/alloc.json new file mode 100644 index 000000000000..199de13285d0 --- /dev/null +++ b/cmd/evm/testdata/34/alloc.json @@ -0,0 +1,6 @@ +{ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x1000000000000000000", + "nonce": "0x0" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/34/env.json b/cmd/evm/testdata/34/env.json new file mode 100644 index 000000000000..ae2bde5ef392 --- /dev/null +++ b/cmd/evm/testdata/34/env.json @@ -0,0 +1,18 @@ +{ + "currentCoinbase": "0x0000000000000000000000000000000000000000", + "currentDifficulty": "0x0", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000000000", + "currentGasLimit": "0x5f5e100", + "currentNumber": "0x1", + "currentTimestamp": "0x1000", + "parentTimestamp": "0x0", + "currentBaseFee": "0x10", + "parentBaseFee": "0x0a", + "parentGasUsed": "0x0", + "parentGasLimit": "0x5f5e100", + "currentExcessBlobGas": "0x0", + "parentExcessBlobGas": "0x0", + "parentBlobGasUsed": "0x20000", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "withdrawals": [] +} \ No newline at end of file diff --git a/cmd/evm/testdata/34/exp.json b/cmd/evm/testdata/34/exp.json new file mode 100644 index 000000000000..56d24a532e0d --- /dev/null +++ b/cmd/evm/testdata/34/exp.json @@ -0,0 +1,23 @@ +{ + "alloc": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x1000000000000000000" + } + }, + "result": { + "stateRoot": "0x01c28492482a1a1f66224726ef1059a7036fce69d1d2c991b65cd013725d5742", + "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "currentDifficulty": null, + "receipts": [], + "gasUsed": "0x0", + "currentBaseFee": "0x10", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "currentExcessBlobGas": "0x0", + "blobGasUsed": "0x0", + "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "requests": [] + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/34/txs.json b/cmd/evm/testdata/34/txs.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/cmd/evm/testdata/34/txs.json @@ -0,0 +1 @@ +[] \ No newline at end of file From ce9a0918a1b80e53f14e1772ab0ed8784693e1e7 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 23 Sep 2025 20:45:36 +0800 Subject: [PATCH 055/228] core/txpool/blobpool: add legacy sidecar conversion in reinject (#32688) This adds the conversion for the legacy sidecar if these transactions are reorged out after the osaka. --- core/txpool/blobpool/blobpool.go | 15 +++++++++++++++ crypto/kzg4844/kzg4844_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 1bf48cf949aa..db965bc71a94 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1030,6 +1030,21 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error { // TODO: seems like an easy optimization here would be getting the serialized tx // from limbo instead of re-serializing it here. + // Converts reorged-out legacy blob transactions to the new format to prevent + // them from becoming stuck in the pool until eviction. + // + // Performance note: Conversion takes ~140ms (Mac M1 Pro). Since a maximum of + // 9 legacy blob transactions are allowed in a block pre-Osaka, an adversary + // could theoretically halt a Geth node for ~1.2s by reorging per block. However, + // this attack is financially inefficient to execute. + head := p.head.Load() + if p.chain.Config().IsOsaka(head.Number, head.Time) && tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 { + if err := tx.BlobTxSidecar().ToV1(); err != nil { + log.Error("Failed to convert the legacy sidecar", "err", err) + return err + } + log.Info("Legacy blob transaction is reorged", "hash", tx.Hash()) + } // Serialize the transaction back into the primary datastore. blob, err := rlp.EncodeToBytes(tx) if err != nil { diff --git a/crypto/kzg4844/kzg4844_test.go b/crypto/kzg4844/kzg4844_test.go index d398a0f48b94..743a27719981 100644 --- a/crypto/kzg4844/kzg4844_test.go +++ b/crypto/kzg4844/kzg4844_test.go @@ -225,3 +225,31 @@ func testKZGCells(t *testing.T, ckzg bool) { t.Fatalf("failed to verify KZG proof at point: %v", err) } } + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/crypto/kzg4844 +// cpu: Apple M1 Pro +// BenchmarkGOKZGComputeCellProofs +// BenchmarkGOKZGComputeCellProofs-8 8 139012286 ns/op +func BenchmarkGOKZGComputeCellProofs(b *testing.B) { benchmarkComputeCellProofs(b, false) } +func BenchmarkCKZGComputeCellProofs(b *testing.B) { benchmarkComputeCellProofs(b, true) } + +func benchmarkComputeCellProofs(b *testing.B, ckzg bool) { + if ckzg && !ckzgAvailable { + b.Skip("CKZG unavailable in this test build") + } + defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load()) + useCKZG.Store(ckzg) + + blob := randBlob() + _, _ = ComputeCellProofs(blob) // for kzg initialization + b.ResetTimer() + + for b.Loop() { + _, err := ComputeCellProofs(blob) + if err != nil { + b.Fatalf("failed to create KZG proof at point: %v", err) + } + } +} From dc50e577c6c919cc0e7a439c18f8ce46818b70c7 Mon Sep 17 00:00:00 2001 From: cui Date: Tue, 23 Sep 2025 20:49:15 +0800 Subject: [PATCH 056/228] accounts/keystore: use runtime.AddCleanup (#32610) --- accounts/keystore/keystore.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index fefba026ae4c..f9cc3edac8c4 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -99,9 +99,10 @@ func (ks *KeyStore) init(keydir string) { // TODO: In order for this finalizer to work, there must be no references // to ks. addressCache doesn't keep a reference but unlocked keys do, // so the finalizer will not trigger until all timed unlocks have expired. - runtime.SetFinalizer(ks, func(m *KeyStore) { - m.cache.close() - }) + runtime.AddCleanup(ks, func(c *accountCache) { + c.close() + }, ks.cache) + // Create the initial list of wallets from the cache accs := ks.cache.accounts() ks.wallets = make([]accounts.Wallet, len(accs)) From 4b2d3c60bff5b792c182d0aeb87c1a3e207319bc Mon Sep 17 00:00:00 2001 From: Nikita Mescheryakov Date: Tue, 23 Sep 2025 23:00:16 +0500 Subject: [PATCH 057/228] internal/ethapi: fix merge transition in eth_simulate (#32616) This PR fixes the fork detection of `eth_simulateV1`, particularly for networks that are post-merge since genesis, like Hoodi. --- consensus/beacon/consensus.go | 14 ++------------ internal/ethapi/simulate.go | 14 +++++++++++--- params/config.go | 10 ++++++++++ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 196cbc857ce0..84926c3d0b67 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -71,16 +71,6 @@ func New(ethone consensus.Engine) *Beacon { return &Beacon{ethone: ethone} } -// isPostMerge reports whether the given block number is assumed to be post-merge. -// Here we check the MergeNetsplitBlock to allow configuring networks with a PoW or -// PoA chain for unit testing purposes. -func isPostMerge(config *params.ChainConfig, blockNum uint64, timestamp uint64) bool { - mergedAtGenesis := config.TerminalTotalDifficulty != nil && config.TerminalTotalDifficulty.Sign() == 0 - return mergedAtGenesis || - config.MergeNetsplitBlock != nil && blockNum >= config.MergeNetsplitBlock.Uint64() || - config.ShanghaiTime != nil && timestamp >= *config.ShanghaiTime -} - // Author implements consensus.Engine, returning the verified author of the block. func (beacon *Beacon) Author(header *types.Header) (common.Address, error) { if !beacon.IsPoSHeader(header) { @@ -328,7 +318,7 @@ func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers [ // Prepare implements consensus.Engine, initializing the difficulty field of a // header to conform to the beacon protocol. The changes are done inline. func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { - if !isPostMerge(chain.Config(), header.Number.Uint64(), header.Time) { + if !chain.Config().IsPostMerge(header.Number.Uint64(), header.Time) { return beacon.ethone.Prepare(chain, header) } header.Difficulty = beaconDifficulty @@ -442,7 +432,7 @@ func (beacon *Beacon) SealHash(header *types.Header) common.Hash { // the difficulty that a new block should have when created at time // given the parent block's time and difficulty. func (beacon *Beacon) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { - if !isPostMerge(chain.Config(), parent.Number.Uint64()+1, time) { + if !chain.Config().IsPostMerge(parent.Number.Uint64()+1, time) { return beacon.ethone.CalcDifficulty(chain, time, parent) } return beaconDifficulty diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index e26e5bd0e92c..75b5c5ffa86c 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -474,22 +474,30 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) { overrides := block.BlockOverrides var withdrawalsHash *common.Hash - if sim.chainConfig.IsShanghai(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { + number := overrides.Number.ToInt() + timestamp := (uint64)(*overrides.Time) + if sim.chainConfig.IsShanghai(number, timestamp) { withdrawalsHash = &types.EmptyWithdrawalsHash } var parentBeaconRoot *common.Hash - if sim.chainConfig.IsCancun(overrides.Number.ToInt(), (uint64)(*overrides.Time)) { + if sim.chainConfig.IsCancun(number, timestamp) { parentBeaconRoot = &common.Hash{} if overrides.BeaconRoot != nil { parentBeaconRoot = overrides.BeaconRoot } } + // Set difficulty to zero if the given block is post-merge. Without this, all post-merge hardforks would remain inactive. + // For example, calling eth_simulateV1(..., blockParameter: 0x0) on hoodi network will cause all blocks to have a difficulty of 1 and be treated as pre-merge. + difficulty := header.Difficulty + if sim.chainConfig.IsPostMerge(number.Uint64(), timestamp) { + difficulty = big.NewInt(0) + } header = overrides.MakeHeader(&types.Header{ UncleHash: types.EmptyUncleHash, ReceiptHash: types.EmptyReceiptsHash, TxHash: types.EmptyTxsHash, Coinbase: header.Coinbase, - Difficulty: header.Difficulty, + Difficulty: difficulty, GasLimit: header.GasLimit, WithdrawalsHash: withdrawalsHash, ParentBeaconRoot: parentBeaconRoot, diff --git a/params/config.go b/params/config.go index cf94fb67586c..3788325d5ca2 100644 --- a/params/config.go +++ b/params/config.go @@ -678,6 +678,16 @@ func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *bi return parentTotalDiff.Cmp(c.TerminalTotalDifficulty) < 0 && totalDiff.Cmp(c.TerminalTotalDifficulty) >= 0 } +// IsPostMerge reports whether the given block number is assumed to be post-merge. +// Here we check the MergeNetsplitBlock to allow configuring networks with a PoW or +// PoA chain for unit testing purposes. +func (c *ChainConfig) IsPostMerge(blockNum uint64, timestamp uint64) bool { + mergedAtGenesis := c.TerminalTotalDifficulty != nil && c.TerminalTotalDifficulty.Sign() == 0 + return mergedAtGenesis || + c.MergeNetsplitBlock != nil && blockNum >= c.MergeNetsplitBlock.Uint64() || + c.ShanghaiTime != nil && timestamp >= *c.ShanghaiTime +} + // IsShanghai returns whether time is either equal to the Shanghai fork time or greater. func (c *ChainConfig) IsShanghai(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.ShanghaiTime, time) From 22bce45a315d2e77b2c5ed982abfa9a20a4e7fa7 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Wed, 24 Sep 2025 05:36:56 +0300 Subject: [PATCH 058/228] trie: align AllFFPrefix test assertion and message (#32719) --- trie/iterator_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trie/iterator_test.go b/trie/iterator_test.go index f1451cef904a..3e0ef126bede 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -1005,9 +1005,9 @@ func TestPrefixIteratorEdgeCases(t *testing.T) { count++ } } - // Should find at least the allFF key itself + // Should find exactly the two keys with the all-0xff prefix if count != 2 { - t.Errorf("Expected at least 1 result for all-0xff prefix, got %d", count) + t.Errorf("Expected 2 results for all-0xff prefix, got %d", count) } }) From ec3b9892bbec8fac76f0d653c374cb1b53796418 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 24 Sep 2025 19:40:49 +0200 Subject: [PATCH 059/228] build: module-aware FindMainPackages (#32736) This fixes `go run build/ci.go install`. It was failing because we resolved all main packages by parsing sources, which fails when the source directory contains multiple modules. --- build/ci.go | 2 +- internal/build/util.go | 32 ++++++++++---------------------- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/build/ci.go b/build/ci.go index c5d522c0e621..c6f4f28c87ea 100644 --- a/build/ci.go +++ b/build/ci.go @@ -221,7 +221,7 @@ func doInstall(cmdline []string) { // Default: collect all 'main' packages in cmd/ and build those. packages := flag.Args() if len(packages) == 0 { - packages = build.FindMainPackages("./cmd") + packages = build.FindMainPackages(&tc, "./cmd/...") } // Do the build! diff --git a/internal/build/util.go b/internal/build/util.go index 27531d3d9511..6e6632c75048 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -21,8 +21,6 @@ import ( "bytes" "flag" "fmt" - "go/parser" - "go/token" "io" "log" "os" @@ -219,28 +217,18 @@ func UploadSFTP(identityFile, host, dir string, files []string) error { // FindMainPackages finds all 'main' packages in the given directory and returns their // package paths. -func FindMainPackages(dir string) []string { - var commands []string - cmds, err := os.ReadDir(dir) +func FindMainPackages(tc *GoToolchain, pattern string) []string { + list := tc.Go("list", "-f", `{{if eq .Name "main"}}{{.ImportPath}}{{end}}`, pattern) + output, err := list.Output() if err != nil { - log.Fatal(err) + log.Fatal("go list failed:", err) } - for _, cmd := range cmds { - pkgdir := filepath.Join(dir, cmd.Name()) - if !cmd.IsDir() { - continue - } - pkgs, err := parser.ParseDir(token.NewFileSet(), pkgdir, nil, parser.PackageClauseOnly) - if err != nil { - log.Fatal(err) - } - for name := range pkgs { - if name == "main" { - path := "./" + filepath.ToSlash(pkgdir) - commands = append(commands, path) - break - } + var result []string + for l := range bytes.Lines(output) { + l = bytes.TrimSpace(l) + if len(l) > 0 { + result = append(result, string(l)) } } - return commands + return result } From 560958ff74b4530eaa5dc1ad828dc7271420218e Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 24 Sep 2025 19:41:33 +0200 Subject: [PATCH 060/228] miner: default gaslimit 60M (#32734) --- miner/miner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miner/miner.go b/miner/miner.go index 20845b5036c7..810cc20a6c61 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -52,7 +52,7 @@ type Config struct { // DefaultConfig contains default settings for miner. var DefaultConfig = Config{ - GasCeil: 45_000_000, + GasCeil: 60_000_000, GasPrice: big.NewInt(params.GWei / 1000), // The default recommit time is chosen as two seconds since From f0a60043f271d9c55b7fed8108a90476e34bf2e3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 24 Sep 2025 20:03:17 +0200 Subject: [PATCH 061/228] core/txpool/blobpool: fork boundary conversion 3 (#32716) This implements the conversion of existing blob transactions to the new proof version. Conversion is triggered at the Osaka fork boundary. The conversion is designed to be idempotent, and may be triggered multiple times in case of a reorg around the fork boundary. This change is the last missing piece that completes our strategy for the blobpool conversion. After the Osaka fork, - new transactions will be converted on-the-fly upon entry to the pool - reorged transactions will be converted while being reinjected - (this change) existing transactions will be converted in the background --------- Co-authored-by: Gary Rong Co-authored-by: lightclient --- core/txpool/blobpool/blobpool.go | 170 +++++++++++++++++++++++- core/txpool/blobpool/blobpool_test.go | 181 ++++++++++++++++++++++++++ core/txpool/blobpool/conversion.go | 91 ++++++++++--- core/txpool/blobpool/lookup.go | 10 ++ 4 files changed, 431 insertions(+), 21 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index db965bc71a94..f4aa406e2a1f 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -21,10 +21,12 @@ import ( "container/heap" "errors" "fmt" + "maps" "math" "math/big" "os" "path/filepath" + "slices" "sort" "sync" "sync/atomic" @@ -337,7 +339,7 @@ type BlobPool struct { signer types.Signer // Transaction signer to use for sender recovery chain BlockChain // Chain object to access the state through - cQueue *conversionQueue // The queue for performing legacy sidecar conversion + cQueue *conversionQueue // The queue for performing legacy sidecar conversion (TODO: remove after Osaka) head atomic.Pointer[types.Header] // Current head of the chain state *state.StateDB // Current state at the head of the chain @@ -883,6 +885,172 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { basefeeGauge.Update(int64(basefee.Uint64())) blobfeeGauge.Update(int64(blobfee.Uint64())) p.updateStorageMetrics() + + // Perform the conversion logic at the fork boundary + if !p.chain.Config().IsOsaka(oldHead.Number, oldHead.Time) && p.chain.Config().IsOsaka(newHead.Number, newHead.Time) { + // Deep copy all indexed transaction metadata. + var ( + ids = make(map[common.Address]map[uint64]uint64) + txs = make(map[common.Address]map[uint64]common.Hash) + ) + for sender, list := range p.index { + ids[sender] = make(map[uint64]uint64) + txs[sender] = make(map[uint64]common.Hash) + for _, m := range list { + ids[sender][m.nonce] = m.id + txs[sender][m.nonce] = m.hash + } + } + // Initiate the background conversion thread. + p.cQueue.launchBillyConversion(func() { + p.convertLegacySidecars(ids, txs) + }) + } +} + +// compareAndSwap checks if the specified transaction is still tracked in the pool +// and replace the metadata accordingly. It should only be used in the fork boundary +// bulk conversion. If it fails for some reason, the subsequent txs won't be dropped +// for simplicity which we assume it's very likely to happen. +// +// The returned flag indicates whether the replacement succeeded. +func (p *BlobPool) compareAndSwap(address common.Address, hash common.Hash, blob []byte, oldID uint64, oldStorageSize uint32) bool { + p.lock.Lock() + defer p.lock.Unlock() + + newId, err := p.store.Put(blob) + if err != nil { + log.Error("Failed to store transaction", "hash", hash, "err", err) + return false + } + newSize := uint64(len(blob)) + newStorageSize := p.store.Size(newId) + + // Terminate the procedure if the transaction was already evicted. The + // newly added blob should be removed before return. + if !p.lookup.update(hash, newId, newSize) { + if derr := p.store.Delete(newId); derr != nil { + log.Error("Failed to delete the dangling blob tx", "err", derr) + } else { + log.Warn("Deleted the dangling blob tx", "id", newId) + } + return false + } + // Update the metadata of blob transaction + for _, meta := range p.index[address] { + if meta.hash == hash { + meta.id = newId + meta.version = types.BlobSidecarVersion1 + meta.storageSize = newStorageSize + meta.size = newSize + + p.stored += uint64(newStorageSize) + p.stored -= uint64(oldStorageSize) + break + } + } + if err := p.store.Delete(oldID); err != nil { + log.Error("Failed to delete the legacy transaction", "hash", hash, "id", oldID, "err", err) + } + return true +} + +// convertLegacySidecar fetches transaction data from the store, performs an +// on-the-fly conversion. This function is intended for use only during the +// Osaka fork transition period. +// +// The returned flag indicates whether the replacement succeeds or not. +func (p *BlobPool) convertLegacySidecar(sender common.Address, hash common.Hash, id uint64) bool { + start := time.Now() + + // Retrieves the legacy blob transaction from the underlying store with + // read lock held, preventing any potential data race around the slot + // specified by the id. + p.lock.RLock() + data, err := p.store.Get(id) + if err != nil { + p.lock.RUnlock() + // The transaction may have been evicted simultaneously, safe to skip conversion. + log.Debug("Blob transaction is missing", "hash", hash, "id", id, "err", err) + return false + } + oldStorageSize := p.store.Size(id) + p.lock.RUnlock() + + // Decode the transaction, the failure is not expected and report the error + // loudly if possible. If the blob transaction in this slot is corrupted, + // leave it in the store, it will be dropped during the next pool + // initialization. + var tx types.Transaction + if err = rlp.DecodeBytes(data, &tx); err != nil { + log.Error("Blob transaction is corrupted", "hash", hash, "id", id, "err", err) + return false + } + + // Skip conversion if the transaction does not match the expected hash, or if it was + // already converted. This can occur if the original transaction was evicted from the + // pool and the slot was reused by a new one. + if tx.Hash() != hash { + log.Warn("Blob transaction was replaced", "hash", hash, "id", id, "stored", tx.Hash()) + return false + } + sc := tx.BlobTxSidecar() + if sc.Version >= types.BlobSidecarVersion1 { + log.Debug("Skipping conversion of blob tx", "hash", hash, "id", id) + return false + } + + // Perform the sidecar conversion, the failure is not expected and report the error + // loudly if possible. + if err := tx.BlobTxSidecar().ToV1(); err != nil { + log.Error("Failed to convert blob transaction", "hash", hash, "err", err) + return false + } + + // Encode the converted transaction, the failure is not expected and report + // the error loudly if possible. + blob, err := rlp.EncodeToBytes(&tx) + if err != nil { + log.Error("Failed to encode blob transaction", "hash", tx.Hash(), "err", err) + return false + } + + // Replace the legacy blob transaction with the converted format. + if !p.compareAndSwap(sender, hash, blob, id, oldStorageSize) { + log.Error("Failed to replace the legacy transaction", "hash", hash) + return false + } + log.Debug("Converted legacy blob transaction", "hash", hash, "elapsed", common.PrettyDuration(time.Since(start))) + return true +} + +// convertLegacySidecars converts all given transactions to sidecar version 1. +// +// If any of them fails to be converted, the subsequent transactions will still +// be processed, as we assume the failure is very unlikely to happen. If happens, +// these transactions will be stuck in the pool until eviction. +func (p *BlobPool) convertLegacySidecars(ids map[common.Address]map[uint64]uint64, txs map[common.Address]map[uint64]common.Hash) { + var ( + start = time.Now() + success int + failure int + ) + for addr, list := range txs { + // Transactions evicted from the pool must be contiguous, if in any case, + // the transactions are gapped with each other, they will be discarded. + nonces := slices.Collect(maps.Keys(list)) + slices.Sort(nonces) + + // Convert the txs with nonce order + for _, nonce := range nonces { + if p.convertLegacySidecar(addr, list[nonce], ids[addr][nonce]) { + success++ + } else { + failure++ + } + } + } + log.Info("Completed blob transaction conversion", "discarded", failure, "injected", success, "elapsed", common.PrettyDuration(time.Since(start))) } // reorg assembles all the transactors and missing transactions between an old diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 75a87940bd9a..f0f00c805589 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -2098,6 +2098,185 @@ func TestGetBlobs(t *testing.T) { pool.Close() } +// TestSidecarConversion will verify that after the Osaka fork, all legacy +// sidecars in the pool are successfully convert to v1 sidecars. +func TestSidecarConversion(t *testing.T) { + // log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) + + // Create a temporary folder for the persistent backend + storage := t.TempDir() + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + + var ( + preOsakaTxs = make(types.Transactions, 10) + postOsakaTxs = make(types.Transactions, 3) + keys = make([]*ecdsa.PrivateKey, len(preOsakaTxs)+len(postOsakaTxs)) + addrs = make([]common.Address, len(preOsakaTxs)+len(postOsakaTxs)) + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + ) + for i := range keys { + keys[i], _ = crypto.GenerateKey() + addrs[i] = crypto.PubkeyToAddress(keys[i].PublicKey) + statedb.AddBalance(addrs[i], uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + } + for i := range preOsakaTxs { + preOsakaTxs[i] = makeMultiBlobTx(0, 1, 1000, 100, 2, 0, keys[i], types.BlobSidecarVersion0) + } + for i := range postOsakaTxs { + if i == 0 { + // First has a v0 sidecar. + postOsakaTxs[i] = makeMultiBlobTx(0, 1, 1000, 100, 1, 0, keys[len(preOsakaTxs)+i], types.BlobSidecarVersion0) + } + postOsakaTxs[i] = makeMultiBlobTx(0, 1, 1000, 100, 1, 0, keys[len(preOsakaTxs)+i], types.BlobSidecarVersion1) + } + statedb.Commit(0, true, false) + + // Test plan: + // 1) Create a bunch v0 sidecar txs and add to pool before Osaka. + // 2) Pass in new Osaka header to activate the conversion thread. + // 3) Continue adding both v0 and v1 transactions to the pool. + // 4) Verify that as additional blocks come in, transactions involved in the + // migration are correctly discarded. + + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + LondonBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + CancunTime: newUint64(0), + PragueTime: newUint64(0), + OsakaTime: newUint64(1), + BlobScheduleConfig: params.DefaultBlobSchedule, + } + chain := &testBlockChain{ + config: config, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + blocks: make(map[uint64]*types.Block), + } + + // Create 3 blocks: + // - the current block, before Osaka + // - the first block after Osaka + // - another post-Osaka block with several transactions in it + header0 := chain.CurrentBlock() + header0.Time = 0 + chain.blocks[0] = types.NewBlockWithHeader(header0) + + header1 := chain.CurrentBlock() + header1.Number = big.NewInt(1) + header1.Time = 1 + chain.blocks[1] = types.NewBlockWithHeader(header1) + + header2 := chain.CurrentBlock() + header2.Time = 2 + header2.Number = big.NewInt(2) + + // Make a copy of one of the pre-Osaka transactions and convert it to v1 here + // so that we can add it to the pool later and ensure a duplicate is not added + // by the conversion queue. + tx := preOsakaTxs[len(preOsakaTxs)-1] + sc := *tx.BlobTxSidecar() // copy sidecar + sc.ToV1() + tx.WithBlobTxSidecar(&sc) + + block2 := types.NewBlockWithHeader(header2).WithBody(types.Body{Transactions: append(postOsakaTxs, tx)}) + chain.blocks[2] = block2 + + pool := New(Config{Datadir: storage}, chain, nil) + if err := pool.Init(1, header0, newReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + + errs := pool.Add(preOsakaTxs, true) + for i, err := range errs { + if err != nil { + t.Errorf("failed to insert blob tx from %s: %s", addrs[i], errs[i]) + } + } + + // Kick off migration. + pool.Reset(header0, header1) + + // Add the v0 sidecar tx, but don't block so we can keep doing other stuff + // while it converts the sidecar. + addDone := make(chan struct{}) + go func() { + pool.Add(types.Transactions{postOsakaTxs[0]}, false) + close(addDone) + }() + + // Add the post-Osaka v1 sidecar txs. + errs = pool.Add(postOsakaTxs[1:], false) + for _, err := range errs { + if err != nil { + t.Fatalf("expected tx add to succeed: %v", err) + } + } + + // Wait for the first tx's conversion to complete, then check that all + // transactions added after Osaka can be accounted for in the pool. + <-addDone + pending := pool.Pending(txpool.PendingFilter{BlobTxs: true, BlobVersion: types.BlobSidecarVersion1}) + for _, tx := range postOsakaTxs { + from, _ := pool.signer.Sender(tx) + if len(pending[from]) != 1 || pending[from][0].Hash != tx.Hash() { + t.Fatalf("expected post-Osaka txs to be pending") + } + } + + // Now update the pool with the next block. This should cause the pool to + // clear out the post-Osaka txs since they were included in block 2. Since the + // test blockchain doesn't manage nonces, we'll just do that manually before + // the reset is called. Don't forget about the pre-Osaka transaction we also + // added to block 2! + for i := range postOsakaTxs { + statedb.SetNonce(addrs[len(preOsakaTxs)+i], 1, tracing.NonceChangeEoACall) + } + statedb.SetNonce(addrs[len(preOsakaTxs)-1], 1, tracing.NonceChangeEoACall) + pool.Reset(header1, block2.Header()) + + // Now verify no post-Osaka transactions are tracked by the pool. + for i, tx := range postOsakaTxs { + if pool.Get(tx.Hash()) != nil { + t.Fatalf("expected txs added post-osaka to have been placed in limbo due to inclusion in a block: index %d, hash %s", i, tx.Hash()) + } + } + + // Wait for the pool migration to complete. + <-pool.cQueue.anyBillyConversionDone + + // Verify all transactions in the pool were converted and verify the + // subsequent cell proofs. + count, _ := pool.Stats() + if count != len(preOsakaTxs)-1 { + t.Errorf("expected pending count to match initial tx count: pending=%d, expected=%d", count, len(preOsakaTxs)-1) + } + for addr, acc := range pool.index { + for _, m := range acc { + if m.version != types.BlobSidecarVersion1 { + t.Errorf("expected sidecar to have been converted: from %s, hash %s", addr, m.hash) + } + tx := pool.Get(m.hash) + if tx == nil { + t.Errorf("failed to get tx by hash: %s", m.hash) + } + sc := tx.BlobTxSidecar() + if err := kzg4844.VerifyCellProofs(sc.Blobs, sc.Commitments, sc.Proofs); err != nil { + t.Errorf("failed to verify cell proofs for tx %s after conversion: %s", m.hash, err) + } + } + } + + verifyPoolInternals(t, pool) + + // Launch conversion a second time. + // This is just a sanity check to ensure we can handle it. + pool.Reset(header0, header1) + + pool.Close() +} + // fakeBilly is a billy.Database implementation which just drops data on the floor. type fakeBilly struct { billy.Database @@ -2180,3 +2359,5 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) { } } } + +func newUint64(val uint64) *uint64 { return &val } diff --git a/core/txpool/blobpool/conversion.go b/core/txpool/blobpool/conversion.go index 5026892fc8a1..95828d83b2c6 100644 --- a/core/txpool/blobpool/conversion.go +++ b/core/txpool/blobpool/conversion.go @@ -32,8 +32,8 @@ import ( // with 6 blobs each) would consume approximately 1.5GB of memory. const maxPendingConversionTasks = 2048 -// cTask represents a conversion task with an attached legacy blob transaction. -type cTask struct { +// txConvert represents a conversion task with an attached legacy blob transaction. +type txConvert struct { tx *types.Transaction // Legacy blob transaction done chan error // Channel for signaling back if the conversion succeeds } @@ -43,17 +43,27 @@ type cTask struct { // it is performed in the background by a single thread, ensuring the main Geth // process is not overloaded. type conversionQueue struct { - tasks chan *cTask - quit chan struct{} - closed chan struct{} + tasks chan *txConvert + startBilly chan func() + quit chan struct{} + closed chan struct{} + + billyQueue []func() + billyTaskDone chan struct{} + + // This channel will be closed when the first billy conversion finishes. + // It's added for unit tests to synchronize with the conversion progress. + anyBillyConversionDone chan struct{} } // newConversionQueue constructs the conversion queue. func newConversionQueue() *conversionQueue { q := &conversionQueue{ - tasks: make(chan *cTask), - quit: make(chan struct{}), - closed: make(chan struct{}), + tasks: make(chan *txConvert), + startBilly: make(chan func()), + quit: make(chan struct{}), + closed: make(chan struct{}), + anyBillyConversionDone: make(chan struct{}), } go q.loop() return q @@ -66,13 +76,23 @@ func newConversionQueue() *conversionQueue { func (q *conversionQueue) convert(tx *types.Transaction) error { done := make(chan error, 1) select { - case q.tasks <- &cTask{tx: tx, done: done}: + case q.tasks <- &txConvert{tx: tx, done: done}: return <-done case <-q.closed: return errors.New("conversion queue closed") } } +// launchBillyConversion starts a conversion task in the background. +func (q *conversionQueue) launchBillyConversion(fn func()) error { + select { + case q.startBilly <- fn: + return nil + case <-q.closed: + return errors.New("conversion queue closed") + } +} + // close terminates the conversion queue. func (q *conversionQueue) close() { select { @@ -85,7 +105,7 @@ func (q *conversionQueue) close() { } // run converts a batch of legacy blob txs to the new cell proof format. -func (q *conversionQueue) run(tasks []*cTask, done chan struct{}, interrupt *atomic.Int32) { +func (q *conversionQueue) run(tasks []*txConvert, done chan struct{}, interrupt *atomic.Int32) { defer close(done) for _, t := range tasks { @@ -116,37 +136,68 @@ func (q *conversionQueue) loop() { // The pending tasks for sidecar conversion. We assume the number of legacy // blob transactions requiring conversion will not be excessive. However, // a hard cap is applied as a protective measure. - cTasks []*cTask + txTasks []*txConvert + + firstBilly = true ) + for { select { case t := <-q.tasks: - if len(cTasks) >= maxPendingConversionTasks { + if len(txTasks) >= maxPendingConversionTasks { t.done <- errors.New("conversion queue is overloaded") continue } - cTasks = append(cTasks, t) + txTasks = append(txTasks, t) // Launch the background conversion thread if it's idle if done == nil { done, interrupt = make(chan struct{}), new(atomic.Int32) - tasks := slices.Clone(cTasks) - cTasks = cTasks[:0] + tasks := slices.Clone(txTasks) + txTasks = txTasks[:0] go q.run(tasks, done, interrupt) } case <-done: done, interrupt = nil, nil + case fn := <-q.startBilly: + q.billyQueue = append(q.billyQueue, fn) + q.runNextBillyTask() + + case <-q.billyTaskDone: + if firstBilly { + close(q.anyBillyConversionDone) + firstBilly = false + } + q.runNextBillyTask() + case <-q.quit: - if done == nil { - return + if done != nil { + log.Debug("Waiting for blob proof conversion to exit") + interrupt.Store(1) + <-done + } + if q.billyTaskDone != nil { + log.Debug("Waiting for blobpool billy conversion to exit") + <-q.billyTaskDone } - interrupt.Store(1) - log.Debug("Waiting for blob proof conversion to exit") - <-done return } } } + +func (q *conversionQueue) runNextBillyTask() { + if len(q.billyQueue) == 0 { + q.billyTaskDone = nil + return + } + + fn := q.billyQueue[0] + q.billyQueue = append(q.billyQueue[:0], q.billyQueue[1:]...) + + done := make(chan struct{}) + go func() { defer close(done); fn() }() + q.billyTaskDone = done +} diff --git a/core/txpool/blobpool/lookup.go b/core/txpool/blobpool/lookup.go index 7607cd487a2b..874ca85b8c2d 100644 --- a/core/txpool/blobpool/lookup.go +++ b/core/txpool/blobpool/lookup.go @@ -110,3 +110,13 @@ func (l *lookup) untrack(tx *blobTxMeta) { } } } + +// update updates the transaction index. It should only be used in the conversion. +func (l *lookup) update(hash common.Hash, id uint64, size uint64) bool { + meta, exists := l.txIndex[hash] + if !exists { + return false + } + meta.id, meta.size = id, size + return true +} From 5921af20c94399e0c6475cfc7b14025129e362f6 Mon Sep 17 00:00:00 2001 From: radik878 Date: Thu, 25 Sep 2025 03:57:01 +0300 Subject: [PATCH 062/228] trie: fix TestOneElementProof expected value message (#32738) - Correct the error message in TestOneElementProof to expect 'v' instead of 'k'. - The trie is updated with key "k" and value "v"; on mismatch the expected value must be 'v'. - Aligns the message with the actual test logic and other similar checks in this file, reducing confusion during test failures. No behavioral changes. --- trie/proof_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trie/proof_test.go b/trie/proof_test.go index b3c9dd753c28..40c49f358b33 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -109,7 +109,7 @@ func TestOneElementProof(t *testing.T) { t.Fatalf("prover %d: failed to verify proof: %v\nraw proof: %x", i, err, proof) } if !bytes.Equal(val, []byte("v")) { - t.Fatalf("prover %d: verified value mismatch: have %x, want 'k'", i, val) + t.Fatalf("prover %d: verified value mismatch: have %x, want 'v'", i, val) } } } From 5c7313c1ad9c44ffc6a221037f10e50219a0175e Mon Sep 17 00:00:00 2001 From: viktorking7 <140458814+viktorking7@users.noreply.github.com> Date: Thu, 25 Sep 2025 03:47:55 +0200 Subject: [PATCH 063/228] accounts/keystore: use ticker to avoid timer allocations (#32732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace time.After with a long‑lived time.Ticker in KeyStore.updater, preventing per‑iteration timer allocations and potential timer buildup. Co-authored-by: lightclient --- accounts/keystore/keystore.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index f9cc3edac8c4..3e4266924fd9 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -196,11 +196,14 @@ func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscripti // forces a manual refresh (only triggers for systems where the filesystem notifier // is not running). func (ks *KeyStore) updater() { + ticker := time.NewTicker(walletRefreshCycle) + defer ticker.Stop() + for { // Wait for an account update or a refresh timeout select { case <-ks.changes: - case <-time.After(walletRefreshCycle): + case <-ticker.C: } // Run the wallet refresher ks.refreshWallets() From af56c7824180a22683ee15beaad00b911e7c3eb0 Mon Sep 17 00:00:00 2001 From: hero5512 Date: Wed, 24 Sep 2025 22:05:14 -0400 Subject: [PATCH 064/228] accounts/abi/bind: fix data race in TestWaitDeployedCornerCases (#32740) Fixes race in WaitDeploy test where the backend is closed before goroutine using it wraps up. --------- Co-authored-by: lightclient --- accounts/abi/bind/v2/util_test.go | 63 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/accounts/abi/bind/v2/util_test.go b/accounts/abi/bind/v2/util_test.go index b1b647a7b9c3..a9f5b4035ce1 100644 --- a/accounts/abi/bind/v2/util_test.go +++ b/accounts/abi/bind/v2/util_test.go @@ -100,22 +100,29 @@ func TestWaitDeployed(t *testing.T) { } func TestWaitDeployedCornerCases(t *testing.T) { - backend := simulated.NewBackend( - types.GenesisAlloc{ - crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, - }, + var ( + backend = simulated.NewBackend( + types.GenesisAlloc{ + crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, + }, + ) + head, _ = backend.Client().HeaderByNumber(t.Context(), nil) // Should be child's, good enough + gasPrice = new(big.Int).Add(head.BaseFee, big.NewInt(1)) + signer = types.LatestSigner(params.AllDevChainProtocolChanges) + code = common.FromHex("6060604052600a8060106000396000f360606040526008565b00") + ctx, cancel = context.WithCancel(t.Context()) ) defer backend.Close() - head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - // Create a transaction to an account. - code := "6060604052600a8060106000396000f360606040526008565b00" - tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code)) - tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + // 1. WaitDeploy on a transaction that does not deploy a contract, verify it + // returns an error. + tx := types.MustSignNewTx(testKey, signer, &types.LegacyTx{ + Nonce: 0, + To: &common.Address{0x01}, + Gas: 300000, + GasPrice: gasPrice, + Data: code, + }) if err := backend.Client().SendTransaction(ctx, tx); err != nil { t.Errorf("failed to send transaction: %q", err) } @@ -124,14 +131,23 @@ func TestWaitDeployedCornerCases(t *testing.T) { t.Errorf("error mismatch: want %q, got %q, ", bind.ErrNoAddressInReceipt, err) } - // Create a transaction that is not mined. - tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code)) - tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey) - + // 2. Create a contract, but cancel the WaitDeploy before it is mined. + tx = types.MustSignNewTx(testKey, signer, &types.LegacyTx{ + Nonce: 1, + Gas: 300000, + GasPrice: gasPrice, + Data: code, + }) + + // Wait in another thread so that we can quickly cancel it after submitting + // the transaction. + done := make(chan struct{}) go func() { - contextCanceled := errors.New("context canceled") - if _, err := bind.WaitDeployed(ctx, backend.Client(), tx.Hash()); err.Error() != contextCanceled.Error() { - t.Errorf("error mismatch: want %q, got %q, ", contextCanceled, err) + defer close(done) + want := errors.New("context canceled") + _, err := bind.WaitDeployed(ctx, backend.Client(), tx.Hash()) + if err == nil || errors.Is(want, err) { + t.Errorf("error mismatch: want %v, got %v", want, err) } }() @@ -139,4 +155,11 @@ func TestWaitDeployedCornerCases(t *testing.T) { t.Errorf("failed to send transaction: %q", err) } cancel() + + // Wait for goroutine to exit or for a timeout. + select { + case <-done: + case <-time.After(time.Second * 2): + t.Fatalf("failed to cancel wait deploy") + } } From b3d985cd7fa1a6f633c802b0ac08564a81d64a77 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Thu, 25 Sep 2025 02:21:41 -0600 Subject: [PATCH 065/228] all: add bpo1 and bpo2 overrides (#32737) This adds overrides to the cli for BPO1 and BPO2. --------- Co-authored-by: Gary Rong --- cmd/geth/chaincmd.go | 10 ++++++++++ cmd/geth/config.go | 8 ++++++++ cmd/geth/main.go | 2 ++ cmd/utils/flags.go | 10 ++++++++++ core/genesis.go | 8 ++++++++ eth/backend.go | 6 ++++++ eth/ethconfig/config.go | 6 ++++++ eth/ethconfig/gen_config.go | 12 ++++++++++++ 8 files changed, 62 insertions(+) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 71ff821bb9b7..c5145bbfb738 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -59,6 +59,8 @@ var ( Flags: slices.Concat([]cli.Flag{ utils.CachePreimagesFlag, utils.OverrideOsaka, + utils.OverrideBPO1, + utils.OverrideBPO2, utils.OverrideVerkle, }, utils.DatabaseFlags), Description: ` @@ -274,6 +276,14 @@ func initGenesis(ctx *cli.Context) error { v := ctx.Uint64(utils.OverrideOsaka.Name) overrides.OverrideOsaka = &v } + if ctx.IsSet(utils.OverrideBPO1.Name) { + v := ctx.Uint64(utils.OverrideBPO1.Name) + overrides.OverrideBPO1 = &v + } + if ctx.IsSet(utils.OverrideBPO2.Name) { + v := ctx.Uint64(utils.OverrideBPO2.Name) + overrides.OverrideBPO2 = &v + } if ctx.IsSet(utils.OverrideVerkle.Name) { v := ctx.Uint64(utils.OverrideVerkle.Name) overrides.OverrideVerkle = &v diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 33a3eadea844..fcb315af979c 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -227,6 +227,14 @@ func makeFullNode(ctx *cli.Context) *node.Node { v := ctx.Uint64(utils.OverrideOsaka.Name) cfg.Eth.OverrideOsaka = &v } + if ctx.IsSet(utils.OverrideBPO1.Name) { + v := ctx.Uint64(utils.OverrideBPO1.Name) + cfg.Eth.OverrideBPO1 = &v + } + if ctx.IsSet(utils.OverrideBPO2.Name) { + v := ctx.Uint64(utils.OverrideBPO2.Name) + cfg.Eth.OverrideBPO2 = &v + } if ctx.IsSet(utils.OverrideVerkle.Name) { v := ctx.Uint64(utils.OverrideVerkle.Name) cfg.Eth.OverrideVerkle = &v diff --git a/cmd/geth/main.go b/cmd/geth/main.go index f380c9f2d448..2465b52ad1f0 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -63,6 +63,8 @@ var ( utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.OverrideOsaka, + utils.OverrideBPO1, + utils.OverrideBPO2, utils.OverrideVerkle, utils.EnablePersonal, // deprecated utils.TxPoolLocalsFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 5e96185dbd03..c7775adb53c8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -248,6 +248,16 @@ var ( Usage: "Manually specify the Osaka fork timestamp, overriding the bundled setting", Category: flags.EthCategory, } + OverrideBPO1 = &cli.Uint64Flag{ + Name: "override.bpo1", + Usage: "Manually specify the bpo1 fork timestamp, overriding the bundled setting", + Category: flags.EthCategory, + } + OverrideBPO2 = &cli.Uint64Flag{ + Name: "override.bpo2", + Usage: "Manually specify the bpo2 fork timestamp, overriding the bundled setting", + Category: flags.EthCategory, + } OverrideVerkle = &cli.Uint64Flag{ Name: "override.verkle", Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting", diff --git a/core/genesis.go b/core/genesis.go index 13d4addd7e7b..2fd044c70a7d 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -259,6 +259,8 @@ func (e *GenesisMismatchError) Error() string { // ChainOverrides contains the changes to chain config. type ChainOverrides struct { OverrideOsaka *uint64 + OverrideBPO1 *uint64 + OverrideBPO2 *uint64 OverrideVerkle *uint64 } @@ -270,6 +272,12 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error { if o.OverrideOsaka != nil { cfg.OsakaTime = o.OverrideOsaka } + if o.OverrideBPO1 != nil { + cfg.BPO1Time = o.OverrideBPO1 + } + if o.OverrideBPO2 != nil { + cfg.BPO2Time = o.OverrideBPO2 + } if o.OverrideVerkle != nil { cfg.VerkleTime = o.OverrideVerkle } diff --git a/eth/backend.go b/eth/backend.go index 3bfe0765f4cb..85095618222d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -262,6 +262,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.OverrideOsaka != nil { overrides.OverrideOsaka = config.OverrideOsaka } + if config.OverrideBPO1 != nil { + overrides.OverrideBPO1 = config.OverrideBPO1 + } + if config.OverrideBPO2 != nil { + overrides.OverrideBPO2 = config.OverrideBPO2 + } if config.OverrideVerkle != nil { overrides.OverrideVerkle = config.OverrideVerkle } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 4fe24c0fe077..6020387bcdba 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -175,6 +175,12 @@ type Config struct { // OverrideOsaka (TODO: remove after the fork) OverrideOsaka *uint64 `toml:",omitempty"` + // OverrideBPO1 (TODO: remove after the fork) + OverrideBPO1 *uint64 `toml:",omitempty"` + + // OverrideBPO2 (TODO: remove after the fork) + OverrideBPO2 *uint64 `toml:",omitempty"` + // OverrideVerkle (TODO: remove after the fork) OverrideVerkle *uint64 `toml:",omitempty"` } diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 50eb5c4161f3..6f6e541368fb 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -59,6 +59,8 @@ func (c Config) MarshalTOML() (interface{}, error) { RPCEVMTimeout time.Duration RPCTxFeeCap float64 OverrideOsaka *uint64 `toml:",omitempty"` + OverrideBPO1 *uint64 `toml:",omitempty"` + OverrideBPO2 *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"` } var enc Config @@ -104,6 +106,8 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.RPCEVMTimeout = c.RPCEVMTimeout enc.RPCTxFeeCap = c.RPCTxFeeCap enc.OverrideOsaka = c.OverrideOsaka + enc.OverrideBPO1 = c.OverrideBPO1 + enc.OverrideBPO2 = c.OverrideBPO2 enc.OverrideVerkle = c.OverrideVerkle return &enc, nil } @@ -153,6 +157,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { RPCEVMTimeout *time.Duration RPCTxFeeCap *float64 OverrideOsaka *uint64 `toml:",omitempty"` + OverrideBPO1 *uint64 `toml:",omitempty"` + OverrideBPO2 *uint64 `toml:",omitempty"` OverrideVerkle *uint64 `toml:",omitempty"` } var dec Config @@ -285,6 +291,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.OverrideOsaka != nil { c.OverrideOsaka = dec.OverrideOsaka } + if dec.OverrideBPO1 != nil { + c.OverrideBPO1 = dec.OverrideBPO1 + } + if dec.OverrideBPO2 != nil { + c.OverrideBPO2 = dec.OverrideBPO2 + } if dec.OverrideVerkle != nil { c.OverrideVerkle = dec.OverrideVerkle } From 2e7dc7330fb7a913f0a30e951b040e454d6818c7 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Thu, 25 Sep 2025 10:27:23 +0200 Subject: [PATCH 066/228] core/txpool/blobpool: convert and add one-by-one (#32718) This is a small improvement on #32656 in case Add was called with multiple type 3 transactions, adding transactions to the pool one-by-one as they are converted. Announcement to peers is still done in a batch. Signed-off-by: Csaba Kiraly --- core/txpool/blobpool/blobpool.go | 70 ++++++++++++++------------------ 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index f4aa406e2a1f..bfaf4d5b8e3c 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1640,11 +1640,11 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int { return available } -// preCheck performs the static validation upon the provided txs and converts +// preCheck performs the static validation upon the provided tx and converts // the legacy sidecars if Osaka fork has been activated with a short time window. // // This function is pure static and lock free. -func (p *BlobPool) preCheck(txs []*types.Transaction) ([]*types.Transaction, []error) { +func (p *BlobPool) preCheck(tx *types.Transaction) error { var ( head = p.head.Load() isOsaka = p.chain.Config().IsOsaka(head.Number, head.Time) @@ -1653,56 +1653,46 @@ func (p *BlobPool) preCheck(txs []*types.Transaction) ([]*types.Transaction, []e if isOsaka { deadline = time.Unix(int64(*p.chain.Config().OsakaTime), 0).Add(conversionTimeWindow) } - var errs []error - for _, tx := range txs { - // Validate the transaction statically at first to avoid unnecessary - // conversion. This step doesn't require lock protection. - if err := p.ValidateTxBasics(tx); err != nil { - errs = append(errs, err) - continue - } - // Before the Osaka fork, reject the blob txs with cell proofs - if !isOsaka { - if tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 { - errs = append(errs, nil) - } else { - errs = append(errs, errors.New("cell proof is not supported yet")) - } - continue - } - // After the Osaka fork, reject the legacy blob txs if the conversion - // time window is passed. - if tx.BlobTxSidecar().Version == types.BlobSidecarVersion1 { - errs = append(errs, nil) - continue - } - if head.Time > uint64(deadline.Unix()) { - errs = append(errs, errors.New("legacy blob tx is not supported")) - continue + // Validate the transaction statically at first to avoid unnecessary + // conversion. This step doesn't require lock protection. + if err := p.ValidateTxBasics(tx); err != nil { + return err + } + // Before the Osaka fork, reject the blob txs with cell proofs + if !isOsaka { + if tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 { + return nil + } else { + return errors.New("cell proof is not supported yet") } - // Convert the legacy sidecar after Osaka fork. This could be a long - // procedure which takes a few seconds, even minutes if there is a long - // queue. Fortunately it will only block the routine of the source peer - // announcing the tx, without affecting other parts. - errs = append(errs, p.cQueue.convert(tx)) } - return txs, errs + // After the Osaka fork, reject the legacy blob txs if the conversion + // time window is passed. + if tx.BlobTxSidecar().Version == types.BlobSidecarVersion1 { + return nil + } + if head.Time > uint64(deadline.Unix()) { + return errors.New("legacy blob tx is not supported") + } + // Convert the legacy sidecar after Osaka fork. This could be a long + // procedure which takes a few seconds, even minutes if there is a long + // queue. Fortunately it will only block the routine of the source peer + // announcing the tx, without affecting other parts. + return p.cQueue.convert(tx) } // Add inserts a set of blob transactions into the pool if they pass validation (both // consensus validity and pool restrictions). func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error { var ( - errs []error - adds = make([]*types.Transaction, 0, len(txs)) + errs []error = make([]error, len(txs)) + adds = make([]*types.Transaction, 0, len(txs)) ) - txs, errs = p.preCheck(txs) for i, tx := range txs { - if errs[i] != nil { + if errs[i] = p.preCheck(tx); errs[i] != nil { continue } - errs[i] = p.add(tx) - if errs[i] == nil { + if errs[i] = p.add(tx); errs[i] == nil { adds = append(adds, tx.WithoutBlobTxSidecar()) } } From 7b8fdda56eb4223e74152161c9b5c4eb98a725a4 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 25 Sep 2025 13:14:28 +0200 Subject: [PATCH 067/228] build: upgrade to execution-spec-tests v5.1.0 (#32742) https://github.com/ethereum/execution-spec-tests/releases/tag/v5.1.0 --- build/checksums.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/checksums.txt b/build/checksums.txt index ca937d115c3b..98ee3a91ef6e 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,9 +1,9 @@ # This file contains sha256 checksums of optional build dependencies. -# version:spec-tests v5.0.0 +# version:spec-tests v5.1.0 # https://github.com/ethereum/execution-spec-tests/releases -# https://github.com/ethereum/execution-spec-tests/releases/download/v5.0.0 -a5ed96800ca1af0d86fe2ee894861c24eea079bfb83b924f565bb86ba70021d5 fixtures_develop.tar.gz +# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0 +a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz # version:golang 1.25.1 # https://go.dev/dl/ From 208c4aad5d829835bc5598b2eede9f3363794197 Mon Sep 17 00:00:00 2001 From: Delweng Date: Thu, 25 Sep 2025 19:15:12 +0800 Subject: [PATCH 068/228] core/txpool: add eip2681 check for incoming transactions (#32726) --- core/txpool/validation.go | 4 ++ core/txpool/validation_test.go | 115 +++++++++++++++++++++++++++++++++ eth/api_backend_test.go | 22 +++++++ 3 files changed, 141 insertions(+) create mode 100644 core/txpool/validation_test.go diff --git a/core/txpool/validation.go b/core/txpool/validation.go index df53f30a8668..4b54eac50db8 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -116,6 +116,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types if _, err := types.Sender(signer, tx); err != nil { return fmt.Errorf("%w: %v", ErrInvalidSender, err) } + // Limit nonce to 2^64-1 per EIP-2681 + if tx.Nonce()+1 < tx.Nonce() { + return core.ErrNonceMax + } // Ensure the transaction has more gas than the bare minimum needed to cover // the transaction metadata intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai) diff --git a/core/txpool/validation_test.go b/core/txpool/validation_test.go new file mode 100644 index 000000000000..3945b548c16e --- /dev/null +++ b/core/txpool/validation_test.go @@ -0,0 +1,115 @@ +// 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 . + +package txpool + +import ( + "crypto/ecdsa" + "errors" + "math" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +func TestValidateTransactionEIP2681(t *testing.T) { + key, err := crypto.GenerateKey() + if err != nil { + t.Fatal(err) + } + + head := &types.Header{ + Number: big.NewInt(1), + GasLimit: 5000000, + Time: 1, + Difficulty: big.NewInt(1), + } + + signer := types.LatestSigner(params.TestChainConfig) + + // Create validation options + opts := &ValidationOptions{ + Config: params.TestChainConfig, + Accept: 0xFF, // Accept all transaction types + MaxSize: 32 * 1024, + MaxBlobCount: 6, + MinTip: big.NewInt(0), + } + + tests := []struct { + name string + nonce uint64 + wantErr error + }{ + { + name: "normal nonce", + nonce: 42, + wantErr: nil, + }, + { + name: "max allowed nonce (2^64-2)", + nonce: math.MaxUint64 - 1, + wantErr: nil, + }, + { + name: "EIP-2681 nonce overflow (2^64-1)", + nonce: math.MaxUint64, + wantErr: core.ErrNonceMax, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tx := createTestTransaction(key, tt.nonce) + err := ValidateTransaction(tx, head, signer, opts) + + if tt.wantErr == nil { + if err != nil { + t.Errorf("ValidateTransaction() error = %v, wantErr nil", err) + } + } else { + if err == nil { + t.Errorf("ValidateTransaction() error = nil, wantErr %v", tt.wantErr) + } else if !errors.Is(err, tt.wantErr) { + t.Errorf("ValidateTransaction() error = %v, wantErr %v", err, tt.wantErr) + } + } + }) + } +} + +// createTestTransaction creates a basic transaction for testing +func createTestTransaction(key *ecdsa.PrivateKey, nonce uint64) *types.Transaction { + to := common.HexToAddress("0x0000000000000000000000000000000000000001") + + txdata := &types.LegacyTx{ + Nonce: nonce, + To: &to, + Value: big.NewInt(1000), + Gas: 21000, + GasPrice: big.NewInt(1), + Data: nil, + } + + tx := types.NewTx(txdata) + signedTx, _ := types.SignTx(tx, types.HomesteadSigner{}, key) + return signedTx +} diff --git a/eth/api_backend_test.go b/eth/api_backend_test.go index d0718ede1ce2..aa0539511b02 100644 --- a/eth/api_backend_test.go +++ b/eth/api_backend_test.go @@ -20,6 +20,7 @@ import ( "context" "crypto/ecdsa" "errors" + "math" "math/big" "testing" "time" @@ -130,6 +131,27 @@ func TestSendTx(t *testing.T) { testSendTx(t, true) } +func TestSendTxEIP2681(t *testing.T) { + b := initBackend(false) + + // Test EIP-2681: nonce overflow should be rejected + tx := makeTx(uint64(math.MaxUint64), nil, nil, key) // max uint64 nonce + err := b.SendTx(context.Background(), tx) + if err == nil { + t.Fatal("Expected EIP-2681 nonce overflow error, but transaction was accepted") + } + if !errors.Is(err, core.ErrNonceMax) { + t.Errorf("Expected core.ErrNonceMax, got: %v", err) + } + + // Test normal case: should succeed + normalTx := makeTx(0, nil, nil, key) + err = b.SendTx(context.Background(), normalTx) + if err != nil { + t.Errorf("Normal transaction should succeed, got error: %v", err) + } +} + func testSendTx(t *testing.T, withLocal bool) { b := initBackend(withLocal) From ea52b0c6e3475534f7252b2f6de84379481a8d24 Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 25 Sep 2025 21:05:27 +0800 Subject: [PATCH 069/228] all: refactor to use builtin max/min (#32694) Replaces the last few instances of `math.Max` and `math.Min` with go builtins. --- cmd/utils/flags.go | 3 +-- metrics/runtimehistogram.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c7775adb53c8..c9da08578c96 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -24,7 +24,6 @@ import ( "encoding/json" "errors" "fmt" - "math" "math/big" "net" "net/http" @@ -1620,7 +1619,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } // Ensure Go's GC ignores the database cache for trigger percentage cache := ctx.Int(CacheFlag.Name) - gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024))) + gogc := max(20, min(100, 100/(float64(cache)/1024))) log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) godebug.SetGCPercent(int(gogc)) diff --git a/metrics/runtimehistogram.go b/metrics/runtimehistogram.go index 0ab89146026b..efbed498af8e 100644 --- a/metrics/runtimehistogram.go +++ b/metrics/runtimehistogram.go @@ -204,7 +204,7 @@ func (h *runtimeHistogramSnapshot) Percentiles(ps []float64) []float64 { thresholds := make([]float64, len(ps)) indexes := make([]int, len(ps)) for i, percentile := range ps { - thresholds[i] = count * math.Max(0, math.Min(1.0, percentile)) + thresholds[i] = count * max(0, min(1.0, percentile)) indexes[i] = i } sort.Sort(floatsAscendingKeepingIndex{thresholds, indexes}) From 7fa1c99a0a9d444a9bdbd005972c8200ae71b2ae Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 25 Sep 2025 18:07:33 +0200 Subject: [PATCH 070/228] params: schedule Osaka/BPO1/BPO2 for testnets (#32735) Timestamps taken from: - Holesky: https://github.com/eth-clients/holesky/blob/main/metadata/genesis.json - Sepolia: https://github.com/eth-clients/sepolia/blob/main/metadata/genesis.json - Hoodi: https://github.com/eth-clients/hoodi/blob/main/metadata/genesis.json --- core/forkid/forkid_test.go | 30 ++++++++++++++++++++++------ params/config.go | 26 +++++++++++++++++++++---- tests/init.go | 40 +++++++++++++++++++++++++------------- 3 files changed, 72 insertions(+), 24 deletions(-) diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index 413e4d77a8f6..dc6e6fe81774 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -95,8 +95,14 @@ func TestCreation(t *testing.T) { {1735372, 1706655071, ID{Hash: checksumToBytes(0xf7f9bc08), Next: 1706655072}}, // Last Shanghai block {1735372, 1706655072, ID{Hash: checksumToBytes(0x88cf81d9), Next: 1741159776}}, // First Cancun block {1735372, 1741159775, ID{Hash: checksumToBytes(0x88cf81d9), Next: 1741159776}}, // Last Cancun block - {1735372, 1741159776, ID{Hash: checksumToBytes(0xed88b5fd), Next: 0}}, // First Prague block - {1735372, 2741159776, ID{Hash: checksumToBytes(0xed88b5fd), Next: 0}}, // Future Prague block + {1735372, 1741159776, ID{Hash: checksumToBytes(0xed88b5fd), Next: 1760427360}}, // First Prague block + {1735372, 1760427359, ID{Hash: checksumToBytes(0xed88b5fd), Next: 1760427360}}, // Last Prague block + {1735372, 1760427360, ID{Hash: checksumToBytes(0xe2ae4999), Next: 1761017184}}, // First Osaka block + {1735372, 1761017183, ID{Hash: checksumToBytes(0xe2ae4999), Next: 1761017184}}, // Last Osaka block + {1735372, 1761017184, ID{Hash: checksumToBytes(0x56078a1e), Next: 1761607008}}, // First BPO1 block + {1735372, 1761607007, ID{Hash: checksumToBytes(0x56078a1e), Next: 1761607008}}, // Last BPO1 block + {1735372, 1761607008, ID{Hash: checksumToBytes(0x268956b6), Next: 0}}, // First BPO2 block + {1735372, 2000000000, ID{Hash: checksumToBytes(0x268956b6), Next: 0}}, // Future BPO2 block }, }, // Holesky test cases @@ -110,8 +116,14 @@ func TestCreation(t *testing.T) { {123, 1707305663, ID{Hash: checksumToBytes(0xfd4f016b), Next: 1707305664}}, // Last Shanghai block {123, 1707305664, ID{Hash: checksumToBytes(0x9b192ad0), Next: 1740434112}}, // First Cancun block {123, 1740434111, ID{Hash: checksumToBytes(0x9b192ad0), Next: 1740434112}}, // Last Cancun block - {123, 1740434112, ID{Hash: checksumToBytes(0xdfbd9bed), Next: 0}}, // First Prague block - {123, 2740434112, ID{Hash: checksumToBytes(0xdfbd9bed), Next: 0}}, // Future Prague block + {123, 1740434112, ID{Hash: checksumToBytes(0xdfbd9bed), Next: 1759308480}}, // First Prague block + {123, 1759308479, ID{Hash: checksumToBytes(0xdfbd9bed), Next: 1759308480}}, // Last Prague block + {123, 1759308480, ID{Hash: checksumToBytes(0x783def52), Next: 1759800000}}, // First Osaka block + {123, 1759799999, ID{Hash: checksumToBytes(0x783def52), Next: 1759800000}}, // Last Osaka block + {123, 1759800000, ID{Hash: checksumToBytes(0xa280a45c), Next: 1760389824}}, // First BPO1 block + {123, 1760389823, ID{Hash: checksumToBytes(0xa280a45c), Next: 1760389824}}, // Last BPO1 block + {123, 1760389824, ID{Hash: checksumToBytes(0x9bc6cb31), Next: 0}}, // First BPO2 block + {123, 2000000000, ID{Hash: checksumToBytes(0x9bc6cb31), Next: 0}}, // Future BPO1 block }, }, // Hoodi test cases @@ -121,8 +133,14 @@ func TestCreation(t *testing.T) { []testcase{ {0, 0, ID{Hash: checksumToBytes(0xbef71d30), Next: 1742999832}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople, Petersburg, Istanbul, Berlin, London, Paris, Shanghai, Cancun block {123, 1742999831, ID{Hash: checksumToBytes(0xbef71d30), Next: 1742999832}}, // Last Cancun block - {123, 1742999832, ID{Hash: checksumToBytes(0x0929e24e), Next: 0}}, // First Prague block - {123, 2740434112, ID{Hash: checksumToBytes(0x0929e24e), Next: 0}}, // Future Prague block + {123, 1742999832, ID{Hash: checksumToBytes(0x0929e24e), Next: 1761677592}}, // First Prague block + {123, 1761677591, ID{Hash: checksumToBytes(0x0929e24e), Next: 1761677592}}, // Last Prague block + {123, 1761677592, ID{Hash: checksumToBytes(0xe7e0e7ff), Next: 1762365720}}, // First Osaka block + {123, 1762365719, ID{Hash: checksumToBytes(0xe7e0e7ff), Next: 1762365720}}, // Last Osaka block + {123, 1762365720, ID{Hash: checksumToBytes(0x3893353e), Next: 1762955544}}, // First BPO1 block + {123, 1762955543, ID{Hash: checksumToBytes(0x3893353e), Next: 1762955544}}, // Last BPO1 block + {123, 1762955544, ID{Hash: checksumToBytes(0x23aa1351), Next: 0}}, // First BPO2 block + {123, 2000000000, ID{Hash: checksumToBytes(0x23aa1351), Next: 0}}, // Future BPO2 block }, }, } diff --git a/params/config.go b/params/config.go index 3788325d5ca2..42a2c61ab569 100644 --- a/params/config.go +++ b/params/config.go @@ -91,11 +91,17 @@ var ( ShanghaiTime: newUint64(1696000704), CancunTime: newUint64(1707305664), PragueTime: newUint64(1740434112), + OsakaTime: newUint64(1759308480), + BPO1Time: newUint64(1759800000), + BPO2Time: newUint64(1760389824), DepositContractAddress: common.HexToAddress("0x4242424242424242424242424242424242424242"), Ethash: new(EthashConfig), BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, + Osaka: DefaultOsakaBlobConfig, + BPO1: DefaultBPO1BlobConfig, + BPO2: DefaultBPO2BlobConfig, }, } // SepoliaChainConfig contains the chain parameters to run a node on the Sepolia test network. @@ -121,11 +127,17 @@ var ( ShanghaiTime: newUint64(1677557088), CancunTime: newUint64(1706655072), PragueTime: newUint64(1741159776), + OsakaTime: newUint64(1760427360), + BPO1Time: newUint64(1761017184), + BPO2Time: newUint64(1761607008), DepositContractAddress: common.HexToAddress("0x7f02c3e3c98b133055b8b348b2ac625669ed295d"), Ethash: new(EthashConfig), BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, + Osaka: DefaultOsakaBlobConfig, + BPO1: DefaultBPO1BlobConfig, + BPO2: DefaultBPO2BlobConfig, }, } // HoodiChainConfig contains the chain parameters to run a node on the Hoodi test network. @@ -151,11 +163,17 @@ var ( ShanghaiTime: newUint64(0), CancunTime: newUint64(0), PragueTime: newUint64(1742999832), + OsakaTime: newUint64(1761677592), + BPO1Time: newUint64(1762365720), + BPO2Time: newUint64(1762955544), DepositContractAddress: common.HexToAddress("0x00000000219ab540356cBB839Cbe05303d7705Fa"), Ethash: new(EthashConfig), BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, + Osaka: DefaultOsakaBlobConfig, + BPO1: DefaultBPO1BlobConfig, + BPO2: DefaultBPO2BlobConfig, }, } // AllEthashProtocolChanges contains every protocol change (EIPs) introduced @@ -361,15 +379,15 @@ var ( } // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. DefaultBPO1BlobConfig = &BlobConfig{ - Target: 9, - Max: 14, - UpdateFraction: 8832827, + Target: 10, + Max: 15, + UpdateFraction: 8346193, } // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. DefaultBPO2BlobConfig = &BlobConfig{ Target: 14, Max: 21, - UpdateFraction: 13739630, + UpdateFraction: 11684671, } // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. DefaultBPO3BlobConfig = &BlobConfig{ diff --git a/tests/init.go b/tests/init.go index 71072ac275d7..705e929ae9d7 100644 --- a/tests/init.go +++ b/tests/init.go @@ -490,7 +490,7 @@ var Forks = map[string]*params.ChainConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, Osaka: params.DefaultOsakaBlobConfig, - BPO1: params.DefaultBPO1BlobConfig, + BPO1: bpo1BlobConfig, }, }, "OsakaToBPO1AtTime15k": { @@ -519,7 +519,7 @@ var Forks = map[string]*params.ChainConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, Osaka: params.DefaultOsakaBlobConfig, - BPO1: params.DefaultBPO1BlobConfig, + BPO1: bpo1BlobConfig, }, }, "BPO2": { @@ -549,8 +549,8 @@ var Forks = map[string]*params.ChainConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, Osaka: params.DefaultOsakaBlobConfig, - BPO1: params.DefaultBPO1BlobConfig, - BPO2: params.DefaultBPO2BlobConfig, + BPO1: bpo1BlobConfig, + BPO2: bpo2BlobConfig, }, }, "BPO1ToBPO2AtTime15k": { @@ -580,8 +580,8 @@ var Forks = map[string]*params.ChainConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, Osaka: params.DefaultOsakaBlobConfig, - BPO1: params.DefaultBPO1BlobConfig, - BPO2: params.DefaultBPO2BlobConfig, + BPO1: bpo1BlobConfig, + BPO2: bpo2BlobConfig, }, }, "BPO3": { @@ -612,8 +612,8 @@ var Forks = map[string]*params.ChainConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, Osaka: params.DefaultOsakaBlobConfig, - BPO1: params.DefaultBPO1BlobConfig, - BPO2: params.DefaultBPO2BlobConfig, + BPO1: bpo1BlobConfig, + BPO2: bpo2BlobConfig, BPO3: params.DefaultBPO3BlobConfig, }, }, @@ -645,8 +645,8 @@ var Forks = map[string]*params.ChainConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, Osaka: params.DefaultOsakaBlobConfig, - BPO1: params.DefaultBPO1BlobConfig, - BPO2: params.DefaultBPO2BlobConfig, + BPO1: bpo1BlobConfig, + BPO2: bpo2BlobConfig, BPO3: params.DefaultBPO3BlobConfig, }, }, @@ -679,8 +679,8 @@ var Forks = map[string]*params.ChainConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, Osaka: params.DefaultOsakaBlobConfig, - BPO1: params.DefaultBPO1BlobConfig, - BPO2: params.DefaultBPO2BlobConfig, + BPO1: bpo1BlobConfig, + BPO2: bpo2BlobConfig, BPO3: params.DefaultBPO3BlobConfig, BPO4: params.DefaultBPO4BlobConfig, }, @@ -714,14 +714,26 @@ var Forks = map[string]*params.ChainConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, Osaka: params.DefaultOsakaBlobConfig, - BPO1: params.DefaultBPO1BlobConfig, - BPO2: params.DefaultBPO2BlobConfig, + BPO1: bpo1BlobConfig, + BPO2: bpo2BlobConfig, BPO3: params.DefaultBPO3BlobConfig, BPO4: params.DefaultBPO4BlobConfig, }, }, } +var bpo1BlobConfig = ¶ms.BlobConfig{ + Target: 9, + Max: 14, + UpdateFraction: 8832827, +} + +var bpo2BlobConfig = ¶ms.BlobConfig{ + Target: 14, + Max: 21, + UpdateFraction: 13739630, +} + // AvailableForks returns the set of defined fork names func AvailableForks() []string { var availableForks []string From 80cd4496bbaebdc4b82ae0efa7bcd0ae9c4fd24c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 25 Sep 2025 18:19:00 +0200 Subject: [PATCH 071/228] version: release go-ethereum v1.16.4 stable --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index 092735ff1f35..64301406318a 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 4 // Patch version component of the current release - Meta = "unstable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 4 // Patch version component of the current release + Meta = "stable" // Version metadata to append to the version string ) From 7e84078e84f1d8ca33d3d7f3640dd41f901992f3 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 26 Sep 2025 11:41:02 +0200 Subject: [PATCH 072/228] version: begin v1.16.5 release cycle --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index 64301406318a..db4e5394b909 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 4 // Patch version component of the current release - Meta = "stable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 5 // Patch version component of the current release + Meta = "unstable" // Version metadata to append to the version string ) From 041403dd8ebec0660ceadf04b8990057188a7165 Mon Sep 17 00:00:00 2001 From: wit liu <765765346@qq.com> Date: Fri, 26 Sep 2025 20:11:50 +0800 Subject: [PATCH 073/228] internal/ethapi: fix outdated comments (#32751) Fix outdated comments --- internal/ethapi/api.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ebb8ece73017..c60aad56173b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -460,10 +460,10 @@ func decodeHash(s string) (h common.Hash, inputLength int, err error) { } // GetHeaderByNumber returns the requested canonical block header. -// - When blockNr is -1 the chain pending header is returned. -// - When blockNr is -2 the chain latest header is returned. -// - When blockNr is -3 the chain finalized header is returned. -// - When blockNr is -4 the chain safe header is returned. +// - When number is -1 the chain pending header is returned. +// - When number is -2 the chain latest header is returned. +// - When number is -3 the chain finalized header is returned. +// - When number is -4 the chain safe header is returned. func (api *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { header, err := api.b.HeaderByNumber(ctx, number) if header != nil && err == nil { @@ -489,10 +489,10 @@ func (api *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) } // GetBlockByNumber returns the requested canonical block. -// - When blockNr is -1 the chain pending block is returned. -// - When blockNr is -2 the chain latest block is returned. -// - When blockNr is -3 the chain finalized block is returned. -// - When blockNr is -4 the chain safe block is returned. +// - When number is -1 the chain pending block is returned. +// - When number is -2 the chain latest block is returned. +// - When number is -3 the chain finalized block is returned. +// - When number is -4 the chain safe block is returned. // - When fullTx is true all transactions in the block are returned, otherwise // only the transaction hash is returned. func (api *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { From 344c023d594f11c09923d85fd56a6e246de897bd Mon Sep 17 00:00:00 2001 From: Matus Kysel Date: Fri, 26 Sep 2025 15:12:28 +0200 Subject: [PATCH 074/228] ethapi: reject oversize storage keys before hex decode (#32750) Bail out of decodeHash when the raw hex string is longer than 32 byte before actually decoding. --------- Co-authored-by: lightclient --- internal/ethapi/api.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c60aad56173b..2432bb70b85d 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -449,13 +449,13 @@ func decodeHash(s string) (h common.Hash, inputLength int, err error) { if (len(s) & 1) > 0 { s = "0" + s } + if len(s) > 64 { + return common.Hash{}, len(s) / 2, errors.New("hex string too long, want at most 32 bytes") + } b, err := hex.DecodeString(s) if err != nil { return common.Hash{}, 0, errors.New("hex string invalid") } - if len(b) > 32 { - return common.Hash{}, len(b), errors.New("hex string too long, want at most 32 bytes") - } return common.BytesToHash(b), len(b), nil } From 1d7830d8561f34dfb6f487d09175b452a75e1107 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Fri, 26 Sep 2025 16:26:22 +0300 Subject: [PATCH 075/228] signer/core: fix TestSignTx to decode res2 (#32749) Decode the modified transaction and verify the value differs from original. --------- Co-authored-by: lightclient --- signer/core/api_test.go | 42 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/signer/core/api_test.go b/signer/core/api_test.go index 69229dadaf26..0e16a1b7fd41 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -18,7 +18,6 @@ package core_test import ( "bytes" - "context" "fmt" "math/big" "os" @@ -97,12 +96,12 @@ func (ui *headlessUi) ApproveNewAccount(request *core.NewAccountRequest) (core.N } func (ui *headlessUi) ShowError(message string) { - //stdout is used by communication + // stdout is used by communication fmt.Fprintln(os.Stderr, message) } func (ui *headlessUi) ShowInfo(message string) { - //stdout is used by communication + // stdout is used by communication fmt.Fprintln(os.Stderr, message) } @@ -128,7 +127,7 @@ func setup(t *testing.T) (*core.SignerAPI, *headlessUi) { func createAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { ui.approveCh <- "Y" ui.inputCh <- "a_long_password" - _, err := api.New(context.Background()) + _, err := api.New(t.Context()) if err != nil { t.Fatal(err) } @@ -143,7 +142,7 @@ func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password ui.inputCh <- password ui.inputCh <- password - addr, err := api.New(context.Background()) + addr, err := api.New(t.Context()) if err == nil { t.Fatal("Should have returned an error") } @@ -154,7 +153,7 @@ func failCreateAccountWithPassword(ui *headlessUi, api *core.SignerAPI, password func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { ui.approveCh <- "N" - addr, err := api.New(context.Background()) + addr, err := api.New(t.Context()) if err != core.ErrRequestDenied { t.Fatal(err) } @@ -165,7 +164,7 @@ func failCreateAccount(ui *headlessUi, api *core.SignerAPI, t *testing.T) { func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, error) { ui.approveCh <- "A" - return api.List(context.Background()) + return api.List(t.Context()) } func TestNewAcc(t *testing.T) { @@ -199,7 +198,7 @@ func TestNewAcc(t *testing.T) { // Testing listing: // Listing one Account control.approveCh <- "1" - list, err := api.List(context.Background()) + list, err := api.List(t.Context()) if err != nil { t.Fatal(err) } @@ -208,7 +207,7 @@ func TestNewAcc(t *testing.T) { } // Listing denied control.approveCh <- "Nope" - list, err = api.List(context.Background()) + list, err = api.List(t.Context()) if len(list) != 0 { t.Fatalf("List should be empty") } @@ -246,7 +245,7 @@ func TestSignTx(t *testing.T) { api, control := setup(t) createAccount(control, api, t) control.approveCh <- "A" - list, err = api.List(context.Background()) + list, err = api.List(t.Context()) if err != nil { t.Fatal(err) } @@ -260,7 +259,7 @@ func TestSignTx(t *testing.T) { control.approveCh <- "Y" control.inputCh <- "wrongpassword" - res, err = api.SignTransaction(context.Background(), tx, &methodSig) + res, err = api.SignTransaction(t.Context(), tx, &methodSig) if res != nil { t.Errorf("Expected nil-response, got %v", res) } @@ -268,7 +267,7 @@ func TestSignTx(t *testing.T) { t.Errorf("Expected ErrLocked! %v", err) } control.approveCh <- "No way" - res, err = api.SignTransaction(context.Background(), tx, &methodSig) + res, err = api.SignTransaction(t.Context(), tx, &methodSig) if res != nil { t.Errorf("Expected nil-response, got %v", res) } @@ -278,22 +277,21 @@ func TestSignTx(t *testing.T) { // Sign with correct password control.approveCh <- "Y" control.inputCh <- "a_long_password" - res, err = api.SignTransaction(context.Background(), tx, &methodSig) - + res, err = api.SignTransaction(t.Context(), tx, &methodSig) if err != nil { t.Fatal(err) } parsedTx := &types.Transaction{} rlp.DecodeBytes(res.Raw, parsedTx) - //The tx should NOT be modified by the UI + // The tx should NOT be modified by the UI if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) } control.approveCh <- "Y" control.inputCh <- "a_long_password" - res2, err = api.SignTransaction(context.Background(), tx, &methodSig) + res2, err = api.SignTransaction(t.Context(), tx, &methodSig) if err != nil { t.Fatal(err) } @@ -301,20 +299,20 @@ func TestSignTx(t *testing.T) { t.Error("Expected tx to be unmodified by UI") } - //The tx is modified by the UI + // The tx is modified by the UI control.approveCh <- "M" control.inputCh <- "a_long_password" - res2, err = api.SignTransaction(context.Background(), tx, &methodSig) + res2, err = api.SignTransaction(t.Context(), tx, &methodSig) if err != nil { t.Fatal(err) } parsedTx2 := &types.Transaction{} - rlp.DecodeBytes(res.Raw, parsedTx2) + rlp.DecodeBytes(res2.Raw, parsedTx2) - //The tx should be modified by the UI - if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { - t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value()) + // The tx should be modified by the UI + if parsedTx2.Value().Cmp(tx.Value.ToInt()) == 0 { + t.Errorf("Expected value to be changed, got %v", parsedTx2.Value()) } if bytes.Equal(res.Raw, res2.Raw) { t.Error("Expected tx to be modified by UI") From 1bbcf7724443df6beaeeb6e92546497d9bfc51fc Mon Sep 17 00:00:00 2001 From: MozirDmitriy Date: Fri, 26 Sep 2025 16:47:58 +0300 Subject: [PATCH 076/228] trie: correct error messages for UpdateStorage operations (#32746) Fix incorrect error messages in TestVerkleTreeReadWrite and TestVerkleRollBack functions. --- trie/verkle_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trie/verkle_test.go b/trie/verkle_test.go index f31ab02df926..1832e3db1338 100644 --- a/trie/verkle_test.go +++ b/trie/verkle_test.go @@ -66,7 +66,7 @@ func TestVerkleTreeReadWrite(t *testing.T) { } for key, val := range storages[addr] { if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil { - t.Fatalf("Failed to update account, %v", err) + t.Fatalf("Failed to update storage, %v", err) } } } @@ -107,7 +107,7 @@ func TestVerkleRollBack(t *testing.T) { } for key, val := range storages[addr] { if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil { - t.Fatalf("Failed to update account, %v", err) + t.Fatalf("Failed to update storage, %v", err) } } hash := crypto.Keccak256Hash(code) From 1c5ae75725de804a7b6a4607ef1341c1763efb48 Mon Sep 17 00:00:00 2001 From: Dragan Milic Date: Fri, 26 Sep 2025 18:05:27 +0200 Subject: [PATCH 077/228] eth/tracers/native: add keccak256preimage tracer (#32569) Introduces a new tracer which returns the preimages of evm KECCAK256 hashes. See #32570. --------- Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com> Co-authored-by: Sina Mahmoodi --- eth/tracers/native/keccak256_preimage.go | 86 ++++ eth/tracers/native/keccak256_preimage_test.go | 442 ++++++++++++++++++ 2 files changed, 528 insertions(+) create mode 100644 eth/tracers/native/keccak256_preimage.go create mode 100644 eth/tracers/native/keccak256_preimage_test.go diff --git a/eth/tracers/native/keccak256_preimage.go b/eth/tracers/native/keccak256_preimage.go new file mode 100644 index 000000000000..0c2b7e6e325e --- /dev/null +++ b/eth/tracers/native/keccak256_preimage.go @@ -0,0 +1,86 @@ +package native + +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/internal" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +func init() { + tracers.DefaultDirectory.Register("keccak256PreimageTracer", newKeccak256PreimageTracer, false) +} + +// keccak256PreimageTracer is a native tracer that collects preimages of all KECCAK256 operations. +// This tracer is particularly useful for analyzing smart contract execution patterns, +// especially when debugging storage access in Solidity mappings and dynamic arrays. +type keccak256PreimageTracer struct { + computedHashes map[common.Hash]hexutil.Bytes +} + +// newKeccak256PreimageTracer returns a new keccak256PreimageTracer instance. +func newKeccak256PreimageTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) { + t := &keccak256PreimageTracer{ + computedHashes: make(map[common.Hash]hexutil.Bytes), + } + return &tracers.Tracer{ + Hooks: &tracing.Hooks{ + OnOpcode: t.OnOpcode, + }, + GetResult: t.GetResult, + }, nil +} + +func (t *keccak256PreimageTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + if op == byte(vm.KECCAK256) { + sd := scope.StackData() + // it turns out that sometimes the stack is empty, evm will fail in this case, but we should not panic here + if len(sd) < 2 { + return + } + + dataOffset := internal.StackBack(sd, 0).Uint64() + dataLength := internal.StackBack(sd, 1).Uint64() + preimage, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(dataOffset), int64(dataLength)) + if err != nil { + log.Warn("keccak256PreimageTracer: failed to copy keccak preimage from memory", "err", err) + return + } + + hash := crypto.Keccak256(preimage) + + t.computedHashes[common.Hash(hash)] = hexutil.Bytes(preimage) + } +} + +// GetResult returns the collected keccak256 preimages as a JSON object mapping hashes to preimages. +func (t *keccak256PreimageTracer) GetResult() (json.RawMessage, error) { + msg, err := json.Marshal(t.computedHashes) + if err != nil { + return nil, err + } + return msg, nil +} diff --git a/eth/tracers/native/keccak256_preimage_test.go b/eth/tracers/native/keccak256_preimage_test.go new file mode 100644 index 000000000000..b54b0cc23806 --- /dev/null +++ b/eth/tracers/native/keccak256_preimage_test.go @@ -0,0 +1,442 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package native_test + +import ( + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +// mockOpContext implements tracing.OpContext for testing +type mockOpContext struct { + memory []byte + stack []uint256.Int +} + +// Ensure mockOpContext implements tracing.OpContext +var _ tracing.OpContext = (*mockOpContext)(nil) + +func (m *mockOpContext) MemoryData() []byte { + return m.memory +} + +func (m *mockOpContext) StackData() []uint256.Int { + return m.stack +} + +func (m *mockOpContext) Address() common.Address { + return common.Address{} +} + +func (m *mockOpContext) Caller() common.Address { + return common.Address{} +} + +func (m *mockOpContext) CallValue() *uint256.Int { + return uint256.NewInt(0) +} + +func (m *mockOpContext) CallInput() []byte { + return []byte{} +} + +func (m *mockOpContext) ContractCode() []byte { + return []byte{} +} + +func TestKeccak256PreimageTracerCreation(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + require.NotNil(t, tracer) + require.NotNil(t, tracer.Hooks) + require.NotNil(t, tracer.Hooks.OnOpcode) + require.NotNil(t, tracer.GetResult) +} + +func TestKeccak256PreimageTracerInitialResult(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + require.Empty(t, hashes) +} + +func TestKeccak256PreimageTracerSingleKeccak(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test data: "hello world" + testData := []byte("hello world") + memory := make([]byte, 32) + copy(memory, testData) + + // Create stack with offset=0, length=11 + stack := []uint256.Int{ + *uint256.NewInt(11), // length (stack[1]) + *uint256.NewInt(0), // offset (stack[0]) + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + // Verify the hash and preimage + expectedHash := crypto.Keccak256Hash(testData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerMultipleKeccak(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + testCases := []struct { + name string + data []byte + }{ + {"empty", []byte{}}, + {"hello", []byte("hello")}, + {"world", []byte("world")}, + {"long_data", make([]byte, 100)}, + } + + // Initialize long_data with some pattern + for i := range testCases[3].data { + testCases[3].data[i] = byte(i % 256) + } + + expectedHashes := make(map[common.Hash]hexutil.Bytes) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + memory := make([]byte, max(len(tc.data), 1)) + copy(memory, tc.data) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(tc.data))), // length + *uint256.NewInt(0), // offset + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + expectedHash := crypto.Keccak256Hash(tc.data) + expectedHashes[expectedHash] = hexutil.Bytes(tc.data) + }) + } + + // Get final result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + require.Equal(t, expectedHashes, hashes) +} + +func TestKeccak256PreimageTracerNonKeccakOpcodes(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + testData := []byte("should not be recorded") + memory := make([]byte, 32) + copy(memory, testData) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(testData))), + *uint256.NewInt(0), + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Test various non-KECCAK256 opcodes + nonKeccakOpcodes := []vm.OpCode{ + vm.ADD, vm.MUL, vm.SUB, vm.DIV, vm.SDIV, vm.MOD, vm.SMOD, + vm.ADDMOD, vm.MULMOD, vm.EXP, vm.SIGNEXTEND, vm.SLOAD, + vm.SSTORE, vm.MLOAD, vm.MSTORE, vm.CALL, vm.RETURN, + } + + for _, opcode := range nonKeccakOpcodes { + tracer.OnOpcode(0, byte(opcode), 0, 0, mockScope, nil, 0, nil) + } + + // Get result - should be empty + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + require.Empty(t, hashes) +} + +func TestKeccak256PreimageTracerMemoryOffset(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test data at different memory offset + prefix := []byte("prefix_data_") + testData := []byte("target_data") + memory := make([]byte, len(prefix)+len(testData)+10) + copy(memory, prefix) + copy(memory[len(prefix):], testData) + + // Stack: offset=len(prefix), length=len(testData) + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(testData))), // length + *uint256.NewInt(uint64(len(prefix))), // offset + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + // Verify the hash matches the target data, not the prefix + expectedHash := crypto.Keccak256Hash(testData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerMemoryPadding(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test data that extends beyond memory bounds (should be zero-padded) + testData := []byte("short") + memory := make([]byte, len(testData)) + copy(memory, testData) + + // Request more data than available in memory + requestedLength := len(testData) + 5 + stack := []uint256.Int{ + *uint256.NewInt(uint64(requestedLength)), // length > memory size + *uint256.NewInt(0), // offset + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + // Verify the hash includes zero padding + expectedData := make([]byte, requestedLength) + copy(expectedData, testData) + // Rest is zero-padded by default + + expectedHash := crypto.Keccak256Hash(expectedData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(expectedData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerDuplicateHashes(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + testData := []byte("duplicate_test") + memory := make([]byte, len(testData)) + copy(memory, testData) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(testData))), + *uint256.NewInt(0), + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 multiple times with same data + for i := 0; i < 3; i++ { + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + } + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + // Should only have one entry (duplicates overwrite) + expectedHash := crypto.Keccak256Hash(testData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerWithExecutionError(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + testData := []byte("error_test") + memory := make([]byte, len(testData)) + copy(memory, testData) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(testData))), + *uint256.NewInt(0), + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 and an execution error + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, vm.ErrOutOfGas) + + // Get result - should still record the hash even with execution error + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + expectedHash := crypto.Keccak256Hash(testData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(testData), hashes[expectedHash]) +} + +func TestKeccak256PreimageTracerInsufficientStack(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test with insufficient stack items (should cause panic, but we test it doesn't crash) + testData := []byte("test") + memory := make([]byte, len(testData)) + copy(memory, testData) + + // Stack with only one item (need 2 for KECCAK256) + stack := []uint256.Int{ + *uint256.NewInt(0), // only offset, missing length + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // This should not panic due to insufficient stack + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) +} + +func TestKeccak256PreimageTracerLargeData(t *testing.T) { + tracer, err := tracers.DefaultDirectory.New("keccak256PreimageTracer", &tracers.Context{}, nil, params.MainnetChainConfig) + require.NoError(t, err) + + // Test with large data + largeData := make([]byte, 1024) + for i := range largeData { + largeData[i] = byte(i % 256) + } + + memory := make([]byte, len(largeData)) + copy(memory, largeData) + + stack := []uint256.Int{ + *uint256.NewInt(uint64(len(largeData))), + *uint256.NewInt(0), + } + + mockScope := &mockOpContext{ + memory: memory, + stack: stack, + } + + // Call OnOpcode with KECCAK256 + tracer.OnOpcode(0, byte(vm.KECCAK256), 0, 0, mockScope, nil, 0, nil) + + // Get result + result, err := tracer.GetResult() + require.NoError(t, err) + + var hashes map[common.Hash]hexutil.Bytes + err = json.Unmarshal(result, &hashes) + require.NoError(t, err) + + expectedHash := crypto.Keccak256Hash(largeData) + require.Len(t, hashes, 1) + require.Contains(t, hashes, expectedHash) + require.Equal(t, hexutil.Bytes(largeData), hashes[expectedHash]) +} From 430f657003ccf28bc2412c2b61dbb8f00355ab52 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 26 Sep 2025 23:39:22 +0200 Subject: [PATCH 078/228] params: add amsterdam fork config (#32687) Adds Amsterdam as fork config option. Co-authored-by: lightclient --- params/config.go | 74 +++++++++++++++++++++++++++---------------- params/forks/forks.go | 2 ++ 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/params/config.go b/params/config.go index 42a2c61ab569..0cf3198ff926 100644 --- a/params/config.go +++ b/params/config.go @@ -448,16 +448,17 @@ type ChainConfig struct { // Fork scheduling was switched from blocks to timestamps here - ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) - CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) - PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) - OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka) - VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) - BPO1Time *uint64 `json:"bpo1Time,omitempty"` // BPO1 switch time (nil = no fork, 0 = already on bpo1) - BPO2Time *uint64 `json:"bpo2Time,omitempty"` // BPO2 switch time (nil = no fork, 0 = already on bpo2) - BPO3Time *uint64 `json:"bpo3Time,omitempty"` // BPO3 switch time (nil = no fork, 0 = already on bpo3) - BPO4Time *uint64 `json:"bpo4Time,omitempty"` // BPO4 switch time (nil = no fork, 0 = already on bpo4) - BPO5Time *uint64 `json:"bpo5Time,omitempty"` // BPO5 switch time (nil = no fork, 0 = already on bpo5) + ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) + CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) + PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) + OsakaTime *uint64 `json:"osakaTime,omitempty"` // Osaka switch time (nil = no fork, 0 = already on osaka) + BPO1Time *uint64 `json:"bpo1Time,omitempty"` // BPO1 switch time (nil = no fork, 0 = already on bpo1) + BPO2Time *uint64 `json:"bpo2Time,omitempty"` // BPO2 switch time (nil = no fork, 0 = already on bpo2) + BPO3Time *uint64 `json:"bpo3Time,omitempty"` // BPO3 switch time (nil = no fork, 0 = already on bpo3) + BPO4Time *uint64 `json:"bpo4Time,omitempty"` // BPO4 switch time (nil = no fork, 0 = already on bpo4) + BPO5Time *uint64 `json:"bpo5Time,omitempty"` // BPO5 switch time (nil = no fork, 0 = already on bpo5) + AmsterdamTime *uint64 `json:"amsterdamTime,omitempty"` // Amsterdam switch time (nil = no fork, 0 = already on amsterdam) + VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) // TerminalTotalDifficulty is the amount of total difficulty reached by // the network that triggers the consensus upgrade. @@ -575,9 +576,6 @@ func (c *ChainConfig) Description() string { if c.OsakaTime != nil { banner += fmt.Sprintf(" - Osaka: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/osaka/__init__.py.html)\n", *c.OsakaTime) } - if c.VerkleTime != nil { - banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime) - } if c.BPO1Time != nil { banner += fmt.Sprintf(" - BPO1: @%-10v\n", *c.BPO1Time) } @@ -593,6 +591,12 @@ func (c *ChainConfig) Description() string { if c.BPO5Time != nil { banner += fmt.Sprintf(" - BPO5: @%-10v\n", *c.BPO5Time) } + if c.AmsterdamTime != nil { + banner += fmt.Sprintf(" - Amsterdam: @%-10v\n", *c.AmsterdamTime) + } + if c.VerkleTime != nil { + banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime) + } return banner } @@ -605,15 +609,16 @@ type BlobConfig struct { // BlobScheduleConfig determines target and max number of blobs allow per fork. type BlobScheduleConfig struct { - Cancun *BlobConfig `json:"cancun,omitempty"` - Prague *BlobConfig `json:"prague,omitempty"` - Osaka *BlobConfig `json:"osaka,omitempty"` - Verkle *BlobConfig `json:"verkle,omitempty"` - BPO1 *BlobConfig `json:"bpo1,omitempty"` - BPO2 *BlobConfig `json:"bpo2,omitempty"` - BPO3 *BlobConfig `json:"bpo3,omitempty"` - BPO4 *BlobConfig `json:"bpo4,omitempty"` - BPO5 *BlobConfig `json:"bpo5,omitempty"` + Cancun *BlobConfig `json:"cancun,omitempty"` + Prague *BlobConfig `json:"prague,omitempty"` + Osaka *BlobConfig `json:"osaka,omitempty"` + Verkle *BlobConfig `json:"verkle,omitempty"` + BPO1 *BlobConfig `json:"bpo1,omitempty"` + BPO2 *BlobConfig `json:"bpo2,omitempty"` + BPO3 *BlobConfig `json:"bpo3,omitempty"` + BPO4 *BlobConfig `json:"bpo4,omitempty"` + BPO5 *BlobConfig `json:"bpo5,omitempty"` + Amsterdam *BlobConfig `json:"amsterdam,omitempty"` } // IsHomestead returns whether num is either equal to the homestead block or greater. @@ -726,11 +731,6 @@ func (c *ChainConfig) IsOsaka(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.OsakaTime, time) } -// IsVerkle returns whether time is either equal to the Verkle fork time or greater. -func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { - return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) -} - // IsBPO1 returns whether time is either equal to the BPO1 fork time or greater. func (c *ChainConfig) IsBPO1(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.BPO1Time, time) @@ -756,6 +756,16 @@ func (c *ChainConfig) IsBPO5(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.BPO5Time, time) } +// IsAmsterdam returns whether time is either equal to the Amsterdam fork time or greater. +func (c *ChainConfig) IsAmsterdam(num *big.Int, time uint64) bool { + return c.IsLondon(num) && isTimestampForked(c.AmsterdamTime, time) +} + +// IsVerkle returns whether time is either equal to the Verkle fork time or greater. +func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { + return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) +} + // IsVerkleGenesis checks whether the verkle fork is activated at the genesis block. // // Verkle mode is considered enabled if the verkle fork time is configured, @@ -836,6 +846,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "bpo3", timestamp: c.BPO3Time, optional: true}, {name: "bpo4", timestamp: c.BPO4Time, optional: true}, {name: "bpo5", timestamp: c.BPO5Time, optional: true}, + {name: "amsterdam", timestamp: c.AmsterdamTime, optional: true}, } { if lastFork.name != "" { switch { @@ -890,6 +901,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "bpo3", timestamp: c.BPO3Time, config: bsc.BPO3}, {name: "bpo4", timestamp: c.BPO4Time, config: bsc.BPO4}, {name: "bpo5", timestamp: c.BPO5Time, config: bsc.BPO5}, + {name: "amsterdam", timestamp: c.AmsterdamTime, config: bsc.Amsterdam}, } { if cur.config != nil { if err := cur.config.validate(); err != nil { @@ -1005,6 +1017,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if isForkTimestampIncompatible(c.BPO5Time, newcfg.BPO5Time, headTimestamp) { return newTimestampCompatError("BPO5 fork timestamp", c.BPO5Time, newcfg.BPO5Time) } + if isForkTimestampIncompatible(c.AmsterdamTime, newcfg.AmsterdamTime, headTimestamp) { + return newTimestampCompatError("Amsterdam fork timestamp", c.AmsterdamTime, newcfg.AmsterdamTime) + } return nil } @@ -1024,6 +1039,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork { london := c.LondonBlock switch { + case c.IsAmsterdam(london, time): + return forks.Amsterdam case c.IsBPO5(london, time): return forks.BPO5 case c.IsBPO4(london, time): @@ -1259,7 +1276,7 @@ type Rules struct { IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsBerlin, IsLondon bool IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool - IsVerkle bool + IsAmsterdam, IsVerkle bool } // Rules ensures c's ChainID is not nil. @@ -1289,6 +1306,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsCancun: isMerge && c.IsCancun(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp), IsOsaka: isMerge && c.IsOsaka(num, timestamp), + IsAmsterdam: isMerge && c.IsAmsterdam(num, timestamp), IsVerkle: isVerkle, IsEIP4762: isVerkle, } diff --git a/params/forks/forks.go b/params/forks/forks.go index adb65c862419..641d59434b72 100644 --- a/params/forks/forks.go +++ b/params/forks/forks.go @@ -45,6 +45,7 @@ const ( BPO3 BPO4 BPO5 + Amsterdam ) // String implements fmt.Stringer. @@ -82,4 +83,5 @@ var forkToString = map[Fork]string{ BPO3: "BPO3", BPO4: "BPO4", BPO5: "BPO5", + Amsterdam: "Amsterdam", } From 455369e30d41ee6292d4048a4b794bf88dd4ee21 Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 29 Sep 2025 10:17:47 +0800 Subject: [PATCH 079/228] build: remove duplicated func FileExist (#32768) --- build/ci.go | 5 +++-- internal/build/file.go | 9 --------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/build/ci.go b/build/ci.go index c6f4f28c87ea..da867a151632 100644 --- a/build/ci.go +++ b/build/ci.go @@ -57,6 +57,7 @@ import ( "time" "github.com/cespare/cp" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/signify" "github.com/ethereum/go-ethereum/internal/build" "github.com/ethereum/go-ethereum/internal/download" @@ -148,7 +149,7 @@ func executablePath(name string) string { func main() { log.SetFlags(log.Lshortfile) - if !build.FileExist(filepath.Join("build", "ci.go")) { + if !common.FileExist(filepath.Join("build", "ci.go")) { log.Fatal("this script must be run from the root of the repository") } if len(os.Args) < 2 { @@ -895,7 +896,7 @@ func ppaUpload(workdir, ppa, sshUser string, files []string) { var idfile string if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 { idfile = filepath.Join(workdir, "sshkey") - if !build.FileExist(idfile) { + if !common.FileExist(idfile) { os.WriteFile(idfile, sshkey, 0600) } } diff --git a/internal/build/file.go b/internal/build/file.go index 7490af281e2c..2cd090c42c0e 100644 --- a/internal/build/file.go +++ b/internal/build/file.go @@ -25,15 +25,6 @@ import ( "strings" ) -// FileExist checks if a file exists at path. -func FileExist(path string) bool { - _, err := os.Stat(path) - if err != nil && os.IsNotExist(err) { - return false - } - return true -} - // HashFolder iterates all files under the given directory, computing the hash // of each. func HashFolder(folder string, exlude []string) (map[string][32]byte, error) { From faf987a0541953fb5f7187c86f430813aba2ba09 Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 29 Sep 2025 17:56:39 +0800 Subject: [PATCH 080/228] eth/catalyst: check osaka in engine_getBlobsV1 (#32731) ref https://github.com/ethereum/execution-apis/blob/main/src/engine/osaka.md#cancun-api > Client software MUST return -38005: Unsupported fork error if the Osaka fork has been activated. --------- Signed-off-by: Delweng Co-authored-by: rjl493456442 --- eth/catalyst/api.go | 13 ++++++++++--- eth/catalyst/api_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index b37c26149f64..6dfe24f729bf 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -492,6 +492,12 @@ func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*eng // Client software MAY return an array of all null entries if syncing or otherwise // unable to serve blob pool data. func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProofV1, error) { + // Reject the request if Osaka has been activated. + // follow https://github.com/ethereum/execution-apis/blob/main/src/engine/osaka.md#cancun-api + head := api.eth.BlockChain().CurrentHeader() + if !api.checkFork(head.Time, forks.Cancun, forks.Prague) { + return nil, unsupportedForkErr("engine_getBlobsV1 is only available at Cancun/Prague fork") + } if len(hashes) > 128 { return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) } @@ -532,9 +538,6 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo // - if the request is [A_versioned_hash_for_blob_with_blob_proof], the response // MUST be null as well. // -// Note, geth internally make the conversion from old version to new one, so the -// data will be returned normally. -// // Client software MUST support request sizes of at least 128 blob versioned // hashes. The client MUST return -38004: Too large request error if the number // of requested blobs is too large. @@ -542,6 +545,10 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo // Client software MUST return null if syncing or otherwise unable to serve // blob pool data. func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) { + head := api.eth.BlockChain().CurrentHeader() + if api.config().LatestFork(head.Time) < forks.Osaka { + return nil, unsupportedForkErr("engine_getBlobsV2 is not available before Osaka fork") + } if len(hashes) > 128 { return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 659280bf3b1a..a29fee1a06ca 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1991,6 +1991,31 @@ func TestGetBlobsV1(t *testing.T) { } } +func TestGetBlobsV1AfterOsakaFork(t *testing.T) { + genesis := &core.Genesis{ + Config: params.MergedTestChainConfig, + Alloc: types.GenesisAlloc{testAddr: {Balance: testBalance}}, + Difficulty: common.Big0, + Timestamp: 1, // Timestamp > 0 to ensure Osaka fork is active + } + n, ethServ := startEthService(t, genesis, nil) + defer n.Close() + + var engineErr *engine.EngineAPIError + api := newConsensusAPIWithoutHeartbeat(ethServ) + _, err := api.GetBlobsV1([]common.Hash{testrand.Hash()}) + if !errors.As(err, &engineErr) { + t.Fatalf("Unexpected error: %T", err) + } else { + if engineErr.ErrorCode() != -38005 { + t.Fatalf("Expected error code -38005, got %d", engineErr.ErrorCode()) + } + if engineErr.Error() != "Unsupported fork" { + t.Fatalf("Expected error message 'Unsupported fork', got '%s'", engineErr.Error()) + } + } +} + func TestGetBlobsV2(t *testing.T) { n, api := newGetBlobEnv(t, 1) defer n.Close() From ef132544aa53998cbb1784d0f1dad0c927271484 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Mon, 29 Sep 2025 13:23:43 +0300 Subject: [PATCH 081/228] trie: fix error message in test (#32772) Fixes an error message in TestReplication --- trie/trie_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trie/trie_test.go b/trie/trie_test.go index 22c3494f47c0..b8b8edb33e85 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -326,7 +326,7 @@ func TestReplication(t *testing.T) { updateString(trie2, val.k, val.v) } if trie2.Hash() != hash { - t.Errorf("root failure. expected %x got %x", hash, hash) + t.Errorf("root failure. expected %x got %x", hash, trie2.Hash()) } } From 69b9b72e1fcef216104e52a57c692b7a8a0db028 Mon Sep 17 00:00:00 2001 From: GarmashAlex Date: Mon, 29 Sep 2025 13:31:00 +0300 Subject: [PATCH 082/228] internal/ethapi: remove redundant check in test (#32760) Removes a redundant check in TestCreateAccessListWithStateOverrides --- internal/ethapi/api_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index c0a8fe9a583d..2e0b1c3bc082 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -3746,8 +3746,8 @@ func TestCreateAccessListWithStateOverrides(t *testing.T) { if err != nil { t.Fatalf("Failed to create access list: %v", err) } - if err != nil || result == nil { - t.Fatalf("Failed to create access list: %v", err) + if result == nil { + t.Fatalf("Failed to create access list: result is nil") } require.NotNil(t, result.Accesslist) From 0c1ac59791e4934770a8b4af0204da629ac68690 Mon Sep 17 00:00:00 2001 From: CPerezz <37264926+CPerezz@users.noreply.github.com> Date: Mon, 29 Sep 2025 12:51:46 +0200 Subject: [PATCH 083/228] cmd/evm/internal/t8ntool: panic on database corruption (#32776) These functions were previously ignoring the error returned by both `statedb.Commit()` and the subsequent `state.New()`, which could silently fail and cause panics later when the `statedb` is used. This change adds proper error checking and panics with a descriptive error message if state creation fails. While unlikely in normal operation, this can occur if there are database corruption issues or if invalid root hashes are provided, making debugging significantly easier when such issues do occur. This issue was encountered and fixed in https://github.com/gballet/go-ethereum/pull/552 where the error handling proved essential for debugging cc: @gballet as this was discussed in a call already. --- cmd/evm/internal/t8ntool/execution.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index f1e65afe9c7a..5303d432fb6b 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -374,7 +374,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB { tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true}) sdb := state.NewDatabase(tdb, nil) - statedb, _ := state.New(types.EmptyRootHash, sdb) + statedb, err := state.New(types.EmptyRootHash, sdb) + if err != nil { + panic(fmt.Errorf("failed to create initial state: %v", err)) + } for addr, a := range accounts { statedb.SetCode(addr, a.Code, tracing.CodeChangeUnspecified) statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeGenesis) @@ -384,8 +387,14 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB } } // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(0, false, false) - statedb, _ = state.New(root, sdb) + root, err := statedb.Commit(0, false, false) + if err != nil { + panic(fmt.Errorf("failed to commit initial state: %v", err)) + } + statedb, err = state.New(root, sdb) + if err != nil { + panic(fmt.Errorf("failed to reopen state after commit: %v", err)) + } return statedb } From 079960a4fec72cf175e8bcfd0dd15b1f6debac07 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Mon, 29 Sep 2025 05:44:04 -0600 Subject: [PATCH 084/228] params: fix bpo config comments (#32755) Looks like we forgot to update names when copying. --- params/config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/config.go b/params/config.go index 0cf3198ff926..ff27b6925920 100644 --- a/params/config.go +++ b/params/config.go @@ -377,25 +377,25 @@ var ( Max: 9, UpdateFraction: 5007716, } - // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + // DefaultBPO1BlobConfig is the default blob configuration for the BPO1 fork. DefaultBPO1BlobConfig = &BlobConfig{ Target: 10, Max: 15, UpdateFraction: 8346193, } - // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + // DefaultBPO2BlobConfig is the default blob configuration for the BPO2 fork. DefaultBPO2BlobConfig = &BlobConfig{ Target: 14, Max: 21, UpdateFraction: 11684671, } - // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + // DefaultBPO3BlobConfig is the default blob configuration for the BPO3 fork. DefaultBPO3BlobConfig = &BlobConfig{ Target: 21, Max: 32, UpdateFraction: 20609697, } - // DefaultBPO1BlobConfig is the default blob configuration for the Osaka fork. + // DefaultBPO4BlobConfig is the default blob configuration for the BPO4 fork. DefaultBPO4BlobConfig = &BlobConfig{ Target: 14, Max: 21, From e8dd5a3640cc2d7f5e987eb8ab7ef1202c3bad1c Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Mon, 29 Sep 2025 15:45:00 +0300 Subject: [PATCH 085/228] core/rawdb: update comments (#32668) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace outdated NewFreezer doc that referenced map[string]bool/snappy toggle with accurate description of -map[string]freezerTableConfig (noSnappy, prunable). - Fix misleading field comment on freezerTable.config that spoke as if it were a boolean (“if true”), clarifying it’s a struct and noting compression is non-retroactive. --- core/rawdb/freezer.go | 5 +++-- core/rawdb/freezer_table.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index a9600c1eefc9..98ad174ce0d2 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -76,8 +76,9 @@ type Freezer struct { // NewFreezer creates a freezer instance for maintaining immutable ordered // data according to the given parameters. // -// The 'tables' argument defines the data tables. If the value of a map -// entry is true, snappy compression is disabled for the table. +// The 'tables' argument defines the freezer tables and their configuration. +// Each value is a freezerTableConfig specifying whether snappy compression is +// disabled (noSnappy) and whether the table is prunable (prunable). func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig) (*Freezer, error) { // Create the initial freezer object var ( diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index 19c40cc16ea7..d3a29a73c6a7 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -100,7 +100,7 @@ type freezerTable struct { // should never be lower than itemOffset. itemHidden atomic.Uint64 - config freezerTableConfig // if true, disables snappy compression. Note: does not work retroactively + config freezerTableConfig // table configuration (compression, prunability). Note: compression flag does not apply retroactively to existing files readonly bool maxFileSize uint32 // Max file size for data-files name string From 7cf17ed9c57ba16b55cee363080fa1df4d9f3475 Mon Sep 17 00:00:00 2001 From: futreall <86553580+futreall@users.noreply.github.com> Date: Mon, 29 Sep 2025 15:09:03 +0200 Subject: [PATCH 086/228] params: implement String() method for ChainConfig (#32766) Fixes issue #32762 where ChainConfig logging displays pointer addresses instead of actual timestamp values for fork activation times. Before: ShanghaiTime:(*uint64)(0xc000373fb0), CancunTime:(*uint64)(0xc000373fb8) After: ShanghaiTime: 1681338455, CancunTime: 1710338135, VerkleTime: nil The String() method properly dereferences timestamp pointers and handles nil values for unset fork times, making logs more readable and useful for debugging chain configuration issues. --------- Co-authored-by: rjl493456442 --- params/config.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/params/config.go b/params/config.go index ff27b6925920..b441d60661e6 100644 --- a/params/config.go +++ b/params/config.go @@ -504,6 +504,96 @@ func (c CliqueConfig) String() string { return fmt.Sprintf("clique(period: %d, epoch: %d)", c.Period, c.Epoch) } +// String implements the fmt.Stringer interface, returning a string representation +// of ChainConfig. +func (c *ChainConfig) String() string { + result := fmt.Sprintf("ChainConfig{ChainID: %v", c.ChainID) + + // Add block-based forks + if c.HomesteadBlock != nil { + result += fmt.Sprintf(", HomesteadBlock: %v", c.HomesteadBlock) + } + if c.DAOForkBlock != nil { + result += fmt.Sprintf(", DAOForkBlock: %v", c.DAOForkBlock) + } + if c.EIP150Block != nil { + result += fmt.Sprintf(", EIP150Block: %v", c.EIP150Block) + } + if c.EIP155Block != nil { + result += fmt.Sprintf(", EIP155Block: %v", c.EIP155Block) + } + if c.EIP158Block != nil { + result += fmt.Sprintf(", EIP158Block: %v", c.EIP158Block) + } + if c.ByzantiumBlock != nil { + result += fmt.Sprintf(", ByzantiumBlock: %v", c.ByzantiumBlock) + } + if c.ConstantinopleBlock != nil { + result += fmt.Sprintf(", ConstantinopleBlock: %v", c.ConstantinopleBlock) + } + if c.PetersburgBlock != nil { + result += fmt.Sprintf(", PetersburgBlock: %v", c.PetersburgBlock) + } + if c.IstanbulBlock != nil { + result += fmt.Sprintf(", IstanbulBlock: %v", c.IstanbulBlock) + } + if c.MuirGlacierBlock != nil { + result += fmt.Sprintf(", MuirGlacierBlock: %v", c.MuirGlacierBlock) + } + if c.BerlinBlock != nil { + result += fmt.Sprintf(", BerlinBlock: %v", c.BerlinBlock) + } + if c.LondonBlock != nil { + result += fmt.Sprintf(", LondonBlock: %v", c.LondonBlock) + } + if c.ArrowGlacierBlock != nil { + result += fmt.Sprintf(", ArrowGlacierBlock: %v", c.ArrowGlacierBlock) + } + if c.GrayGlacierBlock != nil { + result += fmt.Sprintf(", GrayGlacierBlock: %v", c.GrayGlacierBlock) + } + if c.MergeNetsplitBlock != nil { + result += fmt.Sprintf(", MergeNetsplitBlock: %v", c.MergeNetsplitBlock) + } + + // Add timestamp-based forks + if c.ShanghaiTime != nil { + result += fmt.Sprintf(", ShanghaiTime: %v", *c.ShanghaiTime) + } + if c.CancunTime != nil { + result += fmt.Sprintf(", CancunTime: %v", *c.CancunTime) + } + if c.PragueTime != nil { + result += fmt.Sprintf(", PragueTime: %v", *c.PragueTime) + } + if c.OsakaTime != nil { + result += fmt.Sprintf(", OsakaTime: %v", *c.OsakaTime) + } + if c.BPO1Time != nil { + result += fmt.Sprintf(", BPO1Time: %v", *c.BPO1Time) + } + if c.BPO2Time != nil { + result += fmt.Sprintf(", BPO2Time: %v", *c.BPO2Time) + } + if c.BPO3Time != nil { + result += fmt.Sprintf(", BPO3Time: %v", *c.BPO3Time) + } + if c.BPO4Time != nil { + result += fmt.Sprintf(", BPO4Time: %v", *c.BPO4Time) + } + if c.BPO5Time != nil { + result += fmt.Sprintf(", BPO5Time: %v", *c.BPO5Time) + } + if c.AmsterdamTime != nil { + result += fmt.Sprintf(", AmsterdamTime: %v", *c.AmsterdamTime) + } + if c.VerkleTime != nil { + result += fmt.Sprintf(", VerkleTime: %v", *c.VerkleTime) + } + result += "}" + return result +} + // Description returns a human-readable description of ChainConfig. func (c *ChainConfig) Description() string { var banner string From c0638cf739f3008e9cb7f9ca8c64b2a6d74267f8 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 30 Sep 2025 10:05:43 +0800 Subject: [PATCH 087/228] params: add blob config information in the banner (#32771) Extend the chain banner with blob config information. Co-authored-by: Felix Lange --- params/config.go | 63 +++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/params/config.go b/params/config.go index b441d60661e6..4e885cbdd4d9 100644 --- a/params/config.go +++ b/params/config.go @@ -618,34 +618,32 @@ func (c *ChainConfig) Description() string { // makes sense for mainnet should be optional at printing to avoid bloating // the output for testnets and private networks. banner += "Pre-Merge hard forks (block based):\n" - banner += fmt.Sprintf(" - Homestead: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/homestead/__init__.py.html)\n", c.HomesteadBlock) + banner += fmt.Sprintf(" - Homestead: #%-8v\n", c.HomesteadBlock) if c.DAOForkBlock != nil { - banner += fmt.Sprintf(" - DAO Fork: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/dao_fork/__init__.py.html)\n", c.DAOForkBlock) - } - banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/tangerine_whistle/__init__.py.html)\n", c.EIP150Block) - banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/spurious_dragon/__init__.py.html)\n", c.EIP155Block) - banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/spurious_dragon/__init__.py.html)\n", c.EIP155Block) - banner += fmt.Sprintf(" - Byzantium: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/byzantium/__init__.py.html)\n", c.ByzantiumBlock) - banner += fmt.Sprintf(" - Constantinople: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/constantinople/__init__.py.html)\n", c.ConstantinopleBlock) - banner += fmt.Sprintf(" - Petersburg: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/constantinople/__init__.py.html)\n", c.PetersburgBlock) - banner += fmt.Sprintf(" - Istanbul: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/istanbul/__init__.py.html)\n", c.IstanbulBlock) + banner += fmt.Sprintf(" - DAO Fork: #%-8v\n", c.DAOForkBlock) + } + banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v\n", c.EIP150Block) + banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v\n", c.EIP155Block) + banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v\n", c.EIP155Block) + banner += fmt.Sprintf(" - Byzantium: #%-8v\n", c.ByzantiumBlock) + banner += fmt.Sprintf(" - Constantinople: #%-8v\n", c.ConstantinopleBlock) + banner += fmt.Sprintf(" - Petersburg: #%-8v\n", c.PetersburgBlock) + banner += fmt.Sprintf(" - Istanbul: #%-8v\n", c.IstanbulBlock) if c.MuirGlacierBlock != nil { - banner += fmt.Sprintf(" - Muir Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/muir_glacier/__init__.py.html)\n", c.MuirGlacierBlock) + banner += fmt.Sprintf(" - Muir Glacier: #%-8v\n", c.MuirGlacierBlock) } - banner += fmt.Sprintf(" - Berlin: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/berlin/__init__.py.html)\n", c.BerlinBlock) - banner += fmt.Sprintf(" - London: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/london/__init__.py.html)\n", c.LondonBlock) + banner += fmt.Sprintf(" - Berlin: #%-8v\n", c.BerlinBlock) + banner += fmt.Sprintf(" - London: #%-8v\n", c.LondonBlock) if c.ArrowGlacierBlock != nil { - banner += fmt.Sprintf(" - Arrow Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/arrow_glacier/__init__.py.html)\n", c.ArrowGlacierBlock) + banner += fmt.Sprintf(" - Arrow Glacier: #%-8v\n", c.ArrowGlacierBlock) } if c.GrayGlacierBlock != nil { - banner += fmt.Sprintf(" - Gray Glacier: #%-8v (https://ethereum.github.io/execution-specs/src/ethereum/forks/gray_glacier/__init__.py.html)\n", c.GrayGlacierBlock) + banner += fmt.Sprintf(" - Gray Glacier: #%-8v\n", c.GrayGlacierBlock) } banner += "\n" // Add a special section for the merge as it's non-obvious banner += "Merge configured:\n" - banner += " - Hard-fork specification: https://ethereum.github.io/execution-specs/src/ethereum/forks/paris/__init__.py.html\n" - banner += " - Network known to be merged\n" banner += fmt.Sprintf(" - Total terminal difficulty: %v\n", c.TerminalTotalDifficulty) if c.MergeNetsplitBlock != nil { banner += fmt.Sprintf(" - Merge netsplit block: #%-8v\n", c.MergeNetsplitBlock) @@ -655,38 +653,39 @@ func (c *ChainConfig) Description() string { // Create a list of forks post-merge banner += "Post-Merge hard forks (timestamp based):\n" if c.ShanghaiTime != nil { - banner += fmt.Sprintf(" - Shanghai: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/shanghai/__init__.py.html)\n", *c.ShanghaiTime) + banner += fmt.Sprintf(" - Shanghai: @%-10v\n", *c.ShanghaiTime) } if c.CancunTime != nil { - banner += fmt.Sprintf(" - Cancun: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/cancun/__init__.py.html)\n", *c.CancunTime) + banner += fmt.Sprintf(" - Cancun: @%-10v blob: (%s)\n", *c.CancunTime, c.BlobScheduleConfig.Cancun) } if c.PragueTime != nil { - banner += fmt.Sprintf(" - Prague: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/prague/__init__.py.html)\n", *c.PragueTime) + banner += fmt.Sprintf(" - Prague: @%-10v blob: (%s)\n", *c.PragueTime, c.BlobScheduleConfig.Prague) } if c.OsakaTime != nil { - banner += fmt.Sprintf(" - Osaka: @%-10v (https://ethereum.github.io/execution-specs/src/ethereum/forks/osaka/__init__.py.html)\n", *c.OsakaTime) + banner += fmt.Sprintf(" - Osaka: @%-10v blob: (%s)\n", *c.OsakaTime, c.BlobScheduleConfig.Osaka) } if c.BPO1Time != nil { - banner += fmt.Sprintf(" - BPO1: @%-10v\n", *c.BPO1Time) + banner += fmt.Sprintf(" - BPO1: @%-10v blob: (%s)\n", *c.BPO1Time, c.BlobScheduleConfig.BPO1) } if c.BPO2Time != nil { - banner += fmt.Sprintf(" - BPO2: @%-10v\n", *c.BPO2Time) + banner += fmt.Sprintf(" - BPO2: @%-10v blob: (%s)\n", *c.BPO2Time, c.BlobScheduleConfig.BPO2) } if c.BPO3Time != nil { - banner += fmt.Sprintf(" - BPO3: @%-10v\n", *c.BPO3Time) + banner += fmt.Sprintf(" - BPO3: @%-10v blob: (%s)\n", *c.BPO3Time, c.BlobScheduleConfig.BPO3) } if c.BPO4Time != nil { - banner += fmt.Sprintf(" - BPO4: @%-10v\n", *c.BPO4Time) + banner += fmt.Sprintf(" - BPO4: @%-10v blob: (%s)\n", *c.BPO4Time, c.BlobScheduleConfig.BPO4) } if c.BPO5Time != nil { - banner += fmt.Sprintf(" - BPO5: @%-10v\n", *c.BPO5Time) + banner += fmt.Sprintf(" - BPO5: @%-10v blob: (%s)\n", *c.BPO5Time, c.BlobScheduleConfig.BPO5) } if c.AmsterdamTime != nil { - banner += fmt.Sprintf(" - Amsterdam: @%-10v\n", *c.AmsterdamTime) + banner += fmt.Sprintf(" - Amsterdam: @%-10v blob: (%s)\n", *c.AmsterdamTime, c.BlobScheduleConfig.Amsterdam) } if c.VerkleTime != nil { - banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime) + banner += fmt.Sprintf(" - Verkle: @%-10v blob: (%s)\n", *c.VerkleTime, c.BlobScheduleConfig.Verkle) } + banner += fmt.Sprintf("\nAll fork specifications can be found at https://ethereum.github.io/execution-specs/src/ethereum/forks/\n") return banner } @@ -697,6 +696,14 @@ type BlobConfig struct { UpdateFraction uint64 `json:"baseFeeUpdateFraction"` } +// String implement fmt.Stringer, returning string format blob config. +func (bc *BlobConfig) String() string { + if bc == nil { + return "nil" + } + return fmt.Sprintf("target: %d, max: %d, fraction: %d", bc.Target, bc.Max, bc.UpdateFraction) +} + // BlobScheduleConfig determines target and max number of blobs allow per fork. type BlobScheduleConfig struct { Cancun *BlobConfig `json:"cancun,omitempty"` From 53dc2dbcdf1d2950d1e1c728911ebabafb5b3979 Mon Sep 17 00:00:00 2001 From: MozirDmitriy Date: Tue, 30 Sep 2025 05:07:54 +0300 Subject: [PATCH 088/228] core/txpool: remove unused signer field from TxPool (#32787) The TxPool.signer field was never read and each subpool (legacy/blob) maintains its own signer instance. This field remained after txpool refactoring into subpools and is dead code. Removing it reduces confusion and simplifies the constructor. --- core/txpool/txpool.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index b5470cd7fc0c..437861efca7c 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -65,7 +65,6 @@ type BlockChain interface { type TxPool struct { subpools []SubPool // List of subpools for specialized transaction handling chain BlockChain - signer types.Signer stateLock sync.RWMutex // The lock for protecting state instance state *state.StateDB // Current state at the blockchain head @@ -98,7 +97,6 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) { pool := &TxPool{ subpools: subpools, chain: chain, - signer: types.LatestSigner(chain.Config()), state: statedb, quit: make(chan chan error), term: make(chan struct{}), From 4c65d03f6b5cb13103321d0af6ae6f1dc3750293 Mon Sep 17 00:00:00 2001 From: Galoretka Date: Tue, 30 Sep 2025 05:11:09 +0300 Subject: [PATCH 089/228] core/state: correct expected value in TestMessageCallGas (#32780) --- core/state/access_events_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state/access_events_test.go b/core/state/access_events_test.go index 5e1fee767c58..e80859a0b428 100644 --- a/core/state/access_events_test.go +++ b/core/state/access_events_test.go @@ -131,7 +131,7 @@ func TestMessageCallGas(t *testing.T) { } gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false) if gas != params.WitnessChunkReadCost { - t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0) + t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost) } // Check warm read cost From 63cab20b9be9b6ed2d945b8a8e7696728db8dcc7 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:50:20 +0200 Subject: [PATCH 090/228] go.mod, cmd/keeper/go.mod: upgrade victoria metrics dependency (#32720) This is required for geth to compile to WASM. --- cmd/keeper/go.mod | 4 ++-- cmd/keeper/go.sum | 14 ++++---------- go.mod | 4 ++-- go.sum | 10 ++++------ 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/cmd/keeper/go.mod b/cmd/keeper/go.mod index d1649da43fc9..16094d16b1d8 100644 --- a/cmd/keeper/go.mod +++ b/cmd/keeper/go.mod @@ -9,7 +9,7 @@ require ( require ( github.com/StackExchange/wmi v1.2.1 // indirect - github.com/VictoriaMetrics/fastcache v1.12.2 // indirect + github.com/VictoriaMetrics/fastcache v1.13.0 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/consensys/gnark-crypto v0.18.0 // indirect @@ -24,7 +24,7 @@ require ( github.com/ferranbt/fastssz v0.1.4 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/gofrs/flock v0.12.1 // indirect - github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/golang/snappy v1.0.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect diff --git a/cmd/keeper/go.sum b/cmd/keeper/go.sum index e3bc204ba83c..3eaef469dcc9 100644 --- a/cmd/keeper/go.sum +++ b/cmd/keeper/go.sum @@ -2,15 +2,14 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= -github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= +github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= @@ -31,7 +30,6 @@ github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= @@ -61,9 +59,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= @@ -112,8 +109,6 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= @@ -134,7 +129,6 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= diff --git a/go.mod b/go.mod index 03cdf3bb2daf..c91cc81d21cc 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 github.com/Microsoft/go-winio v0.6.2 - github.com/VictoriaMetrics/fastcache v1.12.2 + github.com/VictoriaMetrics/fastcache v1.13.0 github.com/aws/aws-sdk-go-v2 v1.21.2 github.com/aws/aws-sdk-go-v2/config v1.18.45 github.com/aws/aws-sdk-go-v2/credentials v1.13.43 @@ -31,7 +31,7 @@ require ( github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/gofrs/flock v0.12.1 github.com/golang-jwt/jwt/v4 v4.5.2 - github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb + github.com/golang/snappy v1.0.0 github.com/google/gofuzz v1.2.0 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.2 diff --git a/go.sum b/go.sum index 764cfdb668ca..779bcde84675 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= -github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= +github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA= @@ -52,7 +52,6 @@ github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3M github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= @@ -165,8 +164,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -450,7 +449,6 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= From 7a7bab7ed0a8c2086b5ca5426cb3e76333f7b333 Mon Sep 17 00:00:00 2001 From: Yuan-Yao Sung Date: Tue, 30 Sep 2025 19:30:10 +0800 Subject: [PATCH 091/228] eth/catalyst: extend payloadVersion support to osaka/post-osaka forks (#32800) This PR updates the `payloadVersion` function in `simulated_beacon.go` to handle additional following forks used during development and testing phases after Osaka. This change ensures that the simulated beacon correctly resolves the payload version for these forks, enabling consistent and valid execution payload handling during local testing or simulation. --- eth/catalyst/simulated_beacon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go index 0642d6a1adad..c10990c233cd 100644 --- a/eth/catalyst/simulated_beacon.go +++ b/eth/catalyst/simulated_beacon.go @@ -100,7 +100,7 @@ type SimulatedBeacon struct { func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion { switch config.LatestFork(time) { - case forks.Prague, forks.Cancun: + case forks.BPO5, forks.BPO4, forks.BPO3, forks.BPO2, forks.BPO1, forks.Osaka, forks.Prague, forks.Cancun: return engine.PayloadV3 case forks.Paris, forks.Shanghai: return engine.PayloadV2 From 3c034588573698898bdba7d25bb9328e007349ab Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 30 Sep 2025 19:30:47 +0800 Subject: [PATCH 092/228] p2p: fix error message in test (#32804) --- p2p/enode/iter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/enode/iter_test.go b/p2p/enode/iter_test.go index 577f9c282565..922e1cde1949 100644 --- a/p2p/enode/iter_test.go +++ b/p2p/enode/iter_test.go @@ -45,7 +45,7 @@ func TestReadNodesCycle(t *testing.T) { nodes := ReadNodes(iter, 10) checkNodes(t, nodes, 3) if iter.count != 10 { - t.Fatalf("%d calls to Next, want %d", iter.count, 100) + t.Fatalf("%d calls to Next, want %d", iter.count, 10) } } From 656ae02a9eb3f05dd6f7e79d0fa31f72ca9d6a32 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 30 Sep 2025 19:31:42 +0800 Subject: [PATCH 093/228] signer/core: fix error message in test (#32807) --- signer/core/api_test.go | 2 +- signer/core/signed_data_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/signer/core/api_test.go b/signer/core/api_test.go index 0e16a1b7fd41..ed4fdc509685 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -264,7 +264,7 @@ func TestSignTx(t *testing.T) { t.Errorf("Expected nil-response, got %v", res) } if err != keystore.ErrDecrypt { - t.Errorf("Expected ErrLocked! %v", err) + t.Errorf("Expected ErrDecrypt! %v", err) } control.approveCh <- "No way" res, err = api.SignTransaction(t.Context(), tx, &methodSig) diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 001f6b6838cd..8455aaf9c590 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -202,7 +202,7 @@ func TestSignData(t *testing.T) { t.Errorf("Expected nil-data, got %x", signature) } if err != keystore.ErrDecrypt { - t.Errorf("Expected ErrLocked! '%v'", err) + t.Errorf("Expected ErrDecrypt! '%v'", err) } control.approveCh <- "No way" signature, err = api.SignData(context.Background(), apitypes.TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) From 4ce18a9cb85f2f030048ecbbc269c108bc20589b Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 30 Sep 2025 19:33:36 +0800 Subject: [PATCH 094/228] params: fix banner message (#32796) --- params/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index 4e885cbdd4d9..e796d75535c6 100644 --- a/params/config.go +++ b/params/config.go @@ -624,7 +624,7 @@ func (c *ChainConfig) Description() string { } banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v\n", c.EIP150Block) banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v\n", c.EIP155Block) - banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v\n", c.EIP155Block) + banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v\n", c.EIP158Block) banner += fmt.Sprintf(" - Byzantium: #%-8v\n", c.ByzantiumBlock) banner += fmt.Sprintf(" - Constantinople: #%-8v\n", c.ConstantinopleBlock) banner += fmt.Sprintf(" - Petersburg: #%-8v\n", c.PetersburgBlock) From e4da48cca535afdd09025e1f122b779b8cc02274 Mon Sep 17 00:00:00 2001 From: Martin HS Date: Wed, 1 Oct 2025 10:05:49 +0200 Subject: [PATCH 095/228] core/types, trie: reduce allocations in derivesha (#30747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alternative to #30746, potential follow-up to #30743 . This PR makes the stacktrie always copy incoming value buffers, and reuse them internally. Improvement in #30743: ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/types cpu: 12th Gen Intel(R) Core(TM) i7-1270P │ derivesha.1 │ derivesha.2 │ │ sec/op │ sec/op vs base │ DeriveSha200/stack_trie-8 477.8µ ± 2% 430.0µ ± 12% -10.00% (p=0.000 n=10) │ derivesha.1 │ derivesha.2 │ │ B/op │ B/op vs base │ DeriveSha200/stack_trie-8 45.17Ki ± 0% 25.65Ki ± 0% -43.21% (p=0.000 n=10) │ derivesha.1 │ derivesha.2 │ │ allocs/op │ allocs/op vs base │ DeriveSha200/stack_trie-8 1259.0 ± 0% 232.0 ± 0% -81.57% (p=0.000 n=10) ``` This PR further enhances that: ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/types cpu: 12th Gen Intel(R) Core(TM) i7-1270P │ derivesha.2 │ derivesha.3 │ │ sec/op │ sec/op vs base │ DeriveSha200/stack_trie-8 430.0µ ± 12% 423.6µ ± 13% ~ (p=0.739 n=10) │ derivesha.2 │ derivesha.3 │ │ B/op │ B/op vs base │ DeriveSha200/stack_trie-8 25.654Ki ± 0% 4.960Ki ± 0% -80.67% (p=0.000 n=10) │ derivesha.2 │ derivesha.3 │ │ allocs/op │ allocs/op vs base │ DeriveSha200/stack_trie-8 232.00 ± 0% 37.00 ± 0% -84.05% (p=0.000 n=10) ``` So the total derivesha-improvement over *both PRS* is: ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/types cpu: 12th Gen Intel(R) Core(TM) i7-1270P │ derivesha.1 │ derivesha.3 │ │ sec/op │ sec/op vs base │ DeriveSha200/stack_trie-8 477.8µ ± 2% 423.6µ ± 13% -11.33% (p=0.015 n=10) │ derivesha.1 │ derivesha.3 │ │ B/op │ B/op vs base │ DeriveSha200/stack_trie-8 45.171Ki ± 0% 4.960Ki ± 0% -89.02% (p=0.000 n=10) │ derivesha.1 │ derivesha.3 │ │ allocs/op │ allocs/op vs base │ DeriveSha200/stack_trie-8 1259.00 ± 0% 37.00 ± 0% -97.06% (p=0.000 n=10) ``` Since this PR always copies the incoming value, it adds a little bit of a penalty on the previous insert-benchmark, which copied nothing (always passed the same empty slice as input) : ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/trie cpu: 12th Gen Intel(R) Core(TM) i7-1270P │ stacktrie.7 │ stacktrie.10 │ │ sec/op │ sec/op vs base │ Insert100K-8 88.21m ± 34% 92.37m ± 31% ~ (p=0.280 n=10) │ stacktrie.7 │ stacktrie.10 │ │ B/op │ B/op vs base │ Insert100K-8 3.424Ki ± 3% 4.581Ki ± 3% +33.80% (p=0.000 n=10) │ stacktrie.7 │ stacktrie.10 │ │ allocs/op │ allocs/op vs base │ Insert100K-8 22.00 ± 5% 26.00 ± 4% +18.18% (p=0.000 n=10) ``` --------- Co-authored-by: Gary Rong Co-authored-by: Felix Lange --- core/types/block.go | 2 +- core/types/hashing.go | 27 ++++++++----- core/types/hashing_test.go | 39 ++++++++++++------ internal/blocktest/test_hash.go | 5 ++- trie/bytepool.go | 51 ++++++++++++++++++++---- trie/list_hasher.go | 56 ++++++++++++++++++++++++++ trie/stacktrie.go | 70 +++++++++++++++++++-------------- trie/trie.go | 4 +- 8 files changed, 189 insertions(+), 65 deletions(-) create mode 100644 trie/list_hasher.go diff --git a/core/types/block.go b/core/types/block.go index da9614793afd..b5b6468a131c 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -240,7 +240,7 @@ type extblock struct { // // The receipt's bloom must already calculated for the block's bloom to be // correctly calculated. -func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block { +func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher ListHasher) *Block { if body == nil { body = &Body{} } diff --git a/core/types/hashing.go b/core/types/hashing.go index 3cc22d50d101..98fe64e15aca 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -// hasherPool holds LegacyKeccak256 hashers for rlpHash. +// hasherPool holds LegacyKeccak256 buffer for rlpHash. var hasherPool = sync.Pool{ New: func() interface{} { return crypto.NewKeccakState() }, } @@ -75,11 +75,17 @@ func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { return h } -// TrieHasher is the tool used to calculate the hash of derivable list. -// This is internal, do not use. -type TrieHasher interface { +// ListHasher defines the interface for computing the hash of a derivable list. +type ListHasher interface { + // Reset clears the internal state of the hasher, preparing it for reuse. Reset() - Update([]byte, []byte) error + + // Update inserts the given key-value pair into the hasher. + // The implementation must copy the provided slices, allowing the caller + // to safely modify them after the call returns. + Update(key []byte, value []byte) error + + // Hash computes and returns the final hash of all inserted key-value pairs. Hash() common.Hash } @@ -91,19 +97,20 @@ type DerivableList interface { EncodeIndex(int, *bytes.Buffer) } +// encodeForDerive encodes the element in the list at the position i into the buffer. func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { buf.Reset() list.EncodeIndex(i, buf) - // It's really unfortunate that we need to perform this copy. - // StackTrie holds onto the values until Hash is called, so the values - // written to it must not alias. - return common.CopyBytes(buf.Bytes()) + return buf.Bytes() } // DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header. -func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { +func DeriveSha(list DerivableList, hasher ListHasher) common.Hash { hasher.Reset() + // Allocate a buffer for value encoding. As the hasher is claimed that all + // supplied key value pairs will be copied by hasher and safe to reuse the + // encoding buffer. valueBuf := encodeBufferPool.Get().(*bytes.Buffer) defer encodeBufferPool.Put(valueBuf) diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go index 54adbc73e8d0..a7153bf09a70 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -26,12 +26,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "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" - "github.com/ethereum/go-ethereum/triedb" ) func TestDeriveSha(t *testing.T) { @@ -40,7 +38,7 @@ func TestDeriveSha(t *testing.T) { t.Fatal(err) } for len(txs) < 1000 { - exp := types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + exp := types.DeriveSha(txs, trie.NewListHasher()) got := types.DeriveSha(txs, trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) @@ -76,30 +74,45 @@ func TestEIP2718DeriveSha(t *testing.T) { } } +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/core/types +// cpu: Apple M1 Pro +// BenchmarkDeriveSha200 +// BenchmarkDeriveSha200/std_trie +// BenchmarkDeriveSha200/std_trie-8 6754 174074 ns/op 80054 B/op 1926 allocs/op +// BenchmarkDeriveSha200/stack_trie +// BenchmarkDeriveSha200/stack_trie-8 7296 162675 ns/op 745 B/op 19 allocs/op func BenchmarkDeriveSha200(b *testing.B) { txs, err := genTxs(200) if err != nil { b.Fatal(err) } - var exp common.Hash - var got common.Hash + want := types.DeriveSha(txs, trie.NewListHasher()) + b.Run("std_trie", func(b *testing.B) { b.ReportAllocs() + var have common.Hash for b.Loop() { - exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + have = types.DeriveSha(txs, trie.NewListHasher()) + } + if have != want { + b.Errorf("have %x want %x", have, want) } }) + st := trie.NewStackTrie(nil) b.Run("stack_trie", func(b *testing.B) { - b.ResetTimer() b.ReportAllocs() + var have common.Hash for b.Loop() { - got = types.DeriveSha(txs, trie.NewStackTrie(nil)) + st.Reset() + have = types.DeriveSha(txs, st) + } + if have != want { + b.Errorf("have %x want %x", have, want) } }) - if got != exp { - b.Errorf("got %x exp %x", got, exp) - } } func TestFuzzDeriveSha(t *testing.T) { @@ -107,7 +120,7 @@ func TestFuzzDeriveSha(t *testing.T) { rndSeed := mrand.Int() for i := 0; i < 10; i++ { seed := rndSeed + i - exp := types.DeriveSha(newDummy(i), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + exp := types.DeriveSha(newDummy(i), trie.NewListHasher()) got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { printList(t, newDummy(seed)) @@ -135,7 +148,7 @@ func TestDerivableList(t *testing.T) { }, } for i, tc := range tcs[1:] { - exp := types.DeriveSha(flatList(tc), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) + exp := types.DeriveSha(flatList(tc), trie.NewListHasher()) got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("case %d: got %x exp %x", i, got, exp) diff --git a/internal/blocktest/test_hash.go b/internal/blocktest/test_hash.go index 4d2b077e89b5..b3e7098e2bb6 100644 --- a/internal/blocktest/test_hash.go +++ b/internal/blocktest/test_hash.go @@ -23,6 +23,7 @@ package blocktest import ( + "bytes" "hash" "github.com/ethereum/go-ethereum/common" @@ -48,8 +49,8 @@ func (h *testHasher) Reset() { // Update updates the hash state with the given key and value. func (h *testHasher) Update(key, val []byte) error { - h.hasher.Write(key) - h.hasher.Write(val) + h.hasher.Write(bytes.Clone(key)) + h.hasher.Write(bytes.Clone(val)) return nil } diff --git a/trie/bytepool.go b/trie/bytepool.go index 4f9c5672fd9b..31be7ae74991 100644 --- a/trie/bytepool.go +++ b/trie/bytepool.go @@ -32,8 +32,8 @@ func newBytesPool(sliceCap, nitems int) *bytesPool { } } -// Get returns a slice. Safe for concurrent use. -func (bp *bytesPool) Get() []byte { +// get returns a slice. Safe for concurrent use. +func (bp *bytesPool) get() []byte { select { case b := <-bp.c: return b @@ -42,18 +42,18 @@ func (bp *bytesPool) Get() []byte { } } -// GetWithSize returns a slice with specified byte slice size. -func (bp *bytesPool) GetWithSize(s int) []byte { - b := bp.Get() +// getWithSize returns a slice with specified byte slice size. +func (bp *bytesPool) getWithSize(s int) []byte { + b := bp.get() if cap(b) < s { return make([]byte, s) } return b[:s] } -// Put returns a slice to the pool. Safe for concurrent use. This method +// put returns a slice to the pool. Safe for concurrent use. This method // will ignore slices that are too small or too large (>3x the cap) -func (bp *bytesPool) Put(b []byte) { +func (bp *bytesPool) put(b []byte) { if c := cap(b); c < bp.w || c > 3*bp.w { return } @@ -62,3 +62,40 @@ func (bp *bytesPool) Put(b []byte) { default: } } + +// unsafeBytesPool is a pool for byte slices. It is not safe for concurrent use. +type unsafeBytesPool struct { + items [][]byte + w int +} + +// newUnsafeBytesPool creates a new unsafeBytesPool. The sliceCap sets the +// capacity of newly allocated slices, and the nitems determines how many +// items the pool will hold, at maximum. +func newUnsafeBytesPool(sliceCap, nitems int) *unsafeBytesPool { + return &unsafeBytesPool{ + items: make([][]byte, 0, nitems), + w: sliceCap, + } +} + +// Get returns a slice with pre-allocated space. +func (bp *unsafeBytesPool) get() []byte { + if len(bp.items) > 0 { + last := bp.items[len(bp.items)-1] + bp.items = bp.items[:len(bp.items)-1] + return last + } + return make([]byte, 0, bp.w) +} + +// put returns a slice to the pool. This method will ignore slices that are +// too small or too large (>3x the cap) +func (bp *unsafeBytesPool) put(b []byte) { + if c := cap(b); c < bp.w || c > 3*bp.w { + return + } + if len(bp.items) < cap(bp.items) { + bp.items = append(bp.items, b) + } +} diff --git a/trie/list_hasher.go b/trie/list_hasher.go new file mode 100644 index 000000000000..8f334f9901b1 --- /dev/null +++ b/trie/list_hasher.go @@ -0,0 +1,56 @@ +// 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 . + +package trie + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" +) + +// ListHasher is a wrapper of the Merkle-Patricia-Trie, which implements +// types.ListHasher. Compared to a Trie instance, the Update method of this +// type always deep-copies its input slices. +// +// This implementation is very inefficient in terms of memory allocation, +// compared with StackTrie. It exists only for correctness comparison purposes. +type ListHasher struct { + tr *Trie +} + +// NewListHasher initializes the list hasher. +func NewListHasher() *ListHasher { + return &ListHasher{ + tr: NewEmpty(nil), + } +} + +// Reset clears the internal state prepares the ListHasher for reuse. +func (h *ListHasher) Reset() { + h.tr.reset() +} + +// Update inserts a key-value pair into the trie. +func (h *ListHasher) Update(key []byte, value []byte) error { + key, value = bytes.Clone(key), bytes.Clone(value) + return h.tr.Update(key, value) +} + +// Hash computes the root hash of all inserted key-value pairs. +func (h *ListHasher) Hash() common.Hash { + return h.tr.Hash() +} diff --git a/trie/stacktrie.go b/trie/stacktrie.go index 2b7366c3c514..18fe1eea7891 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -28,7 +28,7 @@ import ( var ( stPool = sync.Pool{New: func() any { return new(stNode) }} bPool = newBytesPool(32, 100) - _ = types.TrieHasher((*StackTrie)(nil)) + _ = types.ListHasher((*StackTrie)(nil)) ) // OnTrieNode is a callback method invoked when a trie node is committed @@ -50,6 +50,7 @@ type StackTrie struct { onTrieNode OnTrieNode kBuf []byte // buf space used for hex-key during insertions pBuf []byte // buf space used for path during insertions + vPool *unsafeBytesPool } // NewStackTrie allocates and initializes an empty trie. The committed nodes @@ -61,6 +62,7 @@ func NewStackTrie(onTrieNode OnTrieNode) *StackTrie { onTrieNode: onTrieNode, kBuf: make([]byte, 64), pBuf: make([]byte, 64), + vPool: newUnsafeBytesPool(300, 20), } } @@ -74,6 +76,9 @@ func (t *StackTrie) grow(key []byte) { } // Update inserts a (key, value) pair into the stack trie. +// +// Note the supplied key value pair is copied and managed internally, +// they are safe to be modified after this method returns. func (t *StackTrie) Update(key, value []byte) error { if len(value) == 0 { return errors.New("trying to insert empty (deletion)") @@ -88,7 +93,14 @@ func (t *StackTrie) Update(key, value []byte) error { } else { t.last = append(t.last[:0], k...) // reuse key slice } - t.insert(t.root, k, value, t.pBuf[:0]) + vBuf := t.vPool.get() + if cap(vBuf) < len(value) { + vBuf = common.CopyBytes(value) + } else { + vBuf = vBuf[:len(value)] + copy(vBuf, value) + } + t.insert(t.root, k, vBuf, t.pBuf[:0]) return nil } @@ -108,14 +120,16 @@ func (t *StackTrie) TrieKey(key []byte) []byte { // stNode represents a node within a StackTrie type stNode struct { typ uint8 // node type (as in branch, ext, leaf) - key []byte // key chunk covered by this (leaf|ext) node - val []byte // value contained by this node if it's a leaf - children [16]*stNode // list of children (for branch and exts) + key []byte // exclusive owned key chunk covered by this (leaf|ext) node + val []byte // exclusive owned value contained by this node (leaf: value; hash: hash) + children [16]*stNode // list of children (for branch and ext) } -// newLeaf constructs a leaf node with provided node key and value. The key -// will be deep-copied in the function and safe to modify afterwards, but -// value is not. +// newLeaf constructs a leaf node with provided node key and value. +// +// The key is deep-copied within the function, so it can be safely modified +// afterwards. The value is retained directly without copying, as it is +// exclusively owned by the stackTrie. func newLeaf(key, val []byte) *stNode { st := stPool.Get().(*stNode) st.typ = leafNode @@ -146,9 +160,9 @@ const ( func (n *stNode) reset() *stNode { if n.typ == hashedNode { // On hashnodes, we 'own' the val: it is guaranteed to be not held - // by external caller. Hence, when we arrive here, we can put it back - // into the pool - bPool.Put(n.val) + // by external caller. Hence, when we arrive here, we can put it + // back into the pool + bPool.put(n.val) } n.key = n.key[:0] n.val = nil @@ -172,11 +186,6 @@ func (n *stNode) getDiffIndex(key []byte) int { } // Helper function to that inserts a (key, value) pair into the trie. -// -// - The key is not retained by this method, but always copied if needed. -// - The value is retained by this method, as long as the leaf that it represents -// remains unhashed. However: it is never modified. -// - The path is not retained by this method. func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { switch st.typ { case branchNode: /* Branch */ @@ -235,16 +244,14 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { } var p *stNode if diffidx == 0 { - // the break is on the first byte, so - // the current node is converted into - // a branch node. + // the break is on the first byte, so the current node + // is converted into a branch node. st.children[0] = nil - p = st st.typ = branchNode + p = st } else { - // the common prefix is at least one byte - // long, insert a new intermediate branch - // node. + // the common prefix is at least one byte long, insert + // a new intermediate branch node. st.children[0] = stPool.Get().(*stNode) st.children[0].typ = branchNode p = st.children[0] @@ -280,8 +287,8 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { if diffidx == 0 { // Convert current leaf into a branch st.typ = branchNode - p = st st.children[0] = nil + p = st } else { // Convert current node into an ext, // and insert a child branch node. @@ -307,9 +314,7 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { st.val = nil case emptyNode: /* Empty */ - st.typ = leafNode - st.key = append(st.key, key...) // deep-copy the key as it's volatile - st.val = value + *st = *newLeaf(key, value) case hashedNode: panic("trying to insert into hash") @@ -393,18 +398,23 @@ func (t *StackTrie) hash(st *stNode, path []byte) { st.typ = hashedNode st.key = st.key[:0] - st.val = nil // Release reference to potentially externally held slice. + // Release reference to value slice which is exclusively owned + // by stackTrie itself. + if cap(st.val) > 0 && t.vPool != nil { + t.vPool.put(st.val) + } + st.val = nil // Skip committing the non-root node if the size is smaller than 32 bytes // as tiny nodes are always embedded in their parent except root node. if len(blob) < 32 && len(path) > 0 { - st.val = bPool.GetWithSize(len(blob)) + st.val = bPool.getWithSize(len(blob)) copy(st.val, blob) return } // Write the hash to the 'val'. We allocate a new val here to not mutate // input values. - st.val = bPool.GetWithSize(32) + st.val = bPool.getWithSize(32) t.h.hashDataTo(st.val, blob) // Invoke the callback it's provided. Notably, the path and blob slices are diff --git a/trie/trie.go b/trie/trie.go index 36cc732ee850..1ef2c2f1a66e 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -784,8 +784,8 @@ func (t *Trie) Witness() map[string][]byte { return t.prevalueTracer.Values() } -// Reset drops the referenced root node and cleans all internal state. -func (t *Trie) Reset() { +// reset drops the referenced root node and cleans all internal state. +func (t *Trie) reset() { t.root = nil t.owner = common.Hash{} t.unhashed = 0 From 32956699164a9ce46448457c4e2c3df732aa0b40 Mon Sep 17 00:00:00 2001 From: zzzckck <152148891+zzzckck@users.noreply.github.com> Date: Thu, 2 Oct 2025 18:43:31 +0800 Subject: [PATCH 096/228] p2p/enode: fix discovery AyncFilter deadlock on shutdown (#32572) Description: We found a occasionally node hang issue on BSC, I think Geth may also have the issue, so pick the fix patch here. The fix on BSC repo: https://github.com/bnb-chain/bsc/pull/3347 When the hang occurs, there are two routines stuck. - routine 1: AsyncFilter(...) On node start, it will run part of the DiscoveryV4 protocol, which could take considerable time, here is its hang callstack: ``` goroutine 9711 [chan receive]: // this routine was stuck on read channel: `<-f.slots` github.com/ethereum/go-ethereum/p2p/enode.AsyncFilter.func1() github.com/ethereum/go-ethereum/p2p/enode/iter.go:206 +0x125 created by github.com/ethereum/go-ethereum/p2p/enode.AsyncFilter in goroutine 1 github.com/ethereum/go-ethereum/p2p/enode/iter.go:192 +0x205 ``` - Routine 2: Node Stop It is the main routine to shutdown the process, but it got stuck when it tries to shutdown the discovery components, as it tries to drain the channel of `<-f.slots`, but the extra 1 slot will never have chance to be resumed. ``` goroutine 11796 [chan receive]: github.com/ethereum/go-ethereum/p2p/enode.(*asyncFilterIter).Close.func1() github.com/ethereum/go-ethereum/p2p/enode/iter.go:248 +0x5c sync.(*Once).doSlow(0xc032a97cb8?, 0xc032a97d18?) sync/once.go:78 +0xab sync.(*Once).Do(...) sync/once.go:69 github.com/ethereum/go-ethereum/p2p/enode.(*asyncFilterIter).Close(0xc092ff8d00?) github.com/ethereum/go-ethereum/p2p/enode/iter.go:244 +0x36 github.com/ethereum/go-ethereum/p2p/enode.(*bufferIter).Close.func1() github.com/ethereum/go-ethereum/p2p/enode/iter.go:299 +0x24 sync.(*Once).doSlow(0x11a175f?, 0x2bfe63e?) sync/once.go:78 +0xab sync.(*Once).Do(...) sync/once.go:69 github.com/ethereum/go-ethereum/p2p/enode.(*bufferIter).Close(0x30?) github.com/ethereum/go-ethereum/p2p/enode/iter.go:298 +0x36 github.com/ethereum/go-ethereum/p2p/enode.(*FairMix).Close(0xc0004bfea0) github.com/ethereum/go-ethereum/p2p/enode/iter.go:379 +0xb7 github.com/ethereum/go-ethereum/eth.(*Ethereum).Stop(0xc000997b00) github.com/ethereum/go-ethereum/eth/backend.go:960 +0x4a github.com/ethereum/go-ethereum/node.(*Node).stopServices(0xc0001362a0, {0xc012e16330, 0x1, 0xc000111410?}) github.com/ethereum/go-ethereum/node/node.go:333 +0xb3 github.com/ethereum/go-ethereum/node.(*Node).Close(0xc0001362a0) github.com/ethereum/go-ethereum/node/node.go:263 +0x167 created by github.com/ethereum/go-ethereum/cmd/utils.StartNode.func1.1 in goroutine 9729 github.com/ethereum/go-ethereum/cmd/utils/cmd.go:101 +0x78 ``` The rootcause of the hang is caused by the extra 1 slot, which was designed to make sure the routines in `AsyncFilter(...)` can be finished. This PR fixes it by making sure the extra 1 shot can always be resumed when node shutdown. --- p2p/enode/iter.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/p2p/enode/iter.go b/p2p/enode/iter.go index 4890321f4921..265d8648de06 100644 --- a/p2p/enode/iter.go +++ b/p2p/enode/iter.go @@ -178,7 +178,7 @@ type AsyncFilterFunc func(context.Context, *Node) *Node func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { f := &asyncFilterIter{ it: ensureSourceIter(it), - slots: make(chan struct{}, workers+1), + slots: make(chan struct{}, workers+1), // extra 1 slot to make sure all the goroutines can be completed passed: make(chan iteratorItem), } for range cap(f.slots) { @@ -193,6 +193,9 @@ func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { return case <-f.slots: } + defer func() { + f.slots <- struct{}{} // the iterator has ended + }() // read from the iterator and start checking nodes in parallel // when a node is checked, it will be sent to the passed channel // and the slot will be released @@ -201,7 +204,11 @@ func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { nodeSource := f.it.NodeSource() // check the node async, in a separate goroutine - <-f.slots + select { + case <-ctx.Done(): + return + case <-f.slots: + } go func() { if nn := check(ctx, node); nn != nil { item := iteratorItem{nn, nodeSource} @@ -213,8 +220,6 @@ func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { f.slots <- struct{}{} }() } - // the iterator has ended - f.slots <- struct{}{} }() return f From 103861ab873930c9d23f84f6f422f7901b657928 Mon Sep 17 00:00:00 2001 From: hero5512 Date: Thu, 2 Oct 2025 08:34:06 -0400 Subject: [PATCH 097/228] core: refactor StateProcessor to accept ChainContext interface (#32739) This pr implements https://github.com/ethereum/go-ethereum/issues/32733 to make StateProcessor more customisable. ## Compatibility notes This introduces a breaking change to users using geth EVM as a library. The `NewStateProcessor` function now takes one parameter which has the chainConfig embedded instead of 2 parameters. --- core/blockchain.go | 2 +- core/evm.go | 9 ++------- core/state_processor.go | 28 ++++++++++++++++------------ core/stateless.go | 2 +- core/vm/runtime/runtime_test.go | 12 ++++++++++++ eth/tracers/api.go | 1 + eth/tracers/api_test.go | 4 ++++ internal/ethapi/api.go | 16 ++++++++++++++++ internal/ethapi/simulate.go | 20 ++++++++++++++++++++ tests/state_test_util.go | 3 +++ 10 files changed, 76 insertions(+), 21 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 30f3da3004af..71eb4c45a2c3 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -394,7 +394,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine, bc.statedb = state.NewDatabase(bc.triedb, nil) bc.validator = NewBlockValidator(chainConfig, bc) bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc) - bc.processor = NewStateProcessor(chainConfig, bc.hc) + bc.processor = NewStateProcessor(bc.hc) genesisHeader := bc.GetHeaderByNumber(0) if genesisHeader == nil { diff --git a/core/evm.go b/core/evm.go index 41b4e6ac5805..18d940fdd228 100644 --- a/core/evm.go +++ b/core/evm.go @@ -25,21 +25,16 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) // ChainContext supports retrieving headers and consensus parameters from the // current blockchain to be used during transaction processing. type ChainContext interface { + consensus.ChainHeaderReader + // Engine retrieves the chain's consensus engine. Engine() consensus.Engine - - // GetHeader returns the header corresponding to the hash/number argument pair. - GetHeader(common.Hash, uint64) *types.Header - - // Config returns the chain's configuration. - Config() *params.ChainConfig } // NewEVMBlockContext creates a new context for use in the EVM. diff --git a/core/state_processor.go b/core/state_processor.go index 4a5e69ca6e88..b66046f50178 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -35,18 +35,21 @@ import ( // // StateProcessor implements Processor. type StateProcessor struct { - config *params.ChainConfig // Chain configuration options - chain *HeaderChain // Canonical header chain + chain ChainContext // Chain context interface } // NewStateProcessor initialises a new StateProcessor. -func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StateProcessor { +func NewStateProcessor(chain ChainContext) *StateProcessor { return &StateProcessor{ - config: config, - chain: chain, + chain: chain, } } +// chainConfig returns the chain configuration. +func (p *StateProcessor) chainConfig() *params.ChainConfig { + return p.chain.Config() +} + // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. @@ -56,6 +59,7 @@ func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StatePro // transactions failed to execute due to insufficient gas it will return an error. func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) { var ( + config = p.chainConfig() receipts types.Receipts usedGas = new(uint64) header = block.Header() @@ -66,12 +70,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg ) // Mutate the block and state according to any hard-fork specs - if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { + if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) } var ( context vm.BlockContext - signer = types.MakeSigner(p.config, header.Number, header.Time) + signer = types.MakeSigner(config, header.Number, header.Time) ) // Apply pre-execution system calls. @@ -80,12 +84,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg tracingStateDB = state.NewHookedState(statedb, hooks) } context = NewEVMBlockContext(header, p.chain, nil) - evm := vm.NewEVM(context, tracingStateDB, p.config, cfg) + evm := vm.NewEVM(context, tracingStateDB, config, cfg) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { ProcessBeaconBlockRoot(*beaconRoot, evm) } - if p.config.IsPrague(block.Number(), block.Time()) || p.config.IsVerkle(block.Number(), block.Time()) { + if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) { ProcessParentBlockHash(block.ParentHash(), evm) } @@ -106,10 +110,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Read requests if Prague is enabled. var requests [][]byte - if p.config.IsPrague(block.Number(), block.Time()) { + if config.IsPrague(block.Number(), block.Time()) { requests = [][]byte{} // EIP-6110 - if err := ParseDepositLogs(&requests, allLogs, p.config); err != nil { + if err := ParseDepositLogs(&requests, allLogs, config); err != nil { return nil, fmt.Errorf("failed to parse deposit logs: %w", err) } // EIP-7002 @@ -123,7 +127,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body()) + p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body()) return &ProcessResult{ Receipts: receipts, diff --git a/core/stateless.go b/core/stateless.go index d21a62b4a51c..b20c909da670 100644 --- a/core/stateless.go +++ b/core/stateless.go @@ -62,7 +62,7 @@ func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *typ headerCache: lru.NewCache[common.Hash, *types.Header](256), engine: beacon.New(ethash.NewFaker()), } - processor := NewStateProcessor(config, chain) + processor := NewStateProcessor(chain) validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block // Run the stateless blocks processing and self-validate certain fields diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index ddd32df039c3..a001d8162332 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -312,6 +312,18 @@ func (d *dummyChain) Config() *params.ChainConfig { return nil } +func (d *dummyChain) CurrentHeader() *types.Header { + return nil +} + +func (d *dummyChain) GetHeaderByNumber(n uint64) *types.Header { + return d.GetHeader(common.Hash{}, n) +} + +func (d *dummyChain) GetHeaderByHash(h common.Hash) *types.Header { + return nil +} + // TestBlockhash tests the blockhash operation. It's a bit special, since it internally // requires access to a chain reader. func TestBlockhash(t *testing.T) { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index a05b7a7a4a79..aebeb484635f 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -80,6 +80,7 @@ type StateReleaseFunc func() type Backend interface { HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + CurrentHeader() *types.Header BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 39c39ff05dbc..4173d2a79155 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -142,6 +142,10 @@ func (b *testBackend) ChainDb() ethdb.Database { return b.chaindb } +func (b *testBackend) CurrentHeader() *types.Header { + return b.chain.CurrentHeader() +} + // teardown releases the associated resources. func (b *testBackend) teardown() { b.chain.Stop() diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2432bb70b85d..c3f267027c55 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -636,6 +636,8 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp type ChainContextBackend interface { Engine() consensus.Engine HeaderByNumber(context.Context, rpc.BlockNumber) (*types.Header, error) + HeaderByHash(context.Context, common.Hash) (*types.Header, error) + CurrentHeader() *types.Header ChainConfig() *params.ChainConfig } @@ -669,6 +671,20 @@ func (context *ChainContext) Config() *params.ChainConfig { return context.b.ChainConfig() } +func (context *ChainContext) CurrentHeader() *types.Header { + return context.b.CurrentHeader() +} + +func (context *ChainContext) GetHeaderByNumber(number uint64) *types.Header { + header, _ := context.b.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) + return header +} + +func (context *ChainContext) GetHeaderByHash(hash common.Hash) *types.Header { + header, _ := context.b.HeaderByHash(context.ctx, hash) + return header +} + func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) if blockOverrides != nil { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 75b5c5ffa86c..2bda69b31560 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -541,3 +541,23 @@ func (b *simBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) func (b *simBackend) ChainConfig() *params.ChainConfig { return b.b.ChainConfig() } + +func (b *simBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + if b.base.Hash() == hash { + return b.base, nil + } + if header, err := b.b.HeaderByHash(ctx, hash); err == nil { + return header, nil + } + // Check simulated headers + for _, header := range b.headers { + if header.Hash() == hash { + return header, nil + } + } + return nil, errors.New("header not found") +} + +func (b *simBackend) CurrentHeader() *types.Header { + return b.b.CurrentHeader() +} diff --git a/tests/state_test_util.go b/tests/state_test_util.go index b8d3c4fb92e2..1d6cc8db70f9 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -559,3 +559,6 @@ type dummyChain struct { func (d *dummyChain) Engine() consensus.Engine { return nil } func (d *dummyChain) GetHeader(h common.Hash, n uint64) *types.Header { return nil } func (d *dummyChain) Config() *params.ChainConfig { return d.config } +func (d *dummyChain) CurrentHeader() *types.Header { return nil } +func (d *dummyChain) GetHeaderByNumber(n uint64) *types.Header { return nil } +func (d *dummyChain) GetHeaderByHash(h common.Hash) *types.Header { return nil } From af61ba8c5e4f12c169f6084c3944dc82bfe64a99 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Thu, 2 Oct 2025 17:27:35 +0200 Subject: [PATCH 098/228] p2p/enode: fix asyncfilter comment (#32823) just finisher the sentence Signed-off-by: Csaba Kiraly --- p2p/enode/iter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/p2p/enode/iter.go b/p2p/enode/iter.go index 265d8648de06..54c2fc725877 100644 --- a/p2p/enode/iter.go +++ b/p2p/enode/iter.go @@ -174,7 +174,8 @@ type AsyncFilterFunc func(context.Context, *Node) *Node // AsyncFilter creates an iterator which checks nodes in parallel. // The 'check' function is called on multiple goroutines to filter each node // from the upstream iterator. When check returns nil, the node will be skipped. -// It can also return a new node to be returned by the iterator instead of the . +// It can also return a new node to be returned by the iterator instead of the +// original one. func AsyncFilter(it Iterator, check AsyncFilterFunc, workers int) Iterator { f := &asyncFilterIter{ it: ensureSourceIter(it), From 862f0fb259d0d11548080fbe0571defa8283ac3d Mon Sep 17 00:00:00 2001 From: hero5512 Date: Thu, 2 Oct 2025 11:32:20 -0400 Subject: [PATCH 099/228] trie: cleaner array concatenation (#32756) It uses the slices.Concat and slices.Clone methods available now in Go. --- trie/sync.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/trie/sync.go b/trie/sync.go index 8d0ce6901c8d..404d67f154ac 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -19,6 +19,7 @@ package trie import ( "errors" "fmt" + "slices" "sync" "github.com/ethereum/go-ethereum/common" @@ -553,7 +554,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { } children = []childNode{{ node: node.Val, - path: append(append([]byte(nil), req.path...), key...), + path: slices.Concat(req.path, key), }} // Mark all internal nodes between shortNode and its **in disk** // child as invalid. This is essential in the case of path mode @@ -595,7 +596,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { if node.Children[i] != nil { children = append(children, childNode{ node: node.Children[i], - path: append(append([]byte(nil), req.path...), byte(i)), + path: append(slices.Clone(req.path), byte(i)), }) } } From 802b36de8d5d93677194e61f9cf9c77e91a5bbc4 Mon Sep 17 00:00:00 2001 From: Nikita Mescheryakov Date: Mon, 6 Oct 2025 21:19:25 +0500 Subject: [PATCH 100/228] internal/ethapi: add timestamp to logs in eth_simulate (#32831) Adds blockTimestamp to the logs in response of eth_simulateV1. --------- Co-authored-by: Sina Mahmoodi --- internal/ethapi/api_test.go | 30 +++++++++++++++++------------- internal/ethapi/logtracer.go | 21 ++++++++++++--------- internal/ethapi/simulate.go | 2 +- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 2e0b1c3bc082..d3278c04e799 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1327,10 +1327,11 @@ func TestSimulateV1(t *testing.T) { validation = true ) type log struct { - Address common.Address `json:"address"` - Topics []common.Hash `json:"topics"` - Data hexutil.Bytes `json:"data"` - BlockNumber hexutil.Uint64 `json:"blockNumber"` + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data hexutil.Bytes `json:"data"` + BlockNumber hexutil.Uint64 `json:"blockNumber"` + BlockTimestamp hexutil.Uint64 `json:"blockTimestamp"` // Skip txHash //TxHash common.Hash `json:"transactionHash" gencodec:"required"` TxIndex hexutil.Uint `json:"transactionIndex"` @@ -1677,10 +1678,11 @@ func TestSimulateV1(t *testing.T) { Calls: []callRes{{ ReturnValue: "0x", Logs: []log{{ - Address: randomAccounts[2].addr, - Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, - BlockNumber: hexutil.Uint64(11), - Data: hexutil.Bytes{}, + Address: randomAccounts[2].addr, + Topics: []common.Hash{common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}, + BlockNumber: hexutil.Uint64(11), + BlockTimestamp: hexutil.Uint64(0x70), + Data: hexutil.Bytes{}, }}, GasUsed: "0x5508", Status: "0x1", @@ -1853,8 +1855,9 @@ func TestSimulateV1(t *testing.T) { addressToHash(accounts[0].addr), addressToHash(randomAccounts[0].addr), }, - Data: hexutil.Bytes(common.BigToHash(big.NewInt(50)).Bytes()), - BlockNumber: hexutil.Uint64(11), + Data: hexutil.Bytes(common.BigToHash(big.NewInt(50)).Bytes()), + BlockNumber: hexutil.Uint64(11), + BlockTimestamp: hexutil.Uint64(0x70), }, { Address: transferAddress, Topics: []common.Hash{ @@ -1862,9 +1865,10 @@ func TestSimulateV1(t *testing.T) { addressToHash(randomAccounts[0].addr), addressToHash(fixedAccount.addr), }, - Data: hexutil.Bytes(common.BigToHash(big.NewInt(100)).Bytes()), - BlockNumber: hexutil.Uint64(11), - Index: hexutil.Uint(1), + Data: hexutil.Bytes(common.BigToHash(big.NewInt(100)).Bytes()), + BlockNumber: hexutil.Uint64(11), + BlockTimestamp: hexutil.Uint64(0x70), + Index: hexutil.Uint(1), }}, Status: "0x1", }}, diff --git a/internal/ethapi/logtracer.go b/internal/ethapi/logtracer.go index 456aa937367f..54d2d653ea29 100644 --- a/internal/ethapi/logtracer.go +++ b/internal/ethapi/logtracer.go @@ -53,15 +53,17 @@ type tracer struct { count int traceTransfers bool blockNumber uint64 + blockTimestamp uint64 blockHash common.Hash txHash common.Hash txIdx uint } -func newTracer(traceTransfers bool, blockNumber uint64, blockHash, txHash common.Hash, txIndex uint) *tracer { +func newTracer(traceTransfers bool, blockNumber uint64, blockTimestamp uint64, blockHash, txHash common.Hash, txIndex uint) *tracer { return &tracer{ traceTransfers: traceTransfers, blockNumber: blockNumber, + blockTimestamp: blockTimestamp, blockHash: blockHash, txHash: txHash, txIdx: txIndex, @@ -115,14 +117,15 @@ func (t *tracer) onLog(log *types.Log) { func (t *tracer) captureLog(address common.Address, topics []common.Hash, data []byte) { t.logs[len(t.logs)-1] = append(t.logs[len(t.logs)-1], &types.Log{ - Address: address, - Topics: topics, - Data: data, - BlockNumber: t.blockNumber, - BlockHash: t.blockHash, - TxHash: t.txHash, - TxIndex: t.txIdx, - Index: uint(t.count), + Address: address, + Topics: topics, + Data: data, + BlockNumber: t.blockNumber, + BlockTimestamp: t.blockTimestamp, + BlockHash: t.blockHash, + TxHash: t.txHash, + TxIndex: t.txIdx, + Index: uint(t.count), }) t.count++ } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 2bda69b31560..0d1a59b371a4 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -244,7 +244,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, callResults = make([]simCallResult, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls)) // Block hash will be repaired after execution. - tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), common.Hash{}, common.Hash{}, 0) + tracer = newTracer(sim.traceTransfers, blockContext.BlockNumber.Uint64(), blockContext.Time, common.Hash{}, common.Hash{}, 0) vmConfig = &vm.Config{ NoBaseFee: !sim.validate, Tracer: tracer.Hooks(), From db8ef2df915597aa685cbb0f72bc387e4c4a611c Mon Sep 17 00:00:00 2001 From: Sina M <1591639+s1na@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:24:30 +0200 Subject: [PATCH 101/228] build: faster gh actions workflow, no ubuntu on appveyor (#32829) This PR does a few things: - Sets the gh actions runner sizes for lint (s) and test (l) workflows - Runs the tests on gh actions in parallel - Skips fetching the spec tests when unnecessary (on windows in appveyor) - Removes ubuntu appveyor runner since it's essentially duplicate of the gh action workflow now The gh test seems to go down from ~35min to ~13min. --- .github/workflows/go.yml | 6 +++--- appveyor.yml | 22 +--------------------- build/ci.go | 28 +++++++++++++++++----------- 3 files changed, 21 insertions(+), 35 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cc8ea36d7418..b8cf7f75e0dd 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,7 +10,7 @@ on: jobs: lint: name: Lint - runs-on: self-hosted-ghr + runs-on: [self-hosted-ghr, size-s-x64] steps: - uses: actions/checkout@v4 with: @@ -37,7 +37,7 @@ jobs: test: name: Test needs: lint - runs-on: self-hosted-ghr + runs-on: [self-hosted-ghr, size-l-x64] strategy: matrix: go: @@ -55,4 +55,4 @@ jobs: cache: false - name: Run tests - run: go run build/ci.go test + run: go run build/ci.go test -p 8 diff --git a/appveyor.yml b/appveyor.yml index ae1c74c18e10..8dce7f30a29c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,6 @@ clone_depth: 5 version: "{branch}.{build}" image: - - Ubuntu - Visual Studio 2019 environment: @@ -17,25 +16,6 @@ install: - go version for: - # Linux has its own script without -arch and -cc. - # The linux builder also runs lint. - - matrix: - only: - - image: Ubuntu - build_script: - - go run build/ci.go lint - - go run build/ci.go check_generate - - go run build/ci.go check_baddeps - - go run build/ci.go install -dlgo - test_script: - - go run build/ci.go test -dlgo -short - - # linux/386 is disabled. - - matrix: - exclude: - - image: Ubuntu - GETH_ARCH: 386 - # Windows builds for amd64 + 386. - matrix: only: @@ -56,4 +36,4 @@ for: - go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds - go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds test_script: - - go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short + - go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short -skip-spectests diff --git a/build/ci.go b/build/ci.go index da867a151632..905f6e4072fd 100644 --- a/build/ci.go +++ b/build/ci.go @@ -281,20 +281,26 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) ( func doTest(cmdline []string) { var ( - dlgo = flag.Bool("dlgo", false, "Download Go and build with it") - arch = flag.String("arch", "", "Run tests for given architecture") - cc = flag.String("cc", "", "Sets C compiler binary") - coverage = flag.Bool("coverage", false, "Whether to record code coverage") - verbose = flag.Bool("v", false, "Whether to log verbosely") - race = flag.Bool("race", false, "Execute the race detector") - short = flag.Bool("short", false, "Pass the 'short'-flag to go test") - cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads") + dlgo = flag.Bool("dlgo", false, "Download Go and build with it") + arch = flag.String("arch", "", "Run tests for given architecture") + cc = flag.String("cc", "", "Sets C compiler binary") + coverage = flag.Bool("coverage", false, "Whether to record code coverage") + verbose = flag.Bool("v", false, "Whether to log verbosely") + race = flag.Bool("race", false, "Execute the race detector") + short = flag.Bool("short", false, "Pass the 'short'-flag to go test") + cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads") + skipspectests = flag.Bool("skip-spectests", false, "Skip downloading execution-spec-tests fixtures") + threads = flag.Int("p", 1, "Number of CPU threads to use for testing") ) flag.CommandLine.Parse(cmdline) - // Get test fixtures. + // Load checksums file (needed for both spec tests and dlgo) csdb := download.MustLoadChecksums("build/checksums.txt") - downloadSpecTestFixtures(csdb, *cachedir) + + // Get test fixtures. + if !*skipspectests { + downloadSpecTestFixtures(csdb, *cachedir) + } // Configure the toolchain. tc := build.GoToolchain{GOARCH: *arch, CC: *cc} @@ -315,7 +321,7 @@ func doTest(cmdline []string) { // Test a single package at a time. CI builders are slow // and some tests run into timeouts under load. - gotest.Args = append(gotest.Args, "-p", "1") + gotest.Args = append(gotest.Args, "-p", fmt.Sprintf("%d", *threads)) if *coverage { gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") } From 15a0bddfb3aebc6ced3d3c2d88803288bfc6abc6 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 8 Oct 2025 11:14:27 +0200 Subject: [PATCH 102/228] cmd/devp2p/internal/ethtest: update to PoS-only test chain (#32850) --- cmd/devp2p/internal/ethtest/mkchain.sh | 5 +- cmd/devp2p/internal/ethtest/snap.go | 32 +- .../internal/ethtest/testdata/chain.rlp | Bin 341951 -> 451888 bytes .../internal/ethtest/testdata/forkenv.json | 37 +- .../internal/ethtest/testdata/genesis.json | 59 +- .../internal/ethtest/testdata/headblock.json | 21 +- .../internal/ethtest/testdata/headfcu.json | 8 +- .../internal/ethtest/testdata/headstate.json | 5866 +++--- .../internal/ethtest/testdata/newpayload.json | 16955 +++++++++++----- .../internal/ethtest/testdata/txinfo.json | 5058 ++--- 10 files changed, 16880 insertions(+), 11161 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/mkchain.sh b/cmd/devp2p/internal/ethtest/mkchain.sh index b9253e8ca760..fab630d9775d 100644 --- a/cmd/devp2p/internal/ethtest/mkchain.sh +++ b/cmd/devp2p/internal/ethtest/mkchain.sh @@ -1,9 +1,10 @@ #!/bin/sh hivechain generate \ + --pos \ --fork-interval 6 \ --tx-interval 1 \ - --length 500 \ + --length 600 \ --outdir testdata \ - --lastfork cancun \ + --lastfork prague \ --outputs accounts,genesis,chain,headstate,txinfo,headblock,headfcu,newpayload,forkenv diff --git a/cmd/devp2p/internal/ethtest/snap.go b/cmd/devp2p/internal/ethtest/snap.go index 9c1efa0e8e22..f4fce0931f12 100644 --- a/cmd/devp2p/internal/ethtest/snap.go +++ b/cmd/devp2p/internal/ethtest/snap.go @@ -86,9 +86,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) { root: root, startingHash: zero, limitHash: ffHash, - expAccounts: 86, + expAccounts: 67, expFirst: firstKey, - expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"), + expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"), desc: "In this test, we request the entire state range, but limit the response to 4000 bytes.", }, { @@ -96,9 +96,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) { root: root, startingHash: zero, limitHash: ffHash, - expAccounts: 65, + expAccounts: 49, expFirst: firstKey, - expLast: common.HexToHash("0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6"), + expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"), desc: "In this test, we request the entire state range, but limit the response to 3000 bytes.", }, { @@ -106,9 +106,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) { root: root, startingHash: zero, limitHash: ffHash, - expAccounts: 44, + expAccounts: 34, expFirst: firstKey, - expLast: common.HexToHash("0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595"), + expLast: common.HexToHash("0x2ef46ebd2073cecde499c2e8df028ad79a26d57bfaa812c4c6f7eb4c9617b913"), desc: "In this test, we request the entire state range, but limit the response to 2000 bytes.", }, { @@ -177,9 +177,9 @@ The server should return the first available account.`, root: root, startingHash: firstKey, limitHash: ffHash, - expAccounts: 86, + expAccounts: 67, expFirst: firstKey, - expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"), + expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"), 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.`, }, @@ -188,9 +188,9 @@ The server should return the first available account of the state as the first i root: root, startingHash: hashAdd(firstKey, 1), limitHash: ffHash, - expAccounts: 86, + expAccounts: 67, expFirst: secondKey, - expLast: common.HexToHash("0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa"), + expLast: common.HexToHash("0x66192e4c757fba1cdc776e6737008f42d50370d3cd801db3624274283bf7cd63"), 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.`, }, @@ -226,9 +226,9 @@ server to return no data because genesis is older than 127 blocks.`, root: s.chain.RootAt(int(s.chain.Head().Number().Uint64()) - 127), startingHash: zero, limitHash: ffHash, - expAccounts: 84, + expAccounts: 66, expFirst: firstKey, - expLast: common.HexToHash("0x580aa878e2f92d113a12c0a3ce3c21972b03dbe80786858d49a72097e2c491a3"), + expLast: common.HexToHash("0x729953a43ed6c913df957172680a17e5735143ad767bda8f58ac84ec62fbec5e"), desc: `This test requests data at a state root that is 127 blocks old. We expect the server to have this state available.`, }, @@ -657,8 +657,8 @@ The server should reject the request.`, // 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 // way. So you'll have to update this when the test chain is changed. - common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"), - common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"), + common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"), + common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"), 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, @@ -678,8 +678,8 @@ The server should reject the request.`, // be updated when the test chain is changed. expHashes: []common.Hash{ empty, - common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"), - common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"), + common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"), + common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"), }, }, diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp b/cmd/devp2p/internal/ethtest/testdata/chain.rlp index 2964c02bb1fb7f695fe6eb9c1c113f9db0b7c97b..7d4f4b3efe5d204312966e56040e759ce53b6600 100644 GIT binary patch literal 451888 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

9J?uR9fRSN| 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

REClOl)IIGpaOOSU*o%(ovM%%c3*qn6q4VI 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

3MfB$6A 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 PgC@ZCACEHDK6>;YypJV? literal 341951 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

BUlu*YmpL9FoEf$3g&@_?xj1~>XJ8%0Z+ETB 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 Date: Wed, 8 Oct 2025 12:29:51 +0300 Subject: [PATCH 103/228] core/rawdb: remove duplicated type storedReceiptRLP (#32820) Co-authored-by: Felix Lange --- core/rawdb/accessors_chain.go | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 0782a0e7dac7..f20d675ff83e 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -664,15 +664,6 @@ func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { } } -// storedReceiptRLP is the storage encoding of a receipt. -// Re-definition in core/types/receipt.go. -// TODO: Re-use the existing definition. -type storedReceiptRLP struct { - PostStateOrStatus []byte - CumulativeGasUsed uint64 - Logs []*types.Log -} - // ReceiptLogs is a barebone version of ReceiptForStorage which only keeps // the list of logs. When decoding a stored receipt into this object we // avoid creating the bloom filter. @@ -682,11 +673,11 @@ type receiptLogs struct { // DecodeRLP implements rlp.Decoder. func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error { - var stored storedReceiptRLP - if err := s.Decode(&stored); err != nil { + var rs types.ReceiptForStorage + if err := rs.DecodeRLP(s); err != nil { return err } - r.Logs = stored.Logs + r.Logs = rs.Logs return nil } From 6423c7bff03ba5abd81a9025532de94aa98102bd Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 8 Oct 2025 12:50:03 +0200 Subject: [PATCH 104/228] eth/protocols/eth: use BlockChain interface in Handshake (#32847) --- eth/protocols/eth/handshake.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go index 824e49fb2bb3..bb3d1b8eb42c 100644 --- a/eth/protocols/eth/handshake.go +++ b/eth/protocols/eth/handshake.go @@ -22,7 +22,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" @@ -36,7 +35,7 @@ const ( // Handshake executes the eth protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. -func (p *Peer) Handshake(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error { +func (p *Peer) Handshake(networkID uint64, chain forkid.Blockchain, rangeMsg BlockRangeUpdatePacket) error { switch p.version { case ETH69: return p.handshake69(networkID, chain, rangeMsg) @@ -47,10 +46,10 @@ func (p *Peer) Handshake(networkID uint64, chain *core.BlockChain, rangeMsg Bloc } } -func (p *Peer) handshake68(networkID uint64, chain *core.BlockChain) error { +func (p *Peer) handshake68(networkID uint64, chain forkid.Blockchain) error { var ( genesis = chain.Genesis() - latest = chain.CurrentBlock() + latest = chain.CurrentHeader() forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time) forkFilter = forkid.NewFilter(chain) ) @@ -92,10 +91,10 @@ func (p *Peer) readStatus68(networkID uint64, status *StatusPacket68, genesis co return nil } -func (p *Peer) handshake69(networkID uint64, chain *core.BlockChain, rangeMsg BlockRangeUpdatePacket) error { +func (p *Peer) handshake69(networkID uint64, chain forkid.Blockchain, rangeMsg BlockRangeUpdatePacket) error { var ( genesis = chain.Genesis() - latest = chain.CurrentBlock() + latest = chain.CurrentHeader() forkID = forkid.NewID(chain.Config(), genesis, latest.Number.Uint64(), latest.Time) forkFilter = forkid.NewFilter(chain) ) From 9761eb0d162886757070daedfdd0fab0994efd1c Mon Sep 17 00:00:00 2001 From: Alexey Osipov Date: Wed, 8 Oct 2025 20:23:44 +0300 Subject: [PATCH 105/228] cmd/devp2p/internal/ethtest: accept responses in any order (#32834) In both `TestSimultaneousRequests` and `TestSameRequestID`, we send two concurrent requests. The client under test is free to respond in either order, so we need to handle responses both ways. Also fixes an issue where some generated blob transactions didn't have any blobs. --------- Co-authored-by: Felix Lange --- cmd/devp2p/internal/ethtest/protocol.go | 6 ++ cmd/devp2p/internal/ethtest/suite.go | 97 +++++++++++++++---------- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/cmd/devp2p/internal/ethtest/protocol.go b/cmd/devp2p/internal/ethtest/protocol.go index a21d1ca7a1d0..af76082318fe 100644 --- a/cmd/devp2p/internal/ethtest/protocol.go +++ b/cmd/devp2p/internal/ethtest/protocol.go @@ -86,3 +86,9 @@ func protoOffset(proto Proto) uint64 { panic("unhandled protocol") } } + +// msgTypePtr is the constraint for protocol message types. +type msgTypePtr[U any] interface { + *U + Kind() byte +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 47327b6844d4..c23360bf821a 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -196,6 +196,7 @@ to check if the node disconnects after receiving multiple invalid requests.`) func (s *Suite) TestSimultaneousRequests(t *utesting.T) { t.Log(`This test requests blocks headers from the node, performing two requests concurrently, with different request IDs.`) + conn, err := s.dialAndPeer(nil) if err != nil { t.Fatalf("peering failed: %v", err) @@ -235,37 +236,36 @@ concurrently, with different request IDs.`) } // Wait for responses. - headers1 := new(eth.BlockHeadersPacket) - if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil { - t.Fatalf("error reading block headers msg: %v", err) - } - if got, want := headers1.RequestId, req1.RequestId; got != want { - t.Fatalf("unexpected request id in response: got %d, want %d", got, want) - } - headers2 := new(eth.BlockHeadersPacket) - if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil { - t.Fatalf("error reading block headers msg: %v", err) - } - if got, want := headers2.RequestId, req2.RequestId; got != want { - t.Fatalf("unexpected request id in response: got %d, want %d", got, want) + // Note they can arrive in either order. + resp, err := collectResponses(conn, 2, func(msg *eth.BlockHeadersPacket) uint64 { + if msg.RequestId != 111 && msg.RequestId != 222 { + t.Fatalf("response with unknown request ID: %v", msg.RequestId) + } + return msg.RequestId + }) + if err != nil { + t.Fatal(err) } - // Check received headers for accuracy. + // Check if headers match. + resp1 := resp[111] if expected, err := s.chain.GetHeaders(req1); err != nil { t.Fatalf("failed to get expected headers for request 1: %v", err) - } else if !headersMatch(expected, headers1.BlockHeadersRequest) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1) + } else if !headersMatch(expected, resp1.BlockHeadersRequest) { + t.Fatalf("header mismatch for request ID %v: \nexpected %v \ngot %v", 111, expected, resp1) } + resp2 := resp[222] if expected, err := s.chain.GetHeaders(req2); err != nil { t.Fatalf("failed to get expected headers for request 2: %v", err) - } else if !headersMatch(expected, headers2.BlockHeadersRequest) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2) + } else if !headersMatch(expected, resp2.BlockHeadersRequest) { + t.Fatalf("header mismatch for request ID %v: \nexpected %v \ngot %v", 222, expected, resp2) } } func (s *Suite) TestSameRequestID(t *utesting.T) { t.Log(`This test requests block headers, performing two concurrent requests with the same request ID. The node should handle the request by responding to both requests.`) + conn, err := s.dialAndPeer(nil) if err != nil { t.Fatalf("peering failed: %v", err) @@ -289,7 +289,7 @@ same request ID. The node should handle the request by responding to both reques Origin: eth.HashOrNumber{ Number: 33, }, - Amount: 2, + Amount: 3, }, } @@ -301,33 +301,50 @@ same request ID. The node should handle the request by responding to both reques t.Fatalf("failed to write to connection: %v", err) } - // Wait for the responses. - headers1 := new(eth.BlockHeadersPacket) - if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil { - t.Fatalf("error reading from connection: %v", err) - } - if got, want := headers1.RequestId, request1.RequestId; got != want { - t.Fatalf("unexpected request id: got %d, want %d", got, want) - } - headers2 := new(eth.BlockHeadersPacket) - if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil { - t.Fatalf("error reading from connection: %v", err) - } - if got, want := headers2.RequestId, request2.RequestId; got != want { - t.Fatalf("unexpected request id: got %d, want %d", got, want) + // Wait for the responses. They can arrive in either order, and we can't tell them + // apart by their request ID, so use the number of headers instead. + resp, err := collectResponses(conn, 2, func(msg *eth.BlockHeadersPacket) uint64 { + id := uint64(len(msg.BlockHeadersRequest)) + if id != 2 && id != 3 { + t.Fatalf("invalid number of headers in response: %d", id) + } + return id + }) + if err != nil { + t.Fatal(err) } // Check if headers match. + resp1 := resp[2] if expected, err := s.chain.GetHeaders(request1); err != nil { - t.Fatalf("failed to get expected block headers: %v", err) - } else if !headersMatch(expected, headers1.BlockHeadersRequest) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1) + t.Fatalf("failed to get expected headers for request 1: %v", err) + } else if !headersMatch(expected, resp1.BlockHeadersRequest) { + t.Fatalf("headers mismatch: \nexpected %v \ngot %v", expected, resp1) } + resp2 := resp[3] if expected, err := s.chain.GetHeaders(request2); err != nil { - t.Fatalf("failed to get expected block headers: %v", err) - } else if !headersMatch(expected, headers2.BlockHeadersRequest) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2) + t.Fatalf("failed to get expected headers for request 2: %v", err) + } else if !headersMatch(expected, resp2.BlockHeadersRequest) { + t.Fatalf("headers mismatch: \nexpected %v \ngot %v", expected, resp2) + } +} + +// collectResponses waits for n messages of type T on the given connection. +// The messsages are collected according to the 'identity' function. +func collectResponses[T any, P msgTypePtr[T]](conn *Conn, n int, identity func(P) uint64) (map[uint64]P, error) { + resp := make(map[uint64]P, n) + for range n { + r := new(T) + if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, r); err != nil { + return resp, fmt.Errorf("read error: %v", err) + } + id := identity(r) + if resp[id] != nil { + return resp, fmt.Errorf("duplicate response %v", r) + } + resp[id] = r } + return resp, nil } func (s *Suite) TestZeroRequestID(t *utesting.T) { @@ -887,7 +904,7 @@ func (s *Suite) makeBlobTxs(count, blobs int, discriminator byte) (txs types.Tra from, nonce := s.chain.GetSender(5) for i := 0; i < count; i++ { // Make blob data, max of 2 blobs per tx. - blobdata := make([]byte, blobs%3) + blobdata := make([]byte, min(blobs, 2)) for i := range blobdata { blobdata[i] = discriminator blobs -= 1 From 9dcfdaff84253f368aaf1e42a31823b2fea5d0a0 Mon Sep 17 00:00:00 2001 From: phrwlk Date: Thu, 9 Oct 2025 03:59:06 +0300 Subject: [PATCH 106/228] core/rawdb: correct misleading comments for state history accessors (#32783) --- core/rawdb/accessors_state.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 2359fb18f1b2..46aa5fd070ce 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -170,9 +170,11 @@ func ReadStateHistoryMetaList(db ethdb.AncientReaderOp, start uint64, count uint return db.AncientRange(stateHistoryMeta, start-1, count, 0) } -// ReadStateAccountIndex retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). +// ReadStateAccountIndex retrieves the account index blob for the specified +// state history. The index contains fixed-size entries with offsets and lengths +// into the concatenated account data table. Compute the position of state +// history in freezer by minus one since the id of first state history starts +// from one (zero for initial state). func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte { blob, err := db.Ancient(stateHistoryAccountIndex, id-1) if err != nil { @@ -181,9 +183,11 @@ func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte { return blob } -// ReadStateStorageIndex retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). +// ReadStateStorageIndex retrieves the storage index blob for the specified +// state history. The index contains fixed-size entries that locate storage slot +// data in the concatenated storage data table. Compute the position of state +// history in freezer by minus one since the id of first state history starts +// from one (zero for initial state). func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { blob, err := db.Ancient(stateHistoryStorageIndex, id-1) if err != nil { @@ -192,9 +196,10 @@ func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { return blob } -// ReadStateAccountHistory retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). +// ReadStateAccountHistory retrieves the concatenated account data blob for the +// specified state history. Offsets and lengths are resolved via the account +// index. Compute the position of state history in freezer by minus one since +// the id of first state history starts from one (zero for initial state). func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { blob, err := db.Ancient(stateHistoryAccountData, id-1) if err != nil { @@ -203,9 +208,11 @@ func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { return blob } -// ReadStateStorageHistory retrieves the state root corresponding to the specified -// state history. Compute the position of state history in freezer by minus one -// since the id of first state history starts from one(zero for initial state). +// ReadStateStorageHistory retrieves the concatenated storage slot data blob for +// the specified state history. Locations are resolved via the account and +// storage indexes. Compute the position of state history in freezer by minus +// one since the id of first state history starts from one (zero for initial +// state). func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte { blob, err := db.Ancient(stateHistoryStorageData, id-1) if err != nil { From d5bc4abf9545517a19a01164d85010257592dee8 Mon Sep 17 00:00:00 2001 From: CertiK <138698582+CertiK-Geth@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:34:30 +0800 Subject: [PATCH 107/228] eth/filters: terminate pending tx subscription on error (#32794) Fixes issue #32793. When the pending tx subscription ends, the filter is removed from `api.filters`, but it is not terminated. There is no other way to terminate it, so the subscription will leak, and potentially block the producer side. --- eth/filters/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/eth/filters/api.go b/eth/filters/api.go index d678c403894d..334a19f0b5e8 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -152,6 +152,7 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID { api.filtersMu.Lock() delete(api.filters, pendingTxSub.ID) api.filtersMu.Unlock() + pendingTxSub.Unsubscribe() return } } From 619018254ac5dc0c77b2b525e4f771f674d4420e Mon Sep 17 00:00:00 2001 From: 10gic Date: Thu, 9 Oct 2025 20:14:53 +0800 Subject: [PATCH 108/228] eth/filters: add `transactionReceipts` subscription (#32697) - Introduce a new subscription kind `transactionReceipts` to allow clients to receive transaction receipts over WebSocket as soon as they are available. - Accept optional `transactionHashes` filter to subscribe to receipts for specific transactions; an empty or omitted filter subscribes to all receipts. - Preserve the same receipt format as returned by `eth_getTransactionReceipt`. - Avoid additional HTTP polling, reducing RPC load and latency. --------- Co-authored-by: Sina Mahmoodi --- core/blockchain.go | 26 +++++- core/events.go | 4 +- eth/filters/api.go | 68 ++++++++++++++ eth/filters/filter.go | 68 ++++++++++++++ eth/filters/filter_system.go | 35 ++++++++ eth/filters/filter_system_test.go | 141 ++++++++++++++++++++++++++++++ internal/ethapi/api.go | 8 +- 7 files changed, 341 insertions(+), 9 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 71eb4c45a2c3..b7acd12aca78 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1690,7 +1690,12 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types // Set new head. bc.writeHeadBlock(block) - bc.chainFeed.Send(ChainEvent{Header: block.Header()}) + bc.chainFeed.Send(ChainEvent{ + Header: block.Header(), + Receipts: receipts, + Transactions: block.Transactions(), + }) + if len(logs) > 0 { bc.logsFeed.Send(logs) } @@ -2342,6 +2347,13 @@ func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (co // collectLogs collects the logs that were generated or removed during the // processing of a block. These logs are later announced as deleted or reborn. func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { + _, logs := bc.collectReceiptsAndLogs(b, removed) + return logs +} + +// collectReceiptsAndLogs retrieves receipts from the database and returns both receipts and logs. +// This avoids duplicate database reads when both are needed. +func (bc *BlockChain) collectReceiptsAndLogs(b *types.Block, removed bool) ([]*types.Receipt, []*types.Log) { var blobGasPrice *big.Int if b.ExcessBlobGas() != nil { blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, b.Header()) @@ -2359,7 +2371,7 @@ func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { logs = append(logs, log) } } - return logs + return receipts, logs } // reorg takes two blocks, an old chain and a new chain and will reconstruct the @@ -2588,8 +2600,14 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) { bc.writeHeadBlock(head) // Emit events - logs := bc.collectLogs(head, false) - bc.chainFeed.Send(ChainEvent{Header: head.Header()}) + receipts, logs := bc.collectReceiptsAndLogs(head, false) + + bc.chainFeed.Send(ChainEvent{ + Header: head.Header(), + Receipts: receipts, + Transactions: head.Transactions(), + }) + if len(logs) > 0 { bc.logsFeed.Send(logs) } diff --git a/core/events.go b/core/events.go index 5ad2cb1f7b32..ef0de3242621 100644 --- a/core/events.go +++ b/core/events.go @@ -27,7 +27,9 @@ type NewTxsEvent struct{ Txs []*types.Transaction } type RemovedLogsEvent struct{ Logs []*types.Log } type ChainEvent struct { - Header *types.Header + Header *types.Header + Receipts []*types.Receipt + Transactions []*types.Transaction } type ChainHeadEvent struct { diff --git a/eth/filters/api.go b/eth/filters/api.go index 334a19f0b5e8..9f9209aea7e9 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -43,6 +43,7 @@ var ( errPendingLogsUnsupported = errors.New("pending logs are not supported") errExceedMaxTopics = errors.New("exceed max topics") errExceedLogQueryLimit = errors.New("exceed max addresses or topics per search position") + errExceedMaxTxHashes = errors.New("exceed max number of transaction hashes allowed per transactionReceipts subscription") ) const ( @@ -50,6 +51,8 @@ const ( maxTopics = 4 // The maximum number of allowed topics within a topic criteria maxSubTopics = 1000 + // The maximum number of transaction hash criteria allowed in a single subscription + maxTxHashes = 200 ) // filter is a helper struct that holds meta information over the filter type @@ -296,6 +299,71 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc return rpcSub, nil } +// TransactionReceiptsFilter defines criteria for transaction receipts subscription. +// If TransactionHashes is nil or empty, receipts for all transactions included in new blocks will be delivered. +// Otherwise, only receipts for the specified transactions will be delivered. +type TransactionReceiptsFilter struct { + TransactionHashes []common.Hash `json:"transactionHashes,omitempty"` +} + +// TransactionReceipts creates a subscription that fires transaction receipts when transactions are included in blocks. +func (api *FilterAPI) TransactionReceipts(ctx context.Context, filter *TransactionReceiptsFilter) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + + // Validate transaction hashes limit + if filter != nil && len(filter.TransactionHashes) > maxTxHashes { + return nil, errExceedMaxTxHashes + } + + var ( + rpcSub = notifier.CreateSubscription() + matchedReceipts = make(chan []*ReceiptWithTx) + txHashes []common.Hash + ) + + if filter != nil { + txHashes = filter.TransactionHashes + } + + receiptsSub := api.events.SubscribeTransactionReceipts(txHashes, matchedReceipts) + + go func() { + defer receiptsSub.Unsubscribe() + + signer := types.LatestSigner(api.sys.backend.ChainConfig()) + + for { + select { + case receiptsWithTxs := <-matchedReceipts: + if len(receiptsWithTxs) > 0 { + // Convert to the same format as eth_getTransactionReceipt + marshaledReceipts := make([]map[string]interface{}, len(receiptsWithTxs)) + for i, receiptWithTx := range receiptsWithTxs { + marshaledReceipts[i] = ethapi.MarshalReceipt( + receiptWithTx.Receipt, + receiptWithTx.Receipt.BlockHash, + receiptWithTx.Receipt.BlockNumber.Uint64(), + signer, + receiptWithTx.Transaction, + int(receiptWithTx.Receipt.TransactionIndex), + ) + } + + // Send a batch of tx receipts in one notification + notifier.Notify(rpcSub.ID, marshaledReceipts) + } + case <-rpcSub.Err(): + return + } + } + }() + + return rpcSub, nil +} + // FilterCriteria represents a request to create a new filter. // Same as ethereum.FilterQuery but with UnmarshalJSON() method. type FilterCriteria ethereum.FilterQuery diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 1a9918d0ee70..02399bc80180 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -25,6 +25,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/history" "github.com/ethereum/go-ethereum/core/types" @@ -551,3 +552,70 @@ func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]commo } return true } + +// ReceiptWithTx contains a receipt and its corresponding transaction +type ReceiptWithTx struct { + Receipt *types.Receipt + Transaction *types.Transaction +} + +// filterReceipts returns the receipts matching the given criteria +// In addition to returning receipts, it also returns the corresponding transactions. +// This is because receipts only contain low-level data, while user-facing data +// may require additional information from the Transaction. +func filterReceipts(txHashes []common.Hash, ev core.ChainEvent) []*ReceiptWithTx { + var ret []*ReceiptWithTx + + receipts := ev.Receipts + txs := ev.Transactions + + if len(receipts) != len(txs) { + log.Warn("Receipts and transactions length mismatch", "receipts", len(receipts), "transactions", len(txs)) + return ret + } + + if len(txHashes) == 0 { + // No filter, send all receipts with their transactions. + ret = make([]*ReceiptWithTx, len(receipts)) + for i, receipt := range receipts { + ret[i] = &ReceiptWithTx{ + Receipt: receipt, + Transaction: txs[i], + } + } + } else if len(txHashes) == 1 { + // Filter by single transaction hash. + // This is a common case, so we distinguish it from filtering by multiple tx hashes and made a small optimization. + for i, receipt := range receipts { + if receipt.TxHash == txHashes[0] { + ret = append(ret, &ReceiptWithTx{ + Receipt: receipt, + Transaction: txs[i], + }) + break + } + } + } else { + // Filter by multiple transaction hashes. + txHashMap := make(map[common.Hash]bool, len(txHashes)) + for _, hash := range txHashes { + txHashMap[hash] = true + } + + for i, receipt := range receipts { + if txHashMap[receipt.TxHash] { + ret = append(ret, &ReceiptWithTx{ + Receipt: receipt, + Transaction: txs[i], + }) + + // Early exit if all receipts are found + if len(ret) == len(txHashes) { + break + } + } + } + } + + return ret +} diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index ecf1c870c13f..02783fa5ec56 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -158,6 +158,8 @@ const ( PendingTransactionsSubscription // BlocksSubscription queries hashes for blocks that are imported BlocksSubscription + // TransactionReceiptsSubscription queries for transaction receipts when transactions are included in blocks + TransactionReceiptsSubscription // LastIndexSubscription keeps track of the last index LastIndexSubscription ) @@ -182,6 +184,8 @@ type subscription struct { logs chan []*types.Log txs chan []*types.Transaction headers chan *types.Header + receipts chan []*ReceiptWithTx + txHashes []common.Hash // contains transaction hashes for transactionReceipts subscription filtering installed chan struct{} // closed when the filter is installed err chan error // closed when the filter is uninstalled } @@ -268,6 +272,7 @@ func (sub *Subscription) Unsubscribe() { case <-sub.f.logs: case <-sub.f.txs: case <-sub.f.headers: + case <-sub.f.receipts: } } @@ -353,6 +358,7 @@ func (es *EventSystem) subscribeLogs(crit ethereum.FilterQuery, logs chan []*typ logs: logs, txs: make(chan []*types.Transaction), headers: make(chan *types.Header), + receipts: make(chan []*ReceiptWithTx), installed: make(chan struct{}), err: make(chan error), } @@ -369,6 +375,7 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti logs: make(chan []*types.Log), txs: make(chan []*types.Transaction), headers: headers, + receipts: make(chan []*ReceiptWithTx), installed: make(chan struct{}), err: make(chan error), } @@ -385,6 +392,26 @@ func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subsc logs: make(chan []*types.Log), txs: txs, headers: make(chan *types.Header), + receipts: make(chan []*ReceiptWithTx), + installed: make(chan struct{}), + err: make(chan error), + } + return es.subscribe(sub) +} + +// SubscribeTransactionReceipts creates a subscription that writes transaction receipts for +// transactions when they are included in blocks. If txHashes is provided, only receipts +// for those specific transaction hashes will be delivered. +func (es *EventSystem) SubscribeTransactionReceipts(txHashes []common.Hash, receipts chan []*ReceiptWithTx) *Subscription { + sub := &subscription{ + id: rpc.NewID(), + typ: TransactionReceiptsSubscription, + created: time.Now(), + logs: make(chan []*types.Log), + txs: make(chan []*types.Transaction), + headers: make(chan *types.Header), + receipts: receipts, + txHashes: txHashes, installed: make(chan struct{}), err: make(chan error), } @@ -415,6 +442,14 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) for _, f := range filters[BlocksSubscription] { f.headers <- ev.Header } + + // Handle transaction receipts subscriptions when a new block is added + for _, f := range filters[TransactionReceiptsSubscription] { + matchedReceipts := filterReceipts(f.txHashes, ev) + if len(matchedReceipts) > 0 { + f.receipts <- matchedReceipts + } + } } // eventLoop (un)installs filters and processes mux events. diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 0048e74995fc..e5a1a2b25f1b 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/filtermaps" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -781,3 +782,143 @@ func TestPendingTxFilterDeadlock(t *testing.T) { } } } + +// TestTransactionReceiptsSubscription tests the transaction receipts subscription functionality +func TestTransactionReceiptsSubscription(t *testing.T) { + t.Parallel() + + const txNum = 5 + + // Setup test environment + var ( + db = rawdb.NewMemoryDatabase() + backend, sys = newTestFilterSystem(db, Config{}) + api = NewFilterAPI(sys) + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + signer = types.NewLondonSigner(big.NewInt(1)) + genesis = &core.Genesis{ + Alloc: types.GenesisAlloc{addr1: {Balance: big.NewInt(1000000000000000000)}}, // 1 ETH + Config: params.TestChainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + } + _, chain, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, func(i int, gen *core.BlockGen) { + // Add transactions to the block + for j := 0; j < txNum; j++ { + toAddr := common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268") + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(j), + GasPrice: gen.BaseFee(), + Gas: 21000, + To: &toAddr, + Value: big.NewInt(1000), + Data: nil, + }), signer, key1) + gen.AddTx(tx) + } + }) + ) + + // Insert the blocks into the chain + blockchain, err := core.NewBlockChain(db, genesis, ethash.NewFaker(), nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := blockchain.InsertChain(chain); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + + // Prepare test data + receipts := blockchain.GetReceiptsByHash(chain[0].Hash()) + if receipts == nil { + t.Fatalf("failed to get receipts") + } + + chainEvent := core.ChainEvent{ + Header: chain[0].Header(), + Receipts: receipts, + Transactions: chain[0].Transactions(), + } + + txHashes := make([]common.Hash, txNum) + for i := 0; i < txNum; i++ { + txHashes[i] = chain[0].Transactions()[i].Hash() + } + + testCases := []struct { + name string + filterTxHashes []common.Hash + expectedReceiptTxHashes []common.Hash + expectError bool + }{ + { + name: "no filter - should return all receipts", + filterTxHashes: nil, + expectedReceiptTxHashes: txHashes, + expectError: false, + }, + { + name: "single tx hash filter", + filterTxHashes: []common.Hash{txHashes[0]}, + expectedReceiptTxHashes: []common.Hash{txHashes[0]}, + expectError: false, + }, + { + name: "multiple tx hashes filter", + filterTxHashes: []common.Hash{txHashes[0], txHashes[1], txHashes[2]}, + expectedReceiptTxHashes: []common.Hash{txHashes[0], txHashes[1], txHashes[2]}, + expectError: false, + }, + } + + // Run test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + receiptsChan := make(chan []*ReceiptWithTx) + sub := api.events.SubscribeTransactionReceipts(tc.filterTxHashes, receiptsChan) + + // Send chain event + backend.chainFeed.Send(chainEvent) + + // Wait for receipts + timeout := time.After(1 * time.Second) + var receivedReceipts []*types.Receipt + for { + select { + case receiptsWithTx := <-receiptsChan: + for _, receiptWithTx := range receiptsWithTx { + receivedReceipts = append(receivedReceipts, receiptWithTx.Receipt) + } + case <-timeout: + t.Fatalf("timeout waiting for receipts") + } + if len(receivedReceipts) >= len(tc.expectedReceiptTxHashes) { + break + } + } + + // Verify receipt count + if len(receivedReceipts) != len(tc.expectedReceiptTxHashes) { + t.Errorf("Expected %d receipts, got %d", len(tc.expectedReceiptTxHashes), len(receivedReceipts)) + } + + // Verify specific transaction hashes are present + if tc.expectedReceiptTxHashes != nil { + receivedHashes := make(map[common.Hash]bool) + for _, receipt := range receivedReceipts { + receivedHashes[receipt.TxHash] = true + } + + for _, expectedHash := range tc.expectedReceiptTxHashes { + if !receivedHashes[expectedHash] { + t.Errorf("Expected receipt for tx %x not found", expectedHash) + } + } + } + + // Cleanup + sub.Unsubscribe() + <-sub.Err() + }) + } +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c3f267027c55..a2cb28d3b2d1 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -627,7 +627,7 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp result := make([]map[string]interface{}, len(receipts)) for i, receipt := range receipts { - result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i) + result[i] = MarshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i) } return result, nil } @@ -1488,11 +1488,11 @@ func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash commo return nil, err } // Derive the sender. - return marshalReceipt(receipt, blockHash, blockNumber, api.signer, tx, int(index)), nil + return MarshalReceipt(receipt, blockHash, blockNumber, api.signer, tx, int(index)), nil } -// marshalReceipt marshals a transaction receipt into a JSON object. -func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} { +// MarshalReceipt marshals a transaction receipt into a JSON object. +func MarshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} { from, _ := types.Sender(signer, tx) fields := map[string]interface{}{ From b34695d7e35bf2ee90cc2e39f33cffc2a520d7ac Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Fri, 10 Oct 2025 07:40:10 +0200 Subject: [PATCH 109/228] core/txpool/legacypool: fix validTxMeter to count transactions (#32845) invalidTxMeter was counting txs, while validTxMeter was counting accounts. Better make the two comparable. --------- Signed-off-by: Csaba Kiraly --- core/txpool/legacypool/legacypool.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 80a9faf23f63..e199d21c7a89 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -984,17 +984,24 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error { // addTxsLocked attempts to queue a batch of transactions if they are valid. // The transaction pool lock must be held. +// Returns the error for each tx, and the set of accounts that might became promotable. func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accountSet) { - dirty := newAccountSet(pool.signer) - errs := make([]error, len(txs)) + var ( + dirty = newAccountSet(pool.signer) + errs = make([]error, len(txs)) + valid int64 + ) for i, tx := range txs { replaced, err := pool.add(tx) errs[i] = err - if err == nil && !replaced { - dirty.addTx(tx) + if err == nil { + if !replaced { + dirty.addTx(tx) + } + valid++ } } - validTxMeter.Mark(int64(len(dirty.accounts))) + validTxMeter.Mark(valid) return errs, dirty } From 9e7f1ca519984e425a3b2d949bd42ed0b88a20ef Mon Sep 17 00:00:00 2001 From: cui Date: Fri, 10 Oct 2025 13:48:25 +0800 Subject: [PATCH 110/228] eth/protocols/snap: optimize incHash (#32748) --- eth/protocols/snap/range.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/eth/protocols/snap/range.go b/eth/protocols/snap/range.go index 8c98c71d5064..f32cca8d2311 100644 --- a/eth/protocols/snap/range.go +++ b/eth/protocols/snap/range.go @@ -74,8 +74,11 @@ func (r *hashRange) End() common.Hash { // incHash returns the next hash, in lexicographical order (a.k.a plus one) func incHash(h common.Hash) common.Hash { - var a uint256.Int - a.SetBytes32(h[:]) - a.AddUint64(&a, 1) - return common.Hash(a.Bytes32()) + for i := len(h) - 1; i >= 0; i-- { + h[i]++ + if h[i] != 0 { + break + } + } + return h } From 0764ea67e9ccc91c18a3742334a3ee3397951220 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 10 Oct 2025 14:51:27 +0800 Subject: [PATCH 111/228] core/rawdb, triedb/pathdb: introduce trienode history (#32596) It's a pull request based on the #32523 , implementing the structure of trienode history. --- core/rawdb/accessors_history.go | 95 +++- core/rawdb/accessors_state.go | 73 +++ core/rawdb/ancient_scheme.go | 50 +- core/rawdb/schema.go | 43 +- triedb/pathdb/database.go | 4 +- triedb/pathdb/history.go | 57 +- triedb/pathdb/history_index.go | 12 + triedb/pathdb/history_indexer.go | 76 ++- triedb/pathdb/history_state.go | 6 +- triedb/pathdb/history_state_test.go | 20 +- triedb/pathdb/history_trienode.go | 730 ++++++++++++++++++++++++ triedb/pathdb/history_trienode_test.go | 736 +++++++++++++++++++++++++ triedb/pathdb/metrics.go | 21 +- 13 files changed, 1851 insertions(+), 72 deletions(-) create mode 100644 triedb/pathdb/history_trienode.go create mode 100644 triedb/pathdb/history_trienode_test.go diff --git a/core/rawdb/accessors_history.go b/core/rawdb/accessors_history.go index cf1073f387a8..95a8907edc01 100644 --- a/core/rawdb/accessors_history.go +++ b/core/rawdb/accessors_history.go @@ -46,6 +46,27 @@ func DeleteStateHistoryIndexMetadata(db ethdb.KeyValueWriter) { } } +// ReadTrienodeHistoryIndexMetadata retrieves the metadata of trienode history index. +func ReadTrienodeHistoryIndexMetadata(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(headTrienodeHistoryIndexKey) + return data +} + +// WriteTrienodeHistoryIndexMetadata stores the metadata of trienode history index +// into database. +func WriteTrienodeHistoryIndexMetadata(db ethdb.KeyValueWriter, blob []byte) { + if err := db.Put(headTrienodeHistoryIndexKey, blob); err != nil { + log.Crit("Failed to store the metadata of trienode history index", "err", err) + } +} + +// DeleteTrienodeHistoryIndexMetadata removes the metadata of trienode history index. +func DeleteTrienodeHistoryIndexMetadata(db ethdb.KeyValueWriter) { + if err := db.Delete(headTrienodeHistoryIndexKey); err != nil { + log.Crit("Failed to delete the metadata of trienode history index", "err", err) + } +} + // ReadAccountHistoryIndex retrieves the account history index with the provided // account address. func ReadAccountHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash) []byte { @@ -95,6 +116,30 @@ func DeleteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, } } +// ReadTrienodeHistoryIndex retrieves the trienode history index with the provided +// account address and storage key hash. +func ReadTrienodeHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash, path []byte) []byte { + data, err := db.Get(trienodeHistoryIndexKey(addressHash, path)) + if err != nil || len(data) == 0 { + return nil + } + return data +} + +// WriteTrienodeHistoryIndex writes the provided trienode history index into database. +func WriteTrienodeHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, data []byte) { + if err := db.Put(trienodeHistoryIndexKey(addressHash, path), data); err != nil { + log.Crit("Failed to store trienode history index", "err", err) + } +} + +// DeleteTrienodeHistoryIndex deletes the specified trienode index from the database. +func DeleteTrienodeHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte) { + if err := db.Delete(trienodeHistoryIndexKey(addressHash, path)); err != nil { + log.Crit("Failed to delete trienode history index", "err", err) + } +} + // ReadAccountHistoryIndexBlock retrieves the index block with the provided // account address along with the block id. func ReadAccountHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, blockID uint32) []byte { @@ -143,6 +188,30 @@ func DeleteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common. } } +// ReadTrienodeHistoryIndexBlock retrieves the index block with the provided state +// identifier along with the block id. +func ReadTrienodeHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, path []byte, blockID uint32) []byte { + data, err := db.Get(trienodeHistoryIndexBlockKey(addressHash, path, blockID)) + if err != nil || len(data) == 0 { + return nil + } + return data +} + +// WriteTrienodeHistoryIndexBlock writes the provided index block into database. +func WriteTrienodeHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, id uint32, data []byte) { + if err := db.Put(trienodeHistoryIndexBlockKey(addressHash, path, id), data); err != nil { + log.Crit("Failed to store trienode index block", "err", err) + } +} + +// DeleteTrienodeHistoryIndexBlock deletes the specified index block from the database. +func DeleteTrienodeHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, id uint32) { + if err := db.Delete(trienodeHistoryIndexBlockKey(addressHash, path, id)); err != nil { + log.Crit("Failed to delete trienode index block", "err", err) + } +} + // increaseKey increase the input key by one bit. Return nil if the entire // addition operation overflows. func increaseKey(key []byte) []byte { @@ -155,14 +224,26 @@ func increaseKey(key []byte) []byte { return nil } -// DeleteStateHistoryIndex completely removes all history indexing data, including +// DeleteStateHistoryIndexes completely removes all history indexing data, including // indexes for accounts and storages. -// -// Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix` -// is exclusively occupied by the history indexing data! -func DeleteStateHistoryIndex(db ethdb.KeyValueRangeDeleter) { - start := StateHistoryIndexPrefix - limit := increaseKey(bytes.Clone(StateHistoryIndexPrefix)) +func DeleteStateHistoryIndexes(db ethdb.KeyValueRangeDeleter) { + DeleteHistoryByRange(db, StateHistoryAccountMetadataPrefix) + DeleteHistoryByRange(db, StateHistoryStorageMetadataPrefix) + DeleteHistoryByRange(db, StateHistoryAccountBlockPrefix) + DeleteHistoryByRange(db, StateHistoryStorageBlockPrefix) +} + +// DeleteTrienodeHistoryIndexes completely removes all trienode history indexing data. +func DeleteTrienodeHistoryIndexes(db ethdb.KeyValueRangeDeleter) { + DeleteHistoryByRange(db, TrienodeHistoryMetadataPrefix) + DeleteHistoryByRange(db, TrienodeHistoryBlockPrefix) +} + +// DeleteHistoryByRange completely removes all database entries with the specific prefix. +// Note, this method assumes the space with the given prefix is exclusively occupied! +func DeleteHistoryByRange(db ethdb.KeyValueRangeDeleter, prefix []byte) { + start := prefix + limit := increaseKey(bytes.Clone(prefix)) // Try to remove the data in the range by a loop, as the leveldb // doesn't support the native range deletion. diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 46aa5fd070ce..298ad04f40b3 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -299,3 +299,76 @@ func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIn }) return err } + +// ReadTrienodeHistory retrieves the trienode history corresponding to the specified id. +// Compute the position of trienode history in freezer by minus one since the id of first +// trienode history starts from one(zero for initial state). +func ReadTrienodeHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []byte, error) { + header, err := db.Ancient(trienodeHistoryHeaderTable, id-1) + if err != nil { + return nil, nil, nil, err + } + keySection, err := db.Ancient(trienodeHistoryKeySectionTable, id-1) + if err != nil { + return nil, nil, nil, err + } + valueSection, err := db.Ancient(trienodeHistoryValueSectionTable, id-1) + if err != nil { + return nil, nil, nil, err + } + return header, keySection, valueSection, nil +} + +// ReadTrienodeHistoryHeader retrieves the header section of trienode history. +func ReadTrienodeHistoryHeader(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { + return db.Ancient(trienodeHistoryHeaderTable, id-1) +} + +// ReadTrienodeHistoryKeySection retrieves the key section of trienode history. +func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { + return db.Ancient(trienodeHistoryKeySectionTable, id-1) +} + +// ReadTrienodeHistoryValueSection retrieves the value section of trienode history. +func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { + return db.Ancient(trienodeHistoryValueSectionTable, id-1) +} + +// ReadTrienodeHistoryList retrieves the a list of trienode history corresponding +// to the specified range. +// Compute the position of trienode history in freezer by minus one since the id +// of first trienode history starts from one(zero for initial state). +func ReadTrienodeHistoryList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, [][]byte, [][]byte, error) { + header, err := db.AncientRange(trienodeHistoryHeaderTable, start-1, count, 0) + if err != nil { + return nil, nil, nil, err + } + keySection, err := db.AncientRange(trienodeHistoryKeySectionTable, start-1, count, 0) + if err != nil { + return nil, nil, nil, err + } + valueSection, err := db.AncientRange(trienodeHistoryValueSectionTable, start-1, count, 0) + if err != nil { + return nil, nil, nil, err + } + if len(header) != len(keySection) || len(header) != len(valueSection) { + return nil, nil, nil, errors.New("trienode history is corrupted") + } + return header, keySection, valueSection, nil +} + +// WriteTrienodeHistory writes the provided trienode history to database. +// Compute the position of trienode history in freezer by minus one since +// the id of first state history starts from one(zero for initial state). +func WriteTrienodeHistory(db ethdb.AncientWriter, id uint64, header []byte, keySection []byte, valueSection []byte) error { + _, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + if err := op.AppendRaw(trienodeHistoryHeaderTable, id-1, header); err != nil { + return err + } + if err := op.AppendRaw(trienodeHistoryKeySectionTable, id-1, keySection); err != nil { + return err + } + return op.AppendRaw(trienodeHistoryValueSectionTable, id-1, valueSection) + }) + return err +} diff --git a/core/rawdb/ancient_scheme.go b/core/rawdb/ancient_scheme.go index 1ffebed3e7cb..afec7848c882 100644 --- a/core/rawdb/ancient_scheme.go +++ b/core/rawdb/ancient_scheme.go @@ -75,15 +75,38 @@ var stateFreezerTableConfigs = map[string]freezerTableConfig{ stateHistoryStorageData: {noSnappy: false, prunable: true}, } +const ( + trienodeHistoryHeaderTable = "trienode.header" + trienodeHistoryKeySectionTable = "trienode.key" + trienodeHistoryValueSectionTable = "trienode.value" +) + +// trienodeFreezerTableConfigs configures the settings for tables in the trienode freezer. +var trienodeFreezerTableConfigs = map[string]freezerTableConfig{ + trienodeHistoryHeaderTable: {noSnappy: false, prunable: true}, + + // Disable snappy compression to allow efficient partial read. + trienodeHistoryKeySectionTable: {noSnappy: true, prunable: true}, + + // Disable snappy compression to allow efficient partial read. + trienodeHistoryValueSectionTable: {noSnappy: true, prunable: true}, +} + // The list of identifiers of ancient stores. var ( - ChainFreezerName = "chain" // the folder name of chain segment ancient store. - MerkleStateFreezerName = "state" // the folder name of state history ancient store. - VerkleStateFreezerName = "state_verkle" // the folder name of state history ancient store. + ChainFreezerName = "chain" // the folder name of chain segment ancient store. + MerkleStateFreezerName = "state" // the folder name of state history ancient store. + VerkleStateFreezerName = "state_verkle" // the folder name of state history ancient store. + MerkleTrienodeFreezerName = "trienode" // the folder name of trienode history ancient store. + VerkleTrienodeFreezerName = "trienode_verkle" // the folder name of trienode history ancient store. ) // freezers the collections of all builtin freezers. -var freezers = []string{ChainFreezerName, MerkleStateFreezerName, VerkleStateFreezerName} +var freezers = []string{ + ChainFreezerName, + MerkleStateFreezerName, VerkleStateFreezerName, + MerkleTrienodeFreezerName, VerkleTrienodeFreezerName, +} // NewStateFreezer initializes the ancient store for state history. // @@ -103,3 +126,22 @@ func NewStateFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.Reset } return newResettableFreezer(name, "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerTableConfigs) } + +// NewTrienodeFreezer initializes the ancient store for trienode history. +// +// - if the empty directory is given, initializes the pure in-memory +// trienode freezer (e.g. dev mode). +// - if non-empty directory is given, initializes the regular file-based +// trienode freezer. +func NewTrienodeFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.ResettableAncientStore, error) { + if ancientDir == "" { + return NewMemoryFreezer(readOnly, trienodeFreezerTableConfigs), nil + } + var name string + if verkle { + name = filepath.Join(ancientDir, VerkleTrienodeFreezerName) + } else { + name = filepath.Join(ancientDir, MerkleTrienodeFreezerName) + } + return newResettableFreezer(name, "eth/db/trienode", readOnly, stateHistoryTableSize, trienodeFreezerTableConfigs) +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 9a17e1c17384..d9140c5fd658 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -80,6 +80,10 @@ var ( // been indexed. headStateHistoryIndexKey = []byte("LastStateHistoryIndex") + // headTrienodeHistoryIndexKey tracks the ID of the latest state history that has + // been indexed. + headTrienodeHistoryIndexKey = []byte("LastTrienodeHistoryIndex") + // txIndexTailKey tracks the oldest block whose transactions have been indexed. txIndexTailKey = []byte("TransactionIndexTail") @@ -125,8 +129,10 @@ var ( StateHistoryIndexPrefix = []byte("m") // The global prefix of state history index data StateHistoryAccountMetadataPrefix = []byte("ma") // StateHistoryAccountMetadataPrefix + account address hash => account metadata StateHistoryStorageMetadataPrefix = []byte("ms") // StateHistoryStorageMetadataPrefix + account address hash + storage slot hash => slot metadata + TrienodeHistoryMetadataPrefix = []byte("mt") // TrienodeHistoryMetadataPrefix + account address hash + trienode path => trienode metadata StateHistoryAccountBlockPrefix = []byte("mba") // StateHistoryAccountBlockPrefix + account address hash + blockID => account block StateHistoryStorageBlockPrefix = []byte("mbs") // StateHistoryStorageBlockPrefix + account address hash + storage slot hash + blockID => slot block + TrienodeHistoryBlockPrefix = []byte("mbt") // TrienodeHistoryBlockPrefix + account address hash + trienode path + blockID => trienode block // VerklePrefix is the database prefix for Verkle trie data, which includes: // (a) Trie nodes @@ -395,27 +401,34 @@ func storageHistoryIndexKey(addressHash common.Hash, storageHash common.Hash) [] return out } +// trienodeHistoryIndexKey = TrienodeHistoryMetadataPrefix + addressHash + trienode path +func trienodeHistoryIndexKey(addressHash common.Hash, path []byte) []byte { + totalLen := len(TrienodeHistoryMetadataPrefix) + common.HashLength + len(path) + out := make([]byte, totalLen) + + off := 0 + off += copy(out[off:], TrienodeHistoryMetadataPrefix) + off += copy(out[off:], addressHash.Bytes()) + copy(out[off:], path) + + return out +} + // accountHistoryIndexBlockKey = StateHistoryAccountBlockPrefix + addressHash + blockID func accountHistoryIndexBlockKey(addressHash common.Hash, blockID uint32) []byte { - var buf4 [4]byte - binary.BigEndian.PutUint32(buf4[:], blockID) - totalLen := len(StateHistoryAccountBlockPrefix) + common.HashLength + 4 out := make([]byte, totalLen) off := 0 off += copy(out[off:], StateHistoryAccountBlockPrefix) off += copy(out[off:], addressHash.Bytes()) - copy(out[off:], buf4[:]) + binary.BigEndian.PutUint32(out[off:], blockID) return out } // storageHistoryIndexBlockKey = StateHistoryStorageBlockPrefix + addressHash + storageHash + blockID func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte { - var buf4 [4]byte - binary.BigEndian.PutUint32(buf4[:], blockID) - totalLen := len(StateHistoryStorageBlockPrefix) + 2*common.HashLength + 4 out := make([]byte, totalLen) @@ -423,7 +436,21 @@ func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Has off += copy(out[off:], StateHistoryStorageBlockPrefix) off += copy(out[off:], addressHash.Bytes()) off += copy(out[off:], storageHash.Bytes()) - copy(out[off:], buf4[:]) + binary.BigEndian.PutUint32(out[off:], blockID) + + return out +} + +// trienodeHistoryIndexBlockKey = TrienodeHistoryBlockPrefix + addressHash + trienode path + blockID +func trienodeHistoryIndexBlockKey(addressHash common.Hash, path []byte, blockID uint32) []byte { + totalLen := len(TrienodeHistoryBlockPrefix) + common.HashLength + len(path) + 4 + out := make([]byte, totalLen) + + off := 0 + off += copy(out[off:], TrienodeHistoryBlockPrefix) + off += copy(out[off:], addressHash.Bytes()) + off += copy(out[off:], path) + binary.BigEndian.PutUint32(out[off:], blockID) return out } diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index 546d2e0301f9..9fc65de27724 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -232,7 +232,7 @@ func (db *Database) repairHistory() error { // Purge all state history indexing data first batch := db.diskdb.NewBatch() rawdb.DeleteStateHistoryIndexMetadata(batch) - rawdb.DeleteStateHistoryIndex(batch) + rawdb.DeleteStateHistoryIndexes(batch) if err := batch.Write(); err != nil { log.Crit("Failed to purge state history index", "err", err) } @@ -426,7 +426,7 @@ func (db *Database) Enable(root common.Hash) error { // Purge all state history indexing data first batch.Reset() rawdb.DeleteStateHistoryIndexMetadata(batch) - rawdb.DeleteStateHistoryIndex(batch) + rawdb.DeleteStateHistoryIndexes(batch) if err := batch.Write(); err != nil { return err } diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go index 81b843d9f12e..d78999f21835 100644 --- a/triedb/pathdb/history.go +++ b/triedb/pathdb/history.go @@ -32,6 +32,9 @@ type historyType uint8 const ( // typeStateHistory indicates history data related to account or storage changes. typeStateHistory historyType = 0 + + // typeTrienodeHistory indicates history data related to trie node changes. + typeTrienodeHistory historyType = 1 ) // String returns the string format representation. @@ -39,6 +42,8 @@ func (h historyType) String() string { switch h { case typeStateHistory: return "state" + case typeTrienodeHistory: + return "trienode" default: return fmt.Sprintf("unknown type: %d", h) } @@ -48,8 +53,9 @@ func (h historyType) String() string { type elementType uint8 const ( - typeAccount elementType = 0 // represents the account data - typeStorage elementType = 1 // represents the storage slot data + typeAccount elementType = 0 // represents the account data + typeStorage elementType = 1 // represents the storage slot data + typeTrienode elementType = 2 // represents the trie node data ) // String returns the string format representation. @@ -59,6 +65,8 @@ func (e elementType) String() string { return "account" case typeStorage: return "storage" + case typeTrienode: + return "trienode" default: return fmt.Sprintf("unknown element type: %d", e) } @@ -69,11 +77,14 @@ func toHistoryType(typ elementType) historyType { if typ == typeAccount || typ == typeStorage { return typeStateHistory } + if typ == typeTrienode { + return typeTrienodeHistory + } panic(fmt.Sprintf("unknown element type %v", typ)) } // stateIdent represents the identifier of a state element, which can be -// an account or a storage slot. +// an account, a storage slot or a trienode. type stateIdent struct { typ elementType @@ -91,6 +102,12 @@ type stateIdent struct { // // This field is null if the identifier refers to an account or a trie node. storageHash common.Hash + + // The trie node path within the trie. + // + // This field is null if the identifier refers to an account or a storage slot. + // String type is chosen to make stateIdent comparable. + path string } // String returns the string format state identifier. @@ -98,7 +115,10 @@ func (ident stateIdent) String() string { if ident.typ == typeAccount { return ident.addressHash.Hex() } - return ident.addressHash.Hex() + ident.storageHash.Hex() + if ident.typ == typeStorage { + return ident.addressHash.Hex() + ident.storageHash.Hex() + } + return ident.addressHash.Hex() + ident.path } // newAccountIdent constructs a state identifier for an account. @@ -120,8 +140,18 @@ func newStorageIdent(addressHash common.Hash, storageHash common.Hash) stateIden } } -// stateIdentQuery is the extension of stateIdent by adding the account address -// and raw storage key. +// newTrienodeIdent constructs a state identifier for a trie node. +// The address denotes the address hash of the associated account; +// the path denotes the path of the node within the trie; +func newTrienodeIdent(addressHash common.Hash, path string) stateIdent { + return stateIdent{ + typ: typeTrienode, + addressHash: addressHash, + path: path, + } +} + +// stateIdentQuery is the extension of stateIdent by adding the raw storage key. type stateIdentQuery struct { stateIdent @@ -150,8 +180,19 @@ func newStorageIdentQuery(address common.Address, addressHash common.Hash, stora } } -// history defines the interface of historical data, implemented by stateHistory -// and trienodeHistory (in the near future). +// newTrienodeIdentQuery constructs a state identifier for a trie node. +// the addressHash denotes the address hash of the associated account; +// the path denotes the path of the node within the trie; +// +// nolint:unused +func newTrienodeIdentQuery(addrHash common.Hash, path []byte) stateIdentQuery { + return stateIdentQuery{ + stateIdent: newTrienodeIdent(addrHash, string(path)), + } +} + +// history defines the interface of historical data, shared by stateHistory +// and trienodeHistory. type history interface { // typ returns the historical data type held in the history. typ() historyType diff --git a/triedb/pathdb/history_index.go b/triedb/pathdb/history_index.go index 47cee9820dd2..5b4c91d7e61e 100644 --- a/triedb/pathdb/history_index.go +++ b/triedb/pathdb/history_index.go @@ -376,6 +376,8 @@ func readStateIndex(ident stateIdent, db ethdb.KeyValueReader) []byte { return rawdb.ReadAccountHistoryIndex(db, ident.addressHash) case typeStorage: return rawdb.ReadStorageHistoryIndex(db, ident.addressHash, ident.storageHash) + case typeTrienode: + return rawdb.ReadTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path)) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -389,6 +391,8 @@ func writeStateIndex(ident stateIdent, db ethdb.KeyValueWriter, data []byte) { rawdb.WriteAccountHistoryIndex(db, ident.addressHash, data) case typeStorage: rawdb.WriteStorageHistoryIndex(db, ident.addressHash, ident.storageHash, data) + case typeTrienode: + rawdb.WriteTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path), data) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -402,6 +406,8 @@ func deleteStateIndex(ident stateIdent, db ethdb.KeyValueWriter) { rawdb.DeleteAccountHistoryIndex(db, ident.addressHash) case typeStorage: rawdb.DeleteStorageHistoryIndex(db, ident.addressHash, ident.storageHash) + case typeTrienode: + rawdb.DeleteTrienodeHistoryIndex(db, ident.addressHash, []byte(ident.path)) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -415,6 +421,8 @@ func readStateIndexBlock(ident stateIdent, db ethdb.KeyValueReader, id uint32) [ return rawdb.ReadAccountHistoryIndexBlock(db, ident.addressHash, id) case typeStorage: return rawdb.ReadStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id) + case typeTrienode: + return rawdb.ReadTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -428,6 +436,8 @@ func writeStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32, rawdb.WriteAccountHistoryIndexBlock(db, ident.addressHash, id, data) case typeStorage: rawdb.WriteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id, data) + case typeTrienode: + rawdb.WriteTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id, data) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } @@ -441,6 +451,8 @@ func deleteStateIndexBlock(ident stateIdent, db ethdb.KeyValueWriter, id uint32) rawdb.DeleteAccountHistoryIndexBlock(db, ident.addressHash, id) case typeStorage: rawdb.DeleteStorageHistoryIndexBlock(db, ident.addressHash, ident.storageHash, id) + case typeTrienode: + rawdb.DeleteTrienodeHistoryIndexBlock(db, ident.addressHash, []byte(ident.path), id) default: panic(fmt.Errorf("unknown type: %v", ident.typ)) } diff --git a/triedb/pathdb/history_indexer.go b/triedb/pathdb/history_indexer.go index d6185859291f..368ff78d415e 100644 --- a/triedb/pathdb/history_indexer.go +++ b/triedb/pathdb/history_indexer.go @@ -36,8 +36,10 @@ const ( // The batch size for reading state histories historyReadBatch = 1000 - stateIndexV0 = uint8(0) // initial version of state index structure - stateIndexVersion = stateIndexV0 // the current state index version + stateHistoryIndexV0 = uint8(0) // initial version of state index structure + stateHistoryIndexVersion = stateHistoryIndexV0 // the current state index version + trienodeHistoryIndexV0 = uint8(0) // initial version of trienode index structure + trienodeHistoryIndexVersion = trienodeHistoryIndexV0 // the current trienode index version ) // indexVersion returns the latest index version for the given history type. @@ -45,7 +47,9 @@ const ( func indexVersion(typ historyType) uint8 { switch typ { case typeStateHistory: - return stateIndexVersion + return stateHistoryIndexVersion + case typeTrienodeHistory: + return trienodeHistoryIndexVersion default: panic(fmt.Errorf("unknown history type: %d", typ)) } @@ -63,6 +67,8 @@ func loadIndexMetadata(db ethdb.KeyValueReader, typ historyType) *indexMetadata switch typ { case typeStateHistory: blob = rawdb.ReadStateHistoryIndexMetadata(db) + case typeTrienodeHistory: + blob = rawdb.ReadTrienodeHistoryIndexMetadata(db) default: panic(fmt.Errorf("unknown history type %d", typ)) } @@ -90,6 +96,8 @@ func storeIndexMetadata(db ethdb.KeyValueWriter, typ historyType, last uint64) { switch typ { case typeStateHistory: rawdb.WriteStateHistoryIndexMetadata(db, blob) + case typeTrienodeHistory: + rawdb.WriteTrienodeHistoryIndexMetadata(db, blob) default: panic(fmt.Errorf("unknown history type %d", typ)) } @@ -101,6 +109,8 @@ func deleteIndexMetadata(db ethdb.KeyValueWriter, typ historyType) { switch typ { case typeStateHistory: rawdb.DeleteStateHistoryIndexMetadata(db) + case typeTrienodeHistory: + rawdb.DeleteTrienodeHistoryIndexMetadata(db) default: panic(fmt.Errorf("unknown history type %d", typ)) } @@ -215,7 +225,11 @@ func (b *batchIndexer) finish(force bool) error { func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader, typ historyType) error { start := time.Now() defer func() { - indexHistoryTimer.UpdateSince(start) + if typ == typeStateHistory { + stateIndexHistoryTimer.UpdateSince(start) + } else if typ == typeTrienodeHistory { + trienodeIndexHistoryTimer.UpdateSince(start) + } }() metadata := loadIndexMetadata(db, typ) @@ -234,7 +248,7 @@ func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancient if typ == typeStateHistory { h, err = readStateHistory(freezer, historyID) } else { - // h, err = readTrienodeHistory(freezer, historyID) + h, err = readTrienodeHistory(freezer, historyID) } if err != nil { return err @@ -253,7 +267,11 @@ func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancient func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.AncientReader, typ historyType) error { start := time.Now() defer func() { - unindexHistoryTimer.UpdateSince(start) + if typ == typeStateHistory { + stateUnindexHistoryTimer.UpdateSince(start) + } else if typ == typeTrienodeHistory { + trienodeUnindexHistoryTimer.UpdateSince(start) + } }() metadata := loadIndexMetadata(db, typ) @@ -272,7 +290,7 @@ func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancie if typ == typeStateHistory { h, err = readStateHistory(freezer, historyID) } else { - // h, err = readTrienodeHistory(freezer, historyID) + h, err = readTrienodeHistory(freezer, historyID) } if err != nil { return err @@ -546,13 +564,13 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID return } } else { - // histories, err = readTrienodeHistories(i.freezer, current, count) - // if err != nil { - // // The history read might fall if the history is truncated from - // // head due to revert operation. - // i.log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err) - // return - // } + histories, err = readTrienodeHistories(i.freezer, current, count) + if err != nil { + // The history read might fall if the history is truncated from + // head due to revert operation. + i.log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err) + return + } } for _, h := range histories { if err := batch.process(h, current); err != nil { @@ -570,7 +588,7 @@ func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID done = current - beginID ) eta := common.CalculateETA(done, left, time.Since(start)) - i.log.Info("Indexing state history", "processed", done, "left", left, "elapsed", common.PrettyDuration(time.Since(start)), "eta", common.PrettyDuration(eta)) + i.log.Info("Indexing history", "processed", done, "left", left, "elapsed", common.PrettyDuration(time.Since(start)), "eta", common.PrettyDuration(eta)) } } i.indexed.Store(current - 1) // update indexing progress @@ -657,6 +675,8 @@ func checkVersion(disk ethdb.KeyValueStore, typ historyType) { var blob []byte if typ == typeStateHistory { blob = rawdb.ReadStateHistoryIndexMetadata(disk) + } else if typ == typeTrienodeHistory { + blob = rawdb.ReadTrienodeHistoryIndexMetadata(disk) } else { panic(fmt.Errorf("unknown history type: %v", typ)) } @@ -666,24 +686,32 @@ func checkVersion(disk ethdb.KeyValueStore, typ historyType) { return } // Short circuit if the metadata is found and the version is matched + ver := stateHistoryIndexVersion + if typ == typeTrienodeHistory { + ver = trienodeHistoryIndexVersion + } var m indexMetadata err := rlp.DecodeBytes(blob, &m) - if err == nil && m.Version == stateIndexVersion { + if err == nil && m.Version == ver { return } // Version is not matched, prune the existing data and re-index from scratch + batch := disk.NewBatch() + if typ == typeStateHistory { + rawdb.DeleteStateHistoryIndexMetadata(batch) + rawdb.DeleteStateHistoryIndexes(batch) + } else { + rawdb.DeleteTrienodeHistoryIndexMetadata(batch) + rawdb.DeleteTrienodeHistoryIndexes(batch) + } + if err := batch.Write(); err != nil { + log.Crit("Failed to purge history index", "type", typ, "err", err) + } version := "unknown" if err == nil { version = fmt.Sprintf("%d", m.Version) } - - batch := disk.NewBatch() - rawdb.DeleteStateHistoryIndexMetadata(batch) - rawdb.DeleteStateHistoryIndex(batch) - if err := batch.Write(); err != nil { - log.Crit("Failed to purge state history index", "err", err) - } - log.Info("Cleaned up obsolete state history index", "version", version, "want", stateIndexVersion) + log.Info("Cleaned up obsolete history index", "type", typ, "version", version, "want", version) } // newHistoryIndexer constructs the history indexer and launches the background diff --git a/triedb/pathdb/history_state.go b/triedb/pathdb/history_state.go index 9d1e4dfb0999..bc21915dbaa8 100644 --- a/triedb/pathdb/history_state.go +++ b/triedb/pathdb/history_state.go @@ -605,9 +605,9 @@ func writeStateHistory(writer ethdb.AncientWriter, dl *diffLayer) error { if err := rawdb.WriteStateHistory(writer, dl.stateID(), history.meta.encode(), accountIndex, storageIndex, accountData, storageData); err != nil { return err } - historyDataBytesMeter.Mark(int64(dataSize)) - historyIndexBytesMeter.Mark(int64(indexSize)) - historyBuildTimeMeter.UpdateSince(start) + stateHistoryDataBytesMeter.Mark(int64(dataSize)) + stateHistoryIndexBytesMeter.Mark(int64(indexSize)) + stateHistoryBuildTimeMeter.UpdateSince(start) log.Debug("Stored state history", "id", dl.stateID(), "block", dl.block, "data", dataSize, "index", indexSize, "elapsed", common.PrettyDuration(time.Since(start))) return nil diff --git a/triedb/pathdb/history_state_test.go b/triedb/pathdb/history_state_test.go index 5718081566c6..4046fb96400a 100644 --- a/triedb/pathdb/history_state_test.go +++ b/triedb/pathdb/history_state_test.go @@ -98,13 +98,13 @@ func testEncodeDecodeStateHistory(t *testing.T, rawStorageKey bool) { if !compareSet(dec.accounts, obj.accounts) { t.Fatal("account data is mismatched") } - if !compareStorages(dec.storages, obj.storages) { + if !compareMapSet(dec.storages, obj.storages) { t.Fatal("storage data is mismatched") } if !compareList(dec.accountList, obj.accountList) { t.Fatal("account list is mismatched") } - if !compareStorageList(dec.storageList, obj.storageList) { + if !compareMapList(dec.storageList, obj.storageList) { t.Fatal("storage list is mismatched") } } @@ -292,32 +292,32 @@ func compareList[k comparable](a, b []k) bool { return true } -func compareStorages(a, b map[common.Address]map[common.Hash][]byte) bool { +func compareMapSet[K1 comparable, K2 comparable](a, b map[K1]map[K2][]byte) bool { if len(a) != len(b) { return false } - for h, subA := range a { - subB, ok := b[h] + for key, subsetA := range a { + subsetB, ok := b[key] if !ok { return false } - if !compareSet(subA, subB) { + if !compareSet(subsetA, subsetB) { return false } } return true } -func compareStorageList(a, b map[common.Address][]common.Hash) bool { +func compareMapList[K comparable, V comparable](a, b map[K][]V) bool { if len(a) != len(b) { return false } - for h, la := range a { - lb, ok := b[h] + for key, listA := range a { + listB, ok := b[key] if !ok { return false } - if !compareList(la, lb) { + if !compareList(listA, listB) { return false } } diff --git a/triedb/pathdb/history_trienode.go b/triedb/pathdb/history_trienode.go new file mode 100644 index 000000000000..2a4459d4ad46 --- /dev/null +++ b/triedb/pathdb/history_trienode.go @@ -0,0 +1,730 @@ +// 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 . + +package pathdb + +import ( + "bytes" + "encoding/binary" + "fmt" + "iter" + "maps" + "math" + "slices" + "sort" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// Each trie node history entry consists of three parts (stored in three freezer +// tables according): +// +// # Header +// The header records metadata, including: +// +// - the history version (1 byte) +// - the parent state root (32 bytes) +// - the current state root (32 bytes) +// - block number (8 bytes) +// +// - a lexicographically sorted list of trie IDs +// - the corresponding offsets into the key and value sections for each trie data chunk +// +// Although some fields (e.g., parent state root, block number) are duplicated +// between the state history and the trienode history, these two histories +// operate independently. To ensure each remains self-contained and self-descriptive, +// we have chosen to maintain these duplicate fields. +// +// # Key section +// The key section stores trie node keys (paths) in a compressed format. +// It also contains relative offsets into the value section for resolving +// the corresponding trie node data. Note that these offsets are relative +// to the data chunk for the trie; the chunk offset must be added to obtain +// the absolute position. +// +// # Value section +// The value section is a concatenated byte stream of all trie node data. +// Each trie node can be retrieved using the offset and length specified +// by its index entry. +// +// The header and key sections are sufficient for locating a trie node, +// while a partial read of the value section is enough to retrieve its data. + +// Header section: +// +// +----------+------------------+---------------------+---------------------+-------+------------------+---------------------+---------------------| +// | metadata | TrieID(32 bytes) | key offset(4 bytes) | val offset(4 bytes) | ... | TrieID(32 bytes) | key offset(4 bytes) | val offset(4 bytes) | +// +----------+------------------+---------------------+---------------------+-------+------------------+---------------------+---------------------| +// +// +// Key section: +// +// + restart point + restart point (depends on restart interval) +// / / +// +---------------+---------------+---------------+---------------+---------+ +// | node entry 1 | node entry 2 | ... | node entry n | trailer | +// +---------------+---------------+---------------+---------------+---------+ +// \ / +// +---- restart block ------+ +// +// node entry: +// +// +---- key len ----+ +// / \ +// +-------+---------+-----------+---------+-----------------------+-----------------+ +// | shared (varint) | not shared (varint) | value length (varlen) | key (varlen) | +// +-----------------+---------------------+-----------------------+-----------------+ +// +// trailer: +// +// +---- 4-bytes ----+ +---- 4-bytes ----+ +// / \ / \ +// +----------------------+------------------------+-----+--------------------------+ +// | restart_1 key offset | restart_1 value offset | ... | restart number (4-bytes) | +// +----------------------+------------------------+-----+--------------------------+ +// +// Note: Both the key offset and the value offset are relative to the start of +// the trie data chunk. To obtain the absolute offset, add the offset of the +// trie data chunk itself. +// +// Value section: +// +// +--------------+--------------+-------+---------------+ +// | node data 1 | node data 2 | ... | node data n | +// +--------------+--------------+-------+---------------+ +// +// NOTE: All fixed-length integer are big-endian. + +const ( + trienodeHistoryV0 = uint8(0) // initial version of node history structure + trienodeHistoryVersion = trienodeHistoryV0 // the default node history version + trienodeMetadataSize = 1 + 2*common.HashLength + 8 // the size of metadata in the history + trienodeTrieHeaderSize = 8 + common.HashLength // the size of a single trie header in history + trienodeDataBlockRestartLen = 16 // The restart interval length of trie node block +) + +// trienodeMetadata describes the meta data of trienode history. +type trienodeMetadata struct { + version uint8 // version tag of history object + parent common.Hash // prev-state root before the state transition + root common.Hash // post-state root after the state transition + block uint64 // associated block number +} + +// trienodeHistory represents a set of trie node changes resulting from a state +// transition across the main account trie and all associated storage tries. +type trienodeHistory struct { + meta *trienodeMetadata // Metadata of the history + owners []common.Hash // List of trie identifier sorted lexicographically + nodeList map[common.Hash][]string // Set of node paths sorted lexicographically + nodes map[common.Hash]map[string][]byte // Set of original value of trie nodes before state transition +} + +// newTrienodeHistory constructs a trienode history with the provided trie nodes. +func newTrienodeHistory(root common.Hash, parent common.Hash, block uint64, nodes map[common.Hash]map[string][]byte) *trienodeHistory { + nodeList := make(map[common.Hash][]string) + for owner, subset := range nodes { + keys := sort.StringSlice(slices.Collect(maps.Keys(subset))) + keys.Sort() + nodeList[owner] = keys + } + return &trienodeHistory{ + meta: &trienodeMetadata{ + version: trienodeHistoryVersion, + parent: parent, + root: root, + block: block, + }, + owners: slices.SortedFunc(maps.Keys(nodes), common.Hash.Cmp), + nodeList: nodeList, + nodes: nodes, + } +} + +// sharedLen returns the length of the common prefix shared by a and b. +func sharedLen(a, b []byte) int { + n := min(len(a), len(b)) + for i := 0; i < n; i++ { + if a[i] != b[i] { + return i + } + } + return n +} + +// typ implements the history interface, returning the historical data type held. +func (h *trienodeHistory) typ() historyType { + return typeTrienodeHistory +} + +// forEach implements the history interface, returning an iterator to traverse the +// state entries in the history. +func (h *trienodeHistory) forEach() iter.Seq[stateIdent] { + return func(yield func(stateIdent) bool) { + for _, owner := range h.owners { + for _, path := range h.nodeList[owner] { + if !yield(newTrienodeIdent(owner, path)) { + return + } + } + } + } +} + +// encode serializes the contained trie nodes into bytes. +func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) { + var ( + buf = make([]byte, 64) + headerSection bytes.Buffer + keySection bytes.Buffer + valueSection bytes.Buffer + ) + binary.Write(&headerSection, binary.BigEndian, h.meta.version) // 1 byte + headerSection.Write(h.meta.parent.Bytes()) // 32 bytes + headerSection.Write(h.meta.root.Bytes()) // 32 bytes + binary.Write(&headerSection, binary.BigEndian, h.meta.block) // 8 byte + + for _, owner := range h.owners { + // Fill the header section with offsets at key and value section + headerSection.Write(owner.Bytes()) // 32 bytes + binary.Write(&headerSection, binary.BigEndian, uint32(keySection.Len())) // 4 bytes + + // The offset to the value section is theoretically unnecessary, since the + // individual value offset is already tracked in the key section. However, + // we still keep it here for two reasons: + // - It's cheap to store (only 4 bytes for each trie). + // - It can be useful for decoding the trie data when key is not required (e.g., in hash mode). + binary.Write(&headerSection, binary.BigEndian, uint32(valueSection.Len())) // 4 bytes + + // Fill the key section with node index + var ( + prevKey []byte + restarts []uint32 + prefixLen int + + internalKeyOffset uint32 // key offset for the trie internally + internalValOffset uint32 // value offset for the trie internally + ) + for i, path := range h.nodeList[owner] { + key := []byte(path) + if i%trienodeDataBlockRestartLen == 0 { + restarts = append(restarts, internalKeyOffset) + restarts = append(restarts, internalValOffset) + prefixLen = 0 + } else { + prefixLen = sharedLen(prevKey, key) + } + value := h.nodes[owner][path] + + // key section + n := binary.PutUvarint(buf[0:], uint64(prefixLen)) // key length shared (varint) + n += binary.PutUvarint(buf[n:], uint64(len(key)-prefixLen)) // key length not shared (varint) + n += binary.PutUvarint(buf[n:], uint64(len(value))) // value length (varint) + + if _, err := keySection.Write(buf[:n]); err != nil { + return nil, nil, nil, err + } + // unshared key + if _, err := keySection.Write(key[prefixLen:]); err != nil { + return nil, nil, nil, err + } + n += len(key) - prefixLen + prevKey = key + + // value section + if _, err := valueSection.Write(value); err != nil { + return nil, nil, nil, err + } + internalKeyOffset += uint32(n) + internalValOffset += uint32(len(value)) + } + + // Encode trailer, the number of restart sections is len(restarts))/2, + // as we track the offsets of both key and value sections. + var trailer []byte + for _, number := range append(restarts, uint32(len(restarts))/2) { + binary.BigEndian.PutUint32(buf[:4], number) + trailer = append(trailer, buf[:4]...) + } + if _, err := keySection.Write(trailer); err != nil { + return nil, nil, nil, err + } + } + return headerSection.Bytes(), keySection.Bytes(), valueSection.Bytes(), nil +} + +// decodeHeader resolves the metadata from the header section. An error +// should be returned if the header section is corrupted. +func decodeHeader(data []byte) (*trienodeMetadata, []common.Hash, []uint32, []uint32, error) { + if len(data) < trienodeMetadataSize { + return nil, nil, nil, nil, fmt.Errorf("trienode history is too small, index size: %d", len(data)) + } + version := data[0] + if version != trienodeHistoryVersion { + return nil, nil, nil, nil, fmt.Errorf("unregonized trienode history version: %d", version) + } + parent := common.BytesToHash(data[1 : common.HashLength+1]) // 32 bytes + root := common.BytesToHash(data[common.HashLength+1 : common.HashLength*2+1]) // 32 bytes + block := binary.BigEndian.Uint64(data[common.HashLength*2+1 : trienodeMetadataSize]) // 8 bytes + + size := len(data) - trienodeMetadataSize + if size%trienodeTrieHeaderSize != 0 { + return nil, nil, nil, nil, fmt.Errorf("truncated trienode history data, size %d", len(data)) + } + count := size / trienodeTrieHeaderSize + + var ( + owners = make([]common.Hash, 0, count) + keyOffsets = make([]uint32, 0, count) + valOffsets = make([]uint32, 0, count) + ) + for i := 0; i < count; i++ { + n := trienodeMetadataSize + trienodeTrieHeaderSize*i + owner := common.BytesToHash(data[n : n+common.HashLength]) + if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 { + return nil, nil, nil, nil, fmt.Errorf("trienode owners are out of order, prev: %v, cur: %v", owners[i-1], owner) + } + owners = append(owners, owner) + + // Decode the offset to the key section + keyOffset := binary.BigEndian.Uint32(data[n+common.HashLength : n+common.HashLength+4]) + if i != 0 && keyOffset <= keyOffsets[i-1] { + return nil, nil, nil, nil, fmt.Errorf("key offset is out of order, prev: %v, cur: %v", keyOffsets[i-1], keyOffset) + } + keyOffsets = append(keyOffsets, keyOffset) + + // Decode the offset into the value section. Note that identical value offsets + // are valid if the node values in the last trie chunk are all zero (e.g., after + // a trie deletion). + valOffset := binary.BigEndian.Uint32(data[n+common.HashLength+4 : n+common.HashLength+8]) + if i != 0 && valOffset < valOffsets[i-1] { + return nil, nil, nil, nil, fmt.Errorf("value offset is out of order, prev: %v, cur: %v", valOffsets[i-1], valOffset) + } + valOffsets = append(valOffsets, valOffset) + } + return &trienodeMetadata{ + version: version, + parent: parent, + root: root, + block: block, + }, owners, keyOffsets, valOffsets, nil +} + +func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]string, error) { + var ( + prevKey []byte + items int + keyOffsets []uint32 + valOffsets []uint32 + + keyOff int // the key offset within the single trie data + valOff int // the value offset within the single trie data + + keys []string + ) + // Decode the number of restart section + if len(keySection) < 4 { + return nil, fmt.Errorf("key section too short, size: %d", len(keySection)) + } + nRestarts := binary.BigEndian.Uint32(keySection[len(keySection)-4:]) + + if len(keySection) < int(8*nRestarts)+4 { + return nil, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection)) + } + for i := 0; i < int(nRestarts); i++ { + o := len(keySection) - 4 - (int(nRestarts)-i)*8 + keyOffset := binary.BigEndian.Uint32(keySection[o : o+4]) + if i != 0 && keyOffset <= keyOffsets[i-1] { + return nil, fmt.Errorf("key offset is out of order, prev: %v, cur: %v", keyOffsets[i-1], keyOffset) + } + keyOffsets = append(keyOffsets, keyOffset) + + // Same value offset is allowed just in case all the trie nodes in the last + // section have zero-size value. + valOffset := binary.BigEndian.Uint32(keySection[o+4 : o+8]) + if i != 0 && valOffset < valOffsets[i-1] { + return nil, fmt.Errorf("value offset is out of order, prev: %v, cur: %v", valOffsets[i-1], valOffset) + } + valOffsets = append(valOffsets, valOffset) + } + keyLimit := len(keySection) - 4 - int(nRestarts)*8 + + // Decode data + for keyOff < keyLimit { + // Validate the key and value offsets within the single trie data chunk + if items%trienodeDataBlockRestartLen == 0 { + if keyOff != int(keyOffsets[items/trienodeDataBlockRestartLen]) { + return nil, fmt.Errorf("key offset is not matched, recorded: %d, want: %d", keyOffsets[items/trienodeDataBlockRestartLen], keyOff) + } + if valOff != int(valOffsets[items/trienodeDataBlockRestartLen]) { + return nil, fmt.Errorf("value offset is not matched, recorded: %d, want: %d", valOffsets[items/trienodeDataBlockRestartLen], valOff) + } + } + // Resolve the entry from key section + nShared, nn := binary.Uvarint(keySection[keyOff:]) // key length shared (varint) + keyOff += nn + nUnshared, nn := binary.Uvarint(keySection[keyOff:]) // key length not shared (varint) + keyOff += nn + nValue, nn := binary.Uvarint(keySection[keyOff:]) // value length (varint) + keyOff += nn + + // Resolve unshared key + if keyOff+int(nUnshared) > len(keySection) { + return nil, fmt.Errorf("key length too long, unshared key length: %d, off: %d, section size: %d", nUnshared, keyOff, len(keySection)) + } + unsharedKey := keySection[keyOff : keyOff+int(nUnshared)] + keyOff += int(nUnshared) + + // Assemble the full key + var key []byte + if items%trienodeDataBlockRestartLen == 0 { + if nShared != 0 { + return nil, fmt.Errorf("unexpected non-zero shared key prefix: %d", nShared) + } + key = unsharedKey + } else { + if int(nShared) > len(prevKey) { + return nil, fmt.Errorf("unexpected shared key prefix: %d, prefix key length: %d", nShared, len(prevKey)) + } + key = append([]byte{}, prevKey[:nShared]...) + key = append(key, unsharedKey...) + } + if items != 0 && bytes.Compare(prevKey, key) >= 0 { + return nil, fmt.Errorf("trienode paths are out of order, prev: %v, cur: %v", prevKey, key) + } + prevKey = key + + // Resolve value + if onValue != nil { + if err := onValue(key, valOff, valOff+int(nValue)); err != nil { + return nil, err + } + } + valOff += int(nValue) + + items++ + keys = append(keys, string(key)) + } + if keyOff != keyLimit { + return nil, fmt.Errorf("excessive key data after decoding, offset: %d, size: %d", keyOff, keyLimit) + } + return keys, nil +} + +func decodeSingleWithValue(keySection []byte, valueSection []byte) ([]string, map[string][]byte, error) { + var ( + offset int + nodes = make(map[string][]byte) + ) + paths, err := decodeSingle(keySection, func(key []byte, start int, limit int) error { + if start != offset { + return fmt.Errorf("gapped value section offset: %d, want: %d", start, offset) + } + // start == limit is allowed for zero-value trie node (e.g., non-existent node) + if start > limit { + return fmt.Errorf("invalid value offsets, start: %d, limit: %d", start, limit) + } + if start > len(valueSection) || limit > len(valueSection) { + return fmt.Errorf("value section out of range: start: %d, limit: %d, size: %d", start, limit, len(valueSection)) + } + nodes[string(key)] = valueSection[start:limit] + + offset = limit + return nil + }) + if err != nil { + return nil, nil, err + } + if offset != len(valueSection) { + return nil, nil, fmt.Errorf("excessive value data after decoding, offset: %d, size: %d", offset, len(valueSection)) + } + return paths, nodes, nil +} + +// decode deserializes the contained trie nodes from the provided bytes. +func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection []byte) error { + metadata, owners, keyOffsets, valueOffsets, err := decodeHeader(header) + if err != nil { + return err + } + h.meta = metadata + h.owners = owners + h.nodeList = make(map[common.Hash][]string) + h.nodes = make(map[common.Hash]map[string][]byte) + + for i := 0; i < len(owners); i++ { + // Resolve the boundary of key section + keyStart := keyOffsets[i] + keyLimit := len(keySection) + if i != len(owners)-1 { + keyLimit = int(keyOffsets[i+1]) + } + if int(keyStart) > len(keySection) || keyLimit > len(keySection) { + return fmt.Errorf("invalid key offsets: keyStart: %d, keyLimit: %d, size: %d", keyStart, keyLimit, len(keySection)) + } + + // Resolve the boundary of value section + valStart := valueOffsets[i] + valLimit := len(valueSection) + if i != len(owners)-1 { + valLimit = int(valueOffsets[i+1]) + } + if int(valStart) > len(valueSection) || valLimit > len(valueSection) { + return fmt.Errorf("invalid value offsets: valueStart: %d, valueLimit: %d, size: %d", valStart, valLimit, len(valueSection)) + } + + // Decode the key and values for this specific trie + paths, nodes, err := decodeSingleWithValue(keySection[keyStart:keyLimit], valueSection[valStart:valLimit]) + if err != nil { + return err + } + h.nodeList[owners[i]] = paths + h.nodes[owners[i]] = nodes + } + return nil +} + +type iRange struct { + start uint32 + limit uint32 +} + +// singleTrienodeHistoryReader provides read access to a single trie within the +// trienode history. It stores an offset to the trie's position in the history, +// along with a set of per-node offsets that can be resolved on demand. +type singleTrienodeHistoryReader struct { + id uint64 + reader ethdb.AncientReader + valueRange iRange // value range within the total value section + valueInternalOffsets map[string]iRange // value offset within the single trie data +} + +func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRange iRange, valueRange iRange) (*singleTrienodeHistoryReader, error) { + // TODO(rjl493456442) partial freezer read should be supported + keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id) + if err != nil { + return nil, err + } + keyStart := int(keyRange.start) + keyLimit := int(keyRange.limit) + if keyLimit == math.MaxUint32 { + keyLimit = len(keyData) + } + if len(keyData) < keyStart || len(keyData) < keyLimit { + return nil, fmt.Errorf("key section too short, start: %d, limit: %d, size: %d", keyStart, keyLimit, len(keyData)) + } + + valueOffsets := make(map[string]iRange) + _, err = decodeSingle(keyData[keyStart:keyLimit], func(key []byte, start int, limit int) error { + valueOffsets[string(key)] = iRange{ + start: uint32(start), + limit: uint32(limit), + } + return nil + }) + if err != nil { + return nil, err + } + return &singleTrienodeHistoryReader{ + id: id, + reader: reader, + valueRange: valueRange, + valueInternalOffsets: valueOffsets, + }, nil +} + +// read retrieves the trie node data with the provided node path. +func (sr *singleTrienodeHistoryReader) read(path string) ([]byte, error) { + offset, exists := sr.valueInternalOffsets[path] + if !exists { + return nil, fmt.Errorf("trienode %v not found", []byte(path)) + } + // TODO(rjl493456442) partial freezer read should be supported + valueData, err := rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id) + if err != nil { + return nil, err + } + if len(valueData) < int(sr.valueRange.start) { + return nil, fmt.Errorf("value section too short, start: %d, size: %d", sr.valueRange.start, len(valueData)) + } + entryStart := sr.valueRange.start + offset.start + entryLimit := sr.valueRange.start + offset.limit + if len(valueData) < int(entryStart) || len(valueData) < int(entryLimit) { + return nil, fmt.Errorf("value section too short, start: %d, limit: %d, size: %d", entryStart, entryLimit, len(valueData)) + } + return valueData[int(entryStart):int(entryLimit)], nil +} + +// trienodeHistoryReader provides read access to node data in the trie node history. +// It resolves data from the underlying ancient store only when needed, minimizing +// I/O overhead. +type trienodeHistoryReader struct { + id uint64 // ID of the associated trienode history + reader ethdb.AncientReader // Database reader of ancient store + keyRanges map[common.Hash]iRange // Key ranges identifying trie chunks + valRanges map[common.Hash]iRange // Value ranges identifying trie chunks + iReaders map[common.Hash]*singleTrienodeHistoryReader // readers for each individual trie chunk +} + +// newTrienodeHistoryReader constructs the reader for specific trienode history. +func newTrienodeHistoryReader(id uint64, reader ethdb.AncientReader) (*trienodeHistoryReader, error) { + r := &trienodeHistoryReader{ + id: id, + reader: reader, + keyRanges: make(map[common.Hash]iRange), + valRanges: make(map[common.Hash]iRange), + iReaders: make(map[common.Hash]*singleTrienodeHistoryReader), + } + if err := r.decodeHeader(); err != nil { + return nil, err + } + return r, nil +} + +// decodeHeader decodes the header section of trienode history. +func (r *trienodeHistoryReader) decodeHeader() error { + header, err := rawdb.ReadTrienodeHistoryHeader(r.reader, r.id) + if err != nil { + return err + } + _, owners, keyOffsets, valOffsets, err := decodeHeader(header) + if err != nil { + return err + } + for i, owner := range owners { + // Decode the key range for this trie chunk + var keyLimit uint32 + if i == len(owners)-1 { + keyLimit = math.MaxUint32 + } else { + keyLimit = keyOffsets[i+1] + } + r.keyRanges[owner] = iRange{ + start: keyOffsets[i], + limit: keyLimit, + } + + // Decode the value range for this trie chunk + var valLimit uint32 + if i == len(owners)-1 { + valLimit = math.MaxUint32 + } else { + valLimit = valOffsets[i+1] + } + r.valRanges[owner] = iRange{ + start: valOffsets[i], + limit: valLimit, + } + } + return nil +} + +// read retrieves the trie node data with the provided TrieID and node path. +func (r *trienodeHistoryReader) read(owner common.Hash, path string) ([]byte, error) { + ir, ok := r.iReaders[owner] + if !ok { + keyRange, exists := r.keyRanges[owner] + if !exists { + return nil, fmt.Errorf("trie %x is unknown", owner) + } + valRange, exists := r.valRanges[owner] + if !exists { + return nil, fmt.Errorf("trie %x is unknown", owner) + } + var err error + ir, err = newSingleTrienodeHistoryReader(r.id, r.reader, keyRange, valRange) + if err != nil { + return nil, err + } + r.iReaders[owner] = ir + } + return ir.read(path) +} + +// writeTrienodeHistory persists the trienode history associated with the given diff layer. +// nolint:unused +func writeTrienodeHistory(writer ethdb.AncientWriter, dl *diffLayer) error { + start := time.Now() + h := newTrienodeHistory(dl.rootHash(), dl.parent.rootHash(), dl.block, dl.nodes.nodeOrigin) + header, keySection, valueSection, err := h.encode() + if err != nil { + return err + } + // Write history data into five freezer table respectively. + if err := rawdb.WriteTrienodeHistory(writer, dl.stateID(), header, keySection, valueSection); err != nil { + return err + } + trienodeHistoryDataBytesMeter.Mark(int64(len(valueSection))) + trienodeHistoryIndexBytesMeter.Mark(int64(len(header) + len(keySection))) + trienodeHistoryBuildTimeMeter.UpdateSince(start) + + log.Debug( + "Stored trienode history", "id", dl.stateID(), "block", dl.block, + "header", common.StorageSize(len(header)), + "keySection", common.StorageSize(len(keySection)), + "valueSection", common.StorageSize(len(valueSection)), + "elapsed", common.PrettyDuration(time.Since(start)), + ) + return nil +} + +// readTrienodeMetadata resolves the metadata of the specified trienode history. +// nolint:unused +func readTrienodeMetadata(reader ethdb.AncientReader, id uint64) (*trienodeMetadata, error) { + header, err := rawdb.ReadTrienodeHistoryHeader(reader, id) + if err != nil { + return nil, err + } + metadata, _, _, _, err := decodeHeader(header) + if err != nil { + return nil, err + } + return metadata, nil +} + +// readTrienodeHistory resolves a single trienode history object with specific id. +func readTrienodeHistory(reader ethdb.AncientReader, id uint64) (*trienodeHistory, error) { + header, keySection, valueSection, err := rawdb.ReadTrienodeHistory(reader, id) + if err != nil { + return nil, err + } + var h trienodeHistory + if err := h.decode(header, keySection, valueSection); err != nil { + return nil, err + } + return &h, nil +} + +// readTrienodeHistories resolves a list of trienode histories with the specific range. +func readTrienodeHistories(reader ethdb.AncientReader, start uint64, count uint64) ([]history, error) { + headers, keySections, valueSections, err := rawdb.ReadTrienodeHistoryList(reader, start, count) + if err != nil { + return nil, err + } + var res []history + for i, header := range headers { + var h trienodeHistory + if err := h.decode(header, keySections[i], valueSections[i]); err != nil { + return nil, err + } + res = append(res, &h) + } + return res, nil +} diff --git a/triedb/pathdb/history_trienode_test.go b/triedb/pathdb/history_trienode_test.go new file mode 100644 index 000000000000..d6b80f61f56c --- /dev/null +++ b/triedb/pathdb/history_trienode_test.go @@ -0,0 +1,736 @@ +// 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 . + +package pathdb + +import ( + "bytes" + "encoding/binary" + "math/rand" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/testrand" +) + +// randomTrienodes generates a random trienode set. +func randomTrienodes(n int) (map[common.Hash]map[string][]byte, common.Hash) { + var ( + root common.Hash + nodes = make(map[common.Hash]map[string][]byte) + ) + for i := 0; i < n; i++ { + owner := testrand.Hash() + if i == 0 { + owner = common.Hash{} + } + nodes[owner] = make(map[string][]byte) + + for j := 0; j < 10; j++ { + path := testrand.Bytes(rand.Intn(10)) + for z := 0; z < len(path); z++ { + nodes[owner][string(path[:z])] = testrand.Bytes(rand.Intn(128)) + } + } + // zero-size trie node, representing it was non-existent before + for j := 0; j < 10; j++ { + path := testrand.Bytes(32) + nodes[owner][string(path)] = nil + } + // root node with zero-size path + rnode := testrand.Bytes(256) + nodes[owner][""] = rnode + if owner == (common.Hash{}) { + root = crypto.Keccak256Hash(rnode) + } + } + return nodes, root +} + +func makeTrienodeHistory() *trienodeHistory { + nodes, root := randomTrienodes(10) + return newTrienodeHistory(root, common.Hash{}, 1, nodes) +} + +func makeTrienodeHistories(n int) []*trienodeHistory { + var ( + parent common.Hash + result []*trienodeHistory + ) + for i := 0; i < n; i++ { + nodes, root := randomTrienodes(10) + result = append(result, newTrienodeHistory(root, parent, uint64(i+1), nodes)) + parent = root + } + return result +} + +func TestEncodeDecodeTrienodeHistory(t *testing.T) { + var ( + dec trienodeHistory + obj = makeTrienodeHistory() + ) + header, keySection, valueSection, err := obj.encode() + if err != nil { + t.Fatalf("Failed to encode trienode history: %v", err) + } + if err := dec.decode(header, keySection, valueSection); err != nil { + t.Fatalf("Failed to decode trienode history: %v", err) + } + + if !reflect.DeepEqual(obj.meta, dec.meta) { + t.Fatal("trienode metadata is mismatched") + } + if !compareList(dec.owners, obj.owners) { + t.Fatal("trie owner list is mismatched") + } + if !compareMapList(dec.nodeList, obj.nodeList) { + t.Fatal("trienode list is mismatched") + } + if !compareMapSet(dec.nodes, obj.nodes) { + t.Fatal("trienode content is mismatched") + } + + // Re-encode again, ensuring the encoded blob still match + header2, keySection2, valueSection2, err := dec.encode() + if err != nil { + t.Fatalf("Failed to encode trienode history: %v", err) + } + if !bytes.Equal(header, header2) { + t.Fatal("re-encoded header is mismatched") + } + if !bytes.Equal(keySection, keySection2) { + t.Fatal("re-encoded key section is mismatched") + } + if !bytes.Equal(valueSection, valueSection2) { + t.Fatal("re-encoded value section is mismatched") + } +} + +func TestTrienodeHistoryReader(t *testing.T) { + var ( + hs = makeTrienodeHistories(10) + freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false) + ) + defer freezer.Close() + + for i, h := range hs { + header, keySection, valueSection, _ := h.encode() + if err := rawdb.WriteTrienodeHistory(freezer, uint64(i+1), header, keySection, valueSection); err != nil { + t.Fatalf("Failed to write trienode history: %v", err) + } + } + for i, h := range hs { + tr, err := newTrienodeHistoryReader(uint64(i+1), freezer) + if err != nil { + t.Fatalf("Failed to construct the history reader: %v", err) + } + for _, owner := range h.owners { + nodes := h.nodes[owner] + for key, value := range nodes { + blob, err := tr.read(owner, key) + if err != nil { + t.Fatalf("Failed to read trienode history: %v", err) + } + if !bytes.Equal(blob, value) { + t.Fatalf("Unexpected trie node data, want: %v, got: %v", value, blob) + } + } + } + } + for i, h := range hs { + metadata, err := readTrienodeMetadata(freezer, uint64(i+1)) + if err != nil { + t.Fatalf("Failed to read trienode history metadata: %v", err) + } + if !reflect.DeepEqual(h.meta, metadata) { + t.Fatalf("Unexpected trienode metadata, want: %v, got: %v", h.meta, metadata) + } + } +} + +// TestEmptyTrienodeHistory tests encoding/decoding of empty trienode history +func TestEmptyTrienodeHistory(t *testing.T) { + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, make(map[common.Hash]map[string][]byte)) + + // Test encoding empty history + header, keySection, valueSection, err := h.encode() + if err != nil { + t.Fatalf("Failed to encode empty trienode history: %v", err) + } + + // Verify sections are minimal but valid + if len(header) == 0 { + t.Fatal("Header should not be empty") + } + if len(keySection) != 0 { + t.Fatal("Key section should be empty for empty history") + } + if len(valueSection) != 0 { + t.Fatal("Value section should be empty for empty history") + } + + // Test decoding empty history + var decoded trienodeHistory + if err := decoded.decode(header, keySection, valueSection); err != nil { + t.Fatalf("Failed to decode empty trienode history: %v", err) + } + + if len(decoded.owners) != 0 { + t.Fatal("Decoded history should have no owners") + } + if len(decoded.nodeList) != 0 { + t.Fatal("Decoded history should have no node lists") + } + if len(decoded.nodes) != 0 { + t.Fatal("Decoded history should have no nodes") + } +} + +// TestSingleTrieHistory tests encoding/decoding of history with single trie +func TestSingleTrieHistory(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Add some nodes with various sizes + nodes[owner][""] = testrand.Bytes(32) // empty key + nodes[owner]["a"] = testrand.Bytes(1) // small value + nodes[owner]["bb"] = testrand.Bytes(100) // medium value + nodes[owner]["ccc"] = testrand.Bytes(1000) // large value + nodes[owner]["dddd"] = testrand.Bytes(0) // empty value + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + testEncodeDecode(t, h) +} + +// TestMultipleTries tests multiple tries with different node counts +func TestMultipleTries(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + + // First trie with many small nodes + owner1 := testrand.Hash() + nodes[owner1] = make(map[string][]byte) + for i := 0; i < 100; i++ { + key := string(testrand.Bytes(rand.Intn(10))) + nodes[owner1][key] = testrand.Bytes(rand.Intn(50)) + } + + // Second trie with few large nodes + owner2 := testrand.Hash() + nodes[owner2] = make(map[string][]byte) + for i := 0; i < 5; i++ { + key := string(testrand.Bytes(rand.Intn(20))) + nodes[owner2][key] = testrand.Bytes(1000 + rand.Intn(1000)) + } + + // Third trie with nil values (zero-size nodes) + owner3 := testrand.Hash() + nodes[owner3] = make(map[string][]byte) + for i := 0; i < 10; i++ { + key := string(testrand.Bytes(rand.Intn(15))) + nodes[owner3][key] = nil + } + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + testEncodeDecode(t, h) +} + +// TestLargeNodeValues tests encoding/decoding with very large node values +func TestLargeNodeValues(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Test with progressively larger values + sizes := []int{1024, 10 * 1024, 100 * 1024, 1024 * 1024} // 1KB, 10KB, 100KB, 1MB + for _, size := range sizes { + key := string(testrand.Bytes(10)) + nodes[owner][key] = testrand.Bytes(size) + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + testEncodeDecode(t, h) + t.Logf("Successfully tested encoding/decoding with %dKB value", size/1024) + } +} + +// TestNilNodeValues tests encoding/decoding with nil (zero-length) node values +func TestNilNodeValues(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Mix of nil and non-nil values + nodes[owner]["nil"] = nil + nodes[owner]["data1"] = []byte("some data") + nodes[owner]["data2"] = []byte("more data") + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + testEncodeDecode(t, h) + + // Verify nil values are preserved + _, ok := h.nodes[owner]["nil"] + if !ok { + t.Fatal("Nil value should be preserved") + } +} + +// TestCorruptedHeader tests error handling for corrupted header data +func TestCorruptedHeader(t *testing.T) { + h := makeTrienodeHistory() + header, keySection, valueSection, _ := h.encode() + + // Test corrupted version + corruptedHeader := make([]byte, len(header)) + copy(corruptedHeader, header) + corruptedHeader[0] = 0xFF // Invalid version + + var decoded trienodeHistory + if err := decoded.decode(corruptedHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for corrupted version") + } + + // Test truncated header + truncatedHeader := header[:len(header)-5] + if err := decoded.decode(truncatedHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for truncated header") + } + + // Test header with invalid trie header size + invalidHeader := make([]byte, len(header)) + copy(invalidHeader, header) + invalidHeader = invalidHeader[:trienodeMetadataSize+5] // Not divisible by trie header size + + if err := decoded.decode(invalidHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for invalid header size") + } +} + +// TestCorruptedKeySection tests error handling for corrupted key section data +func TestCorruptedKeySection(t *testing.T) { + h := makeTrienodeHistory() + header, keySection, valueSection, _ := h.encode() + + // Test empty key section when header indicates data + if len(keySection) > 0 { + var decoded trienodeHistory + if err := decoded.decode(header, []byte{}, valueSection); err == nil { + t.Fatal("Expected error for empty key section with non-empty header") + } + } + + // Test truncated key section + if len(keySection) > 10 { + truncatedKeySection := keySection[:len(keySection)-10] + var decoded trienodeHistory + if err := decoded.decode(header, truncatedKeySection, valueSection); err == nil { + t.Fatal("Expected error for truncated key section") + } + } + + // Test corrupted key section with invalid varint + corruptedKeySection := make([]byte, len(keySection)) + copy(corruptedKeySection, keySection) + if len(corruptedKeySection) > 5 { + corruptedKeySection[5] = 0xFF // Corrupt varint encoding + var decoded trienodeHistory + if err := decoded.decode(header, corruptedKeySection, valueSection); err == nil { + t.Fatal("Expected error for corrupted varint in key section") + } + } +} + +// TestCorruptedValueSection tests error handling for corrupted value section data +func TestCorruptedValueSection(t *testing.T) { + h := makeTrienodeHistory() + header, keySection, valueSection, _ := h.encode() + + // Test truncated value section + if len(valueSection) > 10 { + truncatedValueSection := valueSection[:len(valueSection)-10] + var decoded trienodeHistory + if err := decoded.decode(header, keySection, truncatedValueSection); err == nil { + t.Fatal("Expected error for truncated value section") + } + } + + // Test empty value section when key section indicates data exists + if len(valueSection) > 0 { + var decoded trienodeHistory + if err := decoded.decode(header, keySection, []byte{}); err == nil { + t.Fatal("Expected error for empty value section with non-empty key section") + } + } +} + +// TestInvalidOffsets tests error handling for invalid offsets in encoded data +func TestInvalidOffsets(t *testing.T) { + h := makeTrienodeHistory() + header, keySection, valueSection, _ := h.encode() + + // Corrupt key offset in header (make it larger than key section) + corruptedHeader := make([]byte, len(header)) + copy(corruptedHeader, header) + corruptedHeader[trienodeMetadataSize+common.HashLength] = 0xff + + var dec1 trienodeHistory + if err := dec1.decode(corruptedHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for invalid key offset") + } + + // Corrupt value offset in header (make it larger than value section) + corruptedHeader = make([]byte, len(header)) + copy(corruptedHeader, header) + corruptedHeader[trienodeMetadataSize+common.HashLength+4] = 0xff + + var dec2 trienodeHistory + if err := dec2.decode(corruptedHeader, keySection, valueSection); err == nil { + t.Fatal("Expected error for invalid value offset") + } +} + +// TestTrienodeHistoryReaderNonExistentPath tests reading non-existent paths +func TestTrienodeHistoryReaderNonExistentPath(t *testing.T) { + var ( + h = makeTrienodeHistory() + freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false) + ) + defer freezer.Close() + + header, keySection, valueSection, _ := h.encode() + if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil { + t.Fatalf("Failed to write trienode history: %v", err) + } + + tr, err := newTrienodeHistoryReader(1, freezer) + if err != nil { + t.Fatalf("Failed to construct history reader: %v", err) + } + + // Try to read a non-existent path + _, err = tr.read(testrand.Hash(), "nonexistent") + if err == nil { + t.Fatal("Expected error for non-existent trie owner") + } + + // Try to read from existing owner but non-existent path + owner := h.owners[0] + _, err = tr.read(owner, "nonexistent-path") + if err == nil { + t.Fatal("Expected error for non-existent path") + } +} + +// TestTrienodeHistoryReaderNilValues tests reading nil (zero-length) values +func TestTrienodeHistoryReaderNilValues(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Add some nil values + nodes[owner]["nil1"] = nil + nodes[owner]["nil2"] = nil + nodes[owner]["data1"] = []byte("some data") + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + + var freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false) + defer freezer.Close() + + header, keySection, valueSection, _ := h.encode() + if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil { + t.Fatalf("Failed to write trienode history: %v", err) + } + + tr, err := newTrienodeHistoryReader(1, freezer) + if err != nil { + t.Fatalf("Failed to construct history reader: %v", err) + } + + // Test reading nil values + data1, err := tr.read(owner, "nil1") + if err != nil { + t.Fatalf("Failed to read nil value: %v", err) + } + if len(data1) != 0 { + t.Fatal("Expected nil data for nil value") + } + + data2, err := tr.read(owner, "nil2") + if err != nil { + t.Fatalf("Failed to read nil value: %v", err) + } + if len(data2) != 0 { + t.Fatal("Expected nil data for nil value") + } + + // Test reading non-nil value + data3, err := tr.read(owner, "data1") + if err != nil { + t.Fatalf("Failed to read non-nil value: %v", err) + } + if !bytes.Equal(data3, []byte("some data")) { + t.Fatal("Data mismatch for non-nil value") + } +} + +// TestTrienodeHistoryReaderNilKey tests reading nil (zero-length) key +func TestTrienodeHistoryReaderNilKey(t *testing.T) { + nodes := make(map[common.Hash]map[string][]byte) + owner := testrand.Hash() + nodes[owner] = make(map[string][]byte) + + // Add some nil values + nodes[owner][""] = []byte("some data") + nodes[owner]["data1"] = []byte("some data") + + h := newTrienodeHistory(common.Hash{}, common.Hash{}, 1, nodes) + + var freezer, _ = rawdb.NewTrienodeFreezer(t.TempDir(), false, false) + defer freezer.Close() + + header, keySection, valueSection, _ := h.encode() + if err := rawdb.WriteTrienodeHistory(freezer, 1, header, keySection, valueSection); err != nil { + t.Fatalf("Failed to write trienode history: %v", err) + } + + tr, err := newTrienodeHistoryReader(1, freezer) + if err != nil { + t.Fatalf("Failed to construct history reader: %v", err) + } + + // Test reading nil values + data1, err := tr.read(owner, "") + if err != nil { + t.Fatalf("Failed to read nil value: %v", err) + } + if !bytes.Equal(data1, []byte("some data")) { + t.Fatal("Data mismatch for nil key") + } + + // Test reading non-nil value + data2, err := tr.read(owner, "data1") + if err != nil { + t.Fatalf("Failed to read non-nil value: %v", err) + } + if !bytes.Equal(data2, []byte("some data")) { + t.Fatal("Data mismatch for non-nil key") + } +} + +// TestTrienodeHistoryReaderIterator tests the iterator functionality +func TestTrienodeHistoryReaderIterator(t *testing.T) { + h := makeTrienodeHistory() + + // Count expected entries + expectedCount := 0 + expectedNodes := make(map[stateIdent]bool) + for owner, nodeList := range h.nodeList { + expectedCount += len(nodeList) + for _, node := range nodeList { + expectedNodes[stateIdent{ + typ: typeTrienode, + addressHash: owner, + path: node, + }] = true + } + } + + // Test the iterator + actualCount := 0 + for x := range h.forEach() { + _ = x + actualCount++ + } + if actualCount != expectedCount { + t.Fatalf("Iterator count mismatch: expected %d, got %d", expectedCount, actualCount) + } + + // Test that iterator yields expected state identifiers + seen := make(map[stateIdent]bool) + for ident := range h.forEach() { + if ident.typ != typeTrienode { + t.Fatal("Iterator should only yield trienode history identifiers") + } + key := stateIdent{typ: ident.typ, addressHash: ident.addressHash, path: ident.path} + if seen[key] { + t.Fatal("Iterator yielded duplicate identifier") + } + seen[key] = true + + if !expectedNodes[key] { + t.Fatalf("Unexpected yielded identifier %v", key) + } + } +} + +// TestSharedLen tests the sharedLen helper function +func TestSharedLen(t *testing.T) { + tests := []struct { + a, b []byte + expected int + }{ + // Empty strings + {[]byte(""), []byte(""), 0}, + // One empty string + {[]byte(""), []byte("abc"), 0}, + {[]byte("abc"), []byte(""), 0}, + // No common prefix + {[]byte("abc"), []byte("def"), 0}, + // Partial common prefix + {[]byte("abc"), []byte("abx"), 2}, + {[]byte("prefix"), []byte("pref"), 4}, + // Complete common prefix (shorter first) + {[]byte("ab"), []byte("abcd"), 2}, + // Complete common prefix (longer first) + {[]byte("abcd"), []byte("ab"), 2}, + // Identical strings + {[]byte("identical"), []byte("identical"), 9}, + // Binary data + {[]byte{0x00, 0x01, 0x02}, []byte{0x00, 0x01, 0x03}, 2}, + // Large strings + {bytes.Repeat([]byte("a"), 1000), bytes.Repeat([]byte("a"), 1000), 1000}, + {bytes.Repeat([]byte("a"), 1000), append(bytes.Repeat([]byte("a"), 999), []byte("b")...), 999}, + } + + for i, test := range tests { + result := sharedLen(test.a, test.b) + if result != test.expected { + t.Errorf("Test %d: sharedLen(%q, %q) = %d, expected %d", + i, test.a, test.b, result, test.expected) + } + // Test commutativity + resultReverse := sharedLen(test.b, test.a) + if result != resultReverse { + t.Errorf("Test %d: sharedLen is not commutative: sharedLen(a,b)=%d, sharedLen(b,a)=%d", + i, result, resultReverse) + } + } +} + +// TestDecodeHeaderCorruptedData tests decodeHeader with corrupted data +func TestDecodeHeaderCorruptedData(t *testing.T) { + // Create valid header data first + h := makeTrienodeHistory() + header, _, _, _ := h.encode() + + // Test with empty header + _, _, _, _, err := decodeHeader([]byte{}) + if err == nil { + t.Fatal("Expected error for empty header") + } + + // Test with invalid version + corruptedVersion := make([]byte, len(header)) + copy(corruptedVersion, header) + corruptedVersion[0] = 0xFF + _, _, _, _, err = decodeHeader(corruptedVersion) + if err == nil { + t.Fatal("Expected error for invalid version") + } + + // Test with truncated header (not divisible by trie header size) + truncated := header[:trienodeMetadataSize+5] + _, _, _, _, err = decodeHeader(truncated) + if err == nil { + t.Fatal("Expected error for truncated header") + } + + // Test with unordered trie owners + unordered := make([]byte, len(header)) + copy(unordered, header) + + // Swap two owner hashes to make them unordered + hash1Start := trienodeMetadataSize + hash2Start := trienodeMetadataSize + trienodeTrieHeaderSize + hash1 := unordered[hash1Start : hash1Start+common.HashLength] + hash2 := unordered[hash2Start : hash2Start+common.HashLength] + + // Only swap if they would be out of order + copy(unordered[hash1Start:hash1Start+common.HashLength], hash2) + copy(unordered[hash2Start:hash2Start+common.HashLength], hash1) + + _, _, _, _, err = decodeHeader(unordered) + if err == nil { + t.Fatal("Expected error for unordered trie owners") + } +} + +// TestDecodeSingleCorruptedData tests decodeSingle with corrupted data +func TestDecodeSingleCorruptedData(t *testing.T) { + h := makeTrienodeHistory() + _, keySection, _, _ := h.encode() + + // Test with empty key section + _, err := decodeSingle([]byte{}, nil) + if err == nil { + t.Fatal("Expected error for empty key section") + } + + // Test with key section too small for trailer + if len(keySection) > 0 { + _, err := decodeSingle(keySection[:3], nil) // Less than 4 bytes for trailer + if err == nil { + t.Fatal("Expected error for key section too small for trailer") + } + } + + // Test with corrupted varint in key section + corrupted := make([]byte, len(keySection)) + copy(corrupted, keySection) + corrupted[5] = 0xFF // Corrupt varint + _, err = decodeSingle(corrupted, nil) + if err == nil { + t.Fatal("Expected error for corrupted varint") + } + + // Test with corrupted trailer (invalid restart count) + corrupted = make([]byte, len(keySection)) + copy(corrupted, keySection) + // Set restart count to something too large + binary.BigEndian.PutUint32(corrupted[len(corrupted)-4:], 10000) + _, err = decodeSingle(corrupted, nil) + if err == nil { + t.Fatal("Expected error for invalid restart count") + } +} + +// Helper function to test encode/decode cycle +func testEncodeDecode(t *testing.T, h *trienodeHistory) { + header, keySection, valueSection, err := h.encode() + if err != nil { + t.Fatalf("Failed to encode trienode history: %v", err) + } + + var decoded trienodeHistory + if err := decoded.decode(header, keySection, valueSection); err != nil { + t.Fatalf("Failed to decode trienode history: %v", err) + } + + // Compare the decoded history with original + if !compareList(decoded.owners, h.owners) { + t.Fatal("Trie owner list mismatch") + } + if !compareMapList(decoded.nodeList, h.nodeList) { + t.Fatal("Trienode list mismatch") + } + if !compareMapSet(decoded.nodes, h.nodes) { + t.Fatal("Trienode content mismatch") + } +} diff --git a/triedb/pathdb/metrics.go b/triedb/pathdb/metrics.go index 779f9d813ffe..31c40053fc26 100644 --- a/triedb/pathdb/metrics.go +++ b/triedb/pathdb/metrics.go @@ -69,12 +69,21 @@ var ( gcStorageMeter = metrics.NewRegisteredMeter("pathdb/gc/storage/count", nil) gcStorageBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/storage/bytes", nil) - historyBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/time", nil) - historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil) - historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil) - - indexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/index/time", nil) - unindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/unindex/time", nil) + stateHistoryBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/state/time", nil) + stateHistoryDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/state/bytes/data", nil) + stateHistoryIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/state/bytes/index", nil) + + //nolint:unused + trienodeHistoryBuildTimeMeter = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/time", nil) + //nolint:unused + trienodeHistoryDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/trienode/bytes/data", nil) + //nolint:unused + trienodeHistoryIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/trienode/bytes/index", nil) + + stateIndexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/state/index/time", nil) + stateUnindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/state/unindex/time", nil) + trienodeIndexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/index/time", nil) + trienodeUnindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/trienode/unindex/time", nil) lookupAddLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/add/time", nil) lookupRemoveLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/remove/time", nil) From 6f1d23640510c87a4a106280a8674515dfa1cd90 Mon Sep 17 00:00:00 2001 From: 10gic Date: Fri, 10 Oct 2025 17:47:33 +0800 Subject: [PATCH 112/228] ethclient: add SubscribeTransactionReceipts (#32869) Add `SubscribeTransactionReceipts` for ethclient. This is a complement to https://github.com/ethereum/go-ethereum/pull/32697. --- eth/filters/api.go | 24 ++++++++++++++++++------ ethclient/ethclient.go | 9 +++++++++ interfaces.go | 12 ++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 9f9209aea7e9..a3ed00f33b3d 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -299,15 +299,27 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc return rpcSub, nil } -// TransactionReceiptsFilter defines criteria for transaction receipts subscription. -// If TransactionHashes is nil or empty, receipts for all transactions included in new blocks will be delivered. -// Otherwise, only receipts for the specified transactions will be delivered. -type TransactionReceiptsFilter struct { - TransactionHashes []common.Hash `json:"transactionHashes,omitempty"` +// TransactionReceiptsQuery defines criteria for transaction receipts subscription. +// Same as ethereum.TransactionReceiptsQuery but with UnmarshalJSON() method. +type TransactionReceiptsQuery ethereum.TransactionReceiptsQuery + +// UnmarshalJSON sets *args fields with given data. +func (args *TransactionReceiptsQuery) UnmarshalJSON(data []byte) error { + type input struct { + TransactionHashes []common.Hash `json:"transactionHashes"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + args.TransactionHashes = raw.TransactionHashes + return nil } // TransactionReceipts creates a subscription that fires transaction receipts when transactions are included in blocks. -func (api *FilterAPI) TransactionReceipts(ctx context.Context, filter *TransactionReceiptsFilter) (*rpc.Subscription, error) { +func (api *FilterAPI) TransactionReceipts(ctx context.Context, filter *TransactionReceiptsQuery) (*rpc.Subscription, error) { notifier, supported := rpc.NotifierFromContext(ctx) if !supported { return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 1195929f7d2e..8b26f5b3cad1 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -350,6 +350,15 @@ func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (* return r, err } +// SubscribeTransactionReceipts subscribes to notifications about transaction receipts. +func (ec *Client) SubscribeTransactionReceipts(ctx context.Context, q *ethereum.TransactionReceiptsQuery, ch chan<- []*types.Receipt) (ethereum.Subscription, error) { + sub, err := ec.c.EthSubscribe(ctx, ch, "transactionReceipts", q) + if err != nil { + return nil, err + } + return sub, nil +} + // SyncProgress retrieves the current progress of the sync algorithm. If there's // no sync currently running, it returns nil. func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { diff --git a/interfaces.go b/interfaces.go index be5b97085155..2828af1cc9a5 100644 --- a/interfaces.go +++ b/interfaces.go @@ -62,6 +62,13 @@ type ChainReader interface { SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (Subscription, error) } +// TransactionReceiptsQuery defines criteria for transaction receipts subscription. +// If TransactionHashes is empty, receipts for all transactions included in new blocks will be delivered. +// Otherwise, only receipts for the specified transactions will be delivered. +type TransactionReceiptsQuery struct { + TransactionHashes []common.Hash +} + // TransactionReader provides access to past transactions and their receipts. // Implementations may impose arbitrary restrictions on the transactions and receipts that // can be retrieved. Historic transactions may not be available. @@ -81,6 +88,11 @@ type TransactionReader interface { // transaction may not be included in the current canonical chain even if a receipt // exists. TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + // SubscribeTransactionReceipts subscribes to notifications about transaction receipts. + // The receipts are delivered in batches when transactions are included in blocks. + // If q is nil or has empty TransactionHashes, all receipts from new blocks will be delivered. + // Otherwise, only receipts for the specified transaction hashes will be delivered. + SubscribeTransactionReceipts(ctx context.Context, q *TransactionReceiptsQuery, ch chan<- []*types.Receipt) (Subscription, error) } // ChainStateReader wraps access to the state trie of the canonical blockchain. Note that From 729484bda7f8bf693fa3361bdfe1696795a7c5fc Mon Sep 17 00:00:00 2001 From: Luke Ma Date: Mon, 13 Oct 2025 15:26:35 +0800 Subject: [PATCH 113/228] node: fix error condition in gzipResponseWriter.init() (#32896) --- node/rpcstack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index 655f7db9e4f3..a1cc832f9f3e 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -500,7 +500,7 @@ func (w *gzipResponseWriter) init() { hdr := w.resp.Header() length := hdr.Get("content-length") if len(length) > 0 { - if n, err := strconv.ParseUint(length, 10, 64); err != nil { + if n, err := strconv.ParseUint(length, 10, 64); err == nil { w.hasLength = true w.contentLength = n } From 740334322fcdcfa81b104103d7a736bc1cfd719b Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 13 Oct 2025 16:39:10 +0800 Subject: [PATCH 114/228] core/types: optimize MergeBloom by using bitutil (#32882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` goos: darwin goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/types cpu: VirtualApple @ 2.50GHz │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ CreateBloom/small-createbloom-10 1.676µ ± 4% 1.646µ ± 1% -1.76% (p=0.000 n=10) CreateBloom/large-createbloom-10 164.8µ ± 3% 164.3µ ± 0% ~ (p=0.247 n=10) CreateBloom/small-mergebloom-10 231.60n ± 0% 68.00n ± 0% -70.64% (p=0.000 n=10) CreateBloom/large-mergebloom-10 21.803µ ± 3% 5.107µ ± 1% -76.58% (p=0.000 n=10) geomean 6.111µ 3.113µ -49.06% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ CreateBloom/small-createbloom-10 112.0 ± 0% 112.0 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/large-createbloom-10 10.94Ki ± 0% 10.94Ki ± 0% ~ (p=0.474 n=10) CreateBloom/small-mergebloom-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/large-mergebloom-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ CreateBloom/small-createbloom-10 6.000 ± 0% 6.000 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/large-createbloom-10 600.0 ± 0% 600.0 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/small-mergebloom-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ CreateBloom/large-mergebloom-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² +0.00% ² ¹ all samples are equal ² summaries must be >0 to compute geomean ``` --- core/types/bloom9.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/types/bloom9.go b/core/types/bloom9.go index 5a6e49c220d4..1d57e8e4bca7 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -21,6 +21,7 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" ) @@ -125,9 +126,7 @@ func MergeBloom(receipts Receipts) Bloom { for _, receipt := range receipts { if len(receipt.Logs) != 0 { bl := receipt.Bloom.Bytes() - for i := range bin { - bin[i] |= bl[i] - } + bitutil.ORBytes(bin[:], bin[:], bl) } } return bin From b7e3cdfc06f13e3f2d11029cda91d41623da716e Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 13 Oct 2025 16:40:08 +0800 Subject: [PATCH 115/228] p2p: rm unused var seedMinTableTime (#32876) --- p2p/discover/table.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 6a1c7494eeb6..e5b2c7c8c5f7 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -54,9 +54,8 @@ const ( bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24 tableIPLimit, tableSubnet = 10, 24 - seedMinTableTime = 5 * time.Minute - seedCount = 30 - seedMaxAge = 5 * 24 * time.Hour + seedCount = 30 + seedMaxAge = 5 * 24 * time.Hour ) // Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps From 85c82d4c374ff20740f8df15faa2615d27aee3aa Mon Sep 17 00:00:00 2001 From: Lewis Date: Mon, 13 Oct 2025 20:10:44 +0900 Subject: [PATCH 116/228] eth/filters: uninstall subscription in filter apis on error (#32894) Fix https://github.com/ethereum/go-ethereum/issues/32893. In the previous https://github.com/ethereum/go-ethereum/pull/32794, it only handles the pending tx filter, while there are also head and log filters. This PR applies the patch to all filter APIs and uses `defer` to maintain code consistency. --- eth/filters/api.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index a3ed00f33b3d..58baf2c3aa89 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -143,6 +143,7 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID { api.filtersMu.Unlock() go func() { + defer pendingTxSub.Unsubscribe() for { select { case pTx := <-pendingTxs: @@ -155,7 +156,6 @@ func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID { api.filtersMu.Lock() delete(api.filters, pendingTxSub.ID) api.filtersMu.Unlock() - pendingTxSub.Unsubscribe() return } } @@ -218,6 +218,7 @@ func (api *FilterAPI) NewBlockFilter() rpc.ID { api.filtersMu.Unlock() go func() { + defer headerSub.Unsubscribe() for { select { case h := <-headers: @@ -403,6 +404,7 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { api.filtersMu.Unlock() go func() { + defer logsSub.Unsubscribe() for { select { case l := <-logs: From f73f576a51ec4d40c727ce36f4964d1b4e658040 Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 13 Oct 2025 19:40:03 +0800 Subject: [PATCH 117/228] triedb, core/rawdb: implement the partial read in freezer (#32132) This PR implements the partial read functionalities in the freezer, optimizing the state history reader by resolving less data from freezer. --------- Signed-off-by: jsvisa Co-authored-by: Gary Rong --- core/rawdb/accessors_state.go | 24 +++--------- core/rawdb/chain_freezer.go | 4 ++ core/rawdb/database.go | 6 +++ core/rawdb/freezer.go | 9 +++++ core/rawdb/freezer_memory.go | 25 ++++++++++++ core/rawdb/freezer_resettable.go | 9 +++++ core/rawdb/freezer_table.go | 65 ++++++++++++++++++++++++++++++++ core/rawdb/freezer_table_test.go | 62 ++++++++++++++++++++++++++++++ core/rawdb/table.go | 6 +++ ethdb/database.go | 4 ++ ethdb/remotedb/remotedb.go | 4 ++ triedb/pathdb/history_reader.go | 38 +++++++------------ 12 files changed, 214 insertions(+), 42 deletions(-) diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 298ad04f40b3..714c1f77d6e1 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -188,24 +188,16 @@ func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte { // data in the concatenated storage data table. Compute the position of state // history in freezer by minus one since the id of first state history starts // from one (zero for initial state). -func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryStorageIndex, id-1) - if err != nil { - return nil - } - return blob +func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) { + return db.AncientBytes(stateHistoryStorageIndex, id-1, uint64(offset), uint64(length)) } // ReadStateAccountHistory retrieves the concatenated account data blob for the // specified state history. Offsets and lengths are resolved via the account // index. Compute the position of state history in freezer by minus one since // the id of first state history starts from one (zero for initial state). -func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryAccountData, id-1) - if err != nil { - return nil - } - return blob +func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) { + return db.AncientBytes(stateHistoryAccountData, id-1, uint64(offset), uint64(length)) } // ReadStateStorageHistory retrieves the concatenated storage slot data blob for @@ -213,12 +205,8 @@ func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { // storage indexes. Compute the position of state history in freezer by minus // one since the id of first state history starts from one (zero for initial // state). -func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte { - blob, err := db.Ancient(stateHistoryStorageData, id-1) - if err != nil { - return nil - } - return blob +func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) { + return db.AncientBytes(stateHistoryStorageData, id-1, uint64(offset), uint64(length)) } // ReadStateHistory retrieves the state history from database with provided id. diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index c12f2ab8feab..d33f7ce33d43 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -403,6 +403,10 @@ func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64) return f.ancients.AncientRange(kind, start, count, maxBytes) } +func (f *chainFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + return f.ancients.AncientBytes(kind, id, offset, length) +} + func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) { return f.ancients.ModifyAncients(fn) } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 626d390c0d35..724c90ead6a8 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -100,6 +100,12 @@ func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64) return nil, errNotSupported } +// AncientBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (db *nofreezedb) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + return nil, errNotSupported +} + // Ancients returns an error as we don't have a backing chain freezer. func (db *nofreezedb) Ancients() (uint64, error) { return 0, errNotSupported diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 98ad174ce0d2..42cd2a7999ab 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -202,6 +202,15 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][] return nil, errUnknownTable } +// AncientBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (f *Freezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + if table := f.tables[kind]; table != nil { + return table.RetrieveBytes(id, offset, length) + } + return nil, errUnknownTable +} + // Ancients returns the length of the frozen items. func (f *Freezer) Ancients() (uint64, error) { return f.frozen.Load(), nil diff --git a/core/rawdb/freezer_memory.go b/core/rawdb/freezer_memory.go index f5621ac4c600..8cb4cc20063c 100644 --- a/core/rawdb/freezer_memory.go +++ b/core/rawdb/freezer_memory.go @@ -412,3 +412,28 @@ func (f *MemoryFreezer) Reset() error { func (f *MemoryFreezer) AncientDatadir() (string, error) { return "", nil } + +// AncientBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (f *MemoryFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + table := f.tables[kind] + if table == nil { + return nil, errUnknownTable + } + entries, err := table.retrieve(id, 1, 0) + if err != nil { + return nil, err + } + if len(entries) == 0 { + return nil, errOutOfBounds + } + data := entries[0] + + if offset > uint64(len(data)) || offset+length > uint64(len(data)) { + return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length) + } + return data[offset : offset+length], nil +} diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go index 9db71cfd0e0d..f531e668c3d1 100644 --- a/core/rawdb/freezer_resettable.go +++ b/core/rawdb/freezer_resettable.go @@ -126,6 +126,15 @@ func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uin return f.freezer.AncientRange(kind, start, count, maxBytes) } +// AncientBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (f *resettableFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.freezer.AncientBytes(kind, id, offset, length) +} + // Ancients returns the length of the frozen items. func (f *resettableFreezer) Ancients() (uint64, error) { f.lock.RLock() diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index d3a29a73c6a7..01a754c5c80f 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -1107,6 +1107,71 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i return output, sizes, nil } +// RetrieveBytes retrieves the value segment of the element specified by the id +// and value offsets. +func (t *freezerTable) RetrieveBytes(item, offset, length uint64) ([]byte, error) { + t.lock.RLock() + defer t.lock.RUnlock() + + if t.index == nil || t.head == nil || t.metadata.file == nil { + return nil, errClosed + } + items, hidden := t.items.Load(), t.itemHidden.Load() + if items <= item || hidden > item { + return nil, errOutOfBounds + } + + // Retrieves the index entries for the specified ID and its immediate successor + indices, err := t.getIndices(item, 1) + if err != nil { + return nil, err + } + index0, index1 := indices[0], indices[1] + + itemStart, itemLimit, fileId := index0.bounds(index1) + itemSize := itemLimit - itemStart + + dataFile, exist := t.files[fileId] + if !exist { + return nil, fmt.Errorf("missing data file %d", fileId) + } + + // Perform the partial read if no-compression was enabled upon + if t.config.noSnappy { + if offset > uint64(itemSize) || offset+length > uint64(itemSize) { + return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", itemSize, offset, length) + } + itemStart += uint32(offset) + + buf := make([]byte, length) + _, err = dataFile.ReadAt(buf, int64(itemStart)) + if err != nil { + return nil, err + } + t.readMeter.Mark(int64(length)) + return buf, nil + } else { + // If compressed, read the full item, decompress, then slice. + // Unfortunately, in this case, there is no performance gain + // by performing the partial read at all. + buf := make([]byte, itemSize) + _, err = dataFile.ReadAt(buf, int64(itemStart)) + if err != nil { + return nil, err + } + t.readMeter.Mark(int64(itemSize)) + + data, err := snappy.Decode(nil, buf) + if err != nil { + return nil, err + } + if offset > uint64(len(data)) || offset+length > uint64(len(data)) { + return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length) + } + return data[offset : offset+length], nil + } +} + // size returns the total data size in the freezer table. func (t *freezerTable) size() (uint64, error) { t.lock.RLock() diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index 96edac7e4a42..fc21ea6c6350 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -1571,3 +1571,65 @@ func TestTailTruncationCrash(t *testing.T) { t.Fatalf("Unexpected index flush offset, want: %d, got: %d", 26*indexEntrySize, f.metadata.flushOffset) } } + +func TestFreezerAncientBytes(t *testing.T) { + t.Parallel() + types := []struct { + name string + config freezerTableConfig + }{ + {"uncompressed", freezerTableConfig{noSnappy: true}}, + {"compressed", freezerTableConfig{noSnappy: false}}, + } + for _, typ := range types { + t.Run(typ.name, func(t *testing.T) { + f, err := newTable(os.TempDir(), fmt.Sprintf("ancientbytes-%s-%d", typ.name, rand.Uint64()), metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 1000, typ.config, false) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + for i := 0; i < 10; i++ { + data := getChunk(100, i) + batch := f.newBatch() + require.NoError(t, batch.AppendRaw(uint64(i), data)) + require.NoError(t, batch.commit()) + } + + for i := 0; i < 10; i++ { + full, err := f.Retrieve(uint64(i)) + require.NoError(t, err) + + // Full read + got, err := f.RetrieveBytes(uint64(i), 0, uint64(len(full))) + require.NoError(t, err) + if !bytes.Equal(got, full) { + t.Fatalf("full read mismatch for entry %d", i) + } + // Empty read + got, err = f.RetrieveBytes(uint64(i), 0, 0) + require.NoError(t, err) + if !bytes.Equal(got, full[:0]) { + t.Fatalf("empty read mismatch for entry %d", i) + } + // Middle slice + got, err = f.RetrieveBytes(uint64(i), 10, 50) + require.NoError(t, err) + if !bytes.Equal(got, full[10:60]) { + t.Fatalf("middle slice mismatch for entry %d", i) + } + // Single byte + got, err = f.RetrieveBytes(uint64(i), 99, 1) + require.NoError(t, err) + if !bytes.Equal(got, full[99:100]) { + t.Fatalf("single byte mismatch for entry %d", i) + } + // Out of bounds + _, err = f.RetrieveBytes(uint64(i), 100, 1) + if err == nil { + t.Fatalf("expected error for out-of-bounds read for entry %d", i) + } + } + }) + } +} diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 45c8aecf0c06..d38afdaa3545 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -62,6 +62,12 @@ func (t *table) AncientRange(kind string, start, count, maxBytes uint64) ([][]by return t.db.AncientRange(kind, start, count, maxBytes) } +// AncientBytes is a noop passthrough that just forwards the request to the underlying +// database. +func (t *table) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + return t.db.AncientBytes(kind, id, offset, length) +} + // Ancients is a noop passthrough that just forwards the request to the underlying // database. func (t *table) Ancients() (uint64, error) { diff --git a/ethdb/database.go b/ethdb/database.go index e665a84a6189..534fcad4fcec 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -121,6 +121,10 @@ type AncientReaderOp interface { // - if maxBytes is not specified, 'count' items will be returned if they are present AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) + // AncientBytes retrieves the value segment of the element specified by the id + // and value offsets. + AncientBytes(kind string, id, offset, length uint64) ([]byte, error) + // Ancients returns the ancient item numbers in the ancient store. Ancients() (uint64, error) diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index 7fe154ea9501..0d0d854fe459 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -140,6 +140,10 @@ func (db *Database) Close() error { return nil } +func (db *Database) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + panic("not supported") +} + func New(client *rpc.Client) ethdb.Database { if client == nil { return nil diff --git a/triedb/pathdb/history_reader.go b/triedb/pathdb/history_reader.go index ce6aa693d117..1bf4cf648d61 100644 --- a/triedb/pathdb/history_reader.go +++ b/triedb/pathdb/history_reader.go @@ -144,25 +144,17 @@ func (r *historyReader) readAccountMetadata(address common.Address, historyID ui // readStorageMetadata resolves the storage slot metadata within the specified // state history. func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) { - // TODO(rj493456442) optimize it with partial read - blob := rawdb.ReadStateStorageIndex(r.freezer, historyID) - if len(blob) == 0 { - return nil, fmt.Errorf("storage index is truncated, historyID: %d", historyID) - } - if len(blob)%slotIndexSize != 0 { - return nil, fmt.Errorf("storage indices is corrupted, historyID: %d, size: %d", historyID, len(blob)) - } - if slotIndexSize*(slotOffset+slotNumber) > len(blob) { - return nil, fmt.Errorf("storage indices is truncated, historyID: %d, size: %d, offset: %d, length: %d", historyID, len(blob), slotOffset, slotNumber) + data, err := rawdb.ReadStateStorageIndex(r.freezer, historyID, slotIndexSize*slotOffset, slotIndexSize*slotNumber) + if err != nil { + msg := fmt.Sprintf("id: %d, slot-offset: %d, slot-length: %d", historyID, slotOffset, slotNumber) + return nil, fmt.Errorf("storage indices corrupted, %s, %w", msg, err) } - subSlice := blob[slotIndexSize*slotOffset : slotIndexSize*(slotOffset+slotNumber)] - // TODO(rj493456442) get rid of the metadata resolution var ( m meta target common.Hash ) - blob = rawdb.ReadStateHistoryMeta(r.freezer, historyID) + blob := rawdb.ReadStateHistoryMeta(r.freezer, historyID) if err := m.decode(blob); err != nil { return nil, err } @@ -172,17 +164,17 @@ func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash target = storageKey } pos := sort.Search(slotNumber, func(i int) bool { - slotID := subSlice[slotIndexSize*i : slotIndexSize*i+common.HashLength] + slotID := data[slotIndexSize*i : slotIndexSize*i+common.HashLength] return bytes.Compare(slotID, target.Bytes()) >= 0 }) if pos == slotNumber { return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) } offset := slotIndexSize * pos - if target != common.BytesToHash(subSlice[offset:offset+common.HashLength]) { + if target != common.BytesToHash(data[offset:offset+common.HashLength]) { return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) } - return subSlice[offset : slotIndexSize*(pos+1)], nil + return data[offset : slotIndexSize*(pos+1)], nil } // readAccount retrieves the account data from the specified state history. @@ -194,12 +186,11 @@ func (r *historyReader) readAccount(address common.Address, historyID uint64) ([ length := int(metadata[common.AddressLength]) // one byte for account data length offset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+1 : common.AddressLength+5])) // four bytes for the account data offset - // TODO(rj493456442) optimize it with partial read - data := rawdb.ReadStateAccountHistory(r.freezer, historyID) - if len(data) < length+offset { + data, err := rawdb.ReadStateAccountHistory(r.freezer, historyID, offset, length) + if err != nil { return nil, fmt.Errorf("account data is truncated, address: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, historyID, len(data), offset, length) } - return data[offset : offset+length], nil + return data, nil } // readStorage retrieves the storage slot data from the specified state history. @@ -222,12 +213,11 @@ func (r *historyReader) readStorage(address common.Address, storageKey common.Ha length := int(slotMetadata[common.HashLength]) // one byte for slot data length offset := int(binary.BigEndian.Uint32(slotMetadata[common.HashLength+1 : common.HashLength+5])) // four bytes for slot data offset - // TODO(rj493456442) optimize it with partial read - data := rawdb.ReadStateStorageHistory(r.freezer, historyID) - if len(data) < offset+length { + data, err := rawdb.ReadStateStorageHistory(r.freezer, historyID, offset, length) + if err != nil { return nil, fmt.Errorf("storage data is truncated, address: %#x, key: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, storageKey, historyID, len(data), offset, length) } - return data[offset : offset+length], nil + return data, nil } // read retrieves the state element data associated with the stateID. From b0af5edc51e661638ab582c0bd21e1c279c3c061 Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 13 Oct 2025 20:00:43 +0800 Subject: [PATCH 118/228] p2p/enode: optimize LogDist (#32887) This speeds up LogDist by 75% using 64-bit operations instead of byte-wise XOR. --------- Co-authored-by: Felix Lange --- p2p/enode/node.go | 11 +++++++---- p2p/enode/node_test.go | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/p2p/enode/node.go b/p2p/enode/node.go index d6f2ac7ff562..8198050353bd 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -19,6 +19,7 @@ package enode import ( "crypto/ecdsa" "encoding/base64" + "encoding/binary" "encoding/hex" "errors" "fmt" @@ -373,12 +374,14 @@ func DistCmp(target, a, b ID) int { // LogDist returns the logarithmic distance between a and b, log2(a ^ b). func LogDist(a, b ID) int { lz := 0 - for i := range a { - x := a[i] ^ b[i] + for i := 0; i < len(a); i += 8 { + ai := binary.BigEndian.Uint64(a[i : i+8]) + bi := binary.BigEndian.Uint64(b[i : i+8]) + x := ai ^ bi if x == 0 { - lz += 8 + lz += 64 } else { - lz += bits.LeadingZeros8(x) + lz += bits.LeadingZeros64(x) break } } diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go index e9fe631f3459..f276af663883 100644 --- a/p2p/enode/node_test.go +++ b/p2p/enode/node_test.go @@ -378,6 +378,28 @@ func TestID_logdist(t *testing.T) { } } +func makeIDs() (ID, ID) { + var a, b ID + size := len(a) + // last byte differs + for i := 0; i < size-1; i++ { + a[i] = 0xAA + b[i] = 0xAA + } + a[size-1] = 0xAA + b[size-1] = 0xAB + return a, b +} + +// Benchmark LogDist +func BenchmarkLogDist(b *testing.B) { + aID, bID := makeIDs() // 256-bit ID + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = LogDist(aID, bID) + } +} + // The random tests is likely to miss the case where a and b are equal, // this test checks it explicitly. func TestID_logdistEqual(t *testing.T) { From 635db50a5a09b62241aaa542ca00e3be8bca595d Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 13 Oct 2025 22:16:07 +0800 Subject: [PATCH 119/228] p2p/enode: optimize DistCmp (#32888) This speeds up DistCmp by 75% through using 64-bit operations instead of byte-wise XOR. --- p2p/enode/node.go | 7 ++++--- p2p/enode/node_test.go | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 8198050353bd..dafde51d6ab5 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -359,9 +359,10 @@ func ParseID(in string) (ID, error) { // Returns -1 if a is closer to target, 1 if b is closer to target // and 0 if they are equal. func DistCmp(target, a, b ID) int { - for i := range target { - da := a[i] ^ target[i] - db := b[i] ^ target[i] + for i := 0; i < len(target); i += 8 { + tn := binary.BigEndian.Uint64(target[i : i+8]) + da := tn ^ binary.BigEndian.Uint64(a[i:i+8]) + db := tn ^ binary.BigEndian.Uint64(b[i:i+8]) if da > db { return 1 } else if da < db { diff --git a/p2p/enode/node_test.go b/p2p/enode/node_test.go index f276af663883..51bc4ebe15e3 100644 --- a/p2p/enode/node_test.go +++ b/p2p/enode/node_test.go @@ -368,6 +368,16 @@ func TestID_distcmpEqual(t *testing.T) { } } +func BenchmarkDistCmp(b *testing.B) { + base := ID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + aID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0} + bID := ID{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1} + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = DistCmp(base, aID, bID) + } +} + func TestID_logdist(t *testing.T) { logdistBig := func(a, b ID) int { abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:]) From 59e7da4d3a49475bdc1c1aa5a54e9fc0e1290f08 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 13 Oct 2025 19:07:36 +0200 Subject: [PATCH 120/228] core/txpool/legacypool: move queue out of main txpool (#32270) This PR move the queue out of the main transaction pool. For now there should be no functional changes. I see this as a first step to refactor the legacypool and make the queue a fully separate concept from the main pending pool. --------- Signed-off-by: Csaba Kiraly Co-authored-by: Csaba Kiraly --- core/txpool/legacypool/legacypool.go | 235 +++++-------------- core/txpool/legacypool/legacypool_test.go | 67 +++--- core/txpool/legacypool/queue.go | 271 ++++++++++++++++++++++ 3 files changed, 367 insertions(+), 206 deletions(-) create mode 100644 core/txpool/legacypool/queue.go diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index e199d21c7a89..b36d86dd19dc 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -23,7 +23,6 @@ import ( "math" "math/big" "slices" - "sort" "sync" "sync/atomic" "time" @@ -238,11 +237,10 @@ type LegacyPool struct { pendingNonces *noncer // Pending state tracking virtual nonces reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools - pending map[common.Address]*list // All currently processable transactions - queue map[common.Address]*list // Queued but non-processable transactions - beats map[common.Address]time.Time // Last heartbeat from each known account - all *lookup // All transactions to allow lookups - priced *pricedList // All transactions sorted by price + pending map[common.Address]*list // All currently processable transactions + queue *queue + all *lookup // All transactions to allow lookups + priced *pricedList // All transactions sorted by price reqResetCh chan *txpoolResetRequest reqPromoteCh chan *accountSet @@ -266,14 +264,14 @@ func New(config Config, chain BlockChain) *LegacyPool { config = (&config).sanitize() // Create the transaction pool with its initial settings + signer := types.LatestSigner(chain.Config()) pool := &LegacyPool{ config: config, chain: chain, chainconfig: chain.Config(), - signer: types.LatestSigner(chain.Config()), + signer: signer, pending: make(map[common.Address]*list), - queue: make(map[common.Address]*list), - beats: make(map[common.Address]time.Time), + queue: newQueue(config, signer), all: newLookup(), reqResetCh: make(chan *txpoolResetRequest), reqPromoteCh: make(chan *accountSet), @@ -369,15 +367,9 @@ func (pool *LegacyPool) loop() { // Handle inactive account transaction eviction case <-evict.C: pool.mu.Lock() - for addr := range pool.queue { - // Any old enough should be removed - if time.Since(pool.beats[addr]) > pool.config.Lifetime { - list := pool.queue[addr].Flatten() - for _, tx := range list { - pool.removeTx(tx.Hash(), true, true) - } - queuedEvictionMeter.Mark(int64(len(list))) - } + evicted := pool.queue.evict(false) + for _, hash := range evicted { + pool.removeTx(hash, true, true) } pool.mu.Unlock() } @@ -459,11 +451,7 @@ func (pool *LegacyPool) stats() (int, int) { for _, list := range pool.pending { pending += list.Len() } - queued := 0 - for _, list := range pool.queue { - queued += list.Len() - } - return pending, queued + return pending, pool.queue.stats() } // Content retrieves the data content of the transaction pool, returning all the @@ -476,10 +464,7 @@ func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[ for addr, list := range pool.pending { pending[addr] = list.Flatten() } - queued := make(map[common.Address][]*types.Transaction, len(pool.queue)) - for addr, list := range pool.queue { - queued[addr] = list.Flatten() - } + queued := pool.queue.content() return pending, queued } @@ -493,10 +478,7 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction, if list, ok := pool.pending[addr]; ok { pending = list.Flatten() } - var queued []*types.Transaction - if list, ok := pool.queue[addr]; ok { - queued = list.Flatten() - } + queued := pool.queue.contentFrom(addr) return pending, queued } @@ -644,7 +626,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { if pending := pool.pending[auth]; pending != nil { count += pending.Len() } - if queue := pool.queue[auth]; queue != nil { + if queue, ok := pool.queue.get(auth); ok { count += queue.Len() } if count > 1 { @@ -691,7 +673,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) { // only by this subpool until all transactions are evicted var ( _, hasPending = pool.pending[from] - _, hasQueued = pool.queue[from] + _, hasQueued = pool.queue.get(from) ) if !hasPending && !hasQueued { if err := pool.reserver.Hold(from); err != nil { @@ -790,7 +772,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) { log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) // Successful promotion, bump the heartbeat - pool.beats[from] = time.Now() + pool.queue.bump(from) return old != nil, nil } // New transaction isn't replacing a pending one, push into queue @@ -815,7 +797,7 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo } // The transaction has a nonce gap with pending list, it's only considered // as executable if transactions in queue can fill up the nonce gap. - queue, ok := pool.queue[from] + queue, ok := pool.queue.get(from) if !ok { return true } @@ -831,25 +813,12 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo // // Note, this method assumes the pool lock is held! func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) { - // Try to insert the transaction into the future queue - from, _ := types.Sender(pool.signer, tx) // already validated - if pool.queue[from] == nil { - pool.queue[from] = newList(false) - } - inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) - if !inserted { - // An older transaction was better, discard this - queuedDiscardMeter.Mark(1) - return false, txpool.ErrReplaceUnderpriced + replaced, err := pool.queue.add(hash, tx) + if err != nil { + return false, err } - // Discard any previous transaction and mark this - if old != nil { - pool.all.Remove(old.Hash()) - pool.priced.Removed(1) - queuedReplaceMeter.Mark(1) - } else { - // Nothing was replaced, bump the queued counter - queuedGauge.Inc(1) + if replaced != nil { + pool.removeTx(*replaced, true, true) } // If the transaction isn't in lookup set but it's expected to be there, // show the error log. @@ -860,11 +829,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl pool.all.Add(tx) pool.priced.Put(tx) } - // If we never record the heartbeat, do it right now. - if _, exist := pool.beats[from]; !exist { - pool.beats[from] = time.Now() - } - return old != nil, nil + return replaced != nil, nil } // promoteTx adds a transaction to the pending (processable) list of transactions @@ -899,7 +864,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ pool.pendingNonces.set(addr, tx.Nonce()+1) // Successful promotion, bump the heartbeat - pool.beats[addr] = time.Now() + pool.queue.bump(addr) return true } @@ -1019,7 +984,7 @@ func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus { if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { return txpool.TxStatusPending - } else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { + } else if txList, ok := pool.queue.get(from); ok && txList.txs.items[tx.Nonce()] != nil { return txpool.TxStatusQueued } return txpool.TxStatusUnknown @@ -1096,7 +1061,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo defer func() { var ( _, hasPending = pool.pending[addr] - _, hasQueued = pool.queue[addr] + _, hasQueued = pool.queue.get(addr) ) if !hasPending && !hasQueued { pool.reserver.Release(addr) @@ -1128,16 +1093,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo } } // Transaction is in the future queue - if future := pool.queue[addr]; future != nil { - if removed, _ := future.Remove(tx); removed { - // Reduce the queued counter - queuedGauge.Dec(1) - } - if future.Empty() { - delete(pool.queue, addr) - delete(pool.beats, addr) - } - } + pool.queue.removeTx(addr, tx) return 0 } @@ -1285,10 +1241,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, } } // Reset needs promote for all addresses - promoteAddrs = make([]common.Address, 0, len(pool.queue)) - for addr := range pool.queue { - promoteAddrs = append(promoteAddrs, addr) - } + promoteAddrs = append(promoteAddrs, pool.queue.addresses()...) } // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) @@ -1442,62 +1395,32 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { // future queue to the set of pending transactions. During this process, all // invalidated transactions (low nonce, low balance) are deleted. func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { - // Track the promoted transactions to broadcast them at once - var promoted []*types.Transaction - - // Iterate over all accounts and promote any executable transactions gasLimit := pool.currentHead.Load().GasLimit - for _, addr := range accounts { - list := pool.queue[addr] - if list == nil { - continue // Just in case someone calls with a non existing account - } - // Drop all transactions that are deemed too old (low nonce) - forwards := list.Forward(pool.currentState.GetNonce(addr)) - for _, tx := range forwards { - pool.all.Remove(tx.Hash()) - } - log.Trace("Removed old queued transactions", "count", len(forwards)) - // Drop all transactions that are too costly (low balance or out of gas) - drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit) - for _, tx := range drops { - pool.all.Remove(tx.Hash()) - } - log.Trace("Removed unpayable queued transactions", "count", len(drops)) - queuedNofundsMeter.Mark(int64(len(drops))) - - // Gather all executable transactions and promote them - readies := list.Ready(pool.pendingNonces.get(addr)) - for _, tx := range readies { - hash := tx.Hash() - if pool.promoteTx(addr, hash, tx) { - promoted = append(promoted, tx) - } + promotable, dropped, removedAddresses := pool.queue.promoteExecutables(accounts, gasLimit, pool.currentState, pool.pendingNonces) + promoted := make([]*types.Transaction, 0, len(promotable)) + + // promote all promoteable transactions + for _, tx := range promotable { + from, _ := pool.signer.Sender(tx) + if pool.promoteTx(from, tx.Hash(), tx) { + promoted = append(promoted, tx) } - log.Trace("Promoted queued transactions", "count", len(promoted)) - queuedGauge.Dec(int64(len(readies))) + } - // Drop all transactions over the allowed limit - var caps = list.Cap(int(pool.config.AccountQueue)) - for _, tx := range caps { - hash := tx.Hash() - pool.all.Remove(hash) - log.Trace("Removed cap-exceeding queued transaction", "hash", hash) - } - queuedRateLimitMeter.Mark(int64(len(caps))) - // Mark all the items dropped as removed - pool.priced.Removed(len(forwards) + len(drops) + len(caps)) - queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) + // remove all removable transactions + for _, hash := range dropped { + pool.all.Remove(hash) + } - // Delete the entire queue entry if it became empty. - if list.Empty() { - delete(pool.queue, addr) - delete(pool.beats, addr) - if _, ok := pool.pending[addr]; !ok { - pool.reserver.Release(addr) - } + // release all accounts that have no more transactions in the pool + for _, addr := range removedAddresses { + _, hasPending := pool.pending[addr] + _, hasQueued := pool.queue.get(addr) + if !hasPending && !hasQueued { + pool.reserver.Release(addr) } } + return promoted } @@ -1585,43 +1508,17 @@ func (pool *LegacyPool) truncatePending() { // truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit. func (pool *LegacyPool) truncateQueue() { - queued := uint64(0) - for _, list := range pool.queue { - queued += uint64(list.Len()) - } - if queued <= pool.config.GlobalQueue { - return - } + removed, removedAddresses := pool.queue.truncate() - // Sort all accounts with queued transactions by heartbeat - addresses := make(addressesByHeartbeat, 0, len(pool.queue)) - for addr := range pool.queue { - addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]}) + // remove all removable transactions + for _, hash := range removed { + pool.all.Remove(hash) } - sort.Sort(sort.Reverse(addresses)) - - // Drop transactions until the total is below the limit - for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; { - addr := addresses[len(addresses)-1] - list := pool.queue[addr.address] - addresses = addresses[:len(addresses)-1] - - // Drop all transactions if they are less than the overflow - if size := uint64(list.Len()); size <= drop { - for _, tx := range list.Flatten() { - pool.removeTx(tx.Hash(), true, true) - } - drop -= size - queuedRateLimitMeter.Mark(int64(size)) - continue - } - // Otherwise drop only last few transactions - txs := list.Flatten() - for i := len(txs) - 1; i >= 0 && drop > 0; i-- { - pool.removeTx(txs[i].Hash(), true, true) - drop-- - queuedRateLimitMeter.Mark(1) + for _, addr := range removedAddresses { + _, hasPending := pool.pending[addr] + if !hasPending { + pool.reserver.Release(addr) } } } @@ -1679,25 +1576,13 @@ func (pool *LegacyPool) demoteUnexecutables() { // Delete the entire pending entry if it became empty. if list.Empty() { delete(pool.pending, addr) - if _, ok := pool.queue[addr]; !ok { + if _, ok := pool.queue.get(addr); !ok { pool.reserver.Release(addr) } } } } -// addressByHeartbeat is an account address tagged with its last activity timestamp. -type addressByHeartbeat struct { - address common.Address - heartbeat time.Time -} - -type addressesByHeartbeat []addressByHeartbeat - -func (a addressesByHeartbeat) Len() int { return len(a) } -func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) } -func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - // accountSet is simply a set of addresses to check for existence, and a signer // capable of deriving addresses from transactions. type accountSet struct { @@ -1938,17 +1823,17 @@ func (pool *LegacyPool) Clear() { // acquire the subpool lock until the transaction addition is completed. for addr := range pool.pending { - if _, ok := pool.queue[addr]; !ok { + if _, ok := pool.queue.get(addr); !ok { pool.reserver.Release(addr) } } - for addr := range pool.queue { + for _, addr := range pool.queue.addresses() { pool.reserver.Release(addr) } pool.all.Clear() pool.priced.Reheap() pool.pending = make(map[common.Address]*list) - pool.queue = make(map[common.Address]*list) + pool.queue = newQueue(pool.config, pool.signer) pool.pendingNonces = newNoncer(pool.currentState) } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 0c8642659d15..fb994d82086d 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -466,8 +466,8 @@ func TestQueue(t *testing.T) { if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok { t.Error("expected transaction to be in tx pool") } - if len(pool.queue) > 0 { - t.Error("expected transaction queue to be empty. is", len(pool.queue)) + if len(pool.queue.queued) > 0 { + t.Error("expected transaction queue to be empty. is", len(pool.queue.queued)) } } @@ -492,8 +492,8 @@ func TestQueue2(t *testing.T) { if len(pool.pending) != 1 { t.Error("expected pending length to be 1, got", len(pool.pending)) } - if pool.queue[from].Len() != 2 { - t.Error("expected len(queue) == 2, got", pool.queue[from].Len()) + if list, _ := pool.queue.get(from); list.Len() != 2 { + t.Error("expected len(queue) == 2, got", list.Len()) } } @@ -639,8 +639,8 @@ func TestMissingNonce(t *testing.T) { if len(pool.pending) != 0 { t.Error("expected 0 pending transactions, got", len(pool.pending)) } - if pool.queue[addr].Len() != 1 { - t.Error("expected 1 queued transaction, got", pool.queue[addr].Len()) + if list, _ := pool.queue.get(addr); list.Len() != 1 { + t.Error("expected 1 queued transaction, got", list.Len()) } if pool.all.Count() != 1 { t.Error("expected 1 total transactions, got", pool.all.Count()) @@ -712,8 +712,8 @@ func TestDropping(t *testing.T) { if pool.pending[account].Len() != 3 { t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) } - if pool.queue[account].Len() != 3 { - t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + if list, _ := pool.queue.get(account); list.Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3) } if pool.all.Count() != 6 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) @@ -722,8 +722,8 @@ func TestDropping(t *testing.T) { if pool.pending[account].Len() != 3 { t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3) } - if pool.queue[account].Len() != 3 { - t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3) + if list, _ := pool.queue.get(account); list.Len() != 3 { + t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3) } if pool.all.Count() != 6 { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6) @@ -741,13 +741,14 @@ func TestDropping(t *testing.T) { if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok { t.Errorf("out-of-fund pending transaction present: %v", tx1) } - if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + list, _ := pool.queue.get(account) + if _, ok := list.txs.items[tx10.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } - if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok { + if _, ok := list.txs.items[tx11.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } - if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok { + if _, ok := list.txs.items[tx12.Nonce()]; ok { t.Errorf("out-of-fund queued transaction present: %v", tx11) } if pool.all.Count() != 4 { @@ -763,10 +764,11 @@ func TestDropping(t *testing.T) { if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok { t.Errorf("over-gased pending transaction present: %v", tx1) } - if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok { + list, _ = pool.queue.get(account) + if _, ok := list.txs.items[tx10.Nonce()]; !ok { t.Errorf("funded queued transaction missing: %v", tx10) } - if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok { + if _, ok := list.txs.items[tx11.Nonce()]; ok { t.Errorf("over-gased queued transaction present: %v", tx11) } if pool.all.Count() != 2 { @@ -820,8 +822,8 @@ func TestPostponing(t *testing.T) { if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) } - if len(pool.queue) != 0 { - t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + if len(pool.queue.addresses()) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0) } if pool.all.Count() != len(txs) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) @@ -830,8 +832,8 @@ func TestPostponing(t *testing.T) { if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) { t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs)) } - if len(pool.queue) != 0 { - t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0) + if len(pool.queue.addresses()) != 0 { + t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0) } if pool.all.Count() != len(txs) { t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)) @@ -847,7 +849,8 @@ func TestPostponing(t *testing.T) { if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok { t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0]) } - if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok { + list, _ := pool.queue.get(accs[0]) + if _, ok := list.txs.items[txs[0].Nonce()]; ok { t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0]) } for i, tx := range txs[1:100] { @@ -855,14 +858,14 @@ func TestPostponing(t *testing.T) { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx) } - if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok { + if _, ok := list.txs.items[tx.Nonce()]; !ok { t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx) } } else { if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx) } - if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok { + if _, ok := list.txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx) } } @@ -872,13 +875,14 @@ func TestPostponing(t *testing.T) { if pool.pending[accs[1]] != nil { t.Errorf("invalidated account still has pending transactions") } + list, _ = pool.queue.get(accs[1]) for i, tx := range txs[100:] { if i%2 == 1 { - if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok { + if _, ok := list.txs.items[tx.Nonce()]; !ok { t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx) } } else { - if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok { + if _, ok := list.txs.items[tx.Nonce()]; ok { t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx) } } @@ -963,13 +967,14 @@ func TestQueueAccountLimiting(t *testing.T) { if len(pool.pending) != 0 { t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0) } + list, _ := pool.queue.get(account) if i <= testTxPoolConfig.AccountQueue { - if pool.queue[account].Len() != int(i) { - t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i) + if list.Len() != int(i) { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, list.Len(), i) } } else { - if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) { - t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue) + if list.Len() != int(testTxPoolConfig.AccountQueue) { + t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, list.Len(), testTxPoolConfig.AccountQueue) } } } @@ -1020,7 +1025,7 @@ func TestQueueGlobalLimiting(t *testing.T) { pool.addRemotesSync(txs) queued := 0 - for addr, list := range pool.queue { + for addr, list := range pool.queue.queued { if list.Len() > int(config.AccountQueue) { t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue) } @@ -1179,8 +1184,8 @@ func TestPendingLimiting(t *testing.T) { if pool.pending[account].Len() != int(i)+1 { t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1) } - if len(pool.queue) != 0 { - t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0) + if len(pool.queue.addresses()) != 0 { + t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, len(pool.queue.addresses()), 0) } } if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) { diff --git a/core/txpool/legacypool/queue.go b/core/txpool/legacypool/queue.go new file mode 100644 index 000000000000..b8417064f73d --- /dev/null +++ b/core/txpool/legacypool/queue.go @@ -0,0 +1,271 @@ +// 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 . + +package legacypool + +import ( + "sort" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +type queue struct { + config Config + signer types.Signer + queued map[common.Address]*list // Queued but non-processable transactions + beats map[common.Address]time.Time // Last heartbeat from each known account +} + +func newQueue(config Config, signer types.Signer) *queue { + return &queue{ + signer: signer, + config: config, + queued: make(map[common.Address]*list), + beats: make(map[common.Address]time.Time), + } +} + +func (q *queue) evict(force bool) []common.Hash { + removed := make([]common.Hash, 0) + for addr, list := range q.queued { + // Any transactions old enough should be removed + if force || time.Since(q.beats[addr]) > q.config.Lifetime { + list := list.Flatten() + for _, tx := range list { + q.removeTx(addr, tx) + removed = append(removed, tx.Hash()) + } + queuedEvictionMeter.Mark(int64(len(list))) + } + } + return removed +} + +func (q *queue) stats() int { + queued := 0 + for _, list := range q.queued { + queued += list.Len() + } + return queued +} + +func (q *queue) content() map[common.Address][]*types.Transaction { + queued := make(map[common.Address][]*types.Transaction, len(q.queued)) + for addr, list := range q.queued { + queued[addr] = list.Flatten() + } + return queued +} + +func (q *queue) contentFrom(addr common.Address) []*types.Transaction { + var queued []*types.Transaction + if list, ok := q.get(addr); ok { + queued = list.Flatten() + } + return queued +} + +func (q *queue) get(addr common.Address) (*list, bool) { + l, ok := q.queued[addr] + return l, ok +} + +func (q *queue) bump(addr common.Address) { + q.beats[addr] = time.Now() +} + +func (q *queue) addresses() []common.Address { + addrs := make([]common.Address, 0, len(q.queued)) + for addr := range q.queued { + addrs = append(addrs, addr) + } + return addrs +} + +func (q queue) removeTx(addr common.Address, tx *types.Transaction) { + if future := q.queued[addr]; future != nil { + if txOld := future.txs.Get(tx.Nonce()); txOld != nil && txOld.Hash() != tx.Hash() { + // Edge case, a different transaction + // with the same nonce is in the queued, just ignore + return + } + if removed, _ := future.Remove(tx); removed { + // Reduce the queued counter + queuedGauge.Dec(1) + } + if future.Empty() { + delete(q.queued, addr) + delete(q.beats, addr) + } + } +} + +func (q *queue) add(hash common.Hash, tx *types.Transaction) (*common.Hash, error) { + // Try to insert the transaction into the future queue + from, _ := types.Sender(q.signer, tx) // already validated + if q.queued[from] == nil { + q.queued[from] = newList(false) + } + inserted, old := q.queued[from].Add(tx, q.config.PriceBump) + if !inserted { + // An older transaction was better, discard this + queuedDiscardMeter.Mark(1) + return nil, txpool.ErrReplaceUnderpriced + } + // If we never record the heartbeat, do it right now. + if _, exist := q.beats[from]; !exist { + q.beats[from] = time.Now() + } + if old == nil { + // Nothing was replaced, bump the queued counter + queuedGauge.Inc(1) + return nil, nil + } + h := old.Hash() + // Transaction was replaced, bump the replacement counter + queuedReplaceMeter.Mark(1) + return &h, nil +} + +// promoteExecutables iterates over all accounts with queued transactions, selecting +// for promotion any that are now executable. It also drops any transactions that are +// deemed too old (nonce too low) or too costly (insufficient funds or over gas limit). +// +// Returns three lists: all transactions that were removed from the queue and selected +// for promotion; all other transactions that were removed from the queue and dropped; +// the list of addresses removed. +func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, currentState *state.StateDB, nonces *noncer) ([]*types.Transaction, []common.Hash, []common.Address) { + // Track the promotable transactions to broadcast them at once + var promotable []*types.Transaction + var dropped []common.Hash + var removedAddresses []common.Address + + // Iterate over all accounts and promote any executable transactions + for _, addr := range accounts { + list := q.queued[addr] + if list == nil { + continue // Just in case someone calls with a non existing account + } + // Drop all transactions that are deemed too old (low nonce) + forwards := list.Forward(currentState.GetNonce(addr)) + for _, tx := range forwards { + dropped = append(dropped, tx.Hash()) + } + log.Trace("Removing old queued transactions", "count", len(forwards)) + // Drop all transactions that are too costly (low balance or out of gas) + drops, _ := list.Filter(currentState.GetBalance(addr), gasLimit) + for _, tx := range drops { + dropped = append(dropped, tx.Hash()) + } + log.Trace("Removing unpayable queued transactions", "count", len(drops)) + queuedNofundsMeter.Mark(int64(len(drops))) + + // Gather all executable transactions and promote them + readies := list.Ready(nonces.get(addr)) + promotable = append(promotable, readies...) + log.Trace("Promoting queued transactions", "count", len(promotable)) + queuedGauge.Dec(int64(len(readies))) + + // Drop all transactions over the allowed limit + var caps = list.Cap(int(q.config.AccountQueue)) + for _, tx := range caps { + hash := tx.Hash() + dropped = append(dropped, hash) + log.Trace("Removing cap-exceeding queued transaction", "hash", hash) + } + queuedRateLimitMeter.Mark(int64(len(caps))) + + // Delete the entire queue entry if it became empty. + if list.Empty() { + delete(q.queued, addr) + delete(q.beats, addr) + removedAddresses = append(removedAddresses, addr) + } + } + queuedGauge.Dec(int64(len(dropped))) + return promotable, dropped, removedAddresses +} + +// truncate drops the oldest transactions from the queue until the total +// number is below the configured limit. +// Returns the hashes of all dropped transactions, and the addresses of +// accounts that became empty due to the truncation. +func (q *queue) truncate() ([]common.Hash, []common.Address) { + queued := uint64(0) + for _, list := range q.queued { + queued += uint64(list.Len()) + } + if queued <= q.config.GlobalQueue { + return nil, nil + } + + // Sort all accounts with queued transactions by heartbeat + addresses := make(addressesByHeartbeat, 0, len(q.queued)) + for addr := range q.queued { + addresses = append(addresses, addressByHeartbeat{addr, q.beats[addr]}) + } + sort.Sort(sort.Reverse(addresses)) + removed := make([]common.Hash, 0) + removedAddresses := make([]common.Address, 0) + + // Drop transactions until the total is below the limit + for drop := queued - q.config.GlobalQueue; drop > 0 && len(addresses) > 0; { + addr := addresses[len(addresses)-1] + list := q.queued[addr.address] + + addresses = addresses[:len(addresses)-1] + + // Drop all transactions if they are less than the overflow + if size := uint64(list.Len()); size <= drop { + for _, tx := range list.Flatten() { + q.removeTx(addr.address, tx) + removed = append(removed, tx.Hash()) + } + drop -= size + queuedRateLimitMeter.Mark(int64(size)) + removedAddresses = append(removedAddresses, addr.address) + continue + } + // Otherwise drop only last few transactions + txs := list.Flatten() + for i := len(txs) - 1; i >= 0 && drop > 0; i-- { + q.removeTx(addr.address, txs[i]) + removed = append(removed, txs[i].Hash()) + drop-- + queuedRateLimitMeter.Mark(1) + } + } + + // no need to clear empty accounts, removeTx already does that + return removed, removedAddresses +} + +// addressByHeartbeat is an account address tagged with its last activity timestamp. +type addressByHeartbeat struct { + address common.Address + heartbeat time.Time +} + +type addressesByHeartbeat []addressByHeartbeat + +func (a addressesByHeartbeat) Len() int { return len(a) } +func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) } +func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] } From f86ec0c245cc86b8aba3255efd3af7f5820eac16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Mon, 13 Oct 2025 19:21:01 +0200 Subject: [PATCH 121/228] cmd/workload: filter fuzzer test (#31613) This PR adds a `filterfuzz` subcommand to the workload tester that generates requests similarly to `filtergen` (though with a much smaller block length limit) and also verifies the results by retrieving all block receipts in the range and locally filtering out relevant results. Unlike `filtergen` that operates on the finalized chain range only, `filterfuzz` does check the head region, actually it seeds a new query at every new chain head. --- cmd/workload/filtertest.go | 15 +- cmd/workload/filtertestfuzz.go | 337 +++++++++++++++++++++++++++++++++ cmd/workload/filtertestgen.go | 59 +++--- cmd/workload/main.go | 1 + 4 files changed, 378 insertions(+), 34 deletions(-) create mode 100644 cmd/workload/filtertestfuzz.go diff --git a/cmd/workload/filtertest.go b/cmd/workload/filtertest.go index 9f0b6cab44bb..d77cbc5768d3 100644 --- a/cmd/workload/filtertest.go +++ b/cmd/workload/filtertest.go @@ -182,13 +182,14 @@ func (s *filterTestSuite) loadQueries() error { // filterQuery is a single query for testing. type filterQuery struct { - FromBlock int64 `json:"fromBlock"` - ToBlock int64 `json:"toBlock"` - Address []common.Address `json:"address"` - Topics [][]common.Hash `json:"topics"` - ResultHash *common.Hash `json:"resultHash,omitempty"` - results []types.Log - Err error `json:"error,omitempty"` + FromBlock int64 `json:"fromBlock"` + ToBlock int64 `json:"toBlock"` + lastBlockHash common.Hash + Address []common.Address `json:"address"` + Topics [][]common.Hash `json:"topics"` + ResultHash *common.Hash `json:"resultHash,omitempty"` + results []types.Log + Err error `json:"error,omitempty"` } func (fq *filterQuery) isWildcard() bool { diff --git a/cmd/workload/filtertestfuzz.go b/cmd/workload/filtertestfuzz.go new file mode 100644 index 000000000000..3549f4db5643 --- /dev/null +++ b/cmd/workload/filtertestfuzz.go @@ -0,0 +1,337 @@ +// Copyright 2025 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 . + +package main + +import ( + "context" + "fmt" + "math/big" + "reflect" + "slices" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/urfave/cli/v2" +) + +const maxFilterRangeForTestFuzz = 300 + +var ( + filterFuzzCommand = &cli.Command{ + Name: "filterfuzz", + Usage: "Generates queries and compares results against matches derived from receipts", + ArgsUsage: "", + Action: filterFuzzCmd, + Flags: []cli.Flag{}, + } +) + +// filterFuzzCmd is the main function of the filter fuzzer. +func filterFuzzCmd(ctx *cli.Context) error { + f := newFilterTestGen(ctx, maxFilterRangeForTestFuzz) + var lastHead *types.Header + headerCache := lru.NewCache[common.Hash, *types.Header](200) + + commonAncestor := func(oldPtr, newPtr *types.Header) *types.Header { + if oldPtr == nil || newPtr == nil { + return nil + } + if newPtr.Number.Uint64() > oldPtr.Number.Uint64()+100 || oldPtr.Number.Uint64() > newPtr.Number.Uint64()+100 { + return nil + } + for oldPtr.Hash() != newPtr.Hash() { + if newPtr.Number.Uint64() >= oldPtr.Number.Uint64() { + if parent, _ := headerCache.Get(newPtr.ParentHash); parent != nil { + newPtr = parent + } else { + newPtr, _ = getHeaderByHash(f.client, newPtr.ParentHash) + if newPtr == nil { + return nil + } + headerCache.Add(newPtr.Hash(), newPtr) + } + } + if oldPtr.Number.Uint64() > newPtr.Number.Uint64() { + oldPtr, _ = headerCache.Get(oldPtr.ParentHash) + if oldPtr == nil { + return nil + } + } + } + return newPtr + } + + fetchHead := func() (*types.Header, bool) { + currentHead, err := getLatestHeader(f.client) + if err != nil { + fmt.Println("Could not fetch head block", err) + return nil, false + } + headerCache.Add(currentHead.Hash(), currentHead) + if lastHead != nil && currentHead.Hash() == lastHead.Hash() { + return currentHead, false + } + f.blockLimit = currentHead.Number.Int64() + ca := commonAncestor(lastHead, currentHead) + fmt.Print("*** New head ", f.blockLimit) + if ca == nil { + fmt.Println(" ") + } else { + if reorged := lastHead.Number.Uint64() - ca.Number.Uint64(); reorged > 0 { + fmt.Print(" reorged ", reorged) + } + if missed := currentHead.Number.Uint64() - ca.Number.Uint64() - 1; missed > 0 { + fmt.Print(" missed ", missed) + } + fmt.Println() + } + lastHead = currentHead + return currentHead, true + } + + tryExtendQuery := func(query *filterQuery) *filterQuery { + for { + extQuery := f.extendRange(query) + if extQuery == nil { + return query + } + extQuery.checkLastBlockHash(f.client) + extQuery.run(f.client, nil) + if extQuery.Err == nil && len(extQuery.results) == 0 { + // query is useless now due to major reorg; abandon and continue + fmt.Println("Zero length results") + return nil + } + if extQuery.Err != nil { + extQuery.printError() + return nil + } + if len(extQuery.results) > maxFilterResultSize { + return query + } + query = extQuery + } + } + + var ( + mmQuery *filterQuery + mmRetry, mmNextRetry int + ) + +mainLoop: + for { + select { + case <-ctx.Done(): + return nil + default: + } + var query *filterQuery + if mmQuery != nil { + if mmRetry == 0 { + query = mmQuery + mmRetry = mmNextRetry + mmNextRetry *= 2 + query.checkLastBlockHash(f.client) + query.run(f.client, nil) + if query.Err != nil { + query.printError() + continue + } + fmt.Println("Retrying query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results)) + } else { + mmRetry-- + } + } + if query == nil { + currentHead, isNewHead := fetchHead() + if currentHead == nil { + select { + case <-ctx.Done(): + return nil + case <-time.After(time.Second): + } + continue mainLoop + } + if isNewHead { + query = f.newHeadSeedQuery(currentHead.Number.Int64()) + } else { + query = f.newQuery() + } + query.checkLastBlockHash(f.client) + query.run(f.client, nil) + if query.Err != nil { + query.printError() + continue + } + fmt.Println("New query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results)) + if len(query.results) == 0 || len(query.results) > maxFilterResultSize { + continue mainLoop + } + if query = tryExtendQuery(query); query == nil { + continue mainLoop + } + } + if !query.checkLastBlockHash(f.client) { + fmt.Println("Reorg during search") + continue mainLoop + } + // now we have a new query; check results + results, err := query.getResultsFromReceipts(f.client) + if err != nil { + fmt.Println("Could not fetch results from receipts", err) + continue mainLoop + } + if !query.checkLastBlockHash(f.client) { + fmt.Println("Reorg during search") + continue mainLoop + } + if !reflect.DeepEqual(query.results, results) { + fmt.Println("Results mismatch from:", query.FromBlock, "to:", query.ToBlock, "addresses:", query.Address, "topics:", query.Topics) + resShared, resGetLogs, resReceipts := compareResults(query.results, results) + fmt.Println(" shared:", len(resShared)) + fmt.Println(" only from getLogs:", len(resGetLogs), resGetLogs) + fmt.Println(" only from receipts:", len(resReceipts), resReceipts) + if mmQuery != query { + mmQuery = query + mmRetry = 0 + mmNextRetry = 1 + } + continue mainLoop + } + fmt.Println("Successful query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results)) + f.storeQuery(query) + } +} + +func compareResults(a, b []types.Log) (shared, onlya, onlyb []types.Log) { + for len(a) > 0 && len(b) > 0 { + if reflect.DeepEqual(a[0], b[0]) { + shared = append(shared, a[0]) + a = a[1:] + b = b[1:] + } else { + for i := 1; ; i++ { + if i >= len(a) { // b[0] not found in a + onlyb = append(onlyb, b[0]) + b = b[1:] + break + } + if i >= len(b) { // a[0] not found in b + onlya = append(onlya, a[0]) + a = a[1:] + break + } + if reflect.DeepEqual(b[0], a[i]) { // a[:i] not found in b + onlya = append(onlya, a[:i]...) + a = a[i:] + break + } + if reflect.DeepEqual(a[0], b[i]) { // b[:i] not found in a + onlyb = append(onlyb, b[:i]...) + b = b[i:] + break + } + } + } + } + onlya = append(onlya, a...) + onlyb = append(onlyb, b...) + return +} + +func getLatestHeader(client *client) (*types.Header, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + return client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.LatestBlockNumber))) +} + +func getHeaderByHash(client *client, hash common.Hash) (*types.Header, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + return client.Eth.HeaderByHash(ctx, hash) +} + +// newHeadSeedQuery creates a query that gets all logs from the latest head. +func (s *filterTestGen) newHeadSeedQuery(head int64) *filterQuery { + return &filterQuery{ + FromBlock: head, + ToBlock: head, + } +} + +func (fq *filterQuery) checkLastBlockHash(client *client) bool { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(fq.ToBlock)) + if err != nil { + fmt.Println("Cound not fetch last block hash of query number:", fq.ToBlock, "error:", err) + fq.lastBlockHash = common.Hash{} + return false + } + hash := header.Hash() + if fq.lastBlockHash == hash { + return true + } + fq.lastBlockHash = hash + return false +} + +func (fq *filterQuery) filterLog(log *types.Log) bool { + if len(fq.Address) > 0 && !slices.Contains(fq.Address, log.Address) { + return false + } + // If the to filtered topics is greater than the amount of topics in logs, skip. + if len(fq.Topics) > len(log.Topics) { + return false + } + for i, sub := range fq.Topics { + if len(sub) == 0 { + continue // empty rule set == wildcard + } + if !slices.Contains(sub, log.Topics[i]) { + return false + } + } + return true +} + +func (fq *filterQuery) getResultsFromReceipts(client *client) ([]types.Log, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + var results []types.Log + for blockNumber := fq.FromBlock; blockNumber <= fq.ToBlock; blockNumber++ { + receipts, err := client.Eth.BlockReceipts(ctx, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(blockNumber))) + if err != nil { + return nil, err + } + for _, receipt := range receipts { + for _, log := range receipt.Logs { + if fq.filterLog(log) { + results = append(results, *log) + } + } + } + } + return results, nil +} diff --git a/cmd/workload/filtertestgen.go b/cmd/workload/filtertestgen.go index 6d1f63981967..603e3dea6706 100644 --- a/cmd/workload/filtertestgen.go +++ b/cmd/workload/filtertestgen.go @@ -32,6 +32,17 @@ import ( "github.com/urfave/cli/v2" ) +const ( + // Parameter of the random filter query generator. + maxFilterRangeForTestGen = 100000000000 + maxFilterResultSize = 1000 + filterBuckets = 10 + maxFilterBucketSize = 100 + filterSeedChance = 10 + filterMergeChance = 45 + filterExtendChance = 50 +) + var ( filterGenerateCommand = &cli.Command{ Name: "filtergen", @@ -58,7 +69,7 @@ var ( // filterGenCmd is the main function of the filter tests generator. func filterGenCmd(ctx *cli.Context) error { - f := newFilterTestGen(ctx) + f := newFilterTestGen(ctx, maxFilterRangeForTestGen) lastWrite := time.Now() for { select { @@ -67,7 +78,7 @@ func filterGenCmd(ctx *cli.Context) error { default: } - f.updateFinalizedBlock() + f.setLimitToFinalizedBlock() query := f.newQuery() query.run(f.client, nil) if query.Err != nil { @@ -75,7 +86,7 @@ func filterGenCmd(ctx *cli.Context) error { exit("filter query failed") } if len(query.results) > 0 && len(query.results) <= maxFilterResultSize { - for { + for rand.Intn(100) < filterExtendChance { extQuery := f.extendRange(query) if extQuery == nil { break @@ -108,39 +119,32 @@ func filterGenCmd(ctx *cli.Context) error { // filterTestGen is the filter query test generator. type filterTestGen struct { - client *client - queryFile string + client *client + queryFile string + maxFilterRange int64 - finalizedBlock int64 - queries [filterBuckets][]*filterQuery + blockLimit int64 + queries [filterBuckets][]*filterQuery } -func newFilterTestGen(ctx *cli.Context) *filterTestGen { +func newFilterTestGen(ctx *cli.Context, maxFilterRange int64) *filterTestGen { return &filterTestGen{ - client: makeClient(ctx), - queryFile: ctx.String(filterQueryFileFlag.Name), + client: makeClient(ctx), + queryFile: ctx.String(filterQueryFileFlag.Name), + maxFilterRange: maxFilterRange, } } -func (s *filterTestGen) updateFinalizedBlock() { - s.finalizedBlock = mustGetFinalizedBlock(s.client) +func (s *filterTestGen) setLimitToFinalizedBlock() { + s.blockLimit = mustGetFinalizedBlock(s.client) } -const ( - // Parameter of the random filter query generator. - maxFilterRange = 10000000 - maxFilterResultSize = 300 - filterBuckets = 10 - maxFilterBucketSize = 100 - filterSeedChance = 10 - filterMergeChance = 45 -) - // storeQuery adds a filter query to the output file. func (s *filterTestGen) storeQuery(query *filterQuery) { query.ResultHash = new(common.Hash) *query.ResultHash = query.calculateHash() - logRatio := math.Log(float64(len(query.results))*float64(s.finalizedBlock)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(s.finalizedBlock)*maxFilterResultSize) + maxFilterRange := min(s.maxFilterRange, s.blockLimit) + logRatio := math.Log(float64(len(query.results))*float64(maxFilterRange)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(maxFilterRange)*maxFilterResultSize) bucket := int(math.Floor(logRatio * filterBuckets)) if bucket >= filterBuckets { bucket = filterBuckets - 1 @@ -160,13 +164,13 @@ func (s *filterTestGen) storeQuery(query *filterQuery) { func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery { rangeLen := q.ToBlock + 1 - q.FromBlock extLen := rand.Int63n(rangeLen) + 1 - if rangeLen+extLen > s.finalizedBlock { + if rangeLen+extLen > min(s.maxFilterRange, s.blockLimit) { return nil } extBefore := min(rand.Int63n(extLen+1), q.FromBlock) extAfter := extLen - extBefore - if q.ToBlock+extAfter > s.finalizedBlock { - d := q.ToBlock + extAfter - s.finalizedBlock + if q.ToBlock+extAfter > s.blockLimit { + d := q.ToBlock + extAfter - s.blockLimit extAfter -= d if extBefore+d <= q.FromBlock { extBefore += d @@ -203,7 +207,7 @@ func (s *filterTestGen) newQuery() *filterQuery { // newSeedQuery creates a query that gets all logs in a random non-finalized block. func (s *filterTestGen) newSeedQuery() *filterQuery { - block := rand.Int63n(s.finalizedBlock + 1) + block := rand.Int63n(s.blockLimit + 1) return &filterQuery{ FromBlock: block, ToBlock: block, @@ -358,6 +362,7 @@ func (s *filterTestGen) writeQueries() { func mustGetFinalizedBlock(client *client) int64 { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() + header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber))) if err != nil { exit(fmt.Errorf("could not fetch finalized header (error: %v)", err)) diff --git a/cmd/workload/main.go b/cmd/workload/main.go index 32618d6a79b5..8ac0e5b6cb12 100644 --- a/cmd/workload/main.go +++ b/cmd/workload/main.go @@ -49,6 +49,7 @@ func init() { filterGenerateCommand, traceGenerateCommand, filterPerfCommand, + filterFuzzCommand, } } From 592c54069da287c828356be647cdd2adefdea3bb Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 14 Oct 2025 01:58:50 +0800 Subject: [PATCH 122/228] p2p/discover: wait for bootstrap to be done (#32881) This ensures the node is ready to accept other nodes into the table before it is used in a test. Closes #32863 --- p2p/discover/v4_udp_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 44863183fa92..287f0c34fa92 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -575,6 +575,13 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 { if err != nil { t.Fatal(err) } + + // Wait for bootstrap to complete. + select { + case <-udp.tab.initDone: + case <-time.After(5 * time.Second): + t.Fatalf("timed out waiting for table initialization") + } return udp } From b01c3e9e6a6784fa2e17da7850e5a0ca05fdc253 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 14 Oct 2025 03:23:05 +0200 Subject: [PATCH 123/228] triedb/pathdb: catch int conversion overflow in 32-bit (#32899) The limit check for `MaxUint32` is done after the cast to `int`. On 64 bits machines, that will work without a problem. On 32 bits machines, that will always fail. The compiler catches it and refuses to build. Note that this only fixes the compiler build. ~~If the limit is above `MaxInt32` but strictly below `MaxUint32` then this will fail at runtime and we have another issue.~~ I checked and this should not happen during regular execution, although it might happen in tests. --- triedb/pathdb/history_trienode.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/triedb/pathdb/history_trienode.go b/triedb/pathdb/history_trienode.go index 2a4459d4ad46..f5eb590a9a5c 100644 --- a/triedb/pathdb/history_trienode.go +++ b/triedb/pathdb/history_trienode.go @@ -161,7 +161,7 @@ func newTrienodeHistory(root common.Hash, parent common.Hash, block uint64, node // sharedLen returns the length of the common prefix shared by a and b. func sharedLen(a, b []byte) int { n := min(len(a), len(b)) - for i := 0; i < n; i++ { + for i := range n { if a[i] != b[i] { return i } @@ -295,7 +295,7 @@ func decodeHeader(data []byte) (*trienodeMetadata, []common.Hash, []uint32, []ui keyOffsets = make([]uint32, 0, count) valOffsets = make([]uint32, 0, count) ) - for i := 0; i < count; i++ { + for i := range count { n := trienodeMetadataSize + trienodeTrieHeaderSize*i owner := common.BytesToHash(data[n : n+common.HashLength]) if i != 0 && bytes.Compare(owner.Bytes(), owners[i-1].Bytes()) <= 0 { @@ -348,7 +348,7 @@ func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]st if len(keySection) < int(8*nRestarts)+4 { return nil, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection)) } - for i := 0; i < int(nRestarts); i++ { + for i := range int(nRestarts) { o := len(keySection) - 4 - (int(nRestarts)-i)*8 keyOffset := binary.BigEndian.Uint32(keySection[o : o+4]) if i != 0 && keyOffset <= keyOffsets[i-1] { @@ -469,7 +469,7 @@ func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection h.nodeList = make(map[common.Hash][]string) h.nodes = make(map[common.Hash]map[string][]byte) - for i := 0; i < len(owners); i++ { + for i := range len(owners) { // Resolve the boundary of key section keyStart := keyOffsets[i] keyLimit := len(keySection) @@ -524,7 +524,7 @@ func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRa } keyStart := int(keyRange.start) keyLimit := int(keyRange.limit) - if keyLimit == math.MaxUint32 { + if keyRange.limit == math.MaxUint32 { keyLimit = len(keyData) } if len(keyData) < keyStart || len(keyData) < keyLimit { From 7fa5365ebdebe65037064970ef610fc8768733ce Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Tue, 14 Oct 2025 05:01:43 +0200 Subject: [PATCH 124/228] eth/catalyst: remove useless log on enabling Engine API (#32901) --- eth/catalyst/api.go | 1 - 1 file changed, 1 deletion(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 6dfe24f729bf..75b263bf6b60 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -47,7 +47,6 @@ import ( // Register adds the engine API to the full node. func Register(stack *node.Node, backend *eth.Ethereum) error { - log.Warn("Engine API enabled", "protocol", "eth") stack.RegisterAPIs([]rpc.API{ { Namespace: "engine", From 7dca2e6e9edd7364b034f032d51cee0d9247a895 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Tue, 14 Oct 2025 05:03:31 +0200 Subject: [PATCH 125/228] eth: do not warn on switching from snap sync to full sync (#32900) This happens normally after a restart, so it is better to use Info level here. Signed-off-by: Csaba Kiraly --- eth/handler.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index 304560a15868..ff970e2ba628 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -181,8 +181,7 @@ func newHandler(config *handlerConfig) (*handler, error) { } else { head := h.chain.CurrentBlock() if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) { - // Print warning log if database is not empty to run snap sync. - log.Warn("Switch sync mode from snap sync to full sync", "reason", "snap sync complete") + log.Info("Switch sync mode from snap sync to full sync", "reason", "snap sync complete") } else { // If snap sync was requested and our database is empty, grant it h.snapSync.Store(true) From 8107de39bcc3de4df57f1bd08856e25faffa1ff6 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 14 Oct 2025 14:40:04 +0800 Subject: [PATCH 126/228] core/txpool/legacypool: fix pricedList updates (#32906) This pr addresses a few issues brought by the #32270 - Add updates to pricedList after dropping transactions. - Remove redundant deletions in queue.evictList, since pool.removeTx(hash, true, true) already performs the removal. - Prevent duplicate addresses during promotion when Reset is not nil. --- core/txpool/legacypool/legacypool.go | 21 +++++------ core/txpool/legacypool/queue.go | 56 +++++++++++++++------------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index b36d86dd19dc..ceedc74a531e 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -367,8 +367,7 @@ func (pool *LegacyPool) loop() { // Handle inactive account transaction eviction case <-evict.C: pool.mu.Lock() - evicted := pool.queue.evict(false) - for _, hash := range evicted { + for _, hash := range pool.queue.evictList() { pool.removeTx(hash, true, true) } pool.mu.Unlock() @@ -813,7 +812,7 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo // // Note, this method assumes the pool lock is held! func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) { - replaced, err := pool.queue.add(hash, tx) + replaced, err := pool.queue.add(tx) if err != nil { return false, err } @@ -1093,7 +1092,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo } } // Transaction is in the future queue - pool.queue.removeTx(addr, tx) + pool.queue.remove(addr, tx) return 0 } @@ -1241,7 +1240,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, } } // Reset needs promote for all addresses - promoteAddrs = append(promoteAddrs, pool.queue.addresses()...) + promoteAddrs = pool.queue.addresses() } // Check for pending transactions for every account that sent new ones promoted := pool.promoteExecutables(promoteAddrs) @@ -1397,9 +1396,9 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { gasLimit := pool.currentHead.Load().GasLimit promotable, dropped, removedAddresses := pool.queue.promoteExecutables(accounts, gasLimit, pool.currentState, pool.pendingNonces) - promoted := make([]*types.Transaction, 0, len(promotable)) - // promote all promoteable transactions + // promote all promotable transactions + promoted := make([]*types.Transaction, 0, len(promotable)) for _, tx := range promotable { from, _ := pool.signer.Sender(tx) if pool.promoteTx(from, tx.Hash(), tx) { @@ -1411,16 +1410,15 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T for _, hash := range dropped { pool.all.Remove(hash) } + pool.priced.Removed(len(dropped)) // release all accounts that have no more transactions in the pool for _, addr := range removedAddresses { _, hasPending := pool.pending[addr] - _, hasQueued := pool.queue.get(addr) - if !hasPending && !hasQueued { + if !hasPending { pool.reserver.Release(addr) } } - return promoted } @@ -1510,10 +1508,11 @@ func (pool *LegacyPool) truncatePending() { func (pool *LegacyPool) truncateQueue() { removed, removedAddresses := pool.queue.truncate() - // remove all removable transactions + // Remove all removable transactions from the lookup and global price list for _, hash := range removed { pool.all.Remove(hash) } + pool.priced.Removed(len(removed)) for _, addr := range removedAddresses { _, hasPending := pool.pending[addr] diff --git a/core/txpool/legacypool/queue.go b/core/txpool/legacypool/queue.go index b8417064f73d..a889debe3705 100644 --- a/core/txpool/legacypool/queue.go +++ b/core/txpool/legacypool/queue.go @@ -27,6 +27,8 @@ import ( "github.com/ethereum/go-ethereum/log" ) +// queue manages nonce-gapped transactions that have been validated but are +// not yet processable. type queue struct { config Config signer types.Signer @@ -43,19 +45,17 @@ func newQueue(config Config, signer types.Signer) *queue { } } -func (q *queue) evict(force bool) []common.Hash { - removed := make([]common.Hash, 0) +// evictList returns the hashes of transactions that are old enough to be evicted. +func (q *queue) evictList() []common.Hash { + var removed []common.Hash for addr, list := range q.queued { - // Any transactions old enough should be removed - if force || time.Since(q.beats[addr]) > q.config.Lifetime { - list := list.Flatten() - for _, tx := range list { - q.removeTx(addr, tx) + if time.Since(q.beats[addr]) > q.config.Lifetime { + for _, tx := range list.Flatten() { removed = append(removed, tx.Hash()) } - queuedEvictionMeter.Mark(int64(len(list))) } } + queuedEvictionMeter.Mark(int64(len(removed))) return removed } @@ -100,7 +100,7 @@ func (q *queue) addresses() []common.Address { return addrs } -func (q queue) removeTx(addr common.Address, tx *types.Transaction) { +func (q *queue) remove(addr common.Address, tx *types.Transaction) { if future := q.queued[addr]; future != nil { if txOld := future.txs.Get(tx.Nonce()); txOld != nil && txOld.Hash() != tx.Hash() { // Edge case, a different transaction @@ -118,7 +118,7 @@ func (q queue) removeTx(addr common.Address, tx *types.Transaction) { } } -func (q *queue) add(hash common.Hash, tx *types.Transaction) (*common.Hash, error) { +func (q *queue) add(tx *types.Transaction) (*common.Hash, error) { // Try to insert the transaction into the future queue from, _ := types.Sender(q.signer, tx) // already validated if q.queued[from] == nil { @@ -149,15 +149,17 @@ func (q *queue) add(hash common.Hash, tx *types.Transaction) (*common.Hash, erro // for promotion any that are now executable. It also drops any transactions that are // deemed too old (nonce too low) or too costly (insufficient funds or over gas limit). // -// Returns three lists: all transactions that were removed from the queue and selected -// for promotion; all other transactions that were removed from the queue and dropped; -// the list of addresses removed. +// Returns three lists: +// - all transactions that were removed from the queue and selected for promotion; +// - all other transactions that were removed from the queue and dropped; +// - the list of addresses removed. func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, currentState *state.StateDB, nonces *noncer) ([]*types.Transaction, []common.Hash, []common.Address) { // Track the promotable transactions to broadcast them at once - var promotable []*types.Transaction - var dropped []common.Hash - var removedAddresses []common.Address - + var ( + promotable []*types.Transaction + dropped []common.Hash + removedAddresses []common.Address + ) // Iterate over all accounts and promote any executable transactions for _, addr := range accounts { list := q.queued[addr] @@ -170,6 +172,7 @@ func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, c dropped = append(dropped, tx.Hash()) } log.Trace("Removing old queued transactions", "count", len(forwards)) + // Drop all transactions that are too costly (low balance or out of gas) drops, _ := list.Filter(currentState.GetBalance(addr), gasLimit) for _, tx := range drops { @@ -205,9 +208,9 @@ func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, c } // truncate drops the oldest transactions from the queue until the total -// number is below the configured limit. -// Returns the hashes of all dropped transactions, and the addresses of -// accounts that became empty due to the truncation. +// number is below the configured limit. Returns the hashes of all dropped +// transactions and the addresses of accounts that became empty due to +// the truncation. func (q *queue) truncate() ([]common.Hash, []common.Address) { queued := uint64(0) for _, list := range q.queued { @@ -223,10 +226,12 @@ func (q *queue) truncate() ([]common.Hash, []common.Address) { addresses = append(addresses, addressByHeartbeat{addr, q.beats[addr]}) } sort.Sort(sort.Reverse(addresses)) - removed := make([]common.Hash, 0) - removedAddresses := make([]common.Address, 0) // Drop transactions until the total is below the limit + var ( + removed = make([]common.Hash, 0) + removedAddresses = make([]common.Address, 0) + ) for drop := queued - q.config.GlobalQueue; drop > 0 && len(addresses) > 0; { addr := addresses[len(addresses)-1] list := q.queued[addr.address] @@ -236,7 +241,7 @@ func (q *queue) truncate() ([]common.Hash, []common.Address) { // Drop all transactions if they are less than the overflow if size := uint64(list.Len()); size <= drop { for _, tx := range list.Flatten() { - q.removeTx(addr.address, tx) + q.remove(addr.address, tx) removed = append(removed, tx.Hash()) } drop -= size @@ -247,14 +252,13 @@ func (q *queue) truncate() ([]common.Hash, []common.Address) { // Otherwise drop only last few transactions txs := list.Flatten() for i := len(txs) - 1; i >= 0 && drop > 0; i-- { - q.removeTx(addr.address, txs[i]) + q.remove(addr.address, txs[i]) removed = append(removed, txs[i].Hash()) drop-- queuedRateLimitMeter.Mark(1) } } - - // no need to clear empty accounts, removeTx already does that + // No need to clear empty accounts, remove already does that return removed, removedAddresses } From 2117944f5a873407da88d71907e6924129d1d91a Mon Sep 17 00:00:00 2001 From: cui Date: Tue, 14 Oct 2025 17:07:48 +0800 Subject: [PATCH 127/228] accounts/abi: check presence of payable fallback or receive before proceeding with transfer (#32374) remove todo --- accounts/abi/abigen/bind_test.go | 6 +++--- accounts/abi/bind/v2/base.go | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/accounts/abi/abigen/bind_test.go b/accounts/abi/abigen/bind_test.go index b3c52e81e525..1651e637c8ab 100644 --- a/accounts/abi/abigen/bind_test.go +++ b/accounts/abi/abigen/bind_test.go @@ -485,13 +485,13 @@ var bindTests = []struct { contract Defaulter { address public caller; - function() { + fallback() external payable { caller = msg.sender; } } `, - []string{`6060604052606a8060106000396000f360606040523615601d5760e060020a6000350463fc9c8d3981146040575b605e6000805473ffffffffffffffffffffffffffffffffffffffff191633179055565b606060005473ffffffffffffffffffffffffffffffffffffffff1681565b005b6060908152602090f3`}, - []string{`[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`}, + []string{`608060405234801561000f575f80fd5b5061013d8061001d5f395ff3fe608060405260043610610021575f3560e01c8063fc9c8d391461006257610022565b5b335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055005b34801561006d575f80fd5b5061007661008c565b60405161008391906100ee565b60405180910390f35b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100d8826100af565b9050919050565b6100e8816100ce565b82525050565b5f6020820190506101015f8301846100df565b9291505056fea26469706673582212201e9273ecfb1f534644c77f09a25c21baaba81cf1c444ebc071e12a225a23c72964736f6c63430008140033`}, + []string{`[{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"caller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]`}, ` "math/big" diff --git a/accounts/abi/bind/v2/base.go b/accounts/abi/bind/v2/base.go index f714848efb50..4f2013b4a329 100644 --- a/accounts/abi/bind/v2/base.go +++ b/accounts/abi/bind/v2/base.go @@ -277,8 +277,10 @@ func (c *BoundContract) RawCreationTransact(opts *TransactOpts, calldata []byte) // Transfer initiates a plain transaction to move funds to the contract, calling // its default method if one is available. func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { - // todo(rjl493456442) check the payable fallback or receive is defined - // or not, reject invalid transaction at the first place + // Check if payable fallback or receive is defined + if !c.abi.HasReceive() && !(c.abi.HasFallback() && c.abi.Fallback.IsPayable()) { + return nil, fmt.Errorf("contract does not have a payable fallback or receive function") + } return c.transact(opts, &c.address, nil) } From c42f8f0574ad987ad5cfa3a1205d456d93e2d400 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 14 Oct 2025 14:40:54 +0200 Subject: [PATCH 128/228] internal/ethapi: convert legacy blobtx proofs in sendRawTransaction (#32849) This adds a temporary conversion path for blob transactions with legacy proof sidecar. This feature will activate after Fusaka. We will phase this out when the fork has sufficiently settled and client side libraries have been upgraded to send the new proofs. --- internal/ethapi/api.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index a2cb28d3b2d1..c10a4754af21 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1619,16 +1619,9 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction // processing (signing + broadcast). func (api *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { // Set some sanity defaults and terminate on failure - sidecarVersion := types.BlobSidecarVersion0 - if len(args.Blobs) > 0 { - h := api.b.CurrentHeader() - if api.b.ChainConfig().IsOsaka(h.Number, h.Time) { - sidecarVersion = types.BlobSidecarVersion1 - } - } config := sidecarConfig{ blobSidecarAllowed: true, - blobSidecarVersion: sidecarVersion, + blobSidecarVersion: api.currentBlobSidecarVersion(), } if err := args.setDefaults(ctx, api.b, config); err != nil { return nil, err @@ -1642,6 +1635,14 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction return &SignTransactionResult{data, tx}, nil } +func (api *TransactionAPI) currentBlobSidecarVersion() byte { + h := api.b.CurrentHeader() + if api.b.ChainConfig().IsOsaka(h.Number, h.Time) { + return types.BlobSidecarVersion1 + } + return types.BlobSidecarVersion0 +} + // SendRawTransaction will add the signed transaction to the transaction pool. // The sender is responsible for signing the transaction and using the correct nonce. func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { @@ -1649,6 +1650,19 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil if err := tx.UnmarshalBinary(input); err != nil { return common.Hash{}, err } + + // Convert legacy blob transaction proofs. + // TODO: remove in go-ethereum v1.17.x + if sc := tx.BlobTxSidecar(); sc != nil { + exp := api.currentBlobSidecarVersion() + if sc.Version == types.BlobSidecarVersion0 && exp == types.BlobSidecarVersion1 { + if err := sc.ToV1(); err != nil { + return common.Hash{}, fmt.Errorf("blob sidecar conversion failed: %v", err) + } + tx = tx.WithBlobTxSidecar(sc) + } + } + return SubmitTransaction(ctx, api.b, tx) } From d2234e829e929039b9553de95a4e8ac25e96e732 Mon Sep 17 00:00:00 2001 From: mishraa-G Date: Wed, 15 Oct 2025 13:09:00 +0530 Subject: [PATCH 129/228] rpc: fix flaky test TestServerWebsocketReadLimit (#32889) --- rpc/server_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpc/server_test.go b/rpc/server_test.go index a38a64b080fd..8334d4e80dd0 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -273,7 +273,8 @@ func TestServerWebsocketReadLimit(t *testing.T) { } } else if !errors.Is(err, websocket.ErrReadLimit) && !strings.Contains(strings.ToLower(err.Error()), "1009") && - !strings.Contains(strings.ToLower(err.Error()), "message too big") { + !strings.Contains(strings.ToLower(err.Error()), "message too big") && + !strings.Contains(strings.ToLower(err.Error()), "connection reset by peer") { // Not the error we expect from exceeding the message size limit. t.Fatalf("unexpected error for read limit violation: %v", err) } From 348df9fa68898c6f76218aed343d308f868f0838 Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 15 Oct 2025 16:24:48 +0800 Subject: [PATCH 130/228] eth/protocols/eth: reject message containing duplicated txs and drop peer (#32728) Drop peer if sending the same transaction multiple times in a single message. Fixes https://github.com/ethereum/go-ethereum/issues/32724 --------- Signed-off-by: Csaba Kiraly Co-authored-by: Gary Rong Co-authored-by: Csaba Kiraly --- eth/protocols/eth/handlers.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 15ad048bcf4a..aad3353d88d7 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -494,12 +494,19 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(&txs); err != nil { return err } + // Duplicate transactions are not allowed + seen := make(map[common.Hash]struct{}) for i, tx := range txs { // Validate and mark the remote transaction if tx == nil { return fmt.Errorf("Transactions: transaction %d is nil", i) } - peer.markTransaction(tx.Hash()) + hash := tx.Hash() + if _, exists := seen[hash]; exists { + return fmt.Errorf("Transactions: multiple copies of the same hash %v", hash) + } + seen[hash] = struct{}{} + peer.markTransaction(hash) } return backend.Handle(peer, &txs) } @@ -514,12 +521,19 @@ func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(&txs); err != nil { return err } + // Duplicate transactions are not allowed + seen := make(map[common.Hash]struct{}) for i, tx := range txs.PooledTransactionsResponse { // Validate and mark the remote transaction if tx == nil { return fmt.Errorf("PooledTransactions: transaction %d is nil", i) } - peer.markTransaction(tx.Hash()) + hash := tx.Hash() + if _, exists := seen[hash]; exists { + return fmt.Errorf("PooledTransactions: multiple copies of the same hash %v", hash) + } + seen[hash] = struct{}{} + peer.markTransaction(hash) } requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId) From f4d138eae616bd95b9a88155675657f9d5626593 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 15 Oct 2025 11:51:33 +0200 Subject: [PATCH 131/228] p2p/discover: remove hot-spin in table refresh trigger (#32912) This fixes a regression introduced in #32518. In that PR, we removed the slowdown logic that would throttle lookups when the table runs empty. Said logic was originally added in #20389. Usually it's fine, but there exist pathological cases, such as hive tests, where the node can only discover one other node, so it can only ever query that node and won't get any results. In cases like these, we need to throttle the creation of lookups to avoid crazy CPU usage. --- p2p/discover/lookup.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index 9cca0118ac65..416256fb361a 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -153,6 +153,7 @@ type lookupIterator struct { cancel func() lookup *lookup tabRefreshing <-chan struct{} + lastLookup time.Time } type lookupFunc func(ctx context.Context) *lookup @@ -185,6 +186,9 @@ func (it *lookupIterator) Next() bool { return false } if it.lookup == nil { + // Ensure enough time has passed between lookup creations. + it.slowdown() + it.lookup = it.nextLookup(it.ctx) if it.lookup.empty() { // If the lookup is empty right after creation, it means the local table @@ -235,6 +239,25 @@ func (it *lookupIterator) lookupFailed(tab *Table, timeout time.Duration) { tab.waitForNodes(tout, 1) } +// slowdown applies a delay between creating lookups. This exists to prevent hot-spinning +// in some test environments where lookups don't yield any results. +func (it *lookupIterator) slowdown() { + const minInterval = 1 * time.Second + + now := time.Now() + diff := now.Sub(it.lastLookup) + it.lastLookup = now + if diff > minInterval { + return + } + wait := time.NewTimer(diff) + defer wait.Stop() + select { + case <-wait.C: + case <-it.ctx.Done(): + } +} + // Close ends the iterator. func (it *lookupIterator) Close() { it.cancel() From 4f5c3f662e60a77541bac6eb27f291434ef2df70 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 16 Oct 2025 09:58:47 +0200 Subject: [PATCH 132/228] version: release go-ethereum v1.16.5 stable --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index db4e5394b909..f50a9892a426 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 5 // Patch version component of the current release - Meta = "unstable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 5 // Patch version component of the current release + Meta = "stable" // Version metadata to append to the version string ) From 2f1c600744a606c655be715aad371c07b0b0763c Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 16 Oct 2025 09:59:52 +0200 Subject: [PATCH 133/228] version: begin v1.16.6 release cycle --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index f50a9892a426..ead2d04f2a74 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 5 // Patch version component of the current release - Meta = "stable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 6 // Patch version component of the current release + Meta = "unstable" // Version metadata to append to the version string ) From 9ba7e2a4395da022e34f93c54827696874f0dcc5 Mon Sep 17 00:00:00 2001 From: Galoretka Date: Thu, 16 Oct 2025 14:49:41 +0300 Subject: [PATCH 134/228] cmd/geth: log current key in expandVerkle instead of keylist[0] (#32689) Fix logging in the verkle dump path to report the actual key being processed. Previously, the loop always logged keylist[0], which misled users when expanding multiple keys and made debugging harder. This change aligns the log with the key passed to root.Get, improving traceability and diagnostics. --- cmd/geth/verkle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/geth/verkle.go b/cmd/geth/verkle.go index 6490f832af0f..67dc7257c011 100644 --- a/cmd/geth/verkle.go +++ b/cmd/geth/verkle.go @@ -201,7 +201,7 @@ func expandVerkle(ctx *cli.Context) error { } for i, key := range keylist { - log.Info("Reading key", "index", i, "key", keylist[0]) + log.Info("Reading key", "index", i, "key", key) root.Get(key, chaindb.Get) } From 8aa1683a06e86ca28602fedd0e7ea5b24c9bcc76 Mon Sep 17 00:00:00 2001 From: hero5512 Date: Thu, 16 Oct 2025 11:32:55 -0400 Subject: [PATCH 135/228] ethclient: add support for eth_simulateV1 (#32856) Adds ethclient support for the eth_simulateV1 RPC method, which allows simulating transactions on top of a base state without making changes to the blockchain. --------- Co-authored-by: Sina Mahmoodi --- ethclient/ethclient.go | 86 +++++++++ ethclient/ethclient_test.go | 247 +++++++++++++++++++++++++ ethclient/gen_simulate_block_result.go | 80 ++++++++ ethclient/gen_simulate_call_result.go | 61 ++++++ ethclient/gethclient/gethclient.go | 98 +--------- interfaces.go | 97 ++++++++++ 6 files changed, 575 insertions(+), 94 deletions(-) create mode 100644 ethclient/gen_simulate_block_result.go create mode 100644 ethclient/gen_simulate_call_result.go diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 8b26f5b3cad1..0b676fccfb9d 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -828,3 +828,89 @@ func (p *rpcProgress) toSyncProgress() *ethereum.SyncProgress { StateIndexRemaining: uint64(p.StateIndexRemaining), } } + +// SimulateOptions represents the options for eth_simulateV1. +type SimulateOptions struct { + BlockStateCalls []SimulateBlock `json:"blockStateCalls"` + TraceTransfers bool `json:"traceTransfers"` + Validation bool `json:"validation"` + ReturnFullTransactions bool `json:"returnFullTransactions"` +} + +// SimulateBlock represents a batch of calls to be simulated. +type SimulateBlock struct { + BlockOverrides *ethereum.BlockOverrides `json:"blockOverrides,omitempty"` + StateOverrides map[common.Address]ethereum.OverrideAccount `json:"stateOverrides,omitempty"` + Calls []ethereum.CallMsg `json:"calls"` +} + +// MarshalJSON implements json.Marshaler for SimulateBlock. +func (s SimulateBlock) MarshalJSON() ([]byte, error) { + type Alias struct { + BlockOverrides *ethereum.BlockOverrides `json:"blockOverrides,omitempty"` + StateOverrides map[common.Address]ethereum.OverrideAccount `json:"stateOverrides,omitempty"` + Calls []interface{} `json:"calls"` + } + calls := make([]interface{}, len(s.Calls)) + for i, call := range s.Calls { + calls[i] = toCallArg(call) + } + return json.Marshal(Alias{ + BlockOverrides: s.BlockOverrides, + StateOverrides: s.StateOverrides, + Calls: calls, + }) +} + +//go:generate go run github.com/fjl/gencodec -type SimulateCallResult -field-override simulateCallResultMarshaling -out gen_simulate_call_result.go + +// SimulateCallResult is the result of a simulated call. +type SimulateCallResult struct { + ReturnValue []byte `json:"returnData"` + Logs []*types.Log `json:"logs"` + GasUsed uint64 `json:"gasUsed"` + Status uint64 `json:"status"` + Error *CallError `json:"error,omitempty"` +} + +type simulateCallResultMarshaling struct { + ReturnValue hexutil.Bytes + GasUsed hexutil.Uint64 + Status hexutil.Uint64 +} + +// CallError represents an error from a simulated call. +type CallError struct { + Code int `json:"code"` + Message string `json:"message"` + Data string `json:"data,omitempty"` +} + +//go:generate go run github.com/fjl/gencodec -type SimulateBlockResult -field-override simulateBlockResultMarshaling -out gen_simulate_block_result.go + +// SimulateBlockResult represents the result of a simulated block. +type SimulateBlockResult struct { + Number *big.Int `json:"number"` + Hash common.Hash `json:"hash"` + Timestamp uint64 `json:"timestamp"` + GasLimit uint64 `json:"gasLimit"` + GasUsed uint64 `json:"gasUsed"` + FeeRecipient common.Address `json:"miner"` + BaseFeePerGas *big.Int `json:"baseFeePerGas,omitempty"` + Calls []SimulateCallResult `json:"calls"` +} + +type simulateBlockResultMarshaling struct { + Number *hexutil.Big + Timestamp hexutil.Uint64 + GasLimit hexutil.Uint64 + GasUsed hexutil.Uint64 + BaseFeePerGas *hexutil.Big +} + +// SimulateV1 executes transactions on top of a base state. +func (ec *Client) SimulateV1(ctx context.Context, opts SimulateOptions, blockNrOrHash *rpc.BlockNumberOrHash) ([]SimulateBlockResult, error) { + var result []SimulateBlockResult + err := ec.c.CallContext(ctx, &result, "eth_simulateV1", opts, blockNrOrHash) + return result, err +} diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 815bc29de457..302ccf2e16c2 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -754,3 +754,250 @@ func ExampleRevertErrorData() { // revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72 // message: user error } + +func TestSimulateV1(t *testing.T) { + backend, _, err := newTestBackend(nil) + if err != nil { + t.Fatalf("Failed to create test backend: %v", err) + } + defer backend.Close() + + client := ethclient.NewClient(backend.Attach()) + defer client.Close() + + ctx := context.Background() + + // Get current base fee + header, err := client.HeaderByNumber(ctx, nil) + if err != nil { + t.Fatalf("Failed to get header: %v", err) + } + + // Simple test: transfer ETH from one account to another + from := testAddr + to := common.HexToAddress("0x0000000000000000000000000000000000000001") + value := big.NewInt(100) + gas := uint64(100000) + maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2)) + + opts := ethclient.SimulateOptions{ + BlockStateCalls: []ethclient.SimulateBlock{ + { + Calls: []ethereum.CallMsg{ + { + From: from, + To: &to, + Value: value, + Gas: gas, + GasFeeCap: maxFeePerGas, + }, + }, + }, + }, + Validation: true, + } + + results, err := client.SimulateV1(ctx, opts, nil) + if err != nil { + t.Fatalf("SimulateV1 failed: %v", err) + } + + if len(results) != 1 { + t.Fatalf("expected 1 block result, got %d", len(results)) + } + + if len(results[0].Calls) != 1 { + t.Fatalf("expected 1 call result, got %d", len(results[0].Calls)) + } + + // Check that the transaction succeeded + if results[0].Calls[0].Status != 1 { + t.Errorf("expected status 1 (success), got %d", results[0].Calls[0].Status) + } + + if results[0].Calls[0].Error != nil { + t.Errorf("expected no error, got %v", results[0].Calls[0].Error) + } +} + +func TestSimulateV1WithBlockOverrides(t *testing.T) { + backend, _, err := newTestBackend(nil) + if err != nil { + t.Fatalf("Failed to create test backend: %v", err) + } + defer backend.Close() + + client := ethclient.NewClient(backend.Attach()) + defer client.Close() + + ctx := context.Background() + + // Get current base fee + header, err := client.HeaderByNumber(ctx, nil) + if err != nil { + t.Fatalf("Failed to get header: %v", err) + } + + from := testAddr + to := common.HexToAddress("0x0000000000000000000000000000000000000001") + value := big.NewInt(100) + gas := uint64(100000) + maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2)) + + // Override timestamp only + timestamp := uint64(1234567890) + + opts := ethclient.SimulateOptions{ + BlockStateCalls: []ethclient.SimulateBlock{ + { + BlockOverrides: ðereum.BlockOverrides{ + Time: timestamp, + }, + Calls: []ethereum.CallMsg{ + { + From: from, + To: &to, + Value: value, + Gas: gas, + GasFeeCap: maxFeePerGas, + }, + }, + }, + }, + Validation: true, + } + + results, err := client.SimulateV1(ctx, opts, nil) + if err != nil { + t.Fatalf("SimulateV1 with block overrides failed: %v", err) + } + + if len(results) != 1 { + t.Fatalf("expected 1 block result, got %d", len(results)) + } + + // Verify the timestamp was overridden + if results[0].Timestamp != timestamp { + t.Errorf("expected timestamp %d, got %d", timestamp, results[0].Timestamp) + } +} + +func TestSimulateV1WithStateOverrides(t *testing.T) { + backend, _, err := newTestBackend(nil) + if err != nil { + t.Fatalf("Failed to create test backend: %v", err) + } + defer backend.Close() + + client := ethclient.NewClient(backend.Attach()) + defer client.Close() + + ctx := context.Background() + + // Get current base fee + header, err := client.HeaderByNumber(ctx, nil) + if err != nil { + t.Fatalf("Failed to get header: %v", err) + } + + from := testAddr + to := common.HexToAddress("0x0000000000000000000000000000000000000001") + value := big.NewInt(1000000000000000000) // 1 ETH + gas := uint64(100000) + maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2)) + + // Override the balance of the 'from' address + balanceStr := "1000000000000000000000" + balance := new(big.Int) + balance.SetString(balanceStr, 10) + + stateOverrides := map[common.Address]ethereum.OverrideAccount{ + from: { + Balance: balance, + }, + } + + opts := ethclient.SimulateOptions{ + BlockStateCalls: []ethclient.SimulateBlock{ + { + StateOverrides: stateOverrides, + Calls: []ethereum.CallMsg{ + { + From: from, + To: &to, + Value: value, + Gas: gas, + GasFeeCap: maxFeePerGas, + }, + }, + }, + }, + Validation: true, + } + + results, err := client.SimulateV1(ctx, opts, nil) + if err != nil { + t.Fatalf("SimulateV1 with state overrides failed: %v", err) + } + + if len(results) != 1 { + t.Fatalf("expected 1 block result, got %d", len(results)) + } + + if results[0].Calls[0].Status != 1 { + t.Errorf("expected status 1 (success), got %d", results[0].Calls[0].Status) + } +} + +func TestSimulateV1WithBlockNumberOrHash(t *testing.T) { + backend, _, err := newTestBackend(nil) + if err != nil { + t.Fatalf("Failed to create test backend: %v", err) + } + defer backend.Close() + + client := ethclient.NewClient(backend.Attach()) + defer client.Close() + + ctx := context.Background() + + // Get current base fee + header, err := client.HeaderByNumber(ctx, nil) + if err != nil { + t.Fatalf("Failed to get header: %v", err) + } + + from := testAddr + to := common.HexToAddress("0x0000000000000000000000000000000000000001") + value := big.NewInt(100) + gas := uint64(100000) + maxFeePerGas := new(big.Int).Mul(header.BaseFee, big.NewInt(2)) + + opts := ethclient.SimulateOptions{ + BlockStateCalls: []ethclient.SimulateBlock{ + { + Calls: []ethereum.CallMsg{ + { + From: from, + To: &to, + Value: value, + Gas: gas, + GasFeeCap: maxFeePerGas, + }, + }, + }, + }, + Validation: true, + } + + // Simulate on the latest block + latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + results, err := client.SimulateV1(ctx, opts, &latest) + if err != nil { + t.Fatalf("SimulateV1 with latest block failed: %v", err) + } + + if len(results) != 1 { + t.Fatalf("expected 1 block result, got %d", len(results)) + } +} diff --git a/ethclient/gen_simulate_block_result.go b/ethclient/gen_simulate_block_result.go new file mode 100644 index 000000000000..b8cd6ebf2fc2 --- /dev/null +++ b/ethclient/gen_simulate_block_result.go @@ -0,0 +1,80 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package ethclient + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*simulateBlockResultMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s SimulateBlockResult) MarshalJSON() ([]byte, error) { + type SimulateBlockResult struct { + Number *hexutil.Big `json:"number"` + Hash common.Hash `json:"hash"` + Timestamp hexutil.Uint64 `json:"timestamp"` + GasLimit hexutil.Uint64 `json:"gasLimit"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + FeeRecipient common.Address `json:"miner"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas,omitempty"` + Calls []SimulateCallResult `json:"calls"` + } + var enc SimulateBlockResult + enc.Number = (*hexutil.Big)(s.Number) + enc.Hash = s.Hash + enc.Timestamp = hexutil.Uint64(s.Timestamp) + enc.GasLimit = hexutil.Uint64(s.GasLimit) + enc.GasUsed = hexutil.Uint64(s.GasUsed) + enc.FeeRecipient = s.FeeRecipient + enc.BaseFeePerGas = (*hexutil.Big)(s.BaseFeePerGas) + enc.Calls = s.Calls + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *SimulateBlockResult) UnmarshalJSON(input []byte) error { + type SimulateBlockResult struct { + Number *hexutil.Big `json:"number"` + Hash *common.Hash `json:"hash"` + Timestamp *hexutil.Uint64 `json:"timestamp"` + GasLimit *hexutil.Uint64 `json:"gasLimit"` + GasUsed *hexutil.Uint64 `json:"gasUsed"` + FeeRecipient *common.Address `json:"miner"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas,omitempty"` + Calls []SimulateCallResult `json:"calls"` + } + var dec SimulateBlockResult + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Number != nil { + s.Number = (*big.Int)(dec.Number) + } + if dec.Hash != nil { + s.Hash = *dec.Hash + } + if dec.Timestamp != nil { + s.Timestamp = uint64(*dec.Timestamp) + } + if dec.GasLimit != nil { + s.GasLimit = uint64(*dec.GasLimit) + } + if dec.GasUsed != nil { + s.GasUsed = uint64(*dec.GasUsed) + } + if dec.FeeRecipient != nil { + s.FeeRecipient = *dec.FeeRecipient + } + if dec.BaseFeePerGas != nil { + s.BaseFeePerGas = (*big.Int)(dec.BaseFeePerGas) + } + if dec.Calls != nil { + s.Calls = dec.Calls + } + return nil +} diff --git a/ethclient/gen_simulate_call_result.go b/ethclient/gen_simulate_call_result.go new file mode 100644 index 000000000000..55e14cd697f6 --- /dev/null +++ b/ethclient/gen_simulate_call_result.go @@ -0,0 +1,61 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package ethclient + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var _ = (*simulateCallResultMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s SimulateCallResult) MarshalJSON() ([]byte, error) { + type SimulateCallResult struct { + ReturnValue hexutil.Bytes `json:"returnData"` + Logs []*types.Log `json:"logs"` + GasUsed hexutil.Uint64 `json:"gasUsed"` + Status hexutil.Uint64 `json:"status"` + Error *CallError `json:"error,omitempty"` + } + var enc SimulateCallResult + enc.ReturnValue = s.ReturnValue + enc.Logs = s.Logs + enc.GasUsed = hexutil.Uint64(s.GasUsed) + enc.Status = hexutil.Uint64(s.Status) + enc.Error = s.Error + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *SimulateCallResult) UnmarshalJSON(input []byte) error { + type SimulateCallResult struct { + ReturnValue *hexutil.Bytes `json:"returnData"` + Logs []*types.Log `json:"logs"` + GasUsed *hexutil.Uint64 `json:"gasUsed"` + Status *hexutil.Uint64 `json:"status"` + Error *CallError `json:"error,omitempty"` + } + var dec SimulateCallResult + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.ReturnValue != nil { + s.ReturnValue = *dec.ReturnValue + } + if dec.Logs != nil { + s.Logs = dec.Logs + } + if dec.GasUsed != nil { + s.GasUsed = uint64(*dec.GasUsed) + } + if dec.Status != nil { + s.Status = uint64(*dec.Status) + } + if dec.Error != nil { + s.Error = dec.Error + } + return nil +} diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go index 54997cbf51a4..6a0f5eb31233 100644 --- a/ethclient/gethclient/gethclient.go +++ b/ethclient/gethclient/gethclient.go @@ -19,7 +19,6 @@ package gethclient import ( "context" - "encoding/json" "fmt" "math/big" "runtime" @@ -280,97 +279,8 @@ func toCallArg(msg ethereum.CallMsg) interface{} { return arg } -// OverrideAccount specifies the state of an account to be overridden. -type OverrideAccount struct { - // Nonce sets nonce of the account. Note: the nonce override will only - // be applied when it is set to a non-zero value. - Nonce uint64 +// OverrideAccount is an alias for ethereum.OverrideAccount. +type OverrideAccount = ethereum.OverrideAccount - // Code sets the contract code. The override will be applied - // when the code is non-nil, i.e. setting empty code is possible - // using an empty slice. - Code []byte - - // Balance sets the account balance. - Balance *big.Int - - // State sets the complete storage. The override will be applied - // when the given map is non-nil. Using an empty map wipes the - // entire contract storage during the call. - State map[common.Hash]common.Hash - - // StateDiff allows overriding individual storage slots. - StateDiff map[common.Hash]common.Hash -} - -func (a OverrideAccount) MarshalJSON() ([]byte, error) { - type acc struct { - Nonce hexutil.Uint64 `json:"nonce,omitempty"` - Code string `json:"code,omitempty"` - Balance *hexutil.Big `json:"balance,omitempty"` - State interface{} `json:"state,omitempty"` - StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"` - } - - output := acc{ - Nonce: hexutil.Uint64(a.Nonce), - Balance: (*hexutil.Big)(a.Balance), - StateDiff: a.StateDiff, - } - if a.Code != nil { - output.Code = hexutil.Encode(a.Code) - } - if a.State != nil { - output.State = a.State - } - return json.Marshal(output) -} - -// BlockOverrides specifies the set of header fields to override. -type BlockOverrides struct { - // Number overrides the block number. - Number *big.Int - // Difficulty overrides the block difficulty. - Difficulty *big.Int - // Time overrides the block timestamp. Time is applied only when - // it is non-zero. - Time uint64 - // GasLimit overrides the block gas limit. GasLimit is applied only when - // it is non-zero. - GasLimit uint64 - // Coinbase overrides the block coinbase. Coinbase is applied only when - // it is different from the zero address. - Coinbase common.Address - // Random overrides the block extra data which feeds into the RANDOM opcode. - // Random is applied only when it is a non-zero hash. - Random common.Hash - // BaseFee overrides the block base fee. - BaseFee *big.Int -} - -func (o BlockOverrides) MarshalJSON() ([]byte, error) { - type override struct { - Number *hexutil.Big `json:"number,omitempty"` - Difficulty *hexutil.Big `json:"difficulty,omitempty"` - Time hexutil.Uint64 `json:"time,omitempty"` - GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"` - Coinbase *common.Address `json:"feeRecipient,omitempty"` - Random *common.Hash `json:"prevRandao,omitempty"` - BaseFee *hexutil.Big `json:"baseFeePerGas,omitempty"` - } - - output := override{ - Number: (*hexutil.Big)(o.Number), - Difficulty: (*hexutil.Big)(o.Difficulty), - Time: hexutil.Uint64(o.Time), - GasLimit: hexutil.Uint64(o.GasLimit), - BaseFee: (*hexutil.Big)(o.BaseFee), - } - if o.Coinbase != (common.Address{}) { - output.Coinbase = &o.Coinbase - } - if o.Random != (common.Hash{}) { - output.Random = &o.Random - } - return json.Marshal(output) -} +// BlockOverrides is an alias for ethereum.BlockOverrides. +type BlockOverrides = ethereum.BlockOverrides diff --git a/interfaces.go b/interfaces.go index 2828af1cc9a5..21d42c6d3479 100644 --- a/interfaces.go +++ b/interfaces.go @@ -19,10 +19,12 @@ package ethereum import ( "context" + "encoding/json" "errors" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" ) @@ -293,3 +295,98 @@ type BlockNumberReader interface { type ChainIDReader interface { ChainID(ctx context.Context) (*big.Int, error) } + +// OverrideAccount specifies the state of an account to be overridden. +type OverrideAccount struct { + // Nonce sets nonce of the account. Note: the nonce override will only + // be applied when it is set to a non-zero value. + Nonce uint64 + + // Code sets the contract code. The override will be applied + // when the code is non-nil, i.e. setting empty code is possible + // using an empty slice. + Code []byte + + // Balance sets the account balance. + Balance *big.Int + + // State sets the complete storage. The override will be applied + // when the given map is non-nil. Using an empty map wipes the + // entire contract storage during the call. + State map[common.Hash]common.Hash + + // StateDiff allows overriding individual storage slots. + StateDiff map[common.Hash]common.Hash +} + +func (a OverrideAccount) MarshalJSON() ([]byte, error) { + type acc struct { + Nonce hexutil.Uint64 `json:"nonce,omitempty"` + Code string `json:"code,omitempty"` + Balance *hexutil.Big `json:"balance,omitempty"` + State interface{} `json:"state,omitempty"` + StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"` + } + + output := acc{ + Nonce: hexutil.Uint64(a.Nonce), + Balance: (*hexutil.Big)(a.Balance), + StateDiff: a.StateDiff, + } + if a.Code != nil { + output.Code = hexutil.Encode(a.Code) + } + if a.State != nil { + output.State = a.State + } + return json.Marshal(output) +} + +// BlockOverrides specifies the set of header fields to override. +type BlockOverrides struct { + // Number overrides the block number. + Number *big.Int + // Difficulty overrides the block difficulty. + Difficulty *big.Int + // Time overrides the block timestamp. Time is applied only when + // it is non-zero. + Time uint64 + // GasLimit overrides the block gas limit. GasLimit is applied only when + // it is non-zero. + GasLimit uint64 + // Coinbase overrides the block coinbase. Coinbase is applied only when + // it is different from the zero address. + Coinbase common.Address + // Random overrides the block extra data which feeds into the RANDOM opcode. + // Random is applied only when it is a non-zero hash. + Random common.Hash + // BaseFee overrides the block base fee. + BaseFee *big.Int +} + +func (o BlockOverrides) MarshalJSON() ([]byte, error) { + type override struct { + Number *hexutil.Big `json:"number,omitempty"` + Difficulty *hexutil.Big `json:"difficulty,omitempty"` + Time hexutil.Uint64 `json:"time,omitempty"` + GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"` + Coinbase *common.Address `json:"feeRecipient,omitempty"` + Random *common.Hash `json:"prevRandao,omitempty"` + BaseFee *hexutil.Big `json:"baseFeePerGas,omitempty"` + } + + output := override{ + Number: (*hexutil.Big)(o.Number), + Difficulty: (*hexutil.Big)(o.Difficulty), + Time: hexutil.Uint64(o.Time), + GasLimit: hexutil.Uint64(o.GasLimit), + BaseFee: (*hexutil.Big)(o.BaseFee), + } + if o.Coinbase != (common.Address{}) { + output.Coinbase = &o.Coinbase + } + if o.Random != (common.Hash{}) { + output.Random = &o.Random + } + return json.Marshal(output) +} From 04011940488ec67ff75ee765e06206041c240329 Mon Sep 17 00:00:00 2001 From: aodhgan <36907214+aodhgan@users.noreply.github.com> Date: Thu, 16 Oct 2025 08:34:47 -0700 Subject: [PATCH 136/228] internal/ethapi: add eth_SendRawTransactionSync (#32830) New RPC method eth_sendRawTransactionSync(rawTx, timeoutMs?) that submits a signed tx and blocks until a receipt is available or a timeout elapses. Two CLI flags to tune server-side limits: --rpc.txsync.defaulttimeout (default wait window) --rpc.txsync.maxtimeout (upper bound; requests are clamped) closes https://github.com/ethereum/go-ethereum/issues/32094 --------- Co-authored-by: aodhgan Co-authored-by: Sina Mahmoodi --- cmd/geth/main.go | 2 + cmd/utils/flags.go | 18 ++++ eth/api_backend.go | 8 ++ eth/ethconfig/config.go | 48 +++++----- ethclient/ethclient.go | 34 +++++++ internal/ethapi/api.go | 87 ++++++++++++++++++ internal/ethapi/api_test.go | 175 ++++++++++++++++++++++++++++++++++-- internal/ethapi/backend.go | 2 + internal/ethapi/errors.go | 11 +++ 9 files changed, 359 insertions(+), 26 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 2465b52ad1f0..cc294b2f3092 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -188,6 +188,8 @@ var ( utils.AllowUnprotectedTxs, utils.BatchRequestLimit, utils.BatchResponseMaxSize, + utils.RPCTxSyncDefaultTimeoutFlag, + utils.RPCTxSyncMaxTimeoutFlag, } metricsFlags = []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c9da08578c96..0c5db9e6d8ba 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -615,6 +615,18 @@ var ( Value: ethconfig.Defaults.LogQueryLimit, Category: flags.APICategory, } + RPCTxSyncDefaultTimeoutFlag = &cli.DurationFlag{ + Name: "rpc.txsync.defaulttimeout", + Usage: "Default timeout for eth_sendRawTransactionSync (e.g. 2s, 500ms)", + Value: ethconfig.Defaults.TxSyncDefaultTimeout, + Category: flags.APICategory, + } + RPCTxSyncMaxTimeoutFlag = &cli.DurationFlag{ + Name: "rpc.txsync.maxtimeout", + Usage: "Maximum allowed timeout for eth_sendRawTransactionSync (e.g. 5m)", + Value: ethconfig.Defaults.TxSyncMaxTimeout, + Category: flags.APICategory, + } // Authenticated RPC HTTP settings AuthListenFlag = &cli.StringFlag{ Name: "authrpc.addr", @@ -1717,6 +1729,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(RPCGlobalLogQueryLimit.Name) { cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name) } + if ctx.IsSet(RPCTxSyncDefaultTimeoutFlag.Name) { + cfg.TxSyncDefaultTimeout = ctx.Duration(RPCTxSyncDefaultTimeoutFlag.Name) + } + if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) { + cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name) + } if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 { // If snap-sync is requested, this flag is also required if cfg.SyncMode == ethconfig.SnapSync { diff --git a/eth/api_backend.go b/eth/api_backend.go index 3ae73e78af18..766a99fc1ef6 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -486,3 +486,11 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) } + +func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration { + return b.eth.config.TxSyncDefaultTimeout +} + +func (b *EthAPIBackend) RPCTxSyncMaxTimeout() time.Duration { + return b.eth.config.TxSyncMaxTimeout +} diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 6020387bcdba..c4a0956b3b47 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -49,27 +49,29 @@ var FullNodeGPO = gasprice.Config{ // Defaults contains default settings for use on the Ethereum main net. var Defaults = Config{ - HistoryMode: history.KeepAll, - SyncMode: SnapSync, - NetworkId: 0, // enable auto configuration of networkID == chainID - TxLookupLimit: 2350000, - TransactionHistory: 2350000, - LogHistory: 2350000, - StateHistory: params.FullImmutabilityThreshold, - DatabaseCache: 512, - TrieCleanCache: 154, - TrieDirtyCache: 256, - TrieTimeout: 60 * time.Minute, - SnapshotCache: 102, - FilterLogCacheSize: 32, - LogQueryLimit: 1000, - Miner: miner.DefaultConfig, - TxPool: legacypool.DefaultConfig, - BlobPool: blobpool.DefaultConfig, - RPCGasCap: 50000000, - RPCEVMTimeout: 5 * time.Second, - GPO: FullNodeGPO, - RPCTxFeeCap: 1, // 1 ether + HistoryMode: history.KeepAll, + SyncMode: SnapSync, + NetworkId: 0, // enable auto configuration of networkID == chainID + TxLookupLimit: 2350000, + TransactionHistory: 2350000, + LogHistory: 2350000, + StateHistory: params.FullImmutabilityThreshold, + DatabaseCache: 512, + TrieCleanCache: 154, + TrieDirtyCache: 256, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 102, + FilterLogCacheSize: 32, + LogQueryLimit: 1000, + Miner: miner.DefaultConfig, + TxPool: legacypool.DefaultConfig, + BlobPool: blobpool.DefaultConfig, + RPCGasCap: 50000000, + RPCEVMTimeout: 5 * time.Second, + GPO: FullNodeGPO, + RPCTxFeeCap: 1, // 1 ether + TxSyncDefaultTimeout: 20 * time.Second, + TxSyncMaxTimeout: 1 * time.Minute, } //go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go @@ -183,6 +185,10 @@ type Config struct { // OverrideVerkle (TODO: remove after the fork) OverrideVerkle *uint64 `toml:",omitempty"` + + // EIP-7966: eth_sendRawTransactionSync timeouts + TxSyncDefaultTimeout time.Duration `toml:",omitempty"` + TxSyncMaxTimeout time.Duration `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 0b676fccfb9d..5008378da6a6 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -705,6 +706,39 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) } +// SendTransactionSync submits a signed tx and waits for a receipt (or until +// the optional timeout elapses on the server side). If timeout == 0, the server +// uses its default. +func (ec *Client) SendTransactionSync( + ctx context.Context, + tx *types.Transaction, + timeout *time.Duration, +) (*types.Receipt, error) { + raw, err := tx.MarshalBinary() + if err != nil { + return nil, err + } + return ec.SendRawTransactionSync(ctx, raw, timeout) +} + +func (ec *Client) SendRawTransactionSync( + ctx context.Context, + rawTx []byte, + timeout *time.Duration, +) (*types.Receipt, error) { + var ms *hexutil.Uint64 + if timeout != nil { + if d := hexutil.Uint64(timeout.Milliseconds()); d > 0 { + ms = &d + } + } + var receipt types.Receipt + if err := ec.c.CallContext(ctx, &receipt, "eth_sendRawTransactionSync", hexutil.Bytes(rawTx), ms); err != nil { + return nil, err + } + return &receipt, nil +} + // RevertErrorData returns the 'revert reason' data of a contract call. // // This can be used with CallContract and EstimateGas, and only when the server is Geth. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c10a4754af21..eb7a34474cf3 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -55,6 +55,7 @@ import ( const estimateGasErrorRatio = 0.015 var errBlobTxNotSupported = errors.New("signing blob transactions not supported") +var errSubClosed = errors.New("chain subscription closed") // EthereumAPI provides an API to access Ethereum related information. type EthereumAPI struct { @@ -1666,6 +1667,92 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil return SubmitTransaction(ctx, api.b, tx) } +// SendRawTransactionSync will add the signed transaction to the transaction pool +// and wait until the transaction has been included in a block and return the receipt, or the timeout. +func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hexutil.Bytes, timeoutMs *hexutil.Uint64) (map[string]interface{}, error) { + tx := new(types.Transaction) + if err := tx.UnmarshalBinary(input); err != nil { + return nil, err + } + + ch := make(chan core.ChainEvent, 128) + sub := api.b.SubscribeChainEvent(ch) + subErrCh := sub.Err() + defer sub.Unsubscribe() + + hash, err := SubmitTransaction(ctx, api.b, tx) + if err != nil { + return nil, err + } + + maxTimeout := api.b.RPCTxSyncMaxTimeout() + defaultTimeout := api.b.RPCTxSyncDefaultTimeout() + + timeout := defaultTimeout + if timeoutMs != nil && *timeoutMs > 0 { + req := time.Duration(*timeoutMs) * time.Millisecond + if req > maxTimeout { + timeout = maxTimeout + } else { + timeout = req + } + } + + receiptCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + // Fast path. + if r, err := api.GetTransactionReceipt(receiptCtx, hash); err == nil && r != nil { + return r, nil + } + + for { + select { + case <-receiptCtx.Done(): + // If server-side wait window elapsed, return the structured timeout. + if errors.Is(receiptCtx.Err(), context.DeadlineExceeded) { + return nil, &txSyncTimeoutError{ + msg: fmt.Sprintf("The transaction was added to the transaction pool but wasn't processed in %v.", timeout), + hash: hash, + } + } + return nil, receiptCtx.Err() + + case err, ok := <-subErrCh: + if !ok { + return nil, errSubClosed + } + return nil, err + + case ev, ok := <-ch: + if !ok { + return nil, errSubClosed + } + rs := ev.Receipts + txs := ev.Transactions + if len(rs) == 0 || len(rs) != len(txs) { + continue + } + for i := range rs { + if rs[i].TxHash == hash { + if rs[i].BlockNumber != nil && rs[i].BlockHash != (common.Hash{}) { + signer := types.LatestSigner(api.b.ChainConfig()) + return MarshalReceipt( + rs[i], + rs[i].BlockHash, + rs[i].BlockNumber.Uint64(), + signer, + txs[i], + int(rs[i].TransactionIndex), + ), nil + } + return api.GetTransactionReceipt(receiptCtx, hash) + } + } + } + } +} + // Sign calculates an ECDSA signature for: // keccak256("\x19Ethereum Signed Message:\n" + len(message) + message). // diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index d3278c04e799..aaa002b5ec00 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -440,6 +440,19 @@ type testBackend struct { pending *types.Block pendingReceipts types.Receipts + + chainFeed *event.Feed + autoMine bool + + sentTx *types.Transaction + sentTxHash common.Hash + + syncDefaultTimeout time.Duration + syncMaxTimeout time.Duration +} + +func fakeBlockHash(txh common.Hash) common.Hash { + return crypto.Keccak256Hash([]byte("testblock"), txh.Bytes()) } func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend { @@ -466,6 +479,7 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.E acc: acc, pending: blocks[n], pendingReceipts: receipts[n], + chainFeed: new(event.Feed), } return backend } @@ -587,19 +601,64 @@ func (b testBackend) GetEVM(ctx context.Context, state *state.StateDB, header *t return vm.NewEVM(context, state, b.chain.Config(), *vmConfig) } func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - panic("implement me") + return b.chainFeed.Subscribe(ch) } func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { panic("implement me") } -func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - panic("implement me") +func (b *testBackend) SendTx(ctx context.Context, tx *types.Transaction) error { + b.sentTx = tx + b.sentTxHash = tx.Hash() + + if b.autoMine { + // Synthesize a "mined" receipt at head+1 + num := b.chain.CurrentHeader().Number.Uint64() + 1 + receipt := &types.Receipt{ + TxHash: tx.Hash(), + Status: types.ReceiptStatusSuccessful, + BlockHash: fakeBlockHash(tx.Hash()), + BlockNumber: new(big.Int).SetUint64(num), + TransactionIndex: 0, + CumulativeGasUsed: 21000, + GasUsed: 21000, + } + // Broadcast a ChainEvent that includes the receipts and txs + b.chainFeed.Send(core.ChainEvent{ + Header: &types.Header{ + Number: new(big.Int).SetUint64(num), + }, + Receipts: types.Receipts{receipt}, + Transactions: types.Transactions{tx}, + }) + } + return nil } -func (b testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { +func (b *testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { + // Treat the auto-mined tx as canonically placed at head+1. + if b.autoMine && txHash == b.sentTxHash { + num := b.chain.CurrentHeader().Number.Uint64() + 1 + return true, b.sentTx, fakeBlockHash(txHash), num, 0 + } tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash) return tx != nil, tx, blockHash, blockNumber, index } -func (b testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) { +func (b *testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) { + if b.autoMine && tx != nil && tx.Hash() == b.sentTxHash && + blockHash == fakeBlockHash(tx.Hash()) && + blockIndex == 0 && + blockNumber == b.chain.CurrentHeader().Number.Uint64()+1 { + return &types.Receipt{ + Type: tx.Type(), + Status: types.ReceiptStatusSuccessful, + CumulativeGasUsed: 21000, + GasUsed: 21000, + EffectiveGasPrice: big.NewInt(1), + BlockHash: blockHash, + BlockNumber: new(big.Int).SetUint64(blockNumber), + TransactionIndex: 0, + TxHash: tx.Hash(), + }, nil + } return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex) } func (b testBackend) TxIndexDone() bool { @@ -3889,3 +3948,109 @@ func (b configTimeBackend) HeaderByNumber(_ context.Context, n rpc.BlockNumber) func (b configTimeBackend) CurrentHeader() *types.Header { return &types.Header{Time: b.time} } + +func (b *testBackend) RPCTxSyncDefaultTimeout() time.Duration { + if b.syncDefaultTimeout != 0 { + return b.syncDefaultTimeout + } + return 2 * time.Second +} +func (b *testBackend) RPCTxSyncMaxTimeout() time.Duration { + if b.syncMaxTimeout != 0 { + return b.syncMaxTimeout + } + return 5 * time.Minute +} +func (b *backendMock) RPCTxSyncDefaultTimeout() time.Duration { return 2 * time.Second } +func (b *backendMock) RPCTxSyncMaxTimeout() time.Duration { return 5 * time.Minute } + +func makeSignedRaw(t *testing.T, api *TransactionAPI, from, to common.Address, value *big.Int) (hexutil.Bytes, *types.Transaction) { + t.Helper() + + fillRes, err := api.FillTransaction(context.Background(), TransactionArgs{ + From: &from, + To: &to, + Value: (*hexutil.Big)(value), + }) + if err != nil { + t.Fatalf("FillTransaction failed: %v", err) + } + signRes, err := api.SignTransaction(context.Background(), argsFromTransaction(fillRes.Tx, from)) + if err != nil { + t.Fatalf("SignTransaction failed: %v", err) + } + return signRes.Raw, signRes.Tx +} + +// makeSelfSignedRaw is a convenience for a 0-ETH self-transfer. +func makeSelfSignedRaw(t *testing.T, api *TransactionAPI, addr common.Address) (hexutil.Bytes, *types.Transaction) { + return makeSignedRaw(t, api, addr, addr, big.NewInt(0)) +} + +func TestSendRawTransactionSync_Success(t *testing.T) { + t.Parallel() + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{}, + } + b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil) + b.autoMine = true // immediately “mines” the tx in-memory + + api := NewTransactionAPI(b, new(AddrLocker)) + + raw, _ := makeSelfSignedRaw(t, api, b.acc.Address) + + receipt, err := api.SendRawTransactionSync(context.Background(), raw, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if receipt == nil { + t.Fatalf("expected non-nil receipt") + } + if _, ok := receipt["blockNumber"]; !ok { + t.Fatalf("expected blockNumber in receipt, got %#v", receipt) + } +} + +func TestSendRawTransactionSync_Timeout(t *testing.T) { + t.Parallel() + + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{}, + } + b := newTestBackend(t, 0, genesis, ethash.NewFaker(), nil) + b.autoMine = false // don't mine, should time out + + api := NewTransactionAPI(b, new(AddrLocker)) + + raw, _ := makeSelfSignedRaw(t, api, b.acc.Address) + + timeout := hexutil.Uint64(200) // 200ms + receipt, err := api.SendRawTransactionSync(context.Background(), raw, &timeout) + + if receipt != nil { + t.Fatalf("expected nil receipt, got %#v", receipt) + } + if err == nil { + t.Fatalf("expected timeout error, got nil") + } + // assert error shape & data (hash) + var de interface { + ErrorCode() int + ErrorData() interface{} + } + if !errors.As(err, &de) { + t.Fatalf("expected data error with code/data, got %T %v", err, err) + } + if de.ErrorCode() != errCodeTxSyncTimeout { + t.Fatalf("expected code %d, got %d", errCodeTxSyncTimeout, de.ErrorCode()) + } + tx := new(types.Transaction) + if e := tx.UnmarshalBinary(raw); e != nil { + t.Fatal(e) + } + if got, want := de.ErrorData(), tx.Hash().Hex(); got != want { + t.Fatalf("expected ErrorData=%s, got %v", want, got) + } +} diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index f709a1fcdcc8..af3d592b82b6 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -53,6 +53,8 @@ type Backend interface { RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs UnprotectedAllowed() bool // allows only for EIP155 transactions. + RPCTxSyncDefaultTimeout() time.Duration + RPCTxSyncMaxTimeout() time.Duration // Blockchain API SetHead(number uint64) diff --git a/internal/ethapi/errors.go b/internal/ethapi/errors.go index 154938fa0e3d..30711a016790 100644 --- a/internal/ethapi/errors.go +++ b/internal/ethapi/errors.go @@ -21,6 +21,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" @@ -33,6 +34,11 @@ type revertError struct { reason string // revert reason hex encoded } +type txSyncTimeoutError struct { + msg string + hash common.Hash +} + // ErrorCode returns the JSON error code for a revert. // See: https://ethereum.org/en/developers/docs/apis/json-rpc/#error-codes func (e *revertError) ErrorCode() int { @@ -108,6 +114,7 @@ const ( errCodeInvalidParams = -32602 errCodeReverted = -32000 errCodeVMError = -32015 + errCodeTxSyncTimeout = 4 ) func txValidationError(err error) *invalidTxError { @@ -168,3 +175,7 @@ type blockGasLimitReachedError struct{ message string } func (e *blockGasLimitReachedError) Error() string { return e.message } func (e *blockGasLimitReachedError) ErrorCode() int { return errCodeBlockGasLimitReached } + +func (e *txSyncTimeoutError) Error() string { return e.msg } +func (e *txSyncTimeoutError) ErrorCode() int { return errCodeTxSyncTimeout } +func (e *txSyncTimeoutError) ErrorData() interface{} { return e.hash.Hex() } From e52642d32723359b2bc2610e8c23fa8fc55520b0 Mon Sep 17 00:00:00 2001 From: Youssef Azzaoui Date: Thu, 16 Oct 2025 14:19:44 -0300 Subject: [PATCH 137/228] core/state: state copy bugfixes with Verkle Trees (#31696) This change addresses critical issues in the state object duplication process specific to Verkle trie implementations. Without these modifications, updates to state objects fail to propagate correctly through the trie structure after a statedb copy operation, leading to inaccuracies in the computation of the state root hash. --------- Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- core/state/database.go | 2 ++ core/state/state_object.go | 15 ++++++++++++++- trie/transition.go | 3 ++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 3a0ac422ee47..58d0ccfe8292 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -302,6 +302,8 @@ func mustCopyTrie(t Trie) Trie { return t.Copy() case *trie.VerkleTrie: return t.Copy() + case *trie.TransitionTrie: + return t.Copy() default: panic(fmt.Errorf("unknown trie type %T", t)) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 293875050313..fdeb4254c1bc 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/holiman/uint256" ) @@ -494,8 +495,20 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { selfDestructed: s.selfDestructed, newContract: s.newContract, } - if s.trie != nil { + + switch s.trie.(type) { + case *trie.VerkleTrie: + // Verkle uses only one tree, and the copy has already been + // made in mustCopyTrie. + obj.trie = db.trie + case *trie.TransitionTrie: + // Same thing for the transition tree, since the MPT is + // read-only. + obj.trie = db.trie + case *trie.StateTrie: obj.trie = mustCopyTrie(s.trie) + case nil: + // do nothing } return obj } diff --git a/trie/transition.go b/trie/transition.go index da49c6cdc2c4..c6eecd39376d 100644 --- a/trie/transition.go +++ b/trie/transition.go @@ -211,7 +211,8 @@ func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error { func (t *TransitionTrie) Copy() *TransitionTrie { return &TransitionTrie{ overlay: t.overlay.Copy(), - base: t.base.Copy(), + // base in immutable, so there is no need to copy it + base: t.base, storage: t.storage, } } From 6fa85f808f6212fe43174fdcba77dff5f2f80ea6 Mon Sep 17 00:00:00 2001 From: ucwong Date: Fri, 17 Oct 2025 03:35:44 +0100 Subject: [PATCH 138/228] eth/ethconfig : fix eth generate config (#32929) --- eth/ethconfig/gen_config.go | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 6f6e541368fb..6f18dc34c5a2 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -58,10 +58,12 @@ func (c Config) MarshalTOML() (interface{}, error) { RPCGasCap uint64 RPCEVMTimeout time.Duration RPCTxFeeCap float64 - OverrideOsaka *uint64 `toml:",omitempty"` - OverrideBPO1 *uint64 `toml:",omitempty"` - OverrideBPO2 *uint64 `toml:",omitempty"` - OverrideVerkle *uint64 `toml:",omitempty"` + OverrideOsaka *uint64 `toml:",omitempty"` + OverrideBPO1 *uint64 `toml:",omitempty"` + OverrideBPO2 *uint64 `toml:",omitempty"` + OverrideVerkle *uint64 `toml:",omitempty"` + TxSyncDefaultTimeout time.Duration `toml:",omitempty"` + TxSyncMaxTimeout time.Duration `toml:",omitempty"` } var enc Config enc.Genesis = c.Genesis @@ -109,6 +111,8 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.OverrideBPO1 = c.OverrideBPO1 enc.OverrideBPO2 = c.OverrideBPO2 enc.OverrideVerkle = c.OverrideVerkle + enc.TxSyncDefaultTimeout = c.TxSyncDefaultTimeout + enc.TxSyncMaxTimeout = c.TxSyncMaxTimeout return &enc, nil } @@ -156,10 +160,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { RPCGasCap *uint64 RPCEVMTimeout *time.Duration RPCTxFeeCap *float64 - OverrideOsaka *uint64 `toml:",omitempty"` - OverrideBPO1 *uint64 `toml:",omitempty"` - OverrideBPO2 *uint64 `toml:",omitempty"` - OverrideVerkle *uint64 `toml:",omitempty"` + OverrideOsaka *uint64 `toml:",omitempty"` + OverrideBPO1 *uint64 `toml:",omitempty"` + OverrideBPO2 *uint64 `toml:",omitempty"` + OverrideVerkle *uint64 `toml:",omitempty"` + TxSyncDefaultTimeout *time.Duration `toml:",omitempty"` + TxSyncMaxTimeout *time.Duration `toml:",omitempty"` } var dec Config if err := unmarshal(&dec); err != nil { @@ -300,5 +306,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.OverrideVerkle != nil { c.OverrideVerkle = dec.OverrideVerkle } + if dec.TxSyncDefaultTimeout != nil { + c.TxSyncDefaultTimeout = *dec.TxSyncDefaultTimeout + } + if dec.TxSyncMaxTimeout != nil { + c.TxSyncMaxTimeout = *dec.TxSyncMaxTimeout + } return nil } From bc5f27ab36d8c85cc3b6d64d49dd3a570ce3ad65 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 17 Oct 2025 13:34:35 +0800 Subject: [PATCH 139/228] eth, internal: add blob conversion for SendRawTransactionSync (#32930) --- internal/ethapi/api.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index eb7a34474cf3..d7cf47468c48 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1675,9 +1675,20 @@ func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hex return nil, err } + // Convert legacy blob transaction proofs. + // TODO: remove in go-ethereum v1.17.x + if sc := tx.BlobTxSidecar(); sc != nil { + exp := api.currentBlobSidecarVersion() + if sc.Version == types.BlobSidecarVersion0 && exp == types.BlobSidecarVersion1 { + if err := sc.ToV1(); err != nil { + return nil, fmt.Errorf("blob sidecar conversion failed: %v", err) + } + tx = tx.WithBlobTxSidecar(sc) + } + } + ch := make(chan core.ChainEvent, 128) sub := api.b.SubscribeChainEvent(ch) - subErrCh := sub.Err() defer sub.Unsubscribe() hash, err := SubmitTransaction(ctx, api.b, tx) @@ -1685,10 +1696,11 @@ func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hex return nil, err } - maxTimeout := api.b.RPCTxSyncMaxTimeout() - defaultTimeout := api.b.RPCTxSyncDefaultTimeout() - - timeout := defaultTimeout + var ( + maxTimeout = api.b.RPCTxSyncMaxTimeout() + defaultTimeout = api.b.RPCTxSyncDefaultTimeout() + timeout = defaultTimeout + ) if timeoutMs != nil && *timeoutMs > 0 { req := time.Duration(*timeoutMs) * time.Millisecond if req > maxTimeout { @@ -1697,7 +1709,6 @@ func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hex timeout = req } } - receiptCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() @@ -1706,19 +1717,20 @@ func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hex return r, nil } + // Monitor the receipts for { select { case <-receiptCtx.Done(): // If server-side wait window elapsed, return the structured timeout. if errors.Is(receiptCtx.Err(), context.DeadlineExceeded) { return nil, &txSyncTimeoutError{ - msg: fmt.Sprintf("The transaction was added to the transaction pool but wasn't processed in %v.", timeout), + msg: fmt.Sprintf("The transaction was added to the transaction pool but wasn't processed in %v", timeout), hash: hash, } } return nil, receiptCtx.Err() - case err, ok := <-subErrCh: + case err, ok := <-sub.Err(): if !ok { return nil, errSubClosed } @@ -1728,8 +1740,7 @@ func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hex if !ok { return nil, errSubClosed } - rs := ev.Receipts - txs := ev.Transactions + rs, txs := ev.Receipts, ev.Transactions if len(rs) == 0 || len(rs) != len(txs) { continue } From a6d0a9d3c5bbd844189ad3b904b557f76b81468c Mon Sep 17 00:00:00 2001 From: CertiK <138698582+CertiK-Geth@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:54:56 +0800 Subject: [PATCH 140/228] cmd/utils: use maximum uint64 value for receipt chain insertion (#32934) --- cmd/utils/cmd.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index db7bd691d8e1..3e337a3d00a3 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -25,6 +25,7 @@ import ( "errors" "fmt" "io" + "math" "math/big" "os" "os/signal" @@ -311,7 +312,7 @@ func ImportHistory(chain *core.BlockChain, dir string, network string) error { return fmt.Errorf("error reading receipts %d: %w", it.Number(), err) } encReceipts := types.EncodeBlockReceiptLists([]types.Receipts{receipts}) - if _, err := chain.InsertReceiptChain([]*types.Block{block}, encReceipts, 2^64-1); err != nil { + if _, err := chain.InsertReceiptChain([]*types.Block{block}, encReceipts, math.MaxUint64); err != nil { return fmt.Errorf("error inserting body %d: %w", it.Number(), err) } imported += 1 From d2e2383e596c9eb9c1bb47e91d246ecbfe95e89d Mon Sep 17 00:00:00 2001 From: Bosul Mun Date: Mon, 20 Oct 2025 11:10:58 +0900 Subject: [PATCH 141/228] eth/fetcher: add metrics for tracking slow peers (#32964) This PR introduces two new metrics to monitor slow peers - One tracks the number of slow peers. - The other measures the time it takes for those peers to become "unfrozen" These metrics help with monitoring and evaluating the need for future optimization of the transaction fetcher and peer management, for example i n peer scoring and prioritization. Additionally, this PR moves the fetcher metrics into a separate file, `eth/fetcher/metrics.go`. --- eth/fetcher/metrics.go | 59 +++++++++++++++++++++++++++++++++++++++ eth/fetcher/tx_fetcher.go | 39 ++++++-------------------- 2 files changed, 68 insertions(+), 30 deletions(-) create mode 100644 eth/fetcher/metrics.go diff --git a/eth/fetcher/metrics.go b/eth/fetcher/metrics.go new file mode 100644 index 000000000000..fd1678dd3060 --- /dev/null +++ b/eth/fetcher/metrics.go @@ -0,0 +1,59 @@ +// 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 Date: Mon, 20 Oct 2025 11:26:55 +0900 Subject: [PATCH 142/228] eth/fetcher: remove dangling peers from alternates (#32947) This PR removes dangling peers in `alternates` map In the current code, a dropped peer is removed from alternates for only the specific transaction hash it was requesting. If that peer is listed as an alternate for other transaction hashes, those entries still stick around in alternates/announced even though that peer already got dropped. --- eth/fetcher/tx_fetcher.go | 6 +++- eth/fetcher/tx_fetcher_test.go | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go index 5ba72e6b013f..d919ac8a5f3d 100644 --- a/eth/fetcher/tx_fetcher.go +++ b/eth/fetcher/tx_fetcher.go @@ -795,6 +795,10 @@ func (f *TxFetcher) loop() { if len(f.announced[hash]) == 0 { delete(f.announced, hash) } + delete(f.alternates[hash], drop.peer) + if len(f.alternates[hash]) == 0 { + delete(f.alternates, hash) + } } delete(f.announces, drop.peer) } @@ -858,7 +862,7 @@ func (f *TxFetcher) rescheduleWait(timer *mclock.Timer, trigger chan struct{}) { // This method is a bit "flaky" "by design". In theory the timeout timer only ever // should be rescheduled if some request is pending. In practice, a timeout will // cause the timer to be rescheduled every 5 secs (until the peer comes through or -// disconnects). This is a limitation of the fetcher code because we don't trac +// disconnects). This is a limitation of the fetcher code because we don't track // pending requests and timed out requests separately. Without double tracking, if // we simply didn't reschedule the timer on all-timeout then the timer would never // be set again since len(request) > 0 => something's running. diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go index 0f05a1c995c9..bb41f6293202 100644 --- a/eth/fetcher/tx_fetcher_test.go +++ b/eth/fetcher/tx_fetcher_test.go @@ -1858,6 +1858,56 @@ func TestBlobTransactionAnnounce(t *testing.T) { }) } +func TestTransactionFetcherDropAlternates(t *testing.T) { + testTransactionFetcherParallel(t, txFetcherTest{ + init: func() *TxFetcher { + return NewTxFetcher( + func(common.Hash) bool { return false }, + func(txs []*types.Transaction) []error { + return make([]error, len(txs)) + }, + func(string, []common.Hash) error { return nil }, + nil, + ) + }, + steps: []interface{}{ + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}}, + doWait{time: txArriveTimeout, step: true}, + doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{uint32(testTxs[0].Size())}}, + + isScheduled{ + tracking: map[string][]announce{ + "A": { + {testTxsHashes[0], testTxs[0].Type(), uint32(testTxs[0].Size())}, + }, + "B": { + {testTxsHashes[0], testTxs[0].Type(), uint32(testTxs[0].Size())}, + }, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + doDrop("B"), + + isScheduled{ + tracking: map[string][]announce{ + "A": { + {testTxsHashes[0], testTxs[0].Type(), uint32(testTxs[0].Size())}, + }, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + }, + }, + doDrop("A"), + isScheduled{ + tracking: nil, fetching: nil, + }, + }, + }) +} + func testTransactionFetcherParallel(t *testing.T, tt txFetcherTest) { t.Parallel() testTransactionFetcher(t, tt) From 5af34cc223a19b21d60c2167cd51fb19d2c29149 Mon Sep 17 00:00:00 2001 From: hero5512 Date: Sun, 19 Oct 2025 22:29:46 -0400 Subject: [PATCH 143/228] triedb/pathdb: fix index out of range panic in decodeSingle (#32937) Fixes TestCorruptedKeySection flaky test failure. https://github.com/ethereum/go-ethereum/actions/runs/18600235182/job/53037084761?pr=32920 --- triedb/pathdb/history_trienode.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/triedb/pathdb/history_trienode.go b/triedb/pathdb/history_trienode.go index f5eb590a9a5c..2f31238612cd 100644 --- a/triedb/pathdb/history_trienode.go +++ b/triedb/pathdb/history_trienode.go @@ -370,11 +370,15 @@ func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]st for keyOff < keyLimit { // Validate the key and value offsets within the single trie data chunk if items%trienodeDataBlockRestartLen == 0 { - if keyOff != int(keyOffsets[items/trienodeDataBlockRestartLen]) { - return nil, fmt.Errorf("key offset is not matched, recorded: %d, want: %d", keyOffsets[items/trienodeDataBlockRestartLen], keyOff) + restartIndex := items / trienodeDataBlockRestartLen + if restartIndex >= len(keyOffsets) { + return nil, fmt.Errorf("restart index out of range: %d, available restarts: %d", restartIndex, len(keyOffsets)) } - if valOff != int(valOffsets[items/trienodeDataBlockRestartLen]) { - return nil, fmt.Errorf("value offset is not matched, recorded: %d, want: %d", valOffsets[items/trienodeDataBlockRestartLen], valOff) + if keyOff != int(keyOffsets[restartIndex]) { + return nil, fmt.Errorf("key offset is not matched, recorded: %d, want: %d", keyOffsets[restartIndex], keyOff) + } + if valOff != int(valOffsets[restartIndex]) { + return nil, fmt.Errorf("value offset is not matched, recorded: %d, want: %d", valOffsets[restartIndex], valOff) } } // Resolve the entry from key section From 4b85736c8d9a34cf82d4061bcbd9c126c94d85e1 Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 20 Oct 2025 10:35:14 +0800 Subject: [PATCH 144/228] core/types: prealloc map in HashDifference as in TxDifference (#32946) --- core/types/transaction.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/transaction.go b/core/types/transaction.go index be8e90364ef9..e98563b85fc8 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -648,7 +648,7 @@ func TxDifference(a, b Transactions) Transactions { func HashDifference(a, b []common.Hash) []common.Hash { keep := make([]common.Hash, 0, len(a)) - remove := make(map[common.Hash]struct{}) + remove := make(map[common.Hash]struct{}, len(b)) for _, hash := range b { remove[hash] = struct{}{} } From e79261da40f2a72dd0f6c86a5f4524839940da06 Mon Sep 17 00:00:00 2001 From: maskpp Date: Mon, 20 Oct 2025 16:18:17 +0800 Subject: [PATCH 145/228] eth/filters: avoid rebuild the hash map multi times (#32965) --- eth/filters/filter.go | 22 ++-------------------- eth/filters/filter_system.go | 12 ++++++++---- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 02399bc80180..422e5cd67b09 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -563,7 +563,7 @@ type ReceiptWithTx struct { // In addition to returning receipts, it also returns the corresponding transactions. // This is because receipts only contain low-level data, while user-facing data // may require additional information from the Transaction. -func filterReceipts(txHashes []common.Hash, ev core.ChainEvent) []*ReceiptWithTx { +func filterReceipts(txHashes map[common.Hash]bool, ev core.ChainEvent) []*ReceiptWithTx { var ret []*ReceiptWithTx receipts := ev.Receipts @@ -583,27 +583,9 @@ func filterReceipts(txHashes []common.Hash, ev core.ChainEvent) []*ReceiptWithTx Transaction: txs[i], } } - } else if len(txHashes) == 1 { - // Filter by single transaction hash. - // This is a common case, so we distinguish it from filtering by multiple tx hashes and made a small optimization. - for i, receipt := range receipts { - if receipt.TxHash == txHashes[0] { - ret = append(ret, &ReceiptWithTx{ - Receipt: receipt, - Transaction: txs[i], - }) - break - } - } } else { - // Filter by multiple transaction hashes. - txHashMap := make(map[common.Hash]bool, len(txHashes)) - for _, hash := range txHashes { - txHashMap[hash] = true - } - for i, receipt := range receipts { - if txHashMap[receipt.TxHash] { + if txHashes[receipt.TxHash] { ret = append(ret, &ReceiptWithTx{ Receipt: receipt, Transaction: txs[i], diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 02783fa5ec56..f10e6a277bb7 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -185,9 +185,9 @@ type subscription struct { txs chan []*types.Transaction headers chan *types.Header receipts chan []*ReceiptWithTx - txHashes []common.Hash // contains transaction hashes for transactionReceipts subscription filtering - installed chan struct{} // closed when the filter is installed - err chan error // closed when the filter is uninstalled + txHashes map[common.Hash]bool // contains transaction hashes for transactionReceipts subscription filtering + installed chan struct{} // closed when the filter is installed + err chan error // closed when the filter is uninstalled } // EventSystem creates subscriptions, processes events and broadcasts them to the @@ -403,6 +403,10 @@ func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subsc // transactions when they are included in blocks. If txHashes is provided, only receipts // for those specific transaction hashes will be delivered. func (es *EventSystem) SubscribeTransactionReceipts(txHashes []common.Hash, receipts chan []*ReceiptWithTx) *Subscription { + hashSet := make(map[common.Hash]bool) + for _, h := range txHashes { + hashSet[h] = true + } sub := &subscription{ id: rpc.NewID(), typ: TransactionReceiptsSubscription, @@ -411,7 +415,7 @@ func (es *EventSystem) SubscribeTransactionReceipts(txHashes []common.Hash, rece txs: make(chan []*types.Transaction), headers: make(chan *types.Header), receipts: receipts, - txHashes: txHashes, + txHashes: hashSet, installed: make(chan struct{}), err: make(chan error), } From 56f4f2b2b0f807d9d986753096ea4f97960e3bdd Mon Sep 17 00:00:00 2001 From: jwasinger Date: Mon, 20 Oct 2025 17:51:29 +0800 Subject: [PATCH 146/228] core/vm: don't call SetCode after contract creation if initcode didn't return anything (#32916) The code change is a noop here, and the tracing hook shouldn't be invoked if the account code doesn't actually change. --- core/vm/evm.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 88ef1cf121e1..8975c791c842 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -601,7 +601,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b } } - evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation) + if len(ret) > 0 { + evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation) + } return ret, nil } From f0f0294051f1b36fb33a1f77c18add7be2f1a420 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:52:02 +0200 Subject: [PATCH 147/228] cmd/keeper: use the ziren keccak precompile (#32816) Uses the go module's `replace` directive to delegate keccak computation to precompiles. This is still in draft because it needs more testing. Also, it relies on a PR that I created, that hasn't been merged yet. _Note that this PR doesn't implement the stateful keccak state structure, and it reverts to the current behavior. This is a bit silly since this is what is used in the tree root computation. The runtime doesn't currently export the sponge. I will see if I can fix that in a further PR, but it is going to take more time. In the meantime, this is a useful first step_ --- cmd/keeper/getpayload_ziren.go | 2 +- cmd/keeper/go.mod | 7 ++-- cmd/keeper/go.sum | 4 +-- crypto/crypto.go | 48 ------------------------- crypto/keccak.go | 63 +++++++++++++++++++++++++++++++++ crypto/keccak_ziren.go | 64 ++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ 8 files changed, 135 insertions(+), 56 deletions(-) create mode 100644 crypto/keccak.go create mode 100644 crypto/keccak_ziren.go diff --git a/cmd/keeper/getpayload_ziren.go b/cmd/keeper/getpayload_ziren.go index 11c5845bcc13..bc373db94f5b 100644 --- a/cmd/keeper/getpayload_ziren.go +++ b/cmd/keeper/getpayload_ziren.go @@ -19,7 +19,7 @@ package main import ( - zkruntime "github.com/zkMIPS/zkMIPS/crates/go-runtime/zkm_runtime" + zkruntime "github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime" ) // getInput reads the input payload from the zkVM runtime environment. diff --git a/cmd/keeper/go.mod b/cmd/keeper/go.mod index 16094d16b1d8..2b12297a7ad6 100644 --- a/cmd/keeper/go.mod +++ b/cmd/keeper/go.mod @@ -3,8 +3,8 @@ module github.com/ethereum/go-ethereum/cmd/keeper go 1.24.0 require ( + github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 github.com/ethereum/go-ethereum v0.0.0-00010101000000-000000000000 - github.com/zkMIPS/zkMIPS/crates/go-runtime/zkm_runtime v0.0.0-20250915074013-fbc07aa2c6f5 ) require ( @@ -43,7 +43,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect ) -replace ( - github.com/ethereum/go-ethereum => ../../ - github.com/zkMIPS/zkMIPS/crates/go-runtime/zkm_runtime => github.com/weilzkm/zkMIPS/crates/go-runtime/zkvm_runtime v0.0.0-20250915074013-fbc07aa2c6f5 -) +replace github.com/ethereum/go-ethereum => ../../ diff --git a/cmd/keeper/go.sum b/cmd/keeper/go.sum index 3eaef469dcc9..b280a368d0fd 100644 --- a/cmd/keeper/go.sum +++ b/cmd/keeper/go.sum @@ -1,5 +1,7 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 h1:1zYrtlhrZ6/b6SAjLSfKzWtdgqK0U+HtH/VcBWh1BaU= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= @@ -117,8 +119,6 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/weilzkm/zkMIPS/crates/go-runtime/zkvm_runtime v0.0.0-20250915074013-fbc07aa2c6f5 h1:MxKlbmI7Dta6O6Nsc9OAer/rOltjoL11CVLMqCiYnxU= -github.com/weilzkm/zkMIPS/crates/go-runtime/zkvm_runtime v0.0.0-20250915074013-fbc07aa2c6f5/go.mod h1:zk/SUgiiVz2U1ufZ+yM2MHPbD93W25KH5zK3qAxXbT4= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= diff --git a/crypto/crypto.go b/crypto/crypto.go index 09596c05ce87..db6b6ee0716a 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -28,12 +28,10 @@ import ( "io" "math/big" "os" - "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) // SignatureLength indicates the byte length required to carry a signature with recovery id. @@ -69,17 +67,6 @@ type KeccakState interface { Read([]byte) (int, error) } -// NewKeccakState creates a new KeccakState -func NewKeccakState() KeccakState { - return sha3.NewLegacyKeccak256().(KeccakState) -} - -var hasherPool = sync.Pool{ - New: func() any { - return sha3.NewLegacyKeccak256().(KeccakState) - }, -} - // HashData hashes the provided data using the KeccakState and returns a 32 byte hash func HashData(kh KeccakState, data []byte) (h common.Hash) { kh.Reset() @@ -88,41 +75,6 @@ func HashData(kh KeccakState, data []byte) (h common.Hash) { return h } -// Keccak256 calculates and returns the Keccak256 hash of the input data. -func Keccak256(data ...[]byte) []byte { - b := make([]byte, 32) - d := hasherPool.Get().(KeccakState) - d.Reset() - for _, b := range data { - d.Write(b) - } - d.Read(b) - hasherPool.Put(d) - return b -} - -// Keccak256Hash calculates and returns the Keccak256 hash of the input data, -// converting it to an internal Hash data structure. -func Keccak256Hash(data ...[]byte) (h common.Hash) { - d := hasherPool.Get().(KeccakState) - d.Reset() - for _, b := range data { - d.Write(b) - } - d.Read(h[:]) - hasherPool.Put(d) - return h -} - -// Keccak512 calculates and returns the Keccak512 hash of the input data. -func Keccak512(data ...[]byte) []byte { - d := sha3.NewLegacyKeccak512() - for _, b := range data { - d.Write(b) - } - return d.Sum(nil) -} - // CreateAddress creates an ethereum address given the bytes and the nonce func CreateAddress(b common.Address, nonce uint64) common.Address { data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) diff --git a/crypto/keccak.go b/crypto/keccak.go new file mode 100644 index 000000000000..0ad79a63c101 --- /dev/null +++ b/crypto/keccak.go @@ -0,0 +1,63 @@ +// 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 . + +//go:build !ziren + +package crypto + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common" + "golang.org/x/crypto/sha3" +) + +// NewKeccakState creates a new KeccakState +func NewKeccakState() KeccakState { + return sha3.NewLegacyKeccak256().(KeccakState) +} + +var hasherPool = sync.Pool{ + New: func() any { + return sha3.NewLegacyKeccak256().(KeccakState) + }, +} + +// Keccak256 calculates and returns the Keccak256 hash of the input data. +func Keccak256(data ...[]byte) []byte { + b := make([]byte, 32) + d := hasherPool.Get().(KeccakState) + d.Reset() + for _, b := range data { + d.Write(b) + } + d.Read(b) + hasherPool.Put(d) + return b +} + +// Keccak256Hash calculates and returns the Keccak256 hash of the input data, +// converting it to an internal Hash data structure. +func Keccak256Hash(data ...[]byte) (h common.Hash) { + d := hasherPool.Get().(KeccakState) + d.Reset() + for _, b := range data { + d.Write(b) + } + d.Read(h[:]) + hasherPool.Put(d) + return h +} diff --git a/crypto/keccak_ziren.go b/crypto/keccak_ziren.go new file mode 100644 index 000000000000..033c0ec42cf9 --- /dev/null +++ b/crypto/keccak_ziren.go @@ -0,0 +1,64 @@ +// 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 . + +//go:build ziren + +package crypto + +import ( + "github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime" + "github.com/ethereum/go-ethereum/common" + "golang.org/x/crypto/sha3" +) + +// NewKeccakState creates a new KeccakState +// For now, we fallback to the original implementation for the stateful interface. +// TODO: Implement a stateful wrapper around zkvm_runtime.Keccak256 if needed. +func NewKeccakState() KeccakState { + return sha3.NewLegacyKeccak256().(KeccakState) +} + +// Keccak256 calculates and returns the Keccak256 hash using the Ziren zkvm_runtime implementation. +func Keccak256(data ...[]byte) []byte { + // For multiple data chunks, concatenate them + if len(data) == 0 { + result := zkvm_runtime.Keccak256(nil) + return result[:] + } + if len(data) == 1 { + result := zkvm_runtime.Keccak256(data[0]) + return result[:] + } + + // Concatenate multiple data chunks + var totalLen int + for _, d := range data { + totalLen += len(d) + } + + combined := make([]byte, 0, totalLen) + for _, d := range data { + combined = append(combined, d...) + } + + result := zkvm_runtime.Keccak256(combined) + return result[:] +} + +// Keccak256Hash calculates and returns the Keccak256 hash as a Hash using the Ziren zkvm_runtime implementation. +func Keccak256Hash(data ...[]byte) common.Hash { + return common.Hash(Keccak256(data...)) +} diff --git a/go.mod b/go.mod index c91cc81d21cc..ae5e4cc11416 100644 --- a/go.mod +++ b/go.mod @@ -81,6 +81,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/DataDog/zstd v1.4.5 // indirect + github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 github.com/StackExchange/wmi v1.2.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect diff --git a/go.sum b/go.sum index 779bcde84675..8122f4b54867 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 h1:1zYrtlhrZ6/b6SAjLSfKzWtdgqK0U+HtH/VcBWh1BaU= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= From 73a2c31751930b37d1d371b4da19d95058bfd105 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 20 Oct 2025 17:24:07 +0200 Subject: [PATCH 148/228] params: enable osaka on dev mode (#32917) enables the osaka fork on dev mode --------- Co-authored-by: Sina Mahmoodi --- core/genesis.go | 35 ++++++++++++++++++----------------- params/config.go | 2 ++ 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/core/genesis.go b/core/genesis.go index 2fd044c70a7d..d0d490874d07 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -669,23 +669,24 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis { BaseFee: big.NewInt(params.InitialBaseFee), Difficulty: big.NewInt(0), Alloc: map[common.Address]types.Account{ - common.BytesToAddress([]byte{0x01}): {Balance: big.NewInt(1)}, // ECRecover - common.BytesToAddress([]byte{0x02}): {Balance: big.NewInt(1)}, // SHA256 - common.BytesToAddress([]byte{0x03}): {Balance: big.NewInt(1)}, // RIPEMD - common.BytesToAddress([]byte{0x04}): {Balance: big.NewInt(1)}, // Identity - common.BytesToAddress([]byte{0x05}): {Balance: big.NewInt(1)}, // ModExp - common.BytesToAddress([]byte{0x06}): {Balance: big.NewInt(1)}, // ECAdd - common.BytesToAddress([]byte{0x07}): {Balance: big.NewInt(1)}, // ECScalarMul - common.BytesToAddress([]byte{0x08}): {Balance: big.NewInt(1)}, // ECPairing - common.BytesToAddress([]byte{0x09}): {Balance: big.NewInt(1)}, // BLAKE2b - common.BytesToAddress([]byte{0x0a}): {Balance: big.NewInt(1)}, // KZGPointEval - common.BytesToAddress([]byte{0x0b}): {Balance: big.NewInt(1)}, // BLSG1Add - common.BytesToAddress([]byte{0x0c}): {Balance: big.NewInt(1)}, // BLSG1MultiExp - common.BytesToAddress([]byte{0x0d}): {Balance: big.NewInt(1)}, // BLSG2Add - common.BytesToAddress([]byte{0x0e}): {Balance: big.NewInt(1)}, // BLSG2MultiExp - common.BytesToAddress([]byte{0x0f}): {Balance: big.NewInt(1)}, // BLSG1Pairing - common.BytesToAddress([]byte{0x10}): {Balance: big.NewInt(1)}, // BLSG1MapG1 - common.BytesToAddress([]byte{0x11}): {Balance: big.NewInt(1)}, // BLSG2MapG2 + common.BytesToAddress([]byte{0x01}): {Balance: big.NewInt(1)}, // ECRecover + common.BytesToAddress([]byte{0x02}): {Balance: big.NewInt(1)}, // SHA256 + common.BytesToAddress([]byte{0x03}): {Balance: big.NewInt(1)}, // RIPEMD + common.BytesToAddress([]byte{0x04}): {Balance: big.NewInt(1)}, // Identity + common.BytesToAddress([]byte{0x05}): {Balance: big.NewInt(1)}, // ModExp + common.BytesToAddress([]byte{0x06}): {Balance: big.NewInt(1)}, // ECAdd + common.BytesToAddress([]byte{0x07}): {Balance: big.NewInt(1)}, // ECScalarMul + common.BytesToAddress([]byte{0x08}): {Balance: big.NewInt(1)}, // ECPairing + common.BytesToAddress([]byte{0x09}): {Balance: big.NewInt(1)}, // BLAKE2b + common.BytesToAddress([]byte{0x0a}): {Balance: big.NewInt(1)}, // KZGPointEval + common.BytesToAddress([]byte{0x0b}): {Balance: big.NewInt(1)}, // BLSG1Add + common.BytesToAddress([]byte{0x0c}): {Balance: big.NewInt(1)}, // BLSG1MultiExp + common.BytesToAddress([]byte{0x0d}): {Balance: big.NewInt(1)}, // BLSG2Add + common.BytesToAddress([]byte{0x0e}): {Balance: big.NewInt(1)}, // BLSG2MultiExp + common.BytesToAddress([]byte{0x0f}): {Balance: big.NewInt(1)}, // BLSG1Pairing + common.BytesToAddress([]byte{0x10}): {Balance: big.NewInt(1)}, // BLSG1MapG1 + common.BytesToAddress([]byte{0x11}): {Balance: big.NewInt(1)}, // BLSG2MapG2 + common.BytesToAddress([]byte{0x1, 00}): {Balance: big.NewInt(1)}, // P256Verify // Pre-deploy system contracts params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0}, params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0}, diff --git a/params/config.go b/params/config.go index e796d75535c6..06288575ae0c 100644 --- a/params/config.go +++ b/params/config.go @@ -225,9 +225,11 @@ var ( CancunTime: newUint64(0), TerminalTotalDifficulty: big.NewInt(0), PragueTime: newUint64(0), + OsakaTime: newUint64(0), BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, + Osaka: DefaultOsakaBlobConfig, }, } From ef02d7f2dd67a22ed982d56358deb8e92657031f Mon Sep 17 00:00:00 2001 From: Kyrin Date: Tue, 21 Oct 2025 15:41:38 +0800 Subject: [PATCH 149/228] core/txpool: Initialize journal writer for tx tracker (#32921) Previously, the journal writer is nil until the first time rejournal (default 1h), which means during this period, txs submitted to this node are not written into journal file (transactions.rlp). If this node is shutdown before the first time rejournal, then txs in pending or queue will get lost. Here, this PR initializes the journal writer soon after launch to solve this issue. --------- Co-authored-by: Gary Rong --- core/txpool/locals/journal.go | 20 +++++++++- core/txpool/locals/tx_tracker.go | 38 ++++++++++++------- core/txpool/locals/tx_tracker_test.go | 53 ++++++++++++++++++++++++--- 3 files changed, 91 insertions(+), 20 deletions(-) diff --git a/core/txpool/locals/journal.go b/core/txpool/locals/journal.go index 46fd6de346db..cd2be8a7942c 100644 --- a/core/txpool/locals/journal.go +++ b/core/txpool/locals/journal.go @@ -117,6 +117,25 @@ func (journal *journal) load(add func([]*types.Transaction) []error) error { return failure } +func (journal *journal) setupWriter() error { + if journal.writer != nil { + if err := journal.writer.Close(); err != nil { + return err + } + journal.writer = nil + } + + // Re-open the journal file for appending + // Use O_APPEND to ensure we always write to the end of the file + sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + return err + } + journal.writer = sink + + return nil +} + // insert adds the specified transaction to the local disk journal. func (journal *journal) insert(tx *types.Transaction) error { if journal.writer == nil { @@ -177,7 +196,6 @@ func (journal *journal) rotate(all map[common.Address]types.Transactions) error // close flushes the transaction journal contents to disk and closes the file. func (journal *journal) close() error { var err error - if journal.writer != nil { err = journal.writer.Close() journal.writer = nil diff --git a/core/txpool/locals/tx_tracker.go b/core/txpool/locals/tx_tracker.go index e08384ce7114..bb178f175e5d 100644 --- a/core/txpool/locals/tx_tracker.go +++ b/core/txpool/locals/tx_tracker.go @@ -114,13 +114,14 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) { } // recheck checks and returns any transactions that needs to be resubmitted. -func (tracker *TxTracker) recheck(journalCheck bool) (resubmits []*types.Transaction, rejournal map[common.Address]types.Transactions) { +func (tracker *TxTracker) recheck(journalCheck bool) []*types.Transaction { tracker.mu.Lock() defer tracker.mu.Unlock() var ( numStales = 0 numOk = 0 + resubmits []*types.Transaction ) for sender, txs := range tracker.byAddr { // Wipe the stales @@ -141,7 +142,7 @@ func (tracker *TxTracker) recheck(journalCheck bool) (resubmits []*types.Transac } if journalCheck { // rejournal - rejournal = make(map[common.Address]types.Transactions) + rejournal := make(map[common.Address]types.Transactions) for _, tx := range tracker.all { addr, _ := types.Sender(tracker.signer, tx) rejournal[addr] = append(rejournal[addr], tx) @@ -153,10 +154,18 @@ func (tracker *TxTracker) recheck(journalCheck bool) (resubmits []*types.Transac return int(a.Nonce() - b.Nonce()) }) } + // Rejournal the tracker while holding the lock. No new transactions will + // be added to the old journal during this period, preventing any potential + // transaction loss. + if tracker.journal != nil { + if err := tracker.journal.rotate(rejournal); err != nil { + log.Warn("Transaction journal rotation failed", "err", err) + } + } } localGauge.Update(int64(len(tracker.all))) log.Debug("Tx tracker status", "need-resubmit", len(resubmits), "stale", numStales, "ok", numOk) - return resubmits, rejournal + return resubmits } // Start implements node.Lifecycle interface @@ -185,6 +194,12 @@ func (tracker *TxTracker) loop() { tracker.TrackAll(transactions) return nil }) + + // Setup the writer for the upcoming transactions + if err := tracker.journal.setupWriter(); err != nil { + log.Error("Failed to setup the journal writer", "err", err) + return + } defer tracker.journal.close() } var ( @@ -196,20 +211,15 @@ func (tracker *TxTracker) loop() { case <-tracker.shutdownCh: return case <-timer.C: - checkJournal := tracker.journal != nil && time.Since(lastJournal) > tracker.rejournal - resubmits, rejournal := tracker.recheck(checkJournal) + var rejournal bool + if tracker.journal != nil && time.Since(lastJournal) > tracker.rejournal { + rejournal, lastJournal = true, time.Now() + log.Debug("Rejournal the transaction tracker") + } + resubmits := tracker.recheck(rejournal) if len(resubmits) > 0 { tracker.pool.Add(resubmits, false) } - if checkJournal { - // Lock to prevent journal.rotate <-> journal.insert (via TrackAll) conflicts - tracker.mu.Lock() - lastJournal = time.Now() - if err := tracker.journal.rotate(rejournal); err != nil { - log.Warn("Transaction journal rotation failed", "err", err) - } - tracker.mu.Unlock() - } timer.Reset(recheckInterval) } } diff --git a/core/txpool/locals/tx_tracker_test.go b/core/txpool/locals/tx_tracker_test.go index 367fb6b6daca..dde875460589 100644 --- a/core/txpool/locals/tx_tracker_test.go +++ b/core/txpool/locals/tx_tracker_test.go @@ -17,7 +17,11 @@ package locals import ( + "fmt" + "maps" "math/big" + "math/rand" + "path/filepath" "testing" "time" @@ -146,20 +150,59 @@ func TestResubmit(t *testing.T) { txsA := txs[:len(txs)/2] txsB := txs[len(txs)/2:] env.pool.Add(txsA, true) + pending, queued := env.pool.ContentFrom(address) if len(pending) != len(txsA) || len(queued) != 0 { t.Fatalf("Unexpected txpool content: %d, %d", len(pending), len(queued)) } env.tracker.TrackAll(txs) - resubmit, all := env.tracker.recheck(true) + resubmit := env.tracker.recheck(true) if len(resubmit) != len(txsB) { t.Fatalf("Unexpected transactions to resubmit, got: %d, want: %d", len(resubmit), len(txsB)) } - if len(all) == 0 || len(all[address]) == 0 { - t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", 0, len(txs)) + env.tracker.mu.Lock() + allCopy := maps.Clone(env.tracker.all) + env.tracker.mu.Unlock() + + if len(allCopy) != len(txs) { + t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", len(allCopy), len(txs)) } - if len(all[address]) != len(txs) { - t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", len(all[address]), len(txs)) +} + +func TestJournal(t *testing.T) { + journalPath := filepath.Join(t.TempDir(), fmt.Sprintf("%d", rand.Int63())) + env := newTestEnv(t, 10, 0, journalPath) + defer env.close() + + env.tracker.Start() + defer env.tracker.Stop() + + txs := env.makeTxs(10) + txsA := txs[:len(txs)/2] + txsB := txs[len(txs)/2:] + env.pool.Add(txsA, true) + + pending, queued := env.pool.ContentFrom(address) + if len(pending) != len(txsA) || len(queued) != 0 { + t.Fatalf("Unexpected txpool content: %d, %d", len(pending), len(queued)) + } + env.tracker.TrackAll(txsA) + env.tracker.TrackAll(txsB) + env.tracker.recheck(true) // manually rejournal the tracker + + // Make sure all the transactions are properly journalled + trackerB := New(journalPath, time.Minute, gspec.Config, env.pool) + trackerB.journal.load(func(transactions []*types.Transaction) []error { + trackerB.TrackAll(transactions) + return nil + }) + + trackerB.mu.Lock() + allCopy := maps.Clone(trackerB.all) + trackerB.mu.Unlock() + + if len(allCopy) != len(txs) { + t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", len(allCopy), len(txs)) } } From e1d7775f9c9ed68e7dfb5106dec73aa51bfecbfe Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 21 Oct 2025 16:03:56 +0800 Subject: [PATCH 150/228] core/state: prevent SetCode hook if contract code is not changed (#32980) This PR prevents the SetCode hook from being called when the contract code remains unchanged. This situation can occur in the following cases: - The deployed runtime code has zero length - An EIP-7702 authorization attempt tries to unset a non-delegated account - An EIP-7702 authorization attempt tries to delegate to the same account --- core/state/statedb_hooked.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index d2595bcefedf..9db201fc2b91 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -191,17 +191,18 @@ func (s *hookedStateDB) SetNonce(address common.Address, nonce uint64, reason tr func (s *hookedStateDB) SetCode(address common.Address, code []byte, reason tracing.CodeChangeReason) []byte { prev := s.inner.SetCode(address, code, reason) + if s.hooks.OnCodeChangeV2 != nil || s.hooks.OnCodeChange != nil { - prevHash := types.EmptyCodeHash - if len(prev) != 0 { - prevHash = crypto.Keccak256Hash(prev) - } + prevHash := crypto.Keccak256Hash(prev) codeHash := crypto.Keccak256Hash(code) - if s.hooks.OnCodeChangeV2 != nil { - s.hooks.OnCodeChangeV2(address, prevHash, prev, codeHash, code, reason) - } else if s.hooks.OnCodeChange != nil { - s.hooks.OnCodeChange(address, prevHash, prev, codeHash, code) + // Invoke the hooks only if the contract code is changed + if prevHash != codeHash { + if s.hooks.OnCodeChangeV2 != nil { + s.hooks.OnCodeChangeV2(address, prevHash, prev, codeHash, code, reason) + } else if s.hooks.OnCodeChange != nil { + s.hooks.OnCodeChange(address, prevHash, prev, codeHash, code) + } } } return prev From cb0fd7b76ba43732cd4a2419f4ae40f911ea3f13 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 21 Oct 2025 19:11:36 +0800 Subject: [PATCH 151/228] triedb/pathdb: make batch with pre-allocated size (#32914) In this PR, the database batch for writing the history index data is pre-allocated. It's observed that database batch repeatedly grows the size of the mega-batch, causing significant memory allocation pressure. This approach can effectively mitigate the overhead. --- triedb/pathdb/history_index_block.go | 8 ++++---- triedb/pathdb/history_indexer.go | 23 ++++++++++++++++++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/triedb/pathdb/history_index_block.go b/triedb/pathdb/history_index_block.go index 7648b99226c9..5abdee682ad6 100644 --- a/triedb/pathdb/history_index_block.go +++ b/triedb/pathdb/history_index_block.go @@ -25,10 +25,10 @@ import ( ) const ( - indexBlockDescSize = 14 // The size of index block descriptor - indexBlockEntriesCap = 4096 // The maximum number of entries can be grouped in a block - indexBlockRestartLen = 256 // The restart interval length of index block - historyIndexBatch = 1_000_000 // The number of state history indexes for constructing or deleting as batch + indexBlockDescSize = 14 // The size of index block descriptor + indexBlockEntriesCap = 4096 // The maximum number of entries can be grouped in a block + indexBlockRestartLen = 256 // The restart interval length of index block + historyIndexBatch = 512 * 1024 // The number of state history indexes for constructing or deleting as batch ) // indexBlockDesc represents a descriptor for an index block, which contains a diff --git a/triedb/pathdb/history_indexer.go b/triedb/pathdb/history_indexer.go index 368ff78d415e..893ccd652368 100644 --- a/triedb/pathdb/history_indexer.go +++ b/triedb/pathdb/history_indexer.go @@ -40,6 +40,11 @@ const ( stateHistoryIndexVersion = stateHistoryIndexV0 // the current state index version trienodeHistoryIndexV0 = uint8(0) // initial version of trienode index structure trienodeHistoryIndexVersion = trienodeHistoryIndexV0 // the current trienode index version + + // estimations for calculating the batch size for atomic database commit + estimatedStateHistoryIndexSize = 3 // The average size of each state history index entry is approximately 2–3 bytes + estimatedTrienodeHistoryIndexSize = 3 // The average size of each trienode history index entry is approximately 2-3 bytes + estimatedIndexBatchSizeFactor = 32 // The factor counts for the write amplification for each entry ) // indexVersion returns the latest index version for the given history type. @@ -150,6 +155,22 @@ func (b *batchIndexer) process(h history, id uint64) error { return b.finish(false) } +// makeBatch constructs a database batch based on the number of pending entries. +// The batch size is roughly estimated to minimize repeated resizing rounds, +// as accurately predicting the exact size is technically challenging. +func (b *batchIndexer) makeBatch() ethdb.Batch { + var size int + switch b.typ { + case typeStateHistory: + size = estimatedStateHistoryIndexSize + case typeTrienodeHistory: + size = estimatedTrienodeHistoryIndexSize + default: + panic(fmt.Sprintf("unknown history type %d", b.typ)) + } + return b.db.NewBatchWithSize(size * estimatedIndexBatchSizeFactor * b.pending) +} + // finish writes the accumulated state indexes into the disk if either the // memory limitation is reached or it's requested forcibly. func (b *batchIndexer) finish(force bool) error { @@ -160,7 +181,7 @@ func (b *batchIndexer) finish(force bool) error { return nil } var ( - batch = b.db.NewBatch() + batch = b.makeBatch() batchMu sync.RWMutex start = time.Now() eg errgroup.Group From 024100649bab73dcf1cf506aec06048bb719b574 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Tue, 21 Oct 2025 08:10:45 -0600 Subject: [PATCH 152/228] cmd/geth: add flag to set genesis (#32844) This PR is an alternative to #32556. Instead of trying to be smart and reuse `geth init`, we can introduce a new flag `--genesis` that loads the `genesis.json` from file into the `Genesis` object in the same path that the other network flags currently work in. Question: is something like `--genesis` enough to start deprecating `geth init`? -- ```console $ geth --datadir data --hoodi .. INFO [10-06|22:37:11.202] - BPO2: @1762955544 .. $ geth --datadir data --genesis genesis.json .. INFO [10-06|22:37:27.988] - BPO2: @1862955544 .. ``` Pull the genesis [from the specs](https://raw.githubusercontent.com/eth-clients/hoodi/refs/heads/main/metadata/genesis.json) and modify one of the BPO timestamps to simulate a shadow fork. --------- Co-authored-by: rjl493456442 --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index cc294b2f3092..109b36836a04 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -66,6 +66,7 @@ var ( utils.OverrideBPO1, utils.OverrideBPO2, utils.OverrideVerkle, + utils.OverrideGenesisFlag, utils.EnablePersonal, // deprecated utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0c5db9e6d8ba..2a3d6af06241 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -262,6 +262,11 @@ var ( Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting", Category: flags.EthCategory, } + OverrideGenesisFlag = &cli.StringFlag{ + Name: "override.genesis", + Usage: "Load genesis block and configuration from file at this path", + Category: flags.EthCategory, + } SyncModeFlag = &cli.StringFlag{ Name: "syncmode", Usage: `Blockchain sync mode ("snap" or "full")`, @@ -1605,7 +1610,7 @@ func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags, don't allow network id override on preset networks - flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag, HoodiFlag, NetworkIdFlag) + flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag, HoodiFlag, NetworkIdFlag, OverrideGenesisFlag) flags.CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer // Set configurations from CLI flags @@ -1891,6 +1896,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if !ctx.IsSet(MinerGasPriceFlag.Name) { cfg.Miner.GasPrice = big.NewInt(1) } + case ctx.String(OverrideGenesisFlag.Name) != "": + f, err := os.Open(ctx.String(OverrideGenesisFlag.Name)) + if err != nil { + Fatalf("Failed to read genesis file: %v", err) + } + defer f.Close() + + genesis := new(core.Genesis) + if err := json.NewDecoder(f).Decode(genesis); err != nil { + Fatalf("Invalid genesis file: %v", err) + } + cfg.Genesis = genesis default: if cfg.NetworkId == 1 { SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) From 66e6d4fc55add90e6240ce9345d36b49991e8db7 Mon Sep 17 00:00:00 2001 From: cui Date: Tue, 21 Oct 2025 23:49:43 +0800 Subject: [PATCH 153/228] core/types: remove unused `ErrInvalidTxType` var (#32989) The var `ErrInvalidTxType` is never used in the code base. --- core/types/transaction.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/types/transaction.go b/core/types/transaction.go index e98563b85fc8..6af960b8c3ea 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -34,7 +34,6 @@ import ( var ( ErrInvalidSig = errors.New("invalid transaction v, r, s values") ErrUnexpectedProtection = errors.New("transaction type does not supported EIP-155 protected signatures") - ErrInvalidTxType = errors.New("transaction type not valid in this context") ErrTxTypeNotSupported = errors.New("transaction type not supported") ErrGasFeeCapTooLow = errors.New("fee cap less than base fee") ErrUint256Overflow = errors.New("bigint overflow, too large for uint256") From f55c45dbad3787b015f8c3b8ad94cd054ce81860 Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 22 Oct 2025 22:35:26 +0800 Subject: [PATCH 154/228] core/state: fix the flaky TestSizeTracker (#32993) --- core/state/state_sizer_test.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/core/state/state_sizer_test.go b/core/state/state_sizer_test.go index cab0c381635a..65f652e424c1 100644 --- a/core/state/state_sizer_test.go +++ b/core/state/state_sizer_test.go @@ -94,6 +94,14 @@ func TestSizeTracker(t *testing.T) { } baselineRoot := currentRoot + // Close and reopen the trie database so all async flushes triggered by the + // baseline commits are written before we measure the baseline snapshot. + if err := tdb.Close(); err != nil { + t.Fatalf("Failed to close triedb before baseline measurement: %v", err) + } + tdb = triedb.NewDatabase(db, &triedb.Config{PathDB: pathdb.Defaults}) + sdb = NewDatabase(tdb, nil) + // Wait for snapshot completion for !tdb.SnapshotCompleted() { time.Sleep(100 * time.Millisecond) @@ -215,13 +223,12 @@ func TestSizeTracker(t *testing.T) { if actualStats.ContractCodeBytes != expectedStats.ContractCodeBytes { t.Errorf("Contract code bytes mismatch: expected %d, got %d", expectedStats.ContractCodeBytes, actualStats.ContractCodeBytes) } - // TODO: failed on github actions, need to investigate - // if actualStats.AccountTrienodes != expectedStats.AccountTrienodes { - // t.Errorf("Account trie nodes mismatch: expected %d, got %d", expectedStats.AccountTrienodes, actualStats.AccountTrienodes) - // } - // if actualStats.AccountTrienodeBytes != expectedStats.AccountTrienodeBytes { - // t.Errorf("Account trie node bytes mismatch: expected %d, got %d", expectedStats.AccountTrienodeBytes, actualStats.AccountTrienodeBytes) - // } + if actualStats.AccountTrienodes != expectedStats.AccountTrienodes { + t.Errorf("Account trie nodes mismatch: expected %d, got %d", expectedStats.AccountTrienodes, actualStats.AccountTrienodes) + } + if actualStats.AccountTrienodeBytes != expectedStats.AccountTrienodeBytes { + t.Errorf("Account trie node bytes mismatch: expected %d, got %d", expectedStats.AccountTrienodeBytes, actualStats.AccountTrienodeBytes) + } if actualStats.StorageTrienodes != expectedStats.StorageTrienodes { t.Errorf("Storage trie nodes mismatch: expected %d, got %d", expectedStats.StorageTrienodes, actualStats.StorageTrienodes) } From 2a2c9c149c90c6b3c5001917ac6127330af72b2f Mon Sep 17 00:00:00 2001 From: Delweng Date: Thu, 23 Oct 2025 00:24:40 +0800 Subject: [PATCH 155/228] cmd/devp2p: distinguish the jwt in devp2p and geth (#32972) This PR fixes some docs for the devp2p suite and uses the CLI library's required value instead of manually checking if required flags are passed. --- cmd/devp2p/README.md | 4 ++-- cmd/devp2p/rlpxcmd.go | 12 ------------ cmd/devp2p/runtest.go | 7 +++++-- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md index ad2985b4b0fb..b20d921dc4d4 100644 --- a/cmd/devp2p/README.md +++ b/cmd/devp2p/README.md @@ -121,7 +121,7 @@ with our test chain. The chain files are located in `./cmd/devp2p/internal/ethte --nat=none \ --networkid 3503995874084926 \ --verbosity 5 \ - --authrpc.jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365 + --authrpc.jwtsecret jwt.secret Note that the tests also require access to the engine API. The test suite can now be executed using the devp2p tool. @@ -130,7 +130,7 @@ The test suite can now be executed using the devp2p tool. --chain internal/ethtest/testdata \ --node enode://.... \ --engineapi http://127.0.0.1:8551 \ - --jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365 + --jwtsecret $(cat jwt.secret) Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again. diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go index 118731fd6c54..1dc8f82460ec 100644 --- a/cmd/devp2p/rlpxcmd.go +++ b/cmd/devp2p/rlpxcmd.go @@ -143,9 +143,6 @@ type testParams struct { func cliTestParams(ctx *cli.Context) *testParams { nodeStr := ctx.String(testNodeFlag.Name) - if nodeStr == "" { - exit(fmt.Errorf("missing -%s", testNodeFlag.Name)) - } node, err := parseNode(nodeStr) if err != nil { exit(err) @@ -156,14 +153,5 @@ func cliTestParams(ctx *cli.Context) *testParams { jwt: ctx.String(testNodeJWTFlag.Name), chainDir: ctx.String(testChainDirFlag.Name), } - if p.engineAPI == "" { - exit(fmt.Errorf("missing -%s", testNodeEngineFlag.Name)) - } - if p.jwt == "" { - exit(fmt.Errorf("missing -%s", testNodeJWTFlag.Name)) - } - if p.chainDir == "" { - exit(fmt.Errorf("missing -%s", testChainDirFlag.Name)) - } return &p } diff --git a/cmd/devp2p/runtest.go b/cmd/devp2p/runtest.go index 7e3723c641dc..c40a4b8a0164 100644 --- a/cmd/devp2p/runtest.go +++ b/cmd/devp2p/runtest.go @@ -39,26 +39,29 @@ var ( } // for eth/snap tests - testChainDirFlag = &cli.StringFlag{ + testChainDirFlag = &cli.PathFlag{ Name: "chain", Usage: "Test chain directory (required)", Category: flags.TestingCategory, + Required: true, } testNodeFlag = &cli.StringFlag{ Name: "node", Usage: "Peer-to-Peer endpoint (ENR) of the test node (required)", Category: flags.TestingCategory, + Required: true, } testNodeJWTFlag = &cli.StringFlag{ Name: "jwtsecret", Usage: "JWT secret for the engine API of the test node (required)", Category: flags.TestingCategory, - Value: "0x7365637265747365637265747365637265747365637265747365637265747365", + Required: true, } testNodeEngineFlag = &cli.StringFlag{ Name: "engineapi", Usage: "Engine API endpoint of the test node (required)", Category: flags.TestingCategory, + Required: true, } // These two are specific to the discovery tests. From b01111fbbe0e6c4b37cba589406e31dbefd0bc6c Mon Sep 17 00:00:00 2001 From: Delweng Date: Thu, 23 Oct 2025 16:44:54 +0800 Subject: [PATCH 156/228] p2p: silence on listener shutdown (#33001) Co-authored-by: Felix Lange --- p2p/server.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/server.go b/p2p/server.go index 1f859089af18..ddd4f5d072d2 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -813,7 +813,9 @@ func (srv *Server) listenLoop() { time.Sleep(time.Millisecond * 200) continue } else if err != nil { - srv.log.Debug("Read error", "err", err) + if !errors.Is(err, net.ErrClosed) { + srv.log.Debug("Read error", "err", err) + } slots <- struct{}{} return } From b7d47f71d6daf41f65c9387893e4db2de1a139cf Mon Sep 17 00:00:00 2001 From: maskpp Date: Thu, 23 Oct 2025 17:56:47 +0800 Subject: [PATCH 157/228] cmd/utils: use IsHexAddress method (#32997) Using the `IsHexAddress` method will result in no gaps in the verification logic, making it simpler. --- cmd/utils/flags.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2a3d6af06241..5a7e40767cfb 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -20,7 +20,6 @@ package utils import ( "context" "crypto/ecdsa" - "encoding/hex" "encoding/json" "errors" "fmt" @@ -1341,15 +1340,10 @@ func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) { return } addr := ctx.String(MinerPendingFeeRecipientFlag.Name) - if strings.HasPrefix(addr, "0x") || strings.HasPrefix(addr, "0X") { - addr = addr[2:] - } - b, err := hex.DecodeString(addr) - if err != nil || len(b) != common.AddressLength { + if !common.IsHexAddress(addr) { Fatalf("-%s: invalid pending block producer address %q", MinerPendingFeeRecipientFlag.Name, addr) - return } - cfg.Miner.PendingFeeRecipient = common.BytesToAddress(b) + cfg.Miner.PendingFeeRecipient = common.HexToAddress(addr) } func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { From 4a134fb25b03ab25054fcfe9f301c8871169863f Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:02:13 +0200 Subject: [PATCH 158/228] crypto: implement ziren keccak state (#32996) The #32816 was only using the keccak precompile for some minor task. This PR implements a keccak state, which is what is used for hashing the tree. --- crypto/keccak_ziren.go | 66 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/crypto/keccak_ziren.go b/crypto/keccak_ziren.go index 033c0ec42cf9..8e967c6dbfd6 100644 --- a/crypto/keccak_ziren.go +++ b/crypto/keccak_ziren.go @@ -21,14 +21,72 @@ package crypto import ( "github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime" "github.com/ethereum/go-ethereum/common" - "golang.org/x/crypto/sha3" ) +// zirenKeccakState implements the KeccakState interface using the Ziren zkvm_runtime. +// It accumulates data written to it and uses the zkvm's Keccak256 system call for hashing. +type zirenKeccakState struct { + buf []byte // accumulated data + result []byte // cached result + dirty bool // whether new data has been written since last hash +} + +func newZirenKeccakState() KeccakState { + return &zirenKeccakState{ + buf: make([]byte, 0, 512), // pre-allocate reasonable capacity + } +} + +func (s *zirenKeccakState) Write(p []byte) (n int, err error) { + s.buf = append(s.buf, p...) + s.dirty = true + return len(p), nil +} + +func (s *zirenKeccakState) Sum(b []byte) []byte { + s.computeHashIfNeeded() + return append(b, s.result...) +} + +func (s *zirenKeccakState) Reset() { + s.buf = s.buf[:0] + s.result = nil + s.dirty = false +} + +func (s *zirenKeccakState) Size() int { + return 32 +} + +func (s *zirenKeccakState) BlockSize() int { + return 136 // Keccak256 rate +} + +func (s *zirenKeccakState) Read(p []byte) (n int, err error) { + s.computeHashIfNeeded() + + if len(p) == 0 { + return 0, nil + } + + // After computeHashIfNeeded(), s.result is always a 32-byte slice + n = copy(p, s.result) + return n, nil +} + +func (s *zirenKeccakState) computeHashIfNeeded() { + if s.dirty || s.result == nil { + // Use the zkvm_runtime Keccak256 which uses SyscallKeccakSponge + hashArray := zkvm_runtime.Keccak256(s.buf) + s.result = hashArray[:] + s.dirty = false + } +} + // NewKeccakState creates a new KeccakState -// For now, we fallback to the original implementation for the stateful interface. -// TODO: Implement a stateful wrapper around zkvm_runtime.Keccak256 if needed. +// This uses a Ziren-optimized implementation that leverages the zkvm_runtime.Keccak256 system call. func NewKeccakState() KeccakState { - return sha3.NewLegacyKeccak256().(KeccakState) + return newZirenKeccakState() } // Keccak256 calculates and returns the Keccak256 hash using the Ziren zkvm_runtime implementation. From a4ca20712dcbe3f485bf726640e5340706049d9a Mon Sep 17 00:00:00 2001 From: Delweng Date: Thu, 23 Oct 2025 20:58:33 +0800 Subject: [PATCH 159/228] rpc: fix a flaky test of the websocket (#33002) Found in https://github.com/ethereum/go-ethereum/actions/runs/17803828253/job/50611300621?pr=32585 ``` --- FAIL: TestClientCancelWebsocket (0.33s) panic: read tcp 127.0.0.1:36048->127.0.0.1:38643: read: connection reset by peer [recovered, repanicked] goroutine 15 [running]: testing.tRunner.func1.2({0x98dd20, 0xc0005b0100}) /opt/actions-runner/_work/_tool/go/1.25.1/x64/src/testing/testing.go:1872 +0x237 testing.tRunner.func1() /opt/actions-runner/_work/_tool/go/1.25.1/x64/src/testing/testing.go:1875 +0x35b panic({0x98dd20?, 0xc0005b0100?}) /opt/actions-runner/_work/_tool/go/1.25.1/x64/src/runtime/panic.go:783 +0x132 github.com/ethereum/go-ethereum/rpc.httpTestClient(0xc0001dc1c0?, {0x9d5e40, 0x2}, 0xc0002bc1c0) /opt/actions-runner/_work/go-ethereum/go-ethereum/rpc/client_test.go:932 +0x2b1 github.com/ethereum/go-ethereum/rpc.testClientCancel({0x9d5e40, 0x2}, 0xc0001dc1c0) /opt/actions-runner/_work/go-ethereum/go-ethereum/rpc/client_test.go:356 +0x15f github.com/ethereum/go-ethereum/rpc.TestClientCancelWebsocket(0xc0001dc1c0?) /opt/actions-runner/_work/go-ethereum/go-ethereum/rpc/client_test.go:319 +0x25 testing.tRunner(0xc0001dc1c0, 0xa07370) /opt/actions-runner/_work/_tool/go/1.25.1/x64/src/testing/testing.go:1934 +0xea created by testing.(*T).Run in goroutine 1 /opt/actions-runner/_work/_tool/go/1.25.1/x64/src/testing/testing.go:1997 +0x465 FAIL github.com/ethereum/go-ethereum/rpc 0.371s ``` In `testClientCancel` we wrap the server listener in `flakeyListener`, which schedules an unconditional close of every accepted connection after a random delay, if the random delay is zero then the timer fires immediately, and then the http client paniced of connection reset by peer. Here we add a minimum 10ms to ensure the timeout won't fire immediately. Signed-off-by: jsvisa --- rpc/client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/client_test.go b/rpc/client_test.go index 6c1a4f8f6c00..03a3410537b2 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -973,7 +973,7 @@ func (l *flakeyListener) Accept() (net.Conn, error) { c, err := l.Listener.Accept() if err == nil { - timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout))) + timeout := max(time.Millisecond*10, time.Duration(rand.Int63n(int64(l.maxKillTimeout)))) time.AfterFunc(timeout, func() { log.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout)) c.Close() From 8736dc4708afd69dff0ddca9cb05f2b7ebeaad1e Mon Sep 17 00:00:00 2001 From: hero5512 Date: Fri, 24 Oct 2025 11:04:09 -0400 Subject: [PATCH 160/228] eth/tracers: fix crasher in TraceCall with BlockOverrides (#33015) fix https://github.com/ethereum/go-ethereum/issues/33014 --------- Co-authored-by: lightclient --- eth/tracers/api.go | 2 +- eth/tracers/api_test.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index aebeb484635f..5cfbc24b8e35 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -959,7 +959,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc // Apply the customization rules if required. if config != nil { - if config.BlockOverrides != nil && config.BlockOverrides.Number.ToInt().Uint64() == h.Number.Uint64()+1 { + if config.BlockOverrides != nil && config.BlockOverrides.Number != nil && config.BlockOverrides.Number.ToInt().Uint64() == h.Number.Uint64()+1 { // Overriding the block number to n+1 is a common way for wallets to // simulate transactions, however without the following fix, a contract // can assert it is being simulated by checking if blockhash(n) == 0x0 and diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 4173d2a79155..609c3f4d8b3b 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -471,6 +471,20 @@ func TestTraceCall(t *testing.T) { {"pc":0,"op":"NUMBER","gas":24946984,"gasCost":2,"depth":1,"stack":[]}, {"pc":1,"op":"STOP","gas":24946982,"gasCost":0,"depth":1,"stack":["0x1337"]}]}`, }, + // Tests issue #33014 where accessing nil block number override panics. + { + blockNumber: rpc.BlockNumber(0), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: &TraceCallConfig{ + BlockOverrides: &override.BlockOverrides{}, + }, + expectErr: nil, + expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`, + }, } for i, testspec := range testSuite { result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config) From 46de01d58fb73c9a3b0c37e006e55037eb5a5194 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 24 Oct 2025 17:19:25 +0200 Subject: [PATCH 161/228] .gitea/workflows, build: add release build for keeper (#32632) --- .gitea/workflows/release.yml | 21 ++++++++ .gitignore | 3 +- build/ci.go | 98 ++++++++++++++++++++++++++++++++++-- 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index f2dcc3ae96a8..41defedd00b8 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -122,6 +122,27 @@ jobs: LINUX_SIGNING_KEY: ${{ secrets.LINUX_SIGNING_KEY }} AZURE_BLOBSTORE_TOKEN: ${{ secrets.AZURE_BLOBSTORE_TOKEN }} + keeper: + name: Keeper Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.24 + cache: false + + - name: Install cross toolchain + run: | + apt-get update + apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib + + - name: Build (amd64) + run: | + go run build/ci.go keeper -dlgo + windows: name: Windows Build runs-on: "win-11" diff --git a/.gitignore b/.gitignore index 269455db7a73..293359a66951 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,5 @@ cmd/ethkey/ethkey cmd/evm/evm cmd/geth/geth cmd/rlpdump/rlpdump -cmd/workload/workload \ No newline at end of file +cmd/workload/workload +cmd/keeper/keeper diff --git a/build/ci.go b/build/ci.go index 905f6e4072fd..99a0e14f1673 100644 --- a/build/ci.go +++ b/build/ci.go @@ -31,6 +31,9 @@ Available commands are: install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables test [ -coverage ] [ packages... ] -- runs the tests + keeper [ -dlgo ] + keeper-archive [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] + archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts importkeys -- imports signing keys from env debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package @@ -86,6 +89,30 @@ var ( executablePath("clef"), } + // Keeper build targets with their configurations + keeperTargets = []struct { + Name string + GOOS string + GOARCH string + CC string + Tags string + Env map[string]string + }{ + { + Name: "ziren", + GOOS: "linux", + GOARCH: "mipsle", + // enable when cgo works + // CC: "mipsel-linux-gnu-gcc", + Tags: "ziren", + Env: map[string]string{"GOMIPS": "softfloat", "CGO_ENABLED": "0"}, + }, + { + Name: "example", + Tags: "example", + }, + } + // A debian package is created for all executables listed here. debExecutables = []debExecutable{ { @@ -178,6 +205,10 @@ func main() { doPurge(os.Args[2:]) case "sanitycheck": doSanityCheck() + case "keeper": + doInstallKeeper(os.Args[2:]) + case "keeper-archive": + doKeeperArchive(os.Args[2:]) default: log.Fatal("unknown command ", os.Args[1]) } @@ -212,9 +243,6 @@ func doInstall(cmdline []string) { // Configure the build. gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...) - // We use -trimpath to avoid leaking local paths into the built executables. - gobuild.Args = append(gobuild.Args, "-trimpath") - // Show packages during build. gobuild.Args = append(gobuild.Args, "-v") @@ -234,6 +262,42 @@ func doInstall(cmdline []string) { } } +// doInstallKeeper builds keeper binaries for all supported targets. +func doInstallKeeper(cmdline []string) { + var dlgo = flag.Bool("dlgo", false, "Download Go and build with it") + + flag.CommandLine.Parse(cmdline) + env := build.Env() + + // Configure the toolchain. + tc := build.GoToolchain{} + if *dlgo { + csdb := download.MustLoadChecksums("build/checksums.txt") + tc.Root = build.DownloadGo(csdb) + } + + for _, target := range keeperTargets { + log.Printf("Building keeper-%s", target.Name) + + // Configure the build. + tc.GOARCH = target.GOARCH + tc.GOOS = target.GOOS + tc.CC = target.CC + gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags})...) + gobuild.Args = append(gobuild.Args, "-v") + + for key, value := range target.Env { + gobuild.Env = append(gobuild.Env, key+"="+value) + } + outputName := fmt.Sprintf("keeper-%s", target.Name) + + args := slices.Clone(gobuild.Args) + args = append(args, "-o", executablePath(outputName)) + args = append(args, "./cmd/keeper") + build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env}) + } +} + // buildFlags returns the go tool flags for building. func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (flags []string) { var ld []string @@ -272,6 +336,8 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) ( if len(buildTags) > 0 { flags = append(flags, "-tags", strings.Join(buildTags, ",")) } + // We use -trimpath to avoid leaking local paths into the built executables. + flags = append(flags, "-trimpath") return flags } @@ -630,6 +696,32 @@ func doArchive(cmdline []string) { } } +func doKeeperArchive(cmdline []string) { + var ( + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + ) + flag.CommandLine.Parse(cmdline) + + var ( + env = build.Env() + vsn = version.Archive(env.Commit) + keeper = "keeper-" + vsn + ".tar.gz" + ) + maybeSkipArchive(env) + files := []string{"COPYING"} + for _, target := range keeperTargets { + files = append(files, executablePath(fmt.Sprintf("keeper-%s", target.Name))) + } + if err := build.WriteArchive(keeper, files); err != nil { + log.Fatal(err) + } + if err := archiveUpload(keeper, *upload, *signer, *signify); err != nil { + log.Fatal(err) + } +} + func archiveBasename(arch string, archiveVersion string) string { platform := runtime.GOOS + "-" + arch if arch == "arm" { From d96a7b3bd45651a9143f8a2edbb9d816676d8588 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 24 Oct 2025 18:25:54 +0200 Subject: [PATCH 162/228] build: fix keeper build (#33018) At the time keeper support was added into ci.go, we were using a go.work file to make ./cmd/keeper accessible from within the main go-ethereum module. The workspace file has since been removed, so we need to build keeper from within its own module instead. --- build/ci.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/ci.go b/build/ci.go index 99a0e14f1673..156626a82df7 100644 --- a/build/ci.go +++ b/build/ci.go @@ -284,6 +284,7 @@ func doInstallKeeper(cmdline []string) { tc.GOOS = target.GOOS tc.CC = target.CC gobuild := tc.Go("build", buildFlags(env, true, []string{target.Tags})...) + gobuild.Dir = "./cmd/keeper" gobuild.Args = append(gobuild.Args, "-v") for key, value := range target.Env { @@ -293,7 +294,7 @@ func doInstallKeeper(cmdline []string) { args := slices.Clone(gobuild.Args) args = append(args, "-o", executablePath(outputName)) - args = append(args, "./cmd/keeper") + args = append(args, ".") build.MustRun(&exec.Cmd{Path: gobuild.Path, Args: args, Env: gobuild.Env}) } } From 38e9a9a51f11625a5c992d9dd2297102c6c174a3 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Sat, 25 Oct 2025 16:16:16 +0800 Subject: [PATCH 163/228] core/rawdb, triedb/pathdb: re-structure the trienode history header (#32907) In this PR, several changes have been made: (a) restructure the trienode history header section Previously, the offsets of the key and value sections were recorded before encoding data into these sections. As a result, these offsets referred to the start position of each chunk rather than the end position. This caused an issue where the end position of the last chunk was unknown, making it incompatible with the freezer partial-read APIs. With this update, all offsets now refer to the end position, and the start position of the first chunk is always 0. (b) Enable partial freezer read for trienode data retrieval The partial freezer read feature is now utilized in trienode data retrieval, improving efficiency. --- core/rawdb/accessors_state.go | 8 +-- triedb/pathdb/history_trienode.go | 106 ++++++++++++------------------ 2 files changed, 47 insertions(+), 67 deletions(-) diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 714c1f77d6e1..b97c7a07a1fe 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -313,13 +313,13 @@ func ReadTrienodeHistoryHeader(db ethdb.AncientReaderOp, id uint64) ([]byte, err } // ReadTrienodeHistoryKeySection retrieves the key section of trienode history. -func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { - return db.Ancient(trienodeHistoryKeySectionTable, id-1) +func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64, offset uint64, length uint64) ([]byte, error) { + return db.AncientBytes(trienodeHistoryKeySectionTable, id-1, offset, length) } // ReadTrienodeHistoryValueSection retrieves the value section of trienode history. -func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) { - return db.Ancient(trienodeHistoryValueSectionTable, id-1) +func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64, offset uint64, length uint64) ([]byte, error) { + return db.AncientBytes(trienodeHistoryValueSectionTable, id-1, offset, length) } // ReadTrienodeHistoryList retrieves the a list of trienode history corresponding diff --git a/triedb/pathdb/history_trienode.go b/triedb/pathdb/history_trienode.go index 2f31238612cd..3f45b41117d4 100644 --- a/triedb/pathdb/history_trienode.go +++ b/triedb/pathdb/history_trienode.go @@ -22,7 +22,6 @@ import ( "fmt" "iter" "maps" - "math" "slices" "sort" "time" @@ -202,17 +201,6 @@ func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) { binary.Write(&headerSection, binary.BigEndian, h.meta.block) // 8 byte for _, owner := range h.owners { - // Fill the header section with offsets at key and value section - headerSection.Write(owner.Bytes()) // 32 bytes - binary.Write(&headerSection, binary.BigEndian, uint32(keySection.Len())) // 4 bytes - - // The offset to the value section is theoretically unnecessary, since the - // individual value offset is already tracked in the key section. However, - // we still keep it here for two reasons: - // - It's cheap to store (only 4 bytes for each trie). - // - It can be useful for decoding the trie data when key is not required (e.g., in hash mode). - binary.Write(&headerSection, binary.BigEndian, uint32(valueSection.Len())) // 4 bytes - // Fill the key section with node index var ( prevKey []byte @@ -266,6 +254,21 @@ func (h *trienodeHistory) encode() ([]byte, []byte, []byte, error) { if _, err := keySection.Write(trailer); err != nil { return nil, nil, nil, err } + + // Fill the header section with the offsets of the key and value sections. + // Note that the key/value offsets are intentionally tracked *after* encoding + // them into their respective sections, ensuring each offset refers to the end + // position. For n trie chunks, n offset pairs are sufficient to uniquely locate + // the corresponding data. + headerSection.Write(owner.Bytes()) // 32 bytes + binary.Write(&headerSection, binary.BigEndian, uint32(keySection.Len())) // 4 bytes + + // The offset to the value section is theoretically unnecessary, since the + // individual value offset is already tracked in the key section. However, + // we still keep it here for two reasons: + // - It's cheap to store (only 4 bytes for each trie). + // - It can be useful for decoding the trie data when key is not required (e.g., in hash mode). + binary.Write(&headerSection, binary.BigEndian, uint32(valueSection.Len())) // 4 bytes } return headerSection.Bytes(), keySection.Bytes(), valueSection.Bytes(), nil } @@ -475,22 +478,22 @@ func (h *trienodeHistory) decode(header []byte, keySection []byte, valueSection for i := range len(owners) { // Resolve the boundary of key section - keyStart := keyOffsets[i] - keyLimit := len(keySection) - if i != len(owners)-1 { - keyLimit = int(keyOffsets[i+1]) + var keyStart, keyLimit uint32 + if i != 0 { + keyStart = keyOffsets[i-1] } - if int(keyStart) > len(keySection) || keyLimit > len(keySection) { + keyLimit = keyOffsets[i] + if int(keyStart) > len(keySection) || int(keyLimit) > len(keySection) { return fmt.Errorf("invalid key offsets: keyStart: %d, keyLimit: %d, size: %d", keyStart, keyLimit, len(keySection)) } // Resolve the boundary of value section - valStart := valueOffsets[i] - valLimit := len(valueSection) - if i != len(owners)-1 { - valLimit = int(valueOffsets[i+1]) + var valStart, valLimit uint32 + if i != 0 { + valStart = valueOffsets[i-1] } - if int(valStart) > len(valueSection) || valLimit > len(valueSection) { + valLimit = valueOffsets[i] + if int(valStart) > len(valueSection) || int(valLimit) > len(valueSection) { return fmt.Errorf("invalid value offsets: valueStart: %d, valueLimit: %d, size: %d", valStart, valLimit, len(valueSection)) } @@ -510,33 +513,27 @@ type iRange struct { limit uint32 } +func (ir iRange) len() uint32 { + return ir.limit - ir.start +} + // singleTrienodeHistoryReader provides read access to a single trie within the // trienode history. It stores an offset to the trie's position in the history, // along with a set of per-node offsets that can be resolved on demand. type singleTrienodeHistoryReader struct { id uint64 reader ethdb.AncientReader - valueRange iRange // value range within the total value section + valueRange iRange // value range within the global value section valueInternalOffsets map[string]iRange // value offset within the single trie data } func newSingleTrienodeHistoryReader(id uint64, reader ethdb.AncientReader, keyRange iRange, valueRange iRange) (*singleTrienodeHistoryReader, error) { - // TODO(rjl493456442) partial freezer read should be supported - keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id) + keyData, err := rawdb.ReadTrienodeHistoryKeySection(reader, id, uint64(keyRange.start), uint64(keyRange.len())) if err != nil { return nil, err } - keyStart := int(keyRange.start) - keyLimit := int(keyRange.limit) - if keyRange.limit == math.MaxUint32 { - keyLimit = len(keyData) - } - if len(keyData) < keyStart || len(keyData) < keyLimit { - return nil, fmt.Errorf("key section too short, start: %d, limit: %d, size: %d", keyStart, keyLimit, len(keyData)) - } - valueOffsets := make(map[string]iRange) - _, err = decodeSingle(keyData[keyStart:keyLimit], func(key []byte, start int, limit int) error { + _, err = decodeSingle(keyData, func(key []byte, start int, limit int) error { valueOffsets[string(key)] = iRange{ start: uint32(start), limit: uint32(limit), @@ -560,20 +557,7 @@ func (sr *singleTrienodeHistoryReader) read(path string) ([]byte, error) { if !exists { return nil, fmt.Errorf("trienode %v not found", []byte(path)) } - // TODO(rjl493456442) partial freezer read should be supported - valueData, err := rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id) - if err != nil { - return nil, err - } - if len(valueData) < int(sr.valueRange.start) { - return nil, fmt.Errorf("value section too short, start: %d, size: %d", sr.valueRange.start, len(valueData)) - } - entryStart := sr.valueRange.start + offset.start - entryLimit := sr.valueRange.start + offset.limit - if len(valueData) < int(entryStart) || len(valueData) < int(entryLimit) { - return nil, fmt.Errorf("value section too short, start: %d, limit: %d, size: %d", entryStart, entryLimit, len(valueData)) - } - return valueData[int(entryStart):int(entryLimit)], nil + return rawdb.ReadTrienodeHistoryValueSection(sr.reader, sr.id, uint64(sr.valueRange.start+offset.start), uint64(offset.len())) } // trienodeHistoryReader provides read access to node data in the trie node history. @@ -614,27 +598,23 @@ func (r *trienodeHistoryReader) decodeHeader() error { } for i, owner := range owners { // Decode the key range for this trie chunk - var keyLimit uint32 - if i == len(owners)-1 { - keyLimit = math.MaxUint32 - } else { - keyLimit = keyOffsets[i+1] + var keyStart uint32 + if i != 0 { + keyStart = keyOffsets[i-1] } r.keyRanges[owner] = iRange{ - start: keyOffsets[i], - limit: keyLimit, + start: keyStart, + limit: keyOffsets[i], } // Decode the value range for this trie chunk - var valLimit uint32 - if i == len(owners)-1 { - valLimit = math.MaxUint32 - } else { - valLimit = valOffsets[i+1] + var valStart uint32 + if i != 0 { + valStart = valOffsets[i-1] } r.valRanges[owner] = iRange{ - start: valOffsets[i], - limit: valLimit, + start: valStart, + limit: valOffsets[i], } } return nil From d34e77fdaa555b41ac19b17ec4c155f6e32ff409 Mon Sep 17 00:00:00 2001 From: Rizky Ikwan Date: Sun, 26 Oct 2025 09:13:04 +0100 Subject: [PATCH 164/228] rpc: remove unused vars (#33012) --- rpc/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/rpc/client.go b/rpc/client.go index ba7e43eb5c4e..9dc36a6105ec 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -32,7 +32,6 @@ import ( ) var ( - ErrBadResult = errors.New("bad result in JSON-RPC response") ErrClientQuit = errors.New("client is closed") ErrNoResult = errors.New("JSON-RPC response has no result") ErrMissingBatchResponse = errors.New("response batch did not contain a response to this call") From 9f6e8b00622290ce79a935b80fa1523cccbd06f6 Mon Sep 17 00:00:00 2001 From: cui Date: Sun, 26 Oct 2025 16:13:59 +0800 Subject: [PATCH 165/228] core/state: improve accessList copy (#33024) --- core/state/access_list.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/state/access_list.go b/core/state/access_list.go index e3f173886482..0b830e72221f 100644 --- a/core/state/access_list.go +++ b/core/state/access_list.go @@ -61,9 +61,10 @@ func newAccessList() *accessList { // Copy creates an independent copy of an accessList. func (al *accessList) Copy() *accessList { - cp := newAccessList() - cp.addresses = maps.Clone(al.addresses) - cp.slots = make([]map[common.Hash]struct{}, len(al.slots)) + cp := &accessList{ + addresses: maps.Clone(al.addresses), + slots: make([]map[common.Hash]struct{}, len(al.slots)), + } for i, slotMap := range al.slots { cp.slots[i] = maps.Clone(slotMap) } From 72ff167de9d0f1b32d7f55a2ab570d35d3f4168d Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 27 Oct 2025 16:53:26 +0800 Subject: [PATCH 166/228] core: don't modify the shared chainId between tests (#33020) --- core/verkle_witness_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/verkle_witness_test.go b/core/verkle_witness_test.go index ca0c928c3c36..9495e325ca9d 100644 --- a/core/verkle_witness_test.go +++ b/core/verkle_witness_test.go @@ -455,7 +455,7 @@ func verkleTestGenesis(config *params.ChainConfig) *Genesis { func TestProcessVerkleContractWithEmptyCode(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig - config.ChainID.SetUint64(69421) + config.ChainID = new(big.Int).SetUint64(69421) gspec := verkleTestGenesis(&config) genesisH, _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { @@ -511,7 +511,7 @@ func TestProcessVerkleContractWithEmptyCode(t *testing.T) { func TestProcessVerkleExtCodeHashOpcode(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig - config.ChainID.SetUint64(69421) + config.ChainID = new(big.Int).SetUint64(69421) var ( signer = types.LatestSigner(&config) @@ -615,7 +615,7 @@ func TestProcessVerkleExtCodeHashOpcode(t *testing.T) { func TestProcessVerkleBalanceOpcode(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig - config.ChainID.SetUint64(69421) + config.ChainID = new(big.Int).SetUint64(69421) var ( signer = types.LatestSigner(&config) @@ -672,7 +672,7 @@ func TestProcessVerkleBalanceOpcode(t *testing.T) { func TestProcessVerkleSelfDestructInSeparateTx(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig - config.ChainID.SetUint64(69421) + config.ChainID = new(big.Int).SetUint64(69421) var ( signer = types.LatestSigner(&config) @@ -792,7 +792,7 @@ func TestProcessVerkleSelfDestructInSeparateTx(t *testing.T) { func TestProcessVerkleSelfDestructInSameTx(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig - config.ChainID.SetUint64(69421) + config.ChainID = new(big.Int).SetUint64(69421) var ( signer = types.LatestSigner(&config) @@ -888,7 +888,7 @@ func TestProcessVerkleSelfDestructInSameTx(t *testing.T) { func TestProcessVerkleSelfDestructInSeparateTxWithSelfBeneficiary(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig - config.ChainID.SetUint64(69421) + config.ChainID = new(big.Int).SetUint64(69421) var ( signer = types.LatestSigner(&config) @@ -978,7 +978,7 @@ func TestProcessVerkleSelfDestructInSeparateTxWithSelfBeneficiary(t *testing.T) func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiary(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig - config.ChainID.SetUint64(69421) + config.ChainID = new(big.Int).SetUint64(69421) var ( signer = types.LatestSigner(&config) @@ -1042,7 +1042,7 @@ func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiary(t *testing.T) { func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiaryAndPrefundedAccount(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig - config.ChainID.SetUint64(69421) + config.ChainID = new(big.Int).SetUint64(69421) var ( signer = types.LatestSigner(&config) From b556c248b06f9fa3fa607270ee8f487b15605abc Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 27 Oct 2025 23:04:06 +0800 Subject: [PATCH 167/228] core/types: optimize modernSigner.Equal (#32971) Equal is called every time the transaction sender is accessed, even when the sender is cached, so it is worth optimizing. --------- Co-authored-by: Felix Lange --- core/types/transaction_signing.go | 33 +++++++++++++++++--------- core/types/transaction_signing_test.go | 12 ++++++++++ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 01aa67c6ba44..ef8fb194d5f9 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -20,7 +20,6 @@ import ( "crypto/ecdsa" "errors" "fmt" - "maps" "math/big" "github.com/ethereum/go-ethereum/common" @@ -183,18 +182,31 @@ type Signer interface { // modernSigner is the signer implementation that handles non-legacy transaction types. // For legacy transactions, it defers to one of the legacy signers (frontier, homestead, eip155). type modernSigner struct { - txtypes map[byte]struct{} + txtypes txtypeSet chainID *big.Int legacy Signer } +// txtypeSet is a bitmap for transaction types. +type txtypeSet [2]uint64 + +func (v *txtypeSet) set(txType byte) { + v[txType/64] |= 1 << (txType % 64) +} + +func (v *txtypeSet) has(txType byte) bool { + if txType >= byte(len(v)*64) { + return false + } + return v[txType/64]&(1<<(txType%64)) != 0 +} + func newModernSigner(chainID *big.Int, fork forks.Fork) Signer { if chainID == nil || chainID.Sign() <= 0 { panic(fmt.Sprintf("invalid chainID %v", chainID)) } s := &modernSigner{ chainID: chainID, - txtypes: make(map[byte]struct{}, 4), } // configure legacy signer switch { @@ -205,19 +217,19 @@ func newModernSigner(chainID *big.Int, fork forks.Fork) Signer { default: s.legacy = FrontierSigner{} } - s.txtypes[LegacyTxType] = struct{}{} + s.txtypes.set(LegacyTxType) // configure tx types if fork >= forks.Berlin { - s.txtypes[AccessListTxType] = struct{}{} + s.txtypes.set(AccessListTxType) } if fork >= forks.London { - s.txtypes[DynamicFeeTxType] = struct{}{} + s.txtypes.set(DynamicFeeTxType) } if fork >= forks.Cancun { - s.txtypes[BlobTxType] = struct{}{} + s.txtypes.set(BlobTxType) } if fork >= forks.Prague { - s.txtypes[SetCodeTxType] = struct{}{} + s.txtypes.set(SetCodeTxType) } return s } @@ -228,7 +240,7 @@ func (s *modernSigner) ChainID() *big.Int { func (s *modernSigner) Equal(s2 Signer) bool { other, ok := s2.(*modernSigner) - return ok && s.chainID.Cmp(other.chainID) == 0 && maps.Equal(s.txtypes, other.txtypes) && s.legacy.Equal(other.legacy) + return ok && s.chainID.Cmp(other.chainID) == 0 && s.txtypes == other.txtypes && s.legacy.Equal(other.legacy) } func (s *modernSigner) Hash(tx *Transaction) common.Hash { @@ -236,8 +248,7 @@ func (s *modernSigner) Hash(tx *Transaction) common.Hash { } func (s *modernSigner) supportsType(txtype byte) bool { - _, ok := s.txtypes[txtype] - return ok + return s.txtypes.has(txtype) } func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) { diff --git a/core/types/transaction_signing_test.go b/core/types/transaction_signing_test.go index b66577f7ed5d..02a65fda1398 100644 --- a/core/types/transaction_signing_test.go +++ b/core/types/transaction_signing_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" "github.com/ethereum/go-ethereum/rlp" ) @@ -188,3 +189,14 @@ func createTestLegacyTxInner() *LegacyTx { Data: nil, } } + +func Benchmark_modernSigner_Equal(b *testing.B) { + signer1 := newModernSigner(big.NewInt(1), forks.Amsterdam) + signer2 := newModernSigner(big.NewInt(1), forks.Amsterdam) + + for b.Loop() { + if !signer1.Equal(signer2) { + b.Fatal("expected signers to be equal") + } + } +} From dca38204c431dfcdd8ceef18b8672ec62a574d3a Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 28 Oct 2025 13:53:42 +0800 Subject: [PATCH 168/228] core: refine condition for using legacy chain freezer directory (#33032) --- common/path.go | 11 +++++++++++ core/rawdb/database.go | 2 +- node/defaults.go | 13 ++----------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/common/path.go b/common/path.go index 49c6a5efc2a4..19f24d426aa0 100644 --- a/common/path.go +++ b/common/path.go @@ -37,3 +37,14 @@ func AbsolutePath(datadir string, filename string) string { } return filepath.Join(datadir, filename) } + +// IsNonEmptyDir checks if a directory exists and is non-empty. +func IsNonEmptyDir(dir string) bool { + f, err := os.Open(dir) + if err != nil { + return false + } + defer f.Close() + names, _ := f.Readdirnames(1) + return len(names) > 0 +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 724c90ead6a8..29483baa5f7b 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -177,7 +177,7 @@ func resolveChainFreezerDir(ancient string) string { // - chain freezer exists in legacy location (root ancient folder) freezer := filepath.Join(ancient, ChainFreezerName) if !common.FileExist(freezer) { - if !common.FileExist(ancient) { + if !common.FileExist(ancient) || !common.IsNonEmptyDir(ancient) { // The entire ancient store is not initialized, still use the sub // folder for initialization. } else { diff --git a/node/defaults.go b/node/defaults.go index 307d9e186a25..6c643e2b540e 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -22,6 +22,7 @@ import ( "path/filepath" "runtime" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/rpc" @@ -90,7 +91,7 @@ func DefaultDataDir() string { // is non-empty, use it, otherwise DTRT and check %LOCALAPPDATA%. fallback := filepath.Join(home, "AppData", "Roaming", "Ethereum") appdata := windowsAppData() - if appdata == "" || isNonEmptyDir(fallback) { + if appdata == "" || common.IsNonEmptyDir(fallback) { return fallback } return filepath.Join(appdata, "Ethereum") @@ -113,16 +114,6 @@ func windowsAppData() string { return v } -func isNonEmptyDir(dir string) bool { - f, err := os.Open(dir) - if err != nil { - return false - } - names, _ := f.Readdir(1) - f.Close() - return len(names) > 0 -} - func homeDir() string { if home := os.Getenv("HOME"); home != "" { return home From baeb82855b25a027198dd9f067c66a9d939304d3 Mon Sep 17 00:00:00 2001 From: anim001k <140460766+anim001k@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:34:14 +0100 Subject: [PATCH 169/228] internal/jsre: pass correct args to setTimeout/setInterval callbacks (#32936) ## Description - Summary: Correct the JS timer callback argument forwarding to match standard JS semantics. - What changed: In `internal/jsre/jsre.go`, the callback is now invoked with only the arguments after the callback and delay. - Why: Previously, the callback received the function and delay as parameters, causing unexpected behavior and logic bugs for consumers. --- internal/jsre/jsre.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/jsre/jsre.go b/internal/jsre/jsre.go index 0dfeae8e1b6d..4512115f1639 100644 --- a/internal/jsre/jsre.go +++ b/internal/jsre/jsre.go @@ -201,7 +201,7 @@ loop: if !isFunc { panic(re.vm.ToValue("js error: timer/timeout callback is not a function")) } - call(goja.Null(), timer.call.Arguments...) + call(goja.Null(), timer.call.Arguments[2:]...) _, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it if timer.interval && inreg { From 071a84942162e7a2b11bd591ae52baaf7ae98e32 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:56:44 +0100 Subject: [PATCH 170/228] .github: add 32-bit CI targets (#32911) This adds two new CI targets. One is for building all supported keeper executables, the other is for running unit tests on 32-bit Linux. --------- Co-authored-by: Felix Lange --- .github/workflows/go.yml | 41 ++++++++++++++++++++++++++++++++++++++++ appveyor.yml | 2 +- beacon/params/config.go | 4 ++++ build/ci.go | 21 ++++++++++---------- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index b8cf7f75e0dd..50c9fe7f7511 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -34,6 +34,47 @@ jobs: go run build/ci.go check_generate go run build/ci.go check_baddeps + keeper: + name: Keeper Builds + needs: test + runs-on: [self-hosted-ghr, size-l-x64] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + cache: false + + - name: Build + run: go run build/ci.go keeper + + test-32bit: + name: "32bit tests" + needs: test + runs-on: [self-hosted-ghr, size-l-x64] + steps: + - uses: actions/checkout@v4 + with: + submodules: false + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + cache: false + + - name: Install cross toolchain + run: | + apt-get update + apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib + + - name: Build + run: go run build/ci.go test -arch 386 -short -p 8 + test: name: Test needs: lint diff --git a/appveyor.yml b/appveyor.yml index 8dce7f30a29c..aeafcfc8380b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,4 +36,4 @@ for: - go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds - go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds test_script: - - go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short -skip-spectests + - go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short diff --git a/beacon/params/config.go b/beacon/params/config.go index 492ee53308fd..b01b739e07fe 100644 --- a/beacon/params/config.go +++ b/beacon/params/config.go @@ -108,6 +108,8 @@ func (c *ChainConfig) LoadForks(file []byte) error { switch version := value.(type) { case int: versions[name] = new(big.Int).SetUint64(uint64(version)).FillBytes(make([]byte, 4)) + case int64: + versions[name] = new(big.Int).SetUint64(uint64(version)).FillBytes(make([]byte, 4)) case uint64: versions[name] = new(big.Int).SetUint64(version).FillBytes(make([]byte, 4)) case string: @@ -125,6 +127,8 @@ func (c *ChainConfig) LoadForks(file []byte) error { switch epoch := value.(type) { case int: epochs[name] = uint64(epoch) + case int64: + epochs[name] = uint64(epoch) case uint64: epochs[name] = epoch case string: diff --git a/build/ci.go b/build/ci.go index 156626a82df7..59c948acb386 100644 --- a/build/ci.go +++ b/build/ci.go @@ -348,16 +348,15 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) ( func doTest(cmdline []string) { var ( - dlgo = flag.Bool("dlgo", false, "Download Go and build with it") - arch = flag.String("arch", "", "Run tests for given architecture") - cc = flag.String("cc", "", "Sets C compiler binary") - coverage = flag.Bool("coverage", false, "Whether to record code coverage") - verbose = flag.Bool("v", false, "Whether to log verbosely") - race = flag.Bool("race", false, "Execute the race detector") - short = flag.Bool("short", false, "Pass the 'short'-flag to go test") - cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads") - skipspectests = flag.Bool("skip-spectests", false, "Skip downloading execution-spec-tests fixtures") - threads = flag.Int("p", 1, "Number of CPU threads to use for testing") + dlgo = flag.Bool("dlgo", false, "Download Go and build with it") + arch = flag.String("arch", "", "Run tests for given architecture") + cc = flag.String("cc", "", "Sets C compiler binary") + coverage = flag.Bool("coverage", false, "Whether to record code coverage") + verbose = flag.Bool("v", false, "Whether to log verbosely") + race = flag.Bool("race", false, "Execute the race detector") + short = flag.Bool("short", false, "Pass the 'short'-flag to go test") + cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads") + threads = flag.Int("p", 1, "Number of CPU threads to use for testing") ) flag.CommandLine.Parse(cmdline) @@ -365,7 +364,7 @@ func doTest(cmdline []string) { csdb := download.MustLoadChecksums("build/checksums.txt") // Get test fixtures. - if !*skipspectests { + if !*short { downloadSpecTestFixtures(csdb, *cachedir) } From c726a2fc178061a8166a83ce244b11dc715917d0 Mon Sep 17 00:00:00 2001 From: Austin Larson <78000745+alarso16@users.noreply.github.com> Date: Wed, 29 Oct 2025 02:59:45 -0400 Subject: [PATCH 171/228] accounts/abi/bind/v2: fix error assertion in test (#33041) --- accounts/abi/bind/v2/util_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/accounts/abi/bind/v2/util_test.go b/accounts/abi/bind/v2/util_test.go index a9f5b4035ce1..5beb0a4faed4 100644 --- a/accounts/abi/bind/v2/util_test.go +++ b/accounts/abi/bind/v2/util_test.go @@ -144,10 +144,9 @@ func TestWaitDeployedCornerCases(t *testing.T) { done := make(chan struct{}) go func() { defer close(done) - want := errors.New("context canceled") _, err := bind.WaitDeployed(ctx, backend.Client(), tx.Hash()) - if err == nil || errors.Is(want, err) { - t.Errorf("error mismatch: want %v, got %v", want, err) + if !errors.Is(err, context.Canceled) { + t.Errorf("error mismatch: want %v, got %v", context.Canceled, err) } }() From 8376501c67ac333cf6b94abc1a67972b08505d24 Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 29 Oct 2025 17:34:19 +0800 Subject: [PATCH 172/228] p2p: cleanup v4 if v5 failed (#33005) Clean the previous resource (v4) if the latter (v5) failed. --- p2p/server.go | 5 +++++ p2p/server_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/p2p/server.go b/p2p/server.go index ddd4f5d072d2..10c855f1c463 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -490,6 +490,11 @@ func (srv *Server) setupDiscovery() error { } srv.discv5, err = discover.ListenV5(sconn, srv.localnode, cfg) if err != nil { + // Clean up v4 if v5 setup fails. + if srv.discv4 != nil { + srv.discv4.Close() + srv.discv4 = nil + } return err } } diff --git a/p2p/server_test.go b/p2p/server_test.go index d42926cf4c65..7bc7379099d6 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -579,6 +579,33 @@ func TestServerInboundThrottle(t *testing.T) { } } +func TestServerDiscoveryV5FailureRollsBackV4(t *testing.T) { + badBootstrap := enode.NewV4(&newkey().PublicKey, net.ParseIP("127.0.0.1"), 30303, 0) // invalid V5 of a V4 node + srv := &Server{ + Config: Config{ + PrivateKey: newkey(), + ListenAddr: "", + DiscAddr: "127.0.0.1:0", + MaxPeers: 5, + DiscoveryV4: true, + DiscoveryV5: true, + BootstrapNodesV5: []*enode.Node{badBootstrap}, + Logger: testlog.Logger(t, log.LvlTrace), + }, + } + err := srv.Start() + if err == nil { + t.Fatal("expected discovery v5 startup failure") + } + if !strings.Contains(err.Error(), "bad bootstrap node") { + t.Fatalf("unexpected error: %v", err) + } + if srv.DiscoveryV4() != nil { + t.Fatal("discovery v4 not cleaned after failure") + } + srv.Stop() +} + func listenFakeAddr(network, laddr string, remoteAddr net.Addr) (net.Listener, error) { l, err := net.Listen(network, laddr) if err == nil { From 1b59ef78465930051aea04a6869ba8f5f4a8075d Mon Sep 17 00:00:00 2001 From: Coder <161350311+MamunC0der@users.noreply.github.com> Date: Thu, 30 Oct 2025 02:20:07 +0100 Subject: [PATCH 173/228] common: simplify FileExist helper (#32969) --- common/path.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/common/path.go b/common/path.go index 19f24d426aa0..841946348ec0 100644 --- a/common/path.go +++ b/common/path.go @@ -17,6 +17,8 @@ package common import ( + "errors" + "io/fs" "os" "path/filepath" ) @@ -24,10 +26,7 @@ import ( // FileExist checks if a file exists at filePath. func FileExist(filePath string) bool { _, err := os.Stat(filePath) - if err != nil && os.IsNotExist(err) { - return false - } - return true + return !errors.Is(err, fs.ErrNotExist) } // AbsolutePath returns datadir + filename, or filename if it is absolute. From 9d8ff5a2ff7cdb36d658c7a2739cd7e286480f5d Mon Sep 17 00:00:00 2001 From: wit liu Date: Thu, 30 Oct 2025 15:39:02 +0800 Subject: [PATCH 174/228] eth/downloader: fix incorrect waitgroup in test `XTestDelivery` (#33047) --- eth/downloader/queue_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go index 120e3f9d2dea..ca71a769de29 100644 --- a/eth/downloader/queue_test.go +++ b/eth/downloader/queue_test.go @@ -351,6 +351,7 @@ func XTestDelivery(t *testing.T) { } } }() + wg.Add(1) go func() { defer wg.Done() // reserve receiptfetch From 55c147e48925dbcb2aac4ccf132a0802565d0be8 Mon Sep 17 00:00:00 2001 From: hero5512 Date: Fri, 31 Oct 2025 13:14:52 -0400 Subject: [PATCH 175/228] eth/tracers: fix prestateTracer for EIP-6780 SELFDESTRUCT (#33050) fix https://github.com/ethereum/go-ethereum/issues/33049 --- .../suicide_cancun.json | 101 ++++++++++++++++++ eth/tracers/native/prestate.go | 10 +- 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/suicide_cancun.json diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/suicide_cancun.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/suicide_cancun.json new file mode 100644 index 000000000000..cdabe669131a --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/suicide_cancun.json @@ -0,0 +1,101 @@ +{ + "context": { + "difficulty": "0", + "gasLimit": "8000000", + "miner": "0x0000000000000000000000000000000000000000", + "number": "1", + "timestamp": "1000", + "baseFeePerGas": "7" + }, + "genesis": { + "alloc": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x10000000000000000", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x1111111111111111111111111111111111111111": { + "balance": "0x0", + "nonce": "0", + "code": "0x", + "storage": {} + }, + "0x2222222222222222222222222222222222222222": { + "balance": "0xde0b6b3a7640000", + "nonce": "1", + "code": "0x6099600155731111111111111111111111111111111111111111ff", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000000000000000000000000000000000000000000000000000000000000abcd", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000001234" + } + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeNetsplitBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true + }, + "difficulty": "0", + "extraData": "0x", + "gasLimit": "8000000", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0" + }, + "input": "0xf860800a830186a094222222222222222222222222222222222222222280801ba0c4829400221936e8016721406f84b4710ead5608f15c785a3cedc20a7aebaab2a033e8e6e12cc432098b5ce8a409691f977867249073a3fc7804e8676c4d159475", + "tracerConfig": { + "diffMode": true + }, + "result": { + "pre": { + "0x2222222222222222222222222222222222222222": { + "balance": "0xde0b6b3a7640000", + "nonce": 1, + "code": "0x6099600155731111111111111111111111111111111111111111ff", + "codeHash": "0x701bdb1d43777a9304905a100f758955d130e09c8e86d97e3f6becccdc001048", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000000000000000000000000000000000000000000000000000000000000abcd" + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x10000000000000000" + } + }, + "post": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x2aed3" + }, + "0x1111111111111111111111111111111111111111": { + "balance": "0xde0b6b3a7640000" + }, + "0x2222222222222222222222222222222222222222": { + "balance": "0x0", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000099" + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xfffffffffff70e96", + "nonce": 1 + } + } + } +} diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 49679d312f91..2e446f729bc2 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -131,7 +131,15 @@ func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scop addr := common.Address(stackData[stackLen-1].Bytes20()) t.lookupAccount(addr) if op == vm.SELFDESTRUCT { - t.deleted[caller] = true + if t.chainConfig.IsCancun(t.env.BlockNumber, t.env.Time) { + // EIP-6780: only delete if created in same transaction + if t.created[caller] { + t.deleted[caller] = true + } + } else { + // Pre-EIP-6780: always delete + t.deleted[caller] = true + } } case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): addr := common.Address(stackData[stackLen-2].Bytes20()) From bd888922d5a451ee15421850f53ab49df9a16b32 Mon Sep 17 00:00:00 2001 From: maradini77 <140460067+maradini77@users.noreply.github.com> Date: Sat, 1 Nov 2025 06:17:45 +0100 Subject: [PATCH 176/228] common: fix duration comparison in PrettyAge (#33064) This pull request updates `PrettyAge.String` so that the age formatter now treats exact unit boundaries (like a full day or week) as that unit instead of spilling into smaller components, keeping duration output aligned with human expectations. --- common/format.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/format.go b/common/format.go index 7af41f52d540..31e08831f554 100644 --- a/common/format.go +++ b/common/format.go @@ -69,7 +69,7 @@ func (t PrettyAge) String() string { result, prec := "", 0 for _, unit := range ageUnits { - if diff > unit.Size { + if diff >= unit.Size { result = fmt.Sprintf("%s%d%s", result, diff/unit.Size, unit.Symbol) diff %= unit.Size From ad97f83627b3047bbca84944a0202c5e4022a53d Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 3 Nov 2025 21:11:14 +0800 Subject: [PATCH 177/228] core/rawdb: fix db inspector by supporting trienode history (#33087) --- core/rawdb/ancient_utils.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/rawdb/ancient_utils.go b/core/rawdb/ancient_utils.go index f4909d86e738..b940d910402c 100644 --- a/core/rawdb/ancient_utils.go +++ b/core/rawdb/ancient_utils.go @@ -105,6 +105,23 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) { } infos = append(infos, info) + case MerkleTrienodeFreezerName, VerkleTrienodeFreezerName: + datadir, err := db.AncientDatadir() + if err != nil { + return nil, err + } + f, err := NewTrienodeFreezer(datadir, freezer == VerkleTrienodeFreezerName, true) + if err != nil { + continue // might be possible the trienode freezer is not existent + } + defer f.Close() + + info, err := inspect(freezer, trienodeFreezerTableConfigs, f) + if err != nil { + return nil, err + } + infos = append(infos, info) + default: return nil, fmt.Errorf("unknown freezer, supported ones: %v", freezers) } From b6e10f41bc0b60e31f7ccfdc60ddda84563fc8b4 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 3 Nov 2025 17:41:22 +0100 Subject: [PATCH 178/228] params: set osaka and BPO1 & BPO2 mainnet dates (#33063) Sets the fusaka, bpo1, bpo2 timestamps for mainnet see: https://notes.ethereum.org/@bbusa/fusaka-bpo-timeline --- core/forkid/forkid_test.go | 19 ++++++++++++++----- core/txpool/blobpool/blobpool_test.go | 6 +++--- params/config.go | 6 ++++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index dc6e6fe81774..c78ff23cd6ae 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -76,10 +76,16 @@ func TestCreation(t *testing.T) { {20000000, 1681338454, ID{Hash: checksumToBytes(0xf0afd0e3), Next: 1681338455}}, // Last Gray Glacier block {20000000, 1681338455, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // First Shanghai block {30000000, 1710338134, ID{Hash: checksumToBytes(0xdce96c2d), Next: 1710338135}}, // Last Shanghai block - {40000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 1746612311}}, // First Cancun block + {30000000, 1710338135, ID{Hash: checksumToBytes(0x9f3d2254), Next: 1746612311}}, // First Cancun block {30000000, 1746022486, ID{Hash: checksumToBytes(0x9f3d2254), Next: 1746612311}}, // Last Cancun block - {30000000, 1746612311, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}}, // First Prague block - {50000000, 2000000000, ID{Hash: checksumToBytes(0xc376cf8b), Next: 0}}, // Future Prague block + {30000000, 1746612311, ID{Hash: checksumToBytes(0xc376cf8b), Next: 1764798551}}, // First Prague block + {30000000, 1764798550, ID{Hash: checksumToBytes(0xc376cf8b), Next: 1764798551}}, // Last Prague block + {30000000, 1764798551, ID{Hash: checksumToBytes(0x5167e2a6), Next: 1765290071}}, // First Osaka block + {30000000, 1765290070, ID{Hash: checksumToBytes(0x5167e2a6), Next: 1765290071}}, // Last Osaka block + {30000000, 1765290071, ID{Hash: checksumToBytes(0xcba2a1c0), Next: 1767747671}}, // First BPO1 block + {30000000, 1767747670, ID{Hash: checksumToBytes(0xcba2a1c0), Next: 1767747671}}, // Last BPO1 block + {30000000, 1767747671, ID{Hash: checksumToBytes(0x07c9462e), Next: 0}}, // First BPO2 block + {50000000, 2000000000, ID{Hash: checksumToBytes(0x07c9462e), Next: 0}}, // Future BPO2 block }, }, // Sepolia test cases @@ -162,6 +168,9 @@ func TestValidation(t *testing.T) { legacyConfig.ShanghaiTime = nil legacyConfig.CancunTime = nil legacyConfig.PragueTime = nil + legacyConfig.OsakaTime = nil + legacyConfig.BPO1Time = nil + legacyConfig.BPO2Time = nil tests := []struct { config *params.ChainConfig @@ -361,11 +370,11 @@ func TestValidation(t *testing.T) { // Local is mainnet Shanghai, remote is random Shanghai. {params.MainnetChainConfig, 20000000, 1681338455, ID{Hash: checksumToBytes(0x12345678), Next: 0}, ErrLocalIncompatibleOrStale}, - // Local is mainnet Prague, far in the future. Remote announces Gopherium (non existing fork) + // Local is mainnet BPO2, far in the future. Remote announces Gopherium (non existing fork) // at some future timestamp 8888888888, for itself, but past block for local. Local is incompatible. // // This case detects non-upgraded nodes with majority hash power (typical Ropsten mess). - {params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0xc376cf8b), Next: 8888888888}, ErrLocalIncompatibleOrStale}, + {params.MainnetChainConfig, 88888888, 8888888888, ID{Hash: checksumToBytes(0x07c9462e), Next: 8888888888}, ErrLocalIncompatibleOrStale}, // Local is mainnet Shanghai. Remote is also in Shanghai, but announces Gopherium (non existing // fork) at timestamp 1668000000, before Cancun. Local is incompatible. diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index f0f00c805589..f7d8ca209b85 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -992,7 +992,7 @@ func TestOpenCap(t *testing.T) { storage := t.TempDir() os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) - store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotterEIP7594(testMaxBlobsPerBlock), nil) // Insert a few transactions from a few accounts var ( @@ -1014,7 +1014,7 @@ func TestOpenCap(t *testing.T) { keep = []common.Address{addr1, addr3} drop = []common.Address{addr2} - size = uint64(2 * (txAvgSize + blobSize)) + size = 2 * (txAvgSize + blobSize + uint64(txBlobOverhead)) ) store.Put(blob1) store.Put(blob2) @@ -1023,7 +1023,7 @@ func TestOpenCap(t *testing.T) { // Verify pool capping twice: first by reducing the data cap, then restarting // with a high cap to ensure everything was persisted previously - for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} { + for _, datacap := range []uint64{2 * (txAvgSize + blobSize + uint64(txBlobOverhead)), 1000 * (txAvgSize + blobSize + uint64(txBlobOverhead))} { // Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) diff --git a/params/config.go b/params/config.go index 06288575ae0c..c5dfad1cd39e 100644 --- a/params/config.go +++ b/params/config.go @@ -61,11 +61,17 @@ var ( ShanghaiTime: newUint64(1681338455), CancunTime: newUint64(1710338135), PragueTime: newUint64(1746612311), + OsakaTime: newUint64(1764798551), + BPO1Time: newUint64(1765290071), + BPO2Time: newUint64(1767747671), DepositContractAddress: common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa"), Ethash: new(EthashConfig), BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, + Osaka: DefaultOsakaBlobConfig, + BPO1: DefaultBPO1BlobConfig, + BPO2: DefaultBPO2BlobConfig, }, } // HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network. From aaa585d7d6317275c5fef93c548ca869ca36db37 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Nov 2025 17:45:47 +0100 Subject: [PATCH 179/228] version: release go-ethereum v1.16.6 --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index ead2d04f2a74..77089a5a8626 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 6 // Patch version component of the current release - Meta = "unstable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 6 // Patch version component of the current release + Meta = "stable" // Version metadata to append to the version string ) From 9a99e0cc49f4255851e307add033e2718a427426 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 3 Nov 2025 17:47:42 +0100 Subject: [PATCH 180/228] version: begin v1.16.7 release cycle --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index 77089a5a8626..d4248013b006 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 6 // Patch version component of the current release - Meta = "stable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 7 // Patch version component of the current release + Meta = "unstable" // Version metadata to append to the version string ) From 3c862f3abb8475f4a69bd4ab2af48b1828cab78c Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 4 Nov 2025 12:48:13 +0100 Subject: [PATCH 181/228] go.mod: update to c-kzg v2.1.5 (#33093) We unfortunately missed this update for the Geth v1.16.6 release, but it is critical. --- cmd/keeper/go.mod | 2 +- cmd/keeper/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/keeper/go.mod b/cmd/keeper/go.mod index 2b12297a7ad6..9486347b1fac 100644 --- a/cmd/keeper/go.mod +++ b/cmd/keeper/go.mod @@ -18,7 +18,7 @@ require ( github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/emicklei/dot v1.6.2 // indirect - github.com/ethereum/c-kzg-4844/v2 v2.1.3 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect diff --git a/cmd/keeper/go.sum b/cmd/keeper/go.sum index b280a368d0fd..ad4c98c4b3b9 100644 --- a/cmd/keeper/go.sum +++ b/cmd/keeper/go.sum @@ -42,8 +42,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= -github.com/ethereum/c-kzg-4844/v2 v2.1.3 h1:DQ21UU0VSsuGy8+pcMJHDS0CV1bKmJmxsJYK8l3MiLU= -github.com/ethereum/c-kzg-4844/v2 v2.1.3/go.mod h1:fyNcYI/yAuLWJxf4uzVtS8VDKeoAaRM8G/+ADz/pRdA= +github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= +github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= diff --git a/go.mod b/go.mod index ae5e4cc11416..3590a5492932 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 - github.com/ethereum/c-kzg-4844/v2 v2.1.3 + github.com/ethereum/c-kzg-4844/v2 v2.1.5 github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab github.com/ethereum/go-verkle v0.2.2 github.com/fatih/color v1.16.0 diff --git a/go.sum b/go.sum index 8122f4b54867..6ecb0b7ec348 100644 --- a/go.sum +++ b/go.sum @@ -113,8 +113,8 @@ github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= -github.com/ethereum/c-kzg-4844/v2 v2.1.3 h1:DQ21UU0VSsuGy8+pcMJHDS0CV1bKmJmxsJYK8l3MiLU= -github.com/ethereum/c-kzg-4844/v2 v2.1.3/go.mod h1:fyNcYI/yAuLWJxf4uzVtS8VDKeoAaRM8G/+ADz/pRdA= +github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= +github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= From 82aea44b5321f7d77fabc4a493fee04da07e5bd9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 4 Nov 2025 13:16:02 +0100 Subject: [PATCH 182/228] version: release go-ethereum v1.16.7 stable --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index d4248013b006..5a39ac6318b9 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 7 // Patch version component of the current release - Meta = "unstable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 7 // Patch version component of the current release + Meta = "stable" // Version metadata to append to the version string ) From d4d9fad762fadd9be49822e9b79b306293422f48 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 4 Nov 2025 13:28:20 +0100 Subject: [PATCH 183/228] version: begin v1.16.8 release cycle --- version/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version/version.go b/version/version.go index 5a39ac6318b9..a3aad5d39898 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ package version const ( - Major = 1 // Major version component of the current release - Minor = 16 // Minor version component of the current release - Patch = 7 // Patch version component of the current release - Meta = "stable" // Version metadata to append to the version string + Major = 1 // Major version component of the current release + Minor = 16 // Minor version component of the current release + Patch = 8 // Patch version component of the current release + Meta = "unstable" // Version metadata to append to the version string ) From 45077677dd4a350d872f5b66f53fa3730a79ed3f Mon Sep 17 00:00:00 2001 From: maskpp Date: Tue, 4 Nov 2025 21:09:36 +0800 Subject: [PATCH 184/228] common: introduce IsHexHash and use it (#32998) --- cmd/geth/config.go | 9 ++++----- cmd/geth/snapshot.go | 6 +++--- common/types.go | 9 +++++++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index fcb315af979c..87627467d2b7 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -35,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/beacon/blsync" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/ethconfig" @@ -273,11 +272,11 @@ func makeFullNode(ctx *cli.Context) *node.Node { // Configure synchronization override service var synctarget common.Hash if ctx.IsSet(utils.SyncTargetFlag.Name) { - hex := hexutil.MustDecode(ctx.String(utils.SyncTargetFlag.Name)) - if len(hex) != common.HashLength { - utils.Fatalf("invalid sync target length: have %d, want %d", len(hex), common.HashLength) + target := ctx.String(utils.SyncTargetFlag.Name) + if !common.IsHexHash(target) { + utils.Fatalf("sync target hash is not a valid hex hash: %s", target) } - synctarget = common.BytesToHash(hex) + synctarget = common.HexToHash(target) } utils.RegisterSyncOverrideService(stack, eth, synctarget, ctx.Bool(utils.ExitWhenSyncedFlag.Name)) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 7621dfa93ca0..fc0658a59c8f 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -639,11 +639,11 @@ func snapshotExportPreimages(ctx *cli.Context) error { var root common.Hash if ctx.NArg() > 1 { - rootBytes := common.FromHex(ctx.Args().Get(1)) - if len(rootBytes) != common.HashLength { + hash := ctx.Args().Get(1) + if !common.IsHexHash(hash) { return fmt.Errorf("invalid hash: %s", ctx.Args().Get(1)) } - root = common.BytesToHash(rootBytes) + root = common.HexToHash(hash) } else { headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { diff --git a/common/types.go b/common/types.go index db4de8bcbd08..a96d6c7c8368 100644 --- a/common/types.go +++ b/common/types.go @@ -71,6 +71,15 @@ func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } // If b is larger than len(h), b will be cropped from the left. func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } +// IsHexHash verifies whether a string can represent a valid hex-encoded +// Ethereum hash or not. +func IsHexHash(s string) bool { + if has0xPrefix(s) { + s = s[2:] + } + return len(s) == 2*HashLength && isHex(s) +} + // Cmp compares two hashes. func (h Hash) Cmp(other Hash) int { return bytes.Compare(h[:], other[:]) From a4b9acd37512c6bb8f91d2929242ac90338557d4 Mon Sep 17 00:00:00 2001 From: MozirDmitriy Date: Tue, 4 Nov 2025 15:09:57 +0200 Subject: [PATCH 185/228] core/rawdb: fix readOnly mode for database (#33025) --- core/rawdb/database.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 29483baa5f7b..d5c0f0aab295 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -330,6 +330,7 @@ func Open(db ethdb.KeyValueStore, opts OpenOptions) (ethdb.Database, error) { }() } return &freezerdb{ + readOnly: opts.ReadOnly, ancientRoot: opts.Ancient, KeyValueStore: db, chainFreezer: frdb, From 873432c4e38bf8d6e90ef67d0889121da53b4f07 Mon Sep 17 00:00:00 2001 From: Rizky Ikwan Date: Thu, 6 Nov 2025 02:25:41 +0100 Subject: [PATCH 186/228] common: fix size comparison in StorageSize (#33105) --- common/size.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/size.go b/common/size.go index 097b6304a8d0..e7f504a08222 100644 --- a/common/size.go +++ b/common/size.go @@ -26,13 +26,13 @@ type StorageSize float64 // String implements the stringer interface. func (s StorageSize) String() string { - if s > 1099511627776 { + if s >= 1099511627776 { return fmt.Sprintf("%.2f TiB", s/1099511627776) - } else if s > 1073741824 { + } else if s >= 1073741824 { return fmt.Sprintf("%.2f GiB", s/1073741824) - } else if s > 1048576 { + } else if s >= 1048576 { return fmt.Sprintf("%.2f MiB", s/1048576) - } else if s > 1024 { + } else if s >= 1024 { return fmt.Sprintf("%.2f KiB", s/1024) } else { return fmt.Sprintf("%.2f B", s) @@ -42,13 +42,13 @@ func (s StorageSize) String() string { // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. func (s StorageSize) TerminalString() string { - if s > 1099511627776 { + if s >= 1099511627776 { return fmt.Sprintf("%.2fTiB", s/1099511627776) - } else if s > 1073741824 { + } else if s >= 1073741824 { return fmt.Sprintf("%.2fGiB", s/1073741824) - } else if s > 1048576 { + } else if s >= 1048576 { return fmt.Sprintf("%.2fMiB", s/1048576) - } else if s > 1024 { + } else if s >= 1024 { return fmt.Sprintf("%.2fKiB", s/1024) } else { return fmt.Sprintf("%.2fB", s) From e72b1457ed8439234f42e1e52fcabfbf7c4e97cd Mon Sep 17 00:00:00 2001 From: maskpp Date: Fri, 7 Nov 2025 11:00:20 +0800 Subject: [PATCH 187/228] core/state: fix bug about getting stable LogsHash result. (#33082) Because the map iteration is unstable, we need to order logs by tx index and keep the same order with receipts and their logs, so we can still get the same `LogsHash` across runs. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: rjl493456442 --- core/state/statedb.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/state/statedb.go b/core/state/statedb.go index b770698255e4..364bc4085035 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -22,6 +22,7 @@ import ( "fmt" "maps" "slices" + "sort" "sync" "sync/atomic" "time" @@ -264,6 +265,9 @@ func (s *StateDB) Logs() []*types.Log { for _, lgs := range s.logs { logs = append(logs, lgs...) } + sort.Slice(logs, func(i, j int) bool { + return logs[i].Index < logs[j].Index + }) return logs } From f70cd5b73dd82c6b6159fea2ca97849baa3329d3 Mon Sep 17 00:00:00 2001 From: Rizky Ikwan Date: Fri, 7 Nov 2025 08:24:11 +0100 Subject: [PATCH 188/228] accounts/usbwallet: fix version check in SignTypedMessage (#33113) The version check incorrectly used `&&` instead of `||`, causing versions like v1.0.x through v1.4.x to be allowed when they should be rejected. These versions don't support EIP-712 signing which was introduced in firmware v1.5.0. --- accounts/usbwallet/ledger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/usbwallet/ledger.go b/accounts/usbwallet/ledger.go index 52595a162120..80e63f18640f 100644 --- a/accounts/usbwallet/ledger.go +++ b/accounts/usbwallet/ledger.go @@ -184,7 +184,7 @@ func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash return nil, accounts.ErrWalletClosed } // Ensure the wallet is capable of signing the given transaction - if w.version[0] < 1 && w.version[1] < 5 { + if w.version[0] < 1 || (w.version[0] == 1 && w.version[1] < 5) { //lint:ignore ST1005 brand name displayed on the console return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2]) } From c752ea545e535617d36984d5cb5626f0c9402cb2 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 7 Nov 2025 14:55:58 +0100 Subject: [PATCH 189/228] core/vm: remove todo (#33120) Removes an unnecessary todo. This case is handled, the comment was an artifact from Kev's refactor --- core/vm/contracts.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index cae0be9f2de6..00ddbebd6baf 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -591,7 +591,6 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { if expLen > 32 { expHead.SetBytes(getData(input, baseLen, 32)) } else { - // TODO: Check that if expLen < baseLen, then getData will return an empty slice expHead.SetBytes(getData(input, baseLen, expLen)) } } From 1fe3eaa1b1e6ecf5902533b1eda35a17ce88463b Mon Sep 17 00:00:00 2001 From: Delweng Date: Sat, 8 Nov 2025 06:06:15 +0800 Subject: [PATCH 190/228] triedb/pathdb: fix 32-bit integer overflow in history trienode decoder (#33098) failed in 32bit: ``` --- FAIL: TestDecodeSingleCorruptedData (0.00s) panic: runtime error: slice bounds out of range [:-1501805520] [recovered, repanicked] goroutine 38872 [running]: testing.tRunner.func1.2({0x838db20, 0xa355620}) /opt/actions-runner/_work/_tool/go/1.25.3/x64/src/testing/testing.go:1872 +0x29b testing.tRunner.func1() /opt/actions-runner/_work/_tool/go/1.25.3/x64/src/testing/testing.go:1875 +0x414 panic({0x838db20, 0xa355620}) /opt/actions-runner/_work/_tool/go/1.25.3/x64/src/runtime/panic.go:783 +0x103 github.com/ethereum/go-ethereum/triedb/pathdb.decodeSingle({0x9e57500, 0x1432, 0x1432}, 0x0) /opt/actions-runner/_work/go-ethereum/go-ethereum/triedb/pathdb/history_trienode.go:399 +0x18d6 github.com/ethereum/go-ethereum/triedb/pathdb.TestDecodeSingleCorruptedData(0xa2db9e8) /opt/actions-runner/_work/go-ethereum/go-ethereum/triedb/pathdb/history_trienode_test.go:698 +0x180 testing.tRunner(0xa2db9e8, 0x83c86e8) /opt/actions-runner/_work/_tool/go/1.25.3/x64/src/testing/testing.go:1934 +0x114 created by testing.(*T).Run in goroutine 1 /opt/actions-runner/_work/_tool/go/1.25.3/x64/src/testing/testing.go:1997 +0x4b4 FAIL github.com/ethereum/go-ethereum/triedb/pathdb 41.453s ? github.com/ethereum/go-ethereum/version [no test files] FAIL ``` Found in https://github.com/ethereum/go-ethereum/actions/runs/18912701345/job/53990136071?pr=33052 --- triedb/pathdb/history_trienode.go | 16 ++++++++++++++++ triedb/pathdb/history_trienode_test.go | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/triedb/pathdb/history_trienode.go b/triedb/pathdb/history_trienode.go index 3f45b41117d4..1004106af9c1 100644 --- a/triedb/pathdb/history_trienode.go +++ b/triedb/pathdb/history_trienode.go @@ -19,9 +19,11 @@ package pathdb import ( "bytes" "encoding/binary" + "errors" "fmt" "iter" "maps" + "math" "slices" "sort" "time" @@ -386,12 +388,26 @@ func decodeSingle(keySection []byte, onValue func([]byte, int, int) error) ([]st } // Resolve the entry from key section nShared, nn := binary.Uvarint(keySection[keyOff:]) // key length shared (varint) + if nn <= 0 { + return nil, fmt.Errorf("corrupted varint encoding for nShared at offset %d", keyOff) + } keyOff += nn nUnshared, nn := binary.Uvarint(keySection[keyOff:]) // key length not shared (varint) + if nn <= 0 { + return nil, fmt.Errorf("corrupted varint encoding for nUnshared at offset %d", keyOff) + } keyOff += nn nValue, nn := binary.Uvarint(keySection[keyOff:]) // value length (varint) + if nn <= 0 { + return nil, fmt.Errorf("corrupted varint encoding for nValue at offset %d", keyOff) + } keyOff += nn + // Validate that the values can fit in an int to prevent overflow on 32-bit systems + if nShared > uint64(math.MaxUint32) || nUnshared > uint64(math.MaxUint32) || nValue > uint64(math.MaxUint32) { + return nil, errors.New("key size too large") + } + // Resolve unshared key if keyOff+int(nUnshared) > len(keySection) { return nil, fmt.Errorf("key length too long, unshared key length: %d, off: %d, section size: %d", nUnshared, keyOff, len(keySection)) diff --git a/triedb/pathdb/history_trienode_test.go b/triedb/pathdb/history_trienode_test.go index d6b80f61f56c..be4740a90457 100644 --- a/triedb/pathdb/history_trienode_test.go +++ b/triedb/pathdb/history_trienode_test.go @@ -694,7 +694,10 @@ func TestDecodeSingleCorruptedData(t *testing.T) { // Test with corrupted varint in key section corrupted := make([]byte, len(keySection)) copy(corrupted, keySection) - corrupted[5] = 0xFF // Corrupt varint + // Fill first 10 bytes with 0xFF to create a varint overflow (>64 bits) + for i := range 10 { + corrupted[i] = 0xFF + } _, err = decodeSingle(corrupted, nil) if err == nil { t.Fatal("Expected error for corrupted varint") From 0b465b62a3d74c25e24ca2cbd4a14927a90c980e Mon Sep 17 00:00:00 2001 From: Lucia Date: Sat, 8 Nov 2025 23:25:53 +1300 Subject: [PATCH 191/228] crypto: validate hash length in no cgo Sign (#33104) - Replace hardcoded DigestLength - Add hash length validation --- crypto/signature_nocgo.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crypto/signature_nocgo.go b/crypto/signature_nocgo.go index d76127c25888..9dce1057faf1 100644 --- a/crypto/signature_nocgo.go +++ b/crypto/signature_nocgo.go @@ -43,6 +43,9 @@ func sigToPub(hash, sig []byte) (*secp256k1.PublicKey, error) { if len(sig) != SignatureLength { return nil, errors.New("invalid signature") } + if len(hash) != DigestLength { + return nil, fmt.Errorf("hash is required to be exactly %d bytes (%d)", DigestLength, len(hash)) + } // Convert to secp256k1 input format with 'recovery id' v at the beginning. btcsig := make([]byte, SignatureLength) btcsig[0] = sig[RecoveryIDOffset] + 27 @@ -76,8 +79,8 @@ func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) { // // The produced signature is in the [R || S || V] format where V is 0 or 1. func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) { - if len(hash) != 32 { - return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash)) + if len(hash) != DigestLength { + return nil, fmt.Errorf("hash is required to be exactly %d bytes (%d)", DigestLength, len(hash)) } if prv.Curve != S256() { return nil, errors.New("private key curve is not secp256k1") From cd45794c1cfa231a9ef1a70edc9fa898c1b0becb Mon Sep 17 00:00:00 2001 From: Tristan-Wilson <87238672+Tristan-Wilson@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:38:28 +0100 Subject: [PATCH 192/228] consensus/misc/eip4844: expose TargetBlobsPerBlock (#32991) Rollups may want to use these to dynamically adjust blobs posted after BPO forks. --- consensus/misc/eip4844/eip4844.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index e14d12956137..c1a21195e320 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -200,6 +200,15 @@ func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int { return bcfg.Max } +// TargetBlobsPerBlock returns the target blobs per block for a block at the given timestamp. +func TargetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int { + blobConfig := latestBlobConfig(cfg, time) + if blobConfig == nil { + return 0 + } + return blobConfig.Target +} + // fakeExponential approximates factor * e ** (numerator / denominator) using // Taylor expansion. func fakeExponential(factor, numerator, denominator *big.Int) *big.Int { From 1fdba72f5ca6cff172e83a0d5d265ca0a12d78e1 Mon Sep 17 00:00:00 2001 From: sashass1315 Date: Mon, 10 Nov 2025 19:44:31 +0200 Subject: [PATCH 193/228] eth/catalyst: always reset timer after sealing error (#33146) The periodic sealing loop failed to reset its timer when sealBlock returned an error, causing the timer to never fire again and effectively halting block production in developer periodic mode after the first failure. This is a bug because the loop relies on the timer to trigger subsequent sealing attempts, and transient errors (e.g., pool races or chain rewinds) should not permanently stop the loop. The change moves timer.Reset after the sealing attempt unconditionally, ensuring the loop continues ticking and retrying even when sealing fails, which matches how other periodic timers in the codebase behave and preserves forward progress. --- eth/catalyst/simulated_beacon.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go index c10990c233cd..d9f01240a7a4 100644 --- a/eth/catalyst/simulated_beacon.go +++ b/eth/catalyst/simulated_beacon.go @@ -280,9 +280,8 @@ func (c *SimulatedBeacon) loop() { case <-timer.C: if err := c.sealBlock(c.withdrawals.pop(10), uint64(time.Now().Unix())); err != nil { log.Warn("Error performing sealing work", "err", err) - } else { - timer.Reset(time.Second * time.Duration(c.period)) } + timer.Reset(time.Second * time.Duration(c.period)) } } } From d1d011ca80e96381c67869b9be6c80143e55fb1b Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 11 Nov 2025 03:07:32 +0100 Subject: [PATCH 194/228] build: add wasm targets for keeper (#33124) [powdr](github.com/powdr-labs/powdr) has tested keeper in their womir system and managed to get it to work. This PR adds wasm as a keeper target. There's another plan by the zkevm team to support wasm with wasi as well, so these PR adds both targets. These currently uses the `example` tag, as there is no precompile intefrace defined for either target yet. Nonetheless, this is useful for testing these zkvms so it makes sense to support these experimental targets already. --- build/ci.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/build/ci.go b/build/ci.go index 59c948acb386..e589cd2b405c 100644 --- a/build/ci.go +++ b/build/ci.go @@ -107,6 +107,18 @@ var ( Tags: "ziren", Env: map[string]string{"GOMIPS": "softfloat", "CGO_ENABLED": "0"}, }, + { + Name: "wasm-js", + GOOS: "js", + GOARCH: "wasm", + Tags: "example", + }, + { + Name: "wasm-wasi", + GOOS: "wasip1", + GOARCH: "wasm", + Tags: "example", + }, { Name: "example", Tags: "example", @@ -331,6 +343,10 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) ( } ld = append(ld, "-extldflags", "'"+strings.Join(extld, " ")+"'") } + // TODO(gballet): revisit after the input api has been defined + if runtime.GOARCH == "wasm" { + ld = append(ld, "-gcflags=all=-d=softfloat") + } if len(ld) > 0 { flags = append(flags, "-ldflags", strings.Join(ld, " ")) } From 1e9895265488ec35e785f86372e939991d373043 Mon Sep 17 00:00:00 2001 From: Matthieu Vachon Date: Tue, 11 Nov 2025 01:57:52 -0500 Subject: [PATCH 195/228] core/state: fixed hooked StateDB handling of `OnCodeChangeV2` (#33148) While updating to latest Geth, I noticed `OnCodeChangeV2` was not properly handled in `SelfDestruct/6780`, this PR fixes this and bring a unit test. Let me know if it's deemed more approriate to merge the tests with the other one. --- core/state/statedb_hooked.go | 4 +-- core/state/statedb_hooked_test.go | 41 +++++++++++++++++++++++++++++++ eth/tracers/native/mux.go | 8 ++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 9db201fc2b91..50acc03aa8be 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -220,7 +220,7 @@ func (s *hookedStateDB) SelfDestruct(address common.Address) uint256.Int { var prevCode []byte var prevCodeHash common.Hash - if s.hooks.OnCodeChange != nil { + if s.hooks.OnCodeChange != nil || s.hooks.OnCodeChangeV2 != nil { prevCode = s.inner.GetCode(address) prevCodeHash = s.inner.GetCodeHash(address) } @@ -246,7 +246,7 @@ func (s *hookedStateDB) SelfDestruct6780(address common.Address) (uint256.Int, b var prevCode []byte var prevCodeHash common.Hash - if s.hooks.OnCodeChange != nil { + if s.hooks.OnCodeChange != nil || s.hooks.OnCodeChangeV2 != nil { prevCodeHash = s.inner.GetCodeHash(address) prevCode = s.inner.GetCode(address) } diff --git a/core/state/statedb_hooked_test.go b/core/state/statedb_hooked_test.go index bacb7baee129..4ff1023eb2c9 100644 --- a/core/state/statedb_hooked_test.go +++ b/core/state/statedb_hooked_test.go @@ -122,6 +122,47 @@ func TestHooks(t *testing.T) { sdb.AddLog(&types.Log{ Address: common.Address{0xbb}, }) + + if len(result) != len(wants) { + t.Fatalf("number of tracing events wrong, have %d want %d", len(result), len(wants)) + } + + for i, want := range wants { + if have := result[i]; have != want { + t.Fatalf("error event %d, have\n%v\nwant%v\n", i, have, want) + } + } +} + +func TestHooks_OnCodeChangeV2(t *testing.T) { + inner, _ := New(types.EmptyRootHash, NewDatabaseForTesting()) + + var result []string + var wants = []string{ + "0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) ContractCreation", + "0xaa00000000000000000000000000000000000000.code: 0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct", + "0xbb00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) ContractCreation", + "0xbb00000000000000000000000000000000000000.code: 0x1326 (0x3c54516221d604e623f358bc95996ca3242aaa109bddabcebda13db9b3f90dcb) -> (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) SelfDestruct", + } + emitF := func(format string, a ...any) { + result = append(result, fmt.Sprintf(format, a...)) + } + sdb := NewHookedState(inner, &tracing.Hooks{ + OnCodeChangeV2: func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) { + emitF("%v.code: %#x (%v) ->%#x (%v) %s", addr, prevCode, prevCodeHash, code, codeHash, reason) + }, + }) + sdb.SetCode(common.Address{0xaa}, []byte{0x13, 37}, tracing.CodeChangeContractCreation) + sdb.SelfDestruct(common.Address{0xaa}) + + sdb.SetCode(common.Address{0xbb}, []byte{0x13, 38}, tracing.CodeChangeContractCreation) + sdb.CreateContract(common.Address{0xbb}) + sdb.SelfDestruct6780(common.Address{0xbb}) + + if len(result) != len(wants) { + t.Fatalf("number of tracing events wrong, have %d want %d", len(result), len(wants)) + } + for i, want := range wants { if have := result[i]; have != want { t.Fatalf("error event %d, have\n%v\nwant%v\n", i, have, want) diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index 77ab254568e6..37fc64f3f5e2 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -156,6 +156,14 @@ func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, pre } } +func (t *muxTracer) OnCodeChangeV2(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte, reason tracing.CodeChangeReason) { + for _, t := range t.tracers { + if t.OnCodeChangeV2 != nil { + t.OnCodeChangeV2(a, prevCodeHash, prev, codeHash, code, reason) + } + } +} + func (t *muxTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) { for _, t := range t.tracers { if t.OnStorageChange != nil { From f67fd7dc148dca64677b1ccf8fb2e478ad6ffe65 Mon Sep 17 00:00:00 2001 From: Lucia Date: Tue, 11 Nov 2025 21:01:37 +1300 Subject: [PATCH 196/228] core/rawdb: capture open file error and fix resource leak (#33147) --- core/rawdb/eradb/eradb.go | 1 + core/rawdb/freezer_batch.go | 10 ++++++++-- core/rawdb/freezer_table.go | 7 ++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/core/rawdb/eradb/eradb.go b/core/rawdb/eradb/eradb.go index 29e658798e99..a552b94da92c 100644 --- a/core/rawdb/eradb/eradb.go +++ b/core/rawdb/eradb/eradb.go @@ -303,6 +303,7 @@ func (db *Store) openEraFile(epoch uint64) (*era.Era, error) { } // Sanity-check start block. if e.Start()%uint64(era.MaxEra1Size) != 0 { + e.Close() return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxEra1Size) } log.Debug("Opened era1 file", "epoch", epoch) diff --git a/core/rawdb/freezer_batch.go b/core/rawdb/freezer_batch.go index 7e46e49f4369..080c0720a17a 100644 --- a/core/rawdb/freezer_batch.go +++ b/core/rawdb/freezer_batch.go @@ -51,12 +51,18 @@ func newFreezerBatch(f *Freezer) *freezerBatch { // Append adds an RLP-encoded item of the given kind. func (batch *freezerBatch) Append(kind string, num uint64, item interface{}) error { - return batch.tables[kind].Append(num, item) + if table := batch.tables[kind]; table != nil { + return table.Append(num, item) + } + return errUnknownTable } // AppendRaw adds an item of the given kind. func (batch *freezerBatch) AppendRaw(kind string, num uint64, item []byte) error { - return batch.tables[kind].AppendRaw(num, item) + if table := batch.tables[kind]; table != nil { + return table.AppendRaw(num, item) + } + return errUnknownTable } // reset initializes the batch. diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index 01a754c5c80f..aedb2d8eedc4 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -1196,8 +1196,7 @@ func (t *freezerTable) sizeNolock() (uint64, error) { } // advanceHead should be called when the current head file would outgrow the file limits, -// and a new file must be opened. The caller of this method must hold the write-lock -// before calling this method. +// and a new file must be opened. This method acquires the write-lock internally. func (t *freezerTable) advanceHead() error { t.lock.Lock() defer t.lock.Unlock() @@ -1218,7 +1217,9 @@ func (t *freezerTable) advanceHead() error { return err } t.releaseFile(t.headId) - t.openFile(t.headId, openFreezerFileForReadOnly) + if _, err := t.openFile(t.headId, openFreezerFileForReadOnly); err != nil { + return err + } // Swap out the current head. t.head = newHead From 32214de3883e87d561fd52fa68eb415b70add4fb Mon Sep 17 00:00:00 2001 From: Michael Kaplan <55204436+michaelkaplan13@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:54:36 -0500 Subject: [PATCH 197/228] rpc: avoid unnecessary RST_STREAM, PING frames sent by client (#33122) Context from Cloudflare blog: https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive We were able to reproduce the same issue discussed by Cloudflare in their recent blog post above using the `ethclient`. --- rpc/http.go | 14 +++++++++++--- rpc/http_test.go | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/rpc/http.go b/rpc/http.go index f4b99429ef4f..a74f36a1b07f 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -168,13 +168,21 @@ func newClientTransportHTTP(endpoint string, cfg *clientConfig) reconnectFunc { } } +// cleanlyCloseBody avoids sending unnecessary RST_STREAM and PING frames by +// ensuring the whole body is read before being closed. +// See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive +func cleanlyCloseBody(body io.ReadCloser) error { + io.Copy(io.Discard, body) + return body.Close() +} + func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { hc := c.writeConn.(*httpConn) respBody, err := hc.doRequest(ctx, msg) if err != nil { return err } - defer respBody.Close() + defer cleanlyCloseBody(respBody) var resp jsonrpcMessage batch := [1]*jsonrpcMessage{&resp} @@ -191,7 +199,7 @@ func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonr if err != nil { return err } - defer respBody.Close() + defer cleanlyCloseBody(respBody) var respmsgs []*jsonrpcMessage if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { @@ -236,7 +244,7 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos if _, err := buf.ReadFrom(resp.Body); err == nil { body = buf.Bytes() } - resp.Body.Close() + cleanlyCloseBody(resp.Body) return nil, HTTPError{ Status: resp.Status, StatusCode: resp.StatusCode, diff --git a/rpc/http_test.go b/rpc/http_test.go index 6c268b62928d..15ddd59bd0a9 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -106,7 +106,7 @@ func confirmHTTPRequestYieldsStatusCode(t *testing.T, method, contentType, body if err != nil { t.Fatalf("request failed: %v", err) } - resp.Body.Close() + cleanlyCloseBody(resp.Body) confirmStatusCode(t, resp.StatusCode, expectedStatusCode) } From 36c2ab4d3e4a2551b8c58f84b49f846568c815de Mon Sep 17 00:00:00 2001 From: oxBoni Date: Wed, 12 Nov 2025 08:30:16 +0100 Subject: [PATCH 198/228] core: remove unused peek function in insertIterator (#33155) --- core/blockchain_insert.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/core/blockchain_insert.go b/core/blockchain_insert.go index ac6a156d3eb1..07a250a1bb96 100644 --- a/core/blockchain_insert.go +++ b/core/blockchain_insert.go @@ -131,28 +131,6 @@ func (it *insertIterator) next() (*types.Block, error) { return it.chain[it.index], it.validator.ValidateBody(it.chain[it.index]) } -// peek returns the next block in the iterator, along with any potential validation -// error for that block, but does **not** advance the iterator. -// -// Both header and body validation errors (nil too) is cached into the iterator -// to avoid duplicating work on the following next() call. -// nolint:unused -func (it *insertIterator) peek() (*types.Block, error) { - // If we reached the end of the chain, abort - if it.index+1 >= len(it.chain) { - return nil, nil - } - // Wait for verification result if not yet done - if len(it.errors) <= it.index+1 { - it.errors = append(it.errors, <-it.results) - } - if it.errors[it.index+1] != nil { - return it.chain[it.index+1], it.errors[it.index+1] - } - // Block header valid, ignore body validation since we don't have a parent anyway - return it.chain[it.index+1], nil -} - // previous returns the previous header that was being processed, or nil. func (it *insertIterator) previous() *types.Header { if it.index < 1 { From 03faa989fe243f82f1be2fe3d57cdf502a20ebe9 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Thu, 13 Nov 2025 07:32:01 +0100 Subject: [PATCH 199/228] core/txpool/blobpool: fix benchmarkPoolPending (#33161) Add BlobTxs flag to filter. Signed-off-by: Csaba Kiraly --- core/txpool/blobpool/blobpool_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index f7d8ca209b85..2fa1927cae2c 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -2353,6 +2353,7 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) { MinTip: uint256.NewInt(1), BaseFee: chain.basefee, BlobFee: chain.blobfee, + BlobTxs: true, }) if len(p) != int(capacity) { b.Fatalf("have %d want %d", len(p), capacity) From 1547bcfd2a672a27a398024d8770aed8ca2a5fe9 Mon Sep 17 00:00:00 2001 From: Marcel <153717436+MonkeyMarcel@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:48:26 +0800 Subject: [PATCH 200/228] eth/filters: further optimize tx hash map in #32965 (#33108) --- eth/filters/filter.go | 4 ++-- eth/filters/filter_system.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 422e5cd67b09..a818f0b60788 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -563,7 +563,7 @@ type ReceiptWithTx struct { // In addition to returning receipts, it also returns the corresponding transactions. // This is because receipts only contain low-level data, while user-facing data // may require additional information from the Transaction. -func filterReceipts(txHashes map[common.Hash]bool, ev core.ChainEvent) []*ReceiptWithTx { +func filterReceipts(txHashes map[common.Hash]struct{}, ev core.ChainEvent) []*ReceiptWithTx { var ret []*ReceiptWithTx receipts := ev.Receipts @@ -585,7 +585,7 @@ func filterReceipts(txHashes map[common.Hash]bool, ev core.ChainEvent) []*Receip } } else { for i, receipt := range receipts { - if txHashes[receipt.TxHash] { + if _, ok := txHashes[receipt.TxHash]; ok { ret = append(ret, &ReceiptWithTx{ Receipt: receipt, Transaction: txs[i], diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index f10e6a277bb7..8b9bce47b911 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -185,9 +185,9 @@ type subscription struct { txs chan []*types.Transaction headers chan *types.Header receipts chan []*ReceiptWithTx - txHashes map[common.Hash]bool // contains transaction hashes for transactionReceipts subscription filtering - installed chan struct{} // closed when the filter is installed - err chan error // closed when the filter is uninstalled + txHashes map[common.Hash]struct{} // contains transaction hashes for transactionReceipts subscription filtering + installed chan struct{} // closed when the filter is installed + err chan error // closed when the filter is uninstalled } // EventSystem creates subscriptions, processes events and broadcasts them to the @@ -403,9 +403,9 @@ func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subsc // transactions when they are included in blocks. If txHashes is provided, only receipts // for those specific transaction hashes will be delivered. func (es *EventSystem) SubscribeTransactionReceipts(txHashes []common.Hash, receipts chan []*ReceiptWithTx) *Subscription { - hashSet := make(map[common.Hash]bool) + hashSet := make(map[common.Hash]struct{}, len(txHashes)) for _, h := range txHashes { - hashSet[h] = true + hashSet[h] = struct{}{} } sub := &subscription{ id: rpc.NewID(), From fdb55d33d344aeae98c0ebc4e88dd93a75420964 Mon Sep 17 00:00:00 2001 From: Forostovec Date: Thu, 13 Nov 2025 08:51:41 +0200 Subject: [PATCH 201/228] triedb/pathdb: fix ID assignment in history inspection (#33103) --- triedb/pathdb/history_inspect.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/triedb/pathdb/history_inspect.go b/triedb/pathdb/history_inspect.go index 9b4eea27b454..a839a184caa4 100644 --- a/triedb/pathdb/history_inspect.go +++ b/triedb/pathdb/history_inspect.go @@ -50,7 +50,7 @@ func sanitizeRange(start, end uint64, freezer ethdb.AncientReader) (uint64, uint if err != nil { return 0, 0, err } - last := head - 1 + last := head if end != 0 && end < last { last = end } @@ -143,7 +143,7 @@ func historyRange(freezer ethdb.AncientReader) (uint64, uint64, error) { if err != nil { return 0, 0, err } - last := head - 1 + last := head fh, err := readStateHistory(freezer, first) if err != nil { From 2cc9396d3a7d59ebfdf006383333ec324373a19b Mon Sep 17 00:00:00 2001 From: Delweng Date: Thu, 13 Nov 2025 15:06:27 +0800 Subject: [PATCH 202/228] eth/syncer: advance safe and finalized block (#33038) --- eth/syncer/syncer.go | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/eth/syncer/syncer.go b/eth/syncer/syncer.go index 6b33ec54ba56..83fe3ad2305f 100644 --- a/eth/syncer/syncer.go +++ b/eth/syncer/syncer.go @@ -22,6 +22,7 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/beacon/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" @@ -133,13 +134,35 @@ func (s *Syncer) run() { } case <-ticker.C: - if target == nil || !s.exitWhenSynced { + if target == nil { continue } - if block := s.backend.BlockChain().GetBlockByHash(target.Hash()); block != nil { - log.Info("Sync target reached", "number", block.NumberU64(), "hash", block.Hash()) - go s.stack.Close() // async since we need to close ourselves - return + + // Terminate the node if the target has been reached + if s.exitWhenSynced { + if block := s.backend.BlockChain().GetBlockByHash(target.Hash()); block != nil { + log.Info("Sync target reached", "number", block.NumberU64(), "hash", block.Hash()) + go s.stack.Close() // async since we need to close ourselves + return + } + } + + // Set the finalized and safe markers relative to the current head. + // The finalized marker is set two epochs behind the target, + // and the safe marker is set one epoch behind the target. + head := s.backend.BlockChain().CurrentHeader() + if head == nil { + continue + } + if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength*2); header != nil { + if final := s.backend.BlockChain().CurrentFinalBlock(); final == nil || final.Number.Cmp(header.Number) < 0 { + s.backend.BlockChain().SetFinalized(header) + } + } + if header := s.backend.BlockChain().GetHeaderByNumber(head.Number.Uint64() - params.EpochLength); header != nil { + if safe := s.backend.BlockChain().CurrentSafeBlock(); safe == nil || safe.Number.Cmp(header.Number) < 0 { + s.backend.BlockChain().SetSafe(header) + } } case <-s.closed: From 64910c1a9dfe4bb181e6c58efb91618b94073503 Mon Sep 17 00:00:00 2001 From: Bashmunta Date: Thu, 13 Nov 2025 10:15:44 +0200 Subject: [PATCH 203/228] core: use scheme-aware empty root in flushAlloc (#33168) --- core/genesis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/genesis.go b/core/genesis.go index d0d490874d07..7d640c8caec1 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -190,7 +190,7 @@ func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, e return common.Hash{}, err } // Commit newly generated states into disk if it's not empty. - if root != types.EmptyRootHash { + if root != emptyRoot { if err := triedb.Commit(root, true); err != nil { return common.Hash{}, err } From b4de681a20470fc435a482e221003cb5f8e00471 Mon Sep 17 00:00:00 2001 From: phrwlk Date: Thu, 13 Nov 2025 10:17:54 +0200 Subject: [PATCH 204/228] accounts/keystore: clear decrypted key after use (#33090) --- accounts/keystore/keystore.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index 3e4266924fd9..29c4bdf2ca43 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -418,6 +418,7 @@ func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) if err != nil { return nil, err } + defer zeroKey(key.PrivateKey) var N, P int if store, ok := ks.storage.(*keyStorePassphrase); ok { N, P = store.scryptN, store.scryptP @@ -477,6 +478,7 @@ func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) if err != nil { return err } + defer zeroKey(key.PrivateKey) return ks.storage.StoreKey(a.URL.Path, key, newPassphrase) } From 131288688c282892994be0bc579626f911e2f8cc Mon Sep 17 00:00:00 2001 From: oooLowNeoNooo Date: Fri, 14 Nov 2025 08:16:03 +0100 Subject: [PATCH 205/228] graphql: add nil check in Transaction.Type() method (#33184) Add nil check before calling tx.Type() to prevent panic when transaction is not found. --- graphql/graphql.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graphql/graphql.go b/graphql/graphql.go index 0b2a77a3c4c7..55da3185dda1 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -575,6 +575,9 @@ func (t *Transaction) getLogs(ctx context.Context, hash common.Hash) (*[]*Log, e func (t *Transaction) Type(ctx context.Context) *hexutil.Uint64 { tx, _ := t.resolve(ctx) + if tx == nil { + return nil + } txType := hexutil.Uint64(tx.Type()) return &txType } From 7fe964541b22085963d3ff609f542a23dc91e133 Mon Sep 17 00:00:00 2001 From: radik878 Date: Fri, 14 Nov 2025 14:55:41 +0200 Subject: [PATCH 206/228] core/rawdb: return iterator error in findTxInBlockBody (#33188) The iterator loop in findTxInBlockBody returned the outer-scoped err when iter.Err() was non-nil, which could incorrectly propagate a nil or stale error and hide actual RLP decoding issues. This patch returns iter.Err() as intended by the rlp list iterator API, matching established patterns elsewhere in the codebase and improving diagnostics when encountering malformed transaction entries. --- core/rawdb/accessors_indexes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index a725f144d46c..10eb454015b1 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -148,7 +148,7 @@ func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Trans txIndex := uint64(0) for iter.Next() { if iter.Err() != nil { - return nil, 0, err + return nil, 0, iter.Err() } // The preimage for the hash calculation of legacy transactions // is just their RLP encoding. For typed (EIP-2718) transactions, From abc9be9995928cfc793ae8226bfa439486c98c9a Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:39:36 +0100 Subject: [PATCH 207/228] core/rawdb: delete dead code to avoid more useless AI submissions (#33186) Co-authored-by: Gary Rong --- core/rawdb/accessors_chain.go | 93 ------------------------------ core/rawdb/accessors_chain_test.go | 51 ---------------- 2 files changed, 144 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index f20d675ff83e..6ae64fb2fd11 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -83,65 +83,6 @@ type NumberHash struct { Hash common.Hash } -// ReadAllHashesInRange retrieves all the hashes assigned to blocks at certain -// heights, both canonical and reorged forks included. -// This method considers both limits to be _inclusive_. -func ReadAllHashesInRange(db ethdb.Iteratee, first, last uint64) []*NumberHash { - var ( - start = encodeBlockNumber(first) - keyLength = len(headerPrefix) + 8 + 32 - hashes = make([]*NumberHash, 0, 1+last-first) - it = db.NewIterator(headerPrefix, start) - ) - defer it.Release() - for it.Next() { - key := it.Key() - if len(key) != keyLength { - continue - } - num := binary.BigEndian.Uint64(key[len(headerPrefix) : len(headerPrefix)+8]) - if num > last { - break - } - hash := common.BytesToHash(key[len(key)-32:]) - hashes = append(hashes, &NumberHash{num, hash}) - } - return hashes -} - -// ReadAllCanonicalHashes retrieves all canonical number and hash mappings at the -// certain chain range. If the accumulated entries reaches the given threshold, -// abort the iteration and return the semi-finish result. -func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int) ([]uint64, []common.Hash) { - // Short circuit if the limit is 0. - if limit == 0 { - return nil, nil - } - var ( - numbers []uint64 - hashes []common.Hash - ) - // Construct the key prefix of start point. - start, end := headerHashKey(from), headerHashKey(to) - it := db.NewIterator(nil, start) - defer it.Release() - - for it.Next() { - if bytes.Compare(it.Key(), end) >= 0 { - break - } - if key := it.Key(); len(key) == len(headerPrefix)+8+1 && bytes.Equal(key[len(key)-1:], headerHashSuffix) { - numbers = append(numbers, binary.BigEndian.Uint64(key[len(headerPrefix):len(headerPrefix)+8])) - hashes = append(hashes, common.BytesToHash(it.Value())) - // If the accumulated entries reaches the limit threshold, return. - if len(numbers) >= limit { - break - } - } - } - return numbers, hashes -} - // ReadHeaderNumber returns the header number assigned to a hash. func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) (uint64, bool) { data, _ := db.Get(headerNumberKey(hash)) @@ -886,40 +827,6 @@ func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) { } } -// DeleteBadBlocks deletes all the bad blocks from the database -func DeleteBadBlocks(db ethdb.KeyValueWriter) { - if err := db.Delete(badBlockKey); err != nil { - log.Crit("Failed to delete bad blocks", "err", err) - } -} - -// FindCommonAncestor returns the last common ancestor of two block headers -func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header { - for bn := b.Number.Uint64(); a.Number.Uint64() > bn; { - a = ReadHeader(db, a.ParentHash, a.Number.Uint64()-1) - if a == nil { - return nil - } - } - for an := a.Number.Uint64(); an < b.Number.Uint64(); { - b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1) - if b == nil { - return nil - } - } - for a.Hash() != b.Hash() { - a = ReadHeader(db, a.ParentHash, a.Number.Uint64()-1) - if a == nil { - return nil - } - b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1) - if b == nil { - return nil - } - } - return a -} - // ReadHeadHeader returns the current canonical head header. func ReadHeadHeader(db ethdb.Reader) *types.Header { headHeaderHash := ReadHeadHeaderHash(db) diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 819788b4dae2..02d51f4dd2a3 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -23,7 +23,6 @@ import ( "math/big" "math/rand" "os" - "reflect" "testing" "github.com/ethereum/go-ethereum/common" @@ -249,13 +248,6 @@ func TestBadBlockStorage(t *testing.T) { t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, badBlocks[i].NumberU64(), i+1, badBlocks[i+1].NumberU64()) } } - - // Delete all bad blocks - DeleteBadBlocks(db) - badBlocks = ReadAllBadBlocks(db) - if len(badBlocks) != 0 { - t.Fatalf("Failed to delete bad blocks") - } } // Tests that canonical numbers can be mapped to hashes and retrieved. @@ -516,37 +508,6 @@ func TestWriteAncientHeaderChain(t *testing.T) { } } -func TestCanonicalHashIteration(t *testing.T) { - var cases = []struct { - from, to uint64 - limit int - expect []uint64 - }{ - {1, 8, 0, nil}, - {1, 8, 1, []uint64{1}}, - {1, 8, 10, []uint64{1, 2, 3, 4, 5, 6, 7}}, - {1, 9, 10, []uint64{1, 2, 3, 4, 5, 6, 7, 8}}, - {2, 9, 10, []uint64{2, 3, 4, 5, 6, 7, 8}}, - {9, 10, 10, nil}, - } - // Test empty db iteration - db := NewMemoryDatabase() - numbers, _ := ReadAllCanonicalHashes(db, 0, 10, 10) - if len(numbers) != 0 { - t.Fatalf("No entry should be returned to iterate an empty db") - } - // Fill database with testing data. - for i := uint64(1); i <= 8; i++ { - WriteCanonicalHash(db, common.Hash{}, i) - } - for i, c := range cases { - numbers, _ := ReadAllCanonicalHashes(db, c.from, c.to, c.limit) - if !reflect.DeepEqual(numbers, c.expect) { - t.Fatalf("Case %d failed, want %v, got %v", i, c.expect, numbers) - } - } -} - func TestHashesInRange(t *testing.T) { mkHeader := func(number, seq int) *types.Header { h := types.Header{ @@ -565,18 +526,6 @@ func TestHashesInRange(t *testing.T) { total++ } } - if have, want := len(ReadAllHashesInRange(db, 10, 10)), 10; have != want { - t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) - } - if have, want := len(ReadAllHashesInRange(db, 10, 9)), 0; have != want { - t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) - } - if have, want := len(ReadAllHashesInRange(db, 0, 100)), total; have != want { - t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) - } - if have, want := len(ReadAllHashesInRange(db, 9, 10)), 9+10; have != want { - t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) - } if have, want := len(ReadAllHashes(db, 10)), 10; have != want { t.Fatalf("Wrong number of hashes read, want %d, got %d", want, have) } From 4fbfa0cc1b5f784565fe5c840e772d88b4589016 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:25:30 +0100 Subject: [PATCH 208/228] cmd/evm/internal/t8ntool, trie: support for verkle-at-genesis, use UBT, and move the transition tree to its own package (#32445) This is broken off of #31730 to only focus on testing networks that start with verkle at genesis. The PR has seen a lot of work since its creation, and it now targets creating and re-executing tests for a binary tree testnet without the transition (so it starts at genesis). The transition tree has been moved to its own package. It also replaces verkle with the binary tree for this specific application. --------- Co-authored-by: Gary Rong --- cmd/evm/internal/t8ntool/execution.go | 38 +- cmd/evm/internal/t8ntool/flags.go | 13 + cmd/evm/internal/t8ntool/transition.go | 225 ++++- cmd/evm/main.go | 48 + core/bintrie_witness_test.go | 237 +++++ core/chain_makers.go | 124 +-- core/genesis_test.go | 2 +- core/state/database.go | 10 +- core/state/dump.go | 24 + core/state/reader.go | 25 +- core/state/state_object.go | 3 +- core/verkle_witness_test.go | 1107 ----------------------- tests/block_test_util.go | 7 +- tests/init.go | 19 + trie/bintrie/binary_node.go | 44 +- trie/bintrie/binary_node_test.go | 18 +- trie/bintrie/hashed_node.go | 30 +- trie/bintrie/hashed_node_test.go | 83 +- trie/bintrie/internal_node.go | 82 +- trie/bintrie/iterator.go | 33 +- trie/bintrie/iterator_test.go | 83 -- trie/bintrie/key_encoding.go | 6 + trie/bintrie/stem_node.go | 31 +- trie/bintrie/stem_node_test.go | 14 +- trie/bintrie/trie.go | 120 ++- trie/bintrie/trie_test.go | 8 +- trie/{ => transitiontrie}/transition.go | 47 +- trie/utils/verkle.go | 114 +++ trie/verkle.go | 3 +- triedb/pathdb/database.go | 12 +- 30 files changed, 1142 insertions(+), 1468 deletions(-) create mode 100644 core/bintrie_witness_test.go delete mode 100644 core/verkle_witness_test.go delete mode 100644 trie/bintrie/iterator_test.go rename trie/{ => transitiontrie}/transition.go (87%) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 5303d432fb6b..44f15c322c4c 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -18,6 +18,7 @@ package t8ntool import ( "fmt" + stdmath "math" "math/big" "github.com/ethereum/go-ethereum/common" @@ -43,8 +44,9 @@ import ( ) type Prestate struct { - Env stEnv `json:"env"` - Pre types.GenesisAlloc `json:"pre"` + Env stEnv `json:"env"` + Pre types.GenesisAlloc `json:"pre"` + TreeLeaves map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"` } //go:generate go run github.com/fjl/gencodec -type ExecutionResult -field-override executionResultMarshaling -out gen_execresult.go @@ -142,7 +144,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, return h } var ( - statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) + isEIP4762 = chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) + statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre, isEIP4762) signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) gaspool = new(core.GasPool) blockHash = common.Hash{0x13, 0x37} @@ -301,6 +304,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, // Amount is in gwei, turn into wei amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei)) statedb.AddBalance(w.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal) + + if isEIP4762 { + statedb.AccessEvents().AddAccount(w.Address, true, stdmath.MaxUint64) + } } // Gather the execution-layer triggered requests. @@ -361,8 +368,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, execRs.Requests = requests } - // Re-create statedb instance with new root upon the updated database - // for accessing latest states. + // Re-create statedb instance with new root for MPT mode statedb, err = state.New(root, statedb.Database()) if err != nil { return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err)) @@ -371,12 +377,17 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, return statedb, execRs, body, nil } -func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB { - tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true}) +func MakePreState(db ethdb.Database, accounts types.GenesisAlloc, isBintrie bool) *state.StateDB { + tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true, IsVerkle: isBintrie}) sdb := state.NewDatabase(tdb, nil) - statedb, err := state.New(types.EmptyRootHash, sdb) + + root := types.EmptyRootHash + if isBintrie { + root = types.EmptyBinaryHash + } + statedb, err := state.New(root, sdb) if err != nil { - panic(fmt.Errorf("failed to create initial state: %v", err)) + panic(fmt.Errorf("failed to create initial statedb: %v", err)) } for addr, a := range accounts { statedb.SetCode(addr, a.Code, tracing.CodeChangeUnspecified) @@ -387,10 +398,15 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB } } // Commit and re-open to start with a clean state. - root, err := statedb.Commit(0, false, false) + root, err = statedb.Commit(0, false, false) if err != nil { panic(fmt.Errorf("failed to commit initial state: %v", err)) } + // If bintrie mode started, check if conversion happened + if isBintrie { + return statedb + } + // For MPT mode, reopen the state with the committed root statedb, err = state.New(root, sdb) if err != nil { panic(fmt.Errorf("failed to reopen state after commit: %v", err)) @@ -398,7 +414,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB return statedb } -func rlpHash(x interface{}) (h common.Hash) { +func rlpHash(x any) (h common.Hash) { hw := sha3.NewLegacyKeccak256() rlp.Encode(hw, x) hw.Sum(h[:0]) diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go index f2606c86d18b..a6ec33eacf04 100644 --- a/cmd/evm/internal/t8ntool/flags.go +++ b/cmd/evm/internal/t8ntool/flags.go @@ -88,6 +88,14 @@ var ( "\t - into the file ", Value: "block.json", } + OutputBTFlag = &cli.StringFlag{ + Name: "output.vkt", + Usage: "Determines where to put the `BT` of the post-state.\n" + + "\t`stdout` - into the stdout output\n" + + "\t`stderr` - into the stderr output\n" + + "\t - into the file ", + Value: "vkt.json", + } InputAllocFlag = &cli.StringFlag{ Name: "input.alloc", Usage: "`stdin` or file name of where to find the prestate alloc to use.", @@ -123,6 +131,11 @@ var ( Usage: "`stdin` or file name of where to find the transactions list in RLP form.", Value: "txs.rlp", } + // TODO(@CPerezz): rename `Name` of the file in a follow-up PR (relays on EEST -> https://github.com/ethereum/execution-spec-tests/tree/verkle/main) + InputBTFlag = &cli.StringFlag{ + Name: "input.vkt", + Usage: "`stdin` or file name of where to find the prestate BT.", + } SealCliqueFlag = &cli.StringFlag{ Name: "seal.clique", Usage: "Seal block with Clique. `stdin` or file name of where to find the Clique sealing data.", diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index e946ccddd567..af60333cbdbc 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -28,15 +28,22 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/tests" + "github.com/ethereum/go-ethereum/trie/bintrie" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/database" + "github.com/holiman/uint256" "github.com/urfave/cli/v2" ) @@ -75,10 +82,11 @@ var ( ) type input struct { - Alloc types.GenesisAlloc `json:"alloc,omitempty"` - Env *stEnv `json:"env,omitempty"` - Txs []*txWithKey `json:"txs,omitempty"` - TxRlp string `json:"txsRlp,omitempty"` + Alloc types.GenesisAlloc `json:"alloc,omitempty"` + Env *stEnv `json:"env,omitempty"` + BT map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"` + Txs []*txWithKey `json:"txs,omitempty"` + TxRlp string `json:"txsRlp,omitempty"` } func Transition(ctx *cli.Context) error { @@ -90,16 +98,16 @@ func Transition(ctx *cli.Context) error { // stdin input or in files. // Check if anything needs to be read from stdin var ( - prestate Prestate - txIt txIterator // txs to apply - allocStr = ctx.String(InputAllocFlag.Name) - + prestate Prestate + txIt txIterator // txs to apply + allocStr = ctx.String(InputAllocFlag.Name) + btStr = ctx.String(InputBTFlag.Name) envStr = ctx.String(InputEnvFlag.Name) txStr = ctx.String(InputTxsFlag.Name) inputData = &input{} ) // Figure out the prestate alloc - if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { + if allocStr == stdinSelector || btStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { decoder := json.NewDecoder(os.Stdin) if err := decoder.Decode(inputData); err != nil { return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err)) @@ -112,6 +120,13 @@ func Transition(ctx *cli.Context) error { } prestate.Pre = inputData.Alloc + if btStr != stdinSelector && btStr != "" { + if err := readFile(btStr, "BT", &inputData.BT); err != nil { + return err + } + } + prestate.TreeLeaves = inputData.BT + // Set the block environment if envStr != stdinSelector { var env stEnv @@ -182,9 +197,21 @@ func Transition(ctx *cli.Context) error { return err } // Dump the execution result - collector := make(Alloc) - s.DumpToCollector(collector, nil) - return dispatchOutput(ctx, baseDir, result, collector, body) + var ( + collector = make(Alloc) + btleaves map[common.Hash]hexutil.Bytes + ) + isBinary := chainConfig.IsVerkle(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) + if !isBinary { + s.DumpToCollector(collector, nil) + } else { + btleaves = make(map[common.Hash]hexutil.Bytes) + if err := s.DumpBinTrieLeaves(btleaves); err != nil { + return err + } + } + + return dispatchOutput(ctx, baseDir, result, collector, body, btleaves) } func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { @@ -306,7 +333,7 @@ func saveFile(baseDir, filename string, data interface{}) error { // dispatchOutput writes the output data to either stderr or stdout, or to the specified // files -func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes) error { +func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes, bt map[common.Hash]hexutil.Bytes) error { stdOutObject := make(map[string]interface{}) stdErrObject := make(map[string]interface{}) dispatch := func(baseDir, fName, name string, obj interface{}) error { @@ -333,6 +360,13 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a if err := dispatch(baseDir, ctx.String(OutputBodyFlag.Name), "body", body); err != nil { return err } + // Only write bt output if we actually have binary trie leaves + if bt != nil { + if err := dispatch(baseDir, ctx.String(OutputBTFlag.Name), "vkt", bt); err != nil { + return err + } + } + if len(stdOutObject) > 0 { b, err := json.MarshalIndent(stdOutObject, "", " ") if err != nil { @@ -351,3 +385,168 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a } return nil } + +// BinKey computes the tree key given an address and an optional slot number. +func BinKey(ctx *cli.Context) error { + if ctx.Args().Len() == 0 || ctx.Args().Len() > 2 { + return errors.New("invalid number of arguments: expecting an address and an optional slot number") + } + + addr, err := hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + return fmt.Errorf("error decoding address: %w", err) + } + + if ctx.Args().Len() == 2 { + slot, err := hexutil.Decode(ctx.Args().Get(1)) + if err != nil { + return fmt.Errorf("error decoding slot: %w", err) + } + fmt.Printf("%#x\n", bintrie.GetBinaryTreeKeyStorageSlot(common.BytesToAddress(addr), slot)) + } else { + fmt.Printf("%#x\n", bintrie.GetBinaryTreeKeyBasicData(common.BytesToAddress(addr))) + } + return nil +} + +// BinKeys computes a set of tree keys given a genesis alloc. +func BinKeys(ctx *cli.Context) error { + var allocStr = ctx.String(InputAllocFlag.Name) + var alloc core.GenesisAlloc + // Figure out the prestate alloc + if allocStr == stdinSelector { + decoder := json.NewDecoder(os.Stdin) + if err := decoder.Decode(&alloc); err != nil { + return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err)) + } + } + if allocStr != stdinSelector { + if err := readFile(allocStr, "alloc", &alloc); err != nil { + return err + } + } + db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults) + defer db.Close() + + bt, err := genBinTrieFromAlloc(alloc, db) + if err != nil { + return fmt.Errorf("error generating bt: %w", err) + } + + collector := make(map[common.Hash]hexutil.Bytes) + it, err := bt.NodeIterator(nil) + if err != nil { + panic(err) + } + for it.Next(true) { + if it.Leaf() { + collector[common.BytesToHash(it.LeafKey())] = it.LeafBlob() + } + } + + output, err := json.MarshalIndent(collector, "", "") + if err != nil { + return fmt.Errorf("error outputting tree: %w", err) + } + + fmt.Println(string(output)) + + return nil +} + +// BinTrieRoot computes the root of a Binary Trie from a genesis alloc. +func BinTrieRoot(ctx *cli.Context) error { + var allocStr = ctx.String(InputAllocFlag.Name) + var alloc core.GenesisAlloc + if allocStr == stdinSelector { + decoder := json.NewDecoder(os.Stdin) + if err := decoder.Decode(&alloc); err != nil { + return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err)) + } + } + if allocStr != stdinSelector { + if err := readFile(allocStr, "alloc", &alloc); err != nil { + return err + } + } + db := triedb.NewDatabase(rawdb.NewMemoryDatabase(), triedb.VerkleDefaults) + defer db.Close() + + bt, err := genBinTrieFromAlloc(alloc, db) + if err != nil { + return fmt.Errorf("error generating bt: %w", err) + } + fmt.Println(bt.Hash().Hex()) + + return nil +} + +// TODO(@CPerezz): Should this go to `bintrie` module? +func genBinTrieFromAlloc(alloc core.GenesisAlloc, db database.NodeDatabase) (*bintrie.BinaryTrie, error) { + bt, err := bintrie.NewBinaryTrie(types.EmptyBinaryHash, db) + if err != nil { + return nil, err + } + for addr, acc := range alloc { + for slot, value := range acc.Storage { + err := bt.UpdateStorage(addr, slot.Bytes(), value.Big().Bytes()) + if err != nil { + return nil, fmt.Errorf("error inserting storage: %w", err) + } + } + account := &types.StateAccount{ + Balance: uint256.MustFromBig(acc.Balance), + Nonce: acc.Nonce, + CodeHash: crypto.Keccak256Hash(acc.Code).Bytes(), + Root: common.Hash{}, + } + err := bt.UpdateAccount(addr, account, len(acc.Code)) + if err != nil { + return nil, fmt.Errorf("error inserting account: %w", err) + } + err = bt.UpdateContractCode(addr, common.BytesToHash(account.CodeHash), acc.Code) + if err != nil { + return nil, fmt.Errorf("error inserting code: %w", err) + } + } + return bt, nil +} + +// BinaryCodeChunkKey computes the tree key of a code-chunk for a given address. +func BinaryCodeChunkKey(ctx *cli.Context) error { + if ctx.Args().Len() == 0 || ctx.Args().Len() > 2 { + return errors.New("invalid number of arguments: expecting an address and an code-chunk number") + } + + addr, err := hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + return fmt.Errorf("error decoding address: %w", err) + } + chunkNumberBytes, err := hexutil.Decode(ctx.Args().Get(1)) + if err != nil { + return fmt.Errorf("error decoding chunk number: %w", err) + } + var chunkNumber uint256.Int + chunkNumber.SetBytes(chunkNumberBytes) + + fmt.Printf("%#x\n", bintrie.GetBinaryTreeKeyCodeChunk(common.BytesToAddress(addr), &chunkNumber)) + + return nil +} + +// BinaryCodeChunkCode returns the code chunkification for a given code. +func BinaryCodeChunkCode(ctx *cli.Context) error { + if ctx.Args().Len() == 0 || ctx.Args().Len() > 1 { + return errors.New("invalid number of arguments: expecting a bytecode") + } + + bytecode, err := hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + return fmt.Errorf("error decoding address: %w", err) + } + + chunkedCode := bintrie.ChunkifyCode(bytecode) + fmt.Printf("%#x\n", chunkedCode) + + return nil +} diff --git a/cmd/evm/main.go b/cmd/evm/main.go index bf5be9a35924..5238d5920ca5 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -146,16 +146,63 @@ var ( t8ntool.TraceEnableCallFramesFlag, t8ntool.OutputBasedir, t8ntool.OutputAllocFlag, + t8ntool.OutputBTFlag, t8ntool.OutputResultFlag, t8ntool.OutputBodyFlag, t8ntool.InputAllocFlag, t8ntool.InputEnvFlag, + t8ntool.InputBTFlag, t8ntool.InputTxsFlag, t8ntool.ForknameFlag, t8ntool.ChainIDFlag, t8ntool.RewardFlag, }, } + + verkleCommand = &cli.Command{ + Name: "verkle", + Aliases: []string{"vkt"}, + Usage: "Binary Trie helpers", + Subcommands: []*cli.Command{ + { + Name: "tree-keys", + Aliases: []string{"v"}, + Usage: "compute a set of binary trie keys, given their source addresses and optional slot numbers", + Action: t8ntool.BinKeys, + Flags: []cli.Flag{ + t8ntool.InputAllocFlag, + }, + }, + { + Name: "single-key", + Aliases: []string{"vk"}, + Usage: "compute the binary trie key given an address and optional slot number", + Action: t8ntool.BinKey, + }, + { + Name: "code-chunk-key", + Aliases: []string{"vck"}, + Usage: "compute the binary trie key given an address and chunk number", + Action: t8ntool.BinaryCodeChunkKey, + }, + { + Name: "chunkify-code", + Aliases: []string{"vcc"}, + Usage: "chunkify a given bytecode for a binary trie", + Action: t8ntool.BinaryCodeChunkCode, + }, + { + Name: "state-root", + Aliases: []string{"vsr"}, + Usage: "compute the state-root of a binary trie for the given alloc", + Action: t8ntool.BinTrieRoot, + Flags: []cli.Flag{ + t8ntool.InputAllocFlag, + }, + }, + }, + } + transactionCommand = &cli.Command{ Name: "transaction", Aliases: []string{"t9n"}, @@ -210,6 +257,7 @@ func init() { stateTransitionCommand, transactionCommand, blockBuilderCommand, + verkleCommand, } app.Before = func(ctx *cli.Context) error { flags.MigrateGlobalFlags(ctx) diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go new file mode 100644 index 000000000000..7704ba41fb66 --- /dev/null +++ b/core/bintrie_witness_test.go @@ -0,0 +1,237 @@ +// Copyright 2024 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 . + +package core + +import ( + "encoding/binary" + "fmt" + "math/big" + "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/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/triedb" +) + +var ( + testVerkleChainConfig = ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + Ethash: new(params.EthashConfig), + ShanghaiTime: u64(0), + VerkleTime: u64(0), + TerminalTotalDifficulty: common.Big0, + EnableVerkleAtGenesis: true, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Verkle: params.DefaultPragueBlobConfig, + }, + } +) + +func TestProcessVerkle(t *testing.T) { + var ( + code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) + intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true) + // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness + // will not contain that copied data. + // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 + codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) + intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true) + signer = types.LatestSigner(testVerkleChainConfig) + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain + coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") + gspec = &Genesis{ + Config: testVerkleChainConfig, + Alloc: GenesisAlloc{ + coinbase: { + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0}, + params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0}, + params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0}, + params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0}, + }, + } + ) + // Verkle trees use the snapshot, which must be enabled before the + // data is saved into the tree+database. + // genesis := gspec.MustCommit(bcdb, triedb) + options := DefaultConfig().WithStateScheme(rawdb.PathScheme) + options.SnapshotLimit = 0 + blockchain, _ := NewBlockChain(bcdb, gspec, beacon.New(ethash.NewFaker()), options) + defer blockchain.Stop() + + txCost1 := params.TxGas + txCost2 := params.TxGas + contractCreationCost := intrinsicContractCreationGas + + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */ + 739 /* execution costs */ + codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */ + params.WitnessChunkReadCost + /* SLOAD in constructor */ + params.WitnessChunkWriteCost + /* SSTORE in constructor */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at PC=0x121) */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */ + params.WitnessChunkReadCost + /* SLOAD in constructor */ + params.WitnessChunkWriteCost + /* SSTORE in constructor */ + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash for tx creation */ + 15*(params.WitnessChunkReadCost+params.WitnessChunkWriteCost) + /* code chunks #0..#14 */ + uint64(4844) /* execution costs */ + blockGasUsagesExpected := []uint64{ + txCost1*2 + txCost2, + txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, + } + _, chain, _ := GenerateChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { + gen.SetPoS() + + // TODO need to check that the tx cost provided is the exact amount used (no remaining left-over) + tx, _ := types.SignTx(types.NewTransaction(uint64(i)*3, common.Address{byte(i), 2, 3}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+1, common.Address{}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+2, common.Address{}, big.NewInt(0), txCost2, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + + // Add two contract creations in block #2 + if i == 1 { + tx, _ = types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 6, + Value: big.NewInt(16), + Gas: 3000000, + GasPrice: big.NewInt(875000000), + Data: code, + }) + gen.AddTx(tx) + + tx, _ = types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 7, + Value: big.NewInt(0), + Gas: 3000000, + GasPrice: big.NewInt(875000000), + Data: codeWithExtCodeCopy, + }) + gen.AddTx(tx) + } + }) + + for i, b := range chain { + fmt.Printf("%d %x\n", i, b.Root()) + } + endnum, err := blockchain.InsertChain(chain) + if err != nil { + t.Fatalf("block %d imported with error: %v", endnum, err) + } + + for i := range 2 { + b := blockchain.GetBlockByNumber(uint64(i) + 1) + if b == nil { + t.Fatalf("expected block %d to be present in chain", i+1) + } + if b.Hash() != chain[i].Hash() { + t.Fatalf("block #%d not found at expected height", b.NumberU64()) + } + if b.GasUsed() != blockGasUsagesExpected[i] { + t.Fatalf("expected block #%d txs to use %d, got %d\n", b.NumberU64(), blockGasUsagesExpected[i], b.GasUsed()) + } + } +} + +func TestProcessParentBlockHash(t *testing.T) { + // This test uses blocks where, + // block 1 parent hash is 0x0100.... + // block 2 parent hash is 0x0200.... + // etc + checkBlockHashes := func(statedb *state.StateDB, isVerkle bool) { + statedb.SetNonce(params.HistoryStorageAddress, 1, tracing.NonceChangeUnspecified) + statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode, tracing.CodeChangeUnspecified) + // Process n blocks, from 1 .. num + var num = 2 + for i := 1; i <= num; i++ { + header := &types.Header{ParentHash: common.Hash{byte(i)}, Number: big.NewInt(int64(i)), Difficulty: new(big.Int)} + chainConfig := params.MergedTestChainConfig + if isVerkle { + chainConfig = testVerkleChainConfig + } + vmContext := NewEVMBlockContext(header, nil, new(common.Address)) + evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{}) + ProcessParentBlockHash(header.ParentHash, evm) + } + // Read block hashes for block 0 .. num-1 + for i := 0; i < num; i++ { + have, want := getContractStoredBlockHash(statedb, uint64(i), isVerkle), common.Hash{byte(i + 1)} + if have != want { + t.Errorf("block %d, verkle=%v, have parent hash %v, want %v", i, isVerkle, have, want) + } + } + } + t.Run("MPT", func(t *testing.T) { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + checkBlockHashes(statedb, false) + }) + t.Run("Verkle", func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme) + cacheConfig.SnapshotLimit = 0 + triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true)) + statedb, _ := state.New(types.EmptyVerkleHash, state.NewDatabase(triedb, nil)) + checkBlockHashes(statedb, true) + }) +} + +// getContractStoredBlockHash is a utility method which reads the stored parent blockhash for block 'number' +func getContractStoredBlockHash(statedb *state.StateDB, number uint64, isVerkle bool) common.Hash { + ringIndex := number % params.HistoryServeWindow + var key common.Hash + binary.BigEndian.PutUint64(key[24:], ringIndex) + if isVerkle { + return statedb.GetState(params.HistoryStorageAddress, key) + } + return statedb.GetState(params.HistoryStorageAddress, key) +} diff --git a/core/chain_makers.go b/core/chain_makers.go index af55716cca3e..a1e07becba88 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -32,7 +32,6 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/triedb" - "github.com/ethereum/go-verkle" "github.com/holiman/uint256" ) @@ -427,7 +426,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } // Forcibly use hash-based state scheme for retaining all nodes in disk. - triedb := triedb.NewDatabase(db, triedb.HashDefaults) + var triedbConfig *triedb.Config = triedb.HashDefaults + if config.IsVerkle(config.ChainID, 0) { + triedbConfig = triedb.VerkleDefaults + } + triedb := triedb.NewDatabase(db, triedbConfig) defer triedb.Close() for i := 0; i < n; i++ { @@ -472,7 +475,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse // then generate chain on top. func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) { db := rawdb.NewMemoryDatabase() - triedb := triedb.NewDatabase(db, triedb.HashDefaults) + var triedbConfig *triedb.Config = triedb.HashDefaults + if genesis.Config != nil && genesis.Config.IsVerkle(genesis.Config.ChainID, 0) { + triedbConfig = triedb.VerkleDefaults + } + triedb := triedb.NewDatabase(db, triedbConfig) defer triedb.Close() _, err := genesis.Commit(db, triedb) if err != nil { @@ -482,117 +489,6 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, return db, blocks, receipts } -func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, trdb *triedb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) { - if config == nil { - config = params.TestChainConfig - } - proofs := make([]*verkle.VerkleProof, 0, n) - keyvals := make([]verkle.StateDiff, 0, n) - cm := newChainMaker(parent, config, engine) - - genblock := func(i int, parent *types.Block, triedb *triedb.Database, statedb *state.StateDB) (*types.Block, types.Receipts) { - b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: engine} - b.header = cm.makeHeader(parent, statedb, b.engine) - - // TODO uncomment when proof generation is merged - // Save pre state for proof generation - // preState := statedb.Copy() - - // EIP-2935 / 7709 - blockContext := NewEVMBlockContext(b.header, cm, &b.header.Coinbase) - blockContext.Random = &common.Hash{} // enable post-merge instruction set - evm := vm.NewEVM(blockContext, statedb, cm.config, vm.Config{}) - ProcessParentBlockHash(b.header.ParentHash, evm) - - // Execute any user modifications to the block. - if gen != nil { - gen(i, b) - } - - requests := b.collectRequests(false) - if requests != nil { - reqHash := types.CalcRequestsHash(requests) - b.header.RequestsHash = &reqHash - } - - body := &types.Body{ - Transactions: b.txs, - Uncles: b.uncles, - Withdrawals: b.withdrawals, - } - block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, body, b.receipts) - if err != nil { - panic(err) - } - - // Write state changes to DB. - root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), config.IsCancun(b.header.Number, b.header.Time)) - if err != nil { - panic(fmt.Sprintf("state write error: %v", err)) - } - if err = triedb.Commit(root, false); err != nil { - panic(fmt.Sprintf("trie write error: %v", err)) - } - - proofs = append(proofs, block.ExecutionWitness().VerkleProof) - keyvals = append(keyvals, block.ExecutionWitness().StateDiff) - - return block, b.receipts - } - - sdb := state.NewDatabase(trdb, nil) - - for i := 0; i < n; i++ { - statedb, err := state.New(parent.Root(), sdb) - if err != nil { - panic(err) - } - block, receipts := genblock(i, parent, trdb, statedb) - - // Post-process the receipts. - // Here we assign the final block hash and other info into the receipt. - // In order for DeriveFields to work, the transaction and receipt lists need to be - // of equal length. If AddUncheckedTx or AddUncheckedReceipt are used, there will be - // extra ones, so we just trim the lists here. - receiptsCount := len(receipts) - txs := block.Transactions() - if len(receipts) > len(txs) { - receipts = receipts[:len(txs)] - } else if len(receipts) < len(txs) { - txs = txs[:len(receipts)] - } - var blobGasPrice *big.Int - if block.ExcessBlobGas() != nil { - blobGasPrice = eip4844.CalcBlobFee(cm.config, block.Header()) - } - if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, txs); err != nil { - panic(err) - } - - // Re-expand to ensure all receipts are returned. - receipts = receipts[:receiptsCount] - - // Advance the chain. - cm.add(block, receipts) - parent = block - } - return cm.chain, cm.receipts, proofs, keyvals -} - -func GenerateVerkleChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (common.Hash, ethdb.Database, []*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) { - db := rawdb.NewMemoryDatabase() - cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme) - cacheConfig.SnapshotLimit = 0 - triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true)) - defer triedb.Close() - genesisBlock, err := genesis.Commit(db, triedb) - if err != nil { - panic(err) - } - blocks, receipts, proofs, keyvals := GenerateVerkleChain(genesis.Config, genesisBlock, engine, db, triedb, n, gen) - return genesisBlock.Hash(), db, blocks, receipts, proofs, keyvals -} - func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { time := parent.Time() + 10 // block time is fixed at 10 seconds parentHeader := parent.Header() diff --git a/core/genesis_test.go b/core/genesis_test.go index a41dfce5783e..1ed475695dfc 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -308,7 +308,7 @@ func TestVerkleGenesisCommit(t *testing.T) { }, } - expected := common.FromHex("018d20eebb130b5e2b796465fe36aafab650650729a92435aec071bf2386f080") + expected := common.FromHex("19056b480530799a4fdaa9fd9407043b965a3a5c37b4d2a1a9a4f3395a327561") got := genesis.ToBlock().Root().Bytes() if !bytes.Equal(got, expected) { t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) diff --git a/core/state/database.go b/core/state/database.go index 58d0ccfe8292..ae177d964f9a 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -28,6 +28,8 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/bintrie" + "github.com/ethereum/go-ethereum/trie/transitiontrie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/triedb" @@ -239,10 +241,12 @@ func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { ts := overlay.LoadTransitionState(db.TrieDB().Disk(), root, db.triedb.IsVerkle()) if ts.InTransition() { - panic("transition isn't supported yet") + panic("state tree transition isn't supported yet") } if ts.Transitioned() { - return trie.NewVerkleTrie(root, db.triedb, db.pointCache) + // Use BinaryTrie instead of VerkleTrie when IsVerkle is set + // (IsVerkle actually means Binary Trie mode in this codebase) + return bintrie.NewBinaryTrie(root, db.triedb) } } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) @@ -302,7 +306,7 @@ func mustCopyTrie(t Trie) Trie { return t.Copy() case *trie.VerkleTrie: return t.Copy() - case *trie.TransitionTrie: + case *transitiontrie.TransitionTrie: return t.Copy() default: panic(fmt.Errorf("unknown trie type %T", t)) diff --git a/core/state/dump.go b/core/state/dump.go index a4abc33733f1..829d106ed3ac 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -18,6 +18,7 @@ package state import ( "encoding/json" + "errors" "fmt" "time" @@ -27,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/bintrie" ) // DumpConfig is a set of options to control what portions of the state will be @@ -221,6 +223,28 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] return nextKey } +// DumpBinTrieLeaves collects all binary trie leaf nodes into the provided map. +func (s *StateDB) DumpBinTrieLeaves(collector map[common.Hash]hexutil.Bytes) error { + tr, err := s.db.OpenTrie(s.originalRoot) + if err != nil { + return err + } + btr, ok := tr.(*bintrie.BinaryTrie) + if !ok { + return errors.New("trie is not a binary trie") + } + it, err := btr.NodeIterator(nil) + if err != nil { + return err + } + for it.Next(true) { + if it.Leaf() { + collector[common.BytesToHash(it.LeafKey())] = it.LeafBlob() + } + } + return nil +} + // RawDump returns the state. If the processing is aborted e.g. due to options // reaching Max, the `Next` key is set on the returned Dump. func (s *StateDB) RawDump(opts *DumpConfig) Dump { diff --git a/core/state/reader.go b/core/state/reader.go index 3e8b31b6be3d..93083c8ae293 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -30,6 +30,8 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/bintrie" + "github.com/ethereum/go-ethereum/trie/transitiontrie" "github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb/database" @@ -242,7 +244,11 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach if !db.IsVerkle() { tr, err = trie.NewStateTrie(trie.StateTrieID(root), db) } else { - tr, err = trie.NewVerkleTrie(root, db, cache) + // When IsVerkle() is true, create a BinaryTrie wrapped in TransitionTrie + binTrie, binErr := bintrie.NewBinaryTrie(root, db) + if binErr != nil { + return nil, binErr + } // Based on the transition status, determine if the overlay // tree needs to be created, or if a single, target tree is @@ -253,7 +259,22 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach if err != nil { return nil, err } - tr = trie.NewTransitionTrie(mpt, tr.(*trie.VerkleTrie), false) + tr = transitiontrie.NewTransitionTrie(mpt, binTrie, false) + } else { + // HACK: Use TransitionTrie with nil base as a wrapper to make BinaryTrie + // satisfy the Trie interface. This works around the import cycle between + // trie and trie/bintrie packages. + // + // TODO: In future PRs, refactor the package structure to avoid this hack: + // - Option 1: Move common interfaces (Trie, NodeIterator) to a separate + // package that both trie and trie/bintrie can import + // - Option 2: Create a factory function in the trie package that returns + // BinaryTrie as a Trie interface without direct import + // - Option 3: Move BinaryTrie to the main trie package + // + // The current approach works but adds unnecessary overhead and complexity + // by using TransitionTrie when there's no actual transition happening. + tr = transitiontrie.NewTransitionTrie(nil, binTrie, false) } } if err != nil { diff --git a/core/state/state_object.go b/core/state/state_object.go index fdeb4254c1bc..8f2f323327dd 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/transitiontrie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/holiman/uint256" ) @@ -501,7 +502,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { // Verkle uses only one tree, and the copy has already been // made in mustCopyTrie. obj.trie = db.trie - case *trie.TransitionTrie: + case *transitiontrie.TransitionTrie: // Same thing for the transition tree, since the MPT is // read-only. obj.trie = db.trie diff --git a/core/verkle_witness_test.go b/core/verkle_witness_test.go deleted file mode 100644 index 9495e325ca9d..000000000000 --- a/core/verkle_witness_test.go +++ /dev/null @@ -1,1107 +0,0 @@ -// Copyright 2024 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 . - -package core - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "fmt" - "math/big" - "slices" - "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/rawdb" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/tracing" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/trie/utils" - "github.com/ethereum/go-ethereum/triedb" - "github.com/ethereum/go-verkle" - "github.com/holiman/uint256" -) - -var ( - testVerkleChainConfig = ¶ms.ChainConfig{ - ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(0), - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), - Ethash: new(params.EthashConfig), - ShanghaiTime: u64(0), - VerkleTime: u64(0), - TerminalTotalDifficulty: common.Big0, - EnableVerkleAtGenesis: true, - BlobScheduleConfig: ¶ms.BlobScheduleConfig{ - Verkle: params.DefaultPragueBlobConfig, - }, - // TODO uncomment when proof generation is merged - // ProofInBlocks: true, - } - testKaustinenLikeChainConfig = ¶ms.ChainConfig{ - ChainID: big.NewInt(69420), - HomesteadBlock: big.NewInt(0), - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), - Ethash: new(params.EthashConfig), - ShanghaiTime: u64(0), - VerkleTime: u64(0), - TerminalTotalDifficulty: common.Big0, - EnableVerkleAtGenesis: true, - BlobScheduleConfig: ¶ms.BlobScheduleConfig{ - Verkle: params.DefaultPragueBlobConfig, - }, - } -) - -func TestProcessVerkle(t *testing.T) { - var ( - code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) - intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true) - // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness - // will not contain that copied data. - // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 - codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) - intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true) - signer = types.LatestSigner(testVerkleChainConfig) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain - coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") - gspec = &Genesis{ - Config: testVerkleChainConfig, - Alloc: GenesisAlloc{ - coinbase: { - Balance: big.NewInt(1000000000000000000), // 1 ether - Nonce: 0, - }, - params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0}, - params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0}, - params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0}, - params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0}, - }, - } - ) - // Verkle trees use the snapshot, which must be enabled before the - // data is saved into the tree+database. - // genesis := gspec.MustCommit(bcdb, triedb) - options := DefaultConfig().WithStateScheme(rawdb.PathScheme) - options.SnapshotLimit = 0 - blockchain, _ := NewBlockChain(bcdb, gspec, beacon.New(ethash.NewFaker()), options) - defer blockchain.Stop() - - txCost1 := params.TxGas - txCost2 := params.TxGas - contractCreationCost := intrinsicContractCreationGas + - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */ - 739 /* execution costs */ - codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */ - params.WitnessChunkReadCost + /* SLOAD in constructor */ - params.WitnessChunkWriteCost + /* SSTORE in constructor */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at PC=0x121) */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */ - params.WitnessChunkReadCost + /* SLOAD in constructor */ - params.WitnessChunkWriteCost + /* SSTORE in constructor */ - params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash for tx creation */ - 15*(params.WitnessChunkReadCost+params.WitnessChunkWriteCost) + /* code chunks #0..#14 */ - uint64(4844) /* execution costs */ - blockGasUsagesExpected := []uint64{ - txCost1*2 + txCost2, - txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, - } - _, _, chain, _, proofs, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { - gen.SetPoS() - - // TODO need to check that the tx cost provided is the exact amount used (no remaining left-over) - tx, _ := types.SignTx(types.NewTransaction(uint64(i)*3, common.Address{byte(i), 2, 3}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey) - gen.AddTx(tx) - tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+1, common.Address{}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey) - gen.AddTx(tx) - tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+2, common.Address{}, big.NewInt(0), txCost2, big.NewInt(875000000), nil), signer, testKey) - gen.AddTx(tx) - - // Add two contract creations in block #2 - if i == 1 { - tx, _ = types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 6, - Value: big.NewInt(16), - Gas: 3000000, - GasPrice: big.NewInt(875000000), - Data: code, - }) - gen.AddTx(tx) - - tx, _ = types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 7, - Value: big.NewInt(0), - Gas: 3000000, - GasPrice: big.NewInt(875000000), - Data: codeWithExtCodeCopy, - }) - gen.AddTx(tx) - } - }) - - // Check proof for both blocks - err := verkle.Verify(proofs[0], gspec.ToBlock().Root().Bytes(), chain[0].Root().Bytes(), statediffs[0]) - if err != nil { - t.Fatal(err) - } - err = verkle.Verify(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), statediffs[1]) - if err != nil { - t.Fatal(err) - } - - t.Log("verified verkle proof, inserting blocks into the chain") - - for i, b := range chain { - fmt.Printf("%d %x\n", i, b.Root()) - } - endnum, err := blockchain.InsertChain(chain) - if err != nil { - t.Fatalf("block %d imported with error: %v", endnum, err) - } - - for i := range 2 { - b := blockchain.GetBlockByNumber(uint64(i) + 1) - if b == nil { - t.Fatalf("expected block %d to be present in chain", i+1) - } - if b.Hash() != chain[i].Hash() { - t.Fatalf("block #%d not found at expected height", b.NumberU64()) - } - if b.GasUsed() != blockGasUsagesExpected[i] { - t.Fatalf("expected block #%d txs to use %d, got %d\n", b.NumberU64(), blockGasUsagesExpected[i], b.GasUsed()) - } - } -} - -func TestProcessParentBlockHash(t *testing.T) { - // This test uses blocks where, - // block 1 parent hash is 0x0100.... - // block 2 parent hash is 0x0200.... - // etc - checkBlockHashes := func(statedb *state.StateDB, isVerkle bool) { - statedb.SetNonce(params.HistoryStorageAddress, 1, tracing.NonceChangeUnspecified) - statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode, tracing.CodeChangeUnspecified) - // Process n blocks, from 1 .. num - var num = 2 - for i := 1; i <= num; i++ { - header := &types.Header{ParentHash: common.Hash{byte(i)}, Number: big.NewInt(int64(i)), Difficulty: new(big.Int)} - chainConfig := params.MergedTestChainConfig - if isVerkle { - chainConfig = testVerkleChainConfig - } - vmContext := NewEVMBlockContext(header, nil, new(common.Address)) - evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{}) - ProcessParentBlockHash(header.ParentHash, evm) - } - // Read block hashes for block 0 .. num-1 - for i := 0; i < num; i++ { - have, want := getContractStoredBlockHash(statedb, uint64(i), isVerkle), common.Hash{byte(i + 1)} - if have != want { - t.Errorf("block %d, verkle=%v, have parent hash %v, want %v", i, isVerkle, have, want) - } - } - } - t.Run("MPT", func(t *testing.T) { - statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) - checkBlockHashes(statedb, false) - }) - t.Run("Verkle", func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - cacheConfig := DefaultConfig().WithStateScheme(rawdb.PathScheme) - cacheConfig.SnapshotLimit = 0 - triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true)) - statedb, _ := state.New(types.EmptyVerkleHash, state.NewDatabase(triedb, nil)) - checkBlockHashes(statedb, true) - }) -} - -// getContractStoredBlockHash is a utility method which reads the stored parent blockhash for block 'number' -func getContractStoredBlockHash(statedb *state.StateDB, number uint64, isVerkle bool) common.Hash { - ringIndex := number % params.HistoryServeWindow - var key common.Hash - binary.BigEndian.PutUint64(key[24:], ringIndex) - if isVerkle { - return statedb.GetState(params.HistoryStorageAddress, key) - } - return statedb.GetState(params.HistoryStorageAddress, key) -} - -// TestProcessVerkleInvalidContractCreation checks for several modes of contract creation failures -func TestProcessVerkleInvalidContractCreation(t *testing.T) { - var ( - account1 = common.HexToAddress("0x687704DB07e902e9A8B3754031D168D46E3D586e") - account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") - gspec = verkleTestGenesis(testKaustinenLikeChainConfig) - ) - // slightly modify it to suit the live txs from the testnet - gspec.Alloc[account2] = types.Account{ - Balance: big.NewInt(1000000000000000000), // 1 ether - Nonce: 1, - } - - // Create two blocks that reproduce what is happening on kaustinen. - // - The first block contains two failing contract creation transactions, that - // write to storage before they revert. - // - // - The second block contains a single failing contract creation transaction, - // that fails right off the bat. - genesisH, _, chain, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { - gen.SetPoS() - - if i == 0 { - for _, rlpData := range []string{ - // SSTORE at slot 41 and reverts - "f8d48084479c2c18830186a08080b8806000602955bda3f9600060ca55600060695523b360006039551983576000601255b0620c2fde2c592ac2600060bc55e0ac6000606455a63e22600060e655eb607e605c5360a2605d5360c7605e53601d605f5360eb606053606b606153608e60625360816063536079606453601e60655360fc60665360b7606753608b60685383021e7ca0cc20c65a97d2e526b8ec0f4266e8b01bdcde43b9aeb59d8bfb44e8eb8119c109a07a8e751813ae1b2ce734960dbc39a4f954917d7822a2c5d1dca18b06c584131f", - // SSTORE at slot 133 and reverts - "02f8db83010f2c01843b9aca0084479c2c18830186a08080b88060006085553fad6000600a55600060565555600060b55506600060cf557f1b8b38183e7bd1bdfaa7123c5a4976e54cce0e42049d841411978fd3595e25c66019527f0538943712953cf08900aae40222a40b2d5a4ac8075ad8cf0870e2be307edbb96039527f9f3174ff85024747041ae7a611acffb987c513c088d90ab288aec080a0cd6ac65ce2cb0a912371f6b5a551ba8caffc22ec55ad4d3cb53de41d05eb77b6a02e0dfe8513dfa6ec7bfd7eda6f5c0dac21b39b982436045e128cec46cfd3f960", - // this one is a simple transfer that succeeds, necessary to get the correct nonce in the other block. - "f8e80184479c2c18830186a094bbbbde4ca27f83fc18aa108170547ff57675936a80b8807ff71f7c15faadb969a76a5f54a81a0117e1e743cb7f24e378eda28442ea4c6eb6604a527fb5409e5718d44e23bfffac926e5ea726067f772772e7e19446acba0c853f62f5606a526020608a536088608b536039608c536004608d5360af608e537f7f7675d9f210e0a61564e6d11e7cd75f5bc9009ac9f6b94a0fc63035441a83021e7ba04a4a172d81ebb02847829b76a387ac09749c8b65668083699abe20c887fb9efca07c5b1a990702ec7b31a5e8e3935cd9a77649f8c25a84131229e24ab61aec6093", - } { - var tx = new(types.Transaction) - if err := tx.UnmarshalBinary(common.Hex2Bytes(rlpData)); err != nil { - t.Fatal(err) - } - gen.AddTx(tx) - } - } else { - var tx = new(types.Transaction) - // immediately reverts - if err := tx.UnmarshalBinary(common.Hex2Bytes("01f8d683010f2c028443ad7d0e830186a08080b880b00e7fa3c849dce891cce5fae8a4c46cbb313d6aec0c0ffe7863e05fb7b22d4807674c6055527ffbfcb0938f3e18f7937aa8fa95d880afebd5c4cec0d85186095832d03c85cf8a60755260ab60955360cf6096536066609753606e60985360fa609953609e609a53608e609b536024609c5360f6609d536072609e5360a4609fc080a08fc6f7101f292ff1fb0de8ac69c2d320fbb23bfe61cf327173786ea5daee6e37a044c42d91838ef06646294bf4f9835588aee66243b16a66a2da37641fae4c045f")); err != nil { - t.Fatal(err) - } - gen.AddTx(tx) - } - }) - - tx1ContractAddress := crypto.CreateAddress(account1, 0) - tx1ContractStem := utils.GetTreeKey(tx1ContractAddress[:], uint256.NewInt(0), 105) - tx1ContractStem = tx1ContractStem[:31] - - tx2ContractAddress := crypto.CreateAddress(account2, 1) - tx2SlotKey := [32]byte{} - tx2SlotKey[31] = 133 - tx2ContractStem := utils.StorageSlotKey(tx2ContractAddress[:], tx2SlotKey[:]) - tx2ContractStem = tx2ContractStem[:31] - - eip2935Stem := utils.GetTreeKey(params.HistoryStorageAddress[:], uint256.NewInt(0), 0) - eip2935Stem = eip2935Stem[:31] - - // Check that the witness contains what we expect: a storage entry for each of the two contract - // creations that failed: one at 133 for the 2nd tx, and one at 105 for the first tx. - for _, stemStateDiff := range statediffs[0] { - // Check that the slot number 133, which is overflowing the account header, - // is present. Note that the offset of the 2nd group (first group after the - // header) is skipping the first 64 values, hence we still have an offset - // of 133, and not 133 - 64. - if bytes.Equal(stemStateDiff.Stem[:], tx2ContractStem[:]) { - for _, suffixDiff := range stemStateDiff.SuffixDiffs { - if suffixDiff.Suffix != 133 { - t.Fatalf("invalid suffix diff found for %x in block #1: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) - } - if suffixDiff.CurrentValue != nil { - t.Fatalf("invalid prestate value found for %x in block #1: %v != nil\n", stemStateDiff.Stem, suffixDiff.CurrentValue) - } - if suffixDiff.NewValue != nil { - t.Fatalf("invalid poststate value found for %x in block #1: %v != nil\n", stemStateDiff.Stem, suffixDiff.NewValue) - } - } - } else if bytes.Equal(stemStateDiff.Stem[:], tx1ContractStem) { - // For this contract creation, check that only the account header and storage slot 41 - // are found in the witness. - for _, suffixDiff := range stemStateDiff.SuffixDiffs { - if suffixDiff.Suffix != 105 && suffixDiff.Suffix != 0 && suffixDiff.Suffix != 1 { - t.Fatalf("invalid suffix diff found for %x in block #1: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) - } - } - } else if bytes.Equal(stemStateDiff.Stem[:], eip2935Stem) { - // Check the eip 2935 group of leaves. - // Check that only one leaf was accessed, and is present in the witness. - if len(stemStateDiff.SuffixDiffs) > 1 { - t.Fatalf("invalid suffix diff count found for BLOCKHASH contract: %d != 1", len(stemStateDiff.SuffixDiffs)) - } - // Check that this leaf is the first storage slot - if stemStateDiff.SuffixDiffs[0].Suffix != 64 { - t.Fatalf("invalid suffix diff value found for BLOCKHASH contract: %d != 64", stemStateDiff.SuffixDiffs[0].Suffix) - } - // check that the prestate value is nil and that the poststate value isn't. - if stemStateDiff.SuffixDiffs[0].CurrentValue != nil { - t.Fatalf("non-nil current value in BLOCKHASH contract insert: %x", stemStateDiff.SuffixDiffs[0].CurrentValue) - } - if stemStateDiff.SuffixDiffs[0].NewValue == nil { - t.Fatalf("nil new value in BLOCKHASH contract insert") - } - if *stemStateDiff.SuffixDiffs[0].NewValue != genesisH { - t.Fatalf("invalid BLOCKHASH value: %x != %x", *stemStateDiff.SuffixDiffs[0].NewValue, genesisH) - } - } else { - // For all other entries present in the witness, check that nothing beyond - // the account header was accessed. - for _, suffixDiff := range stemStateDiff.SuffixDiffs { - if suffixDiff.Suffix > 2 { - t.Fatalf("invalid suffix diff found for %x in block #1: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) - } - } - } - } - - // Check that no account has a value above 4 in the 2nd block as no storage nor - // code should make it to the witness. - for _, stemStateDiff := range statediffs[1] { - for _, suffixDiff := range stemStateDiff.SuffixDiffs { - if bytes.Equal(stemStateDiff.Stem[:], eip2935Stem) { - // BLOCKHASH contract stem - if len(stemStateDiff.SuffixDiffs) > 1 { - t.Fatalf("invalid suffix diff count found for BLOCKHASH contract at block #2: %d != 1", len(stemStateDiff.SuffixDiffs)) - } - if stemStateDiff.SuffixDiffs[0].Suffix != 65 { - t.Fatalf("invalid suffix diff value found for BLOCKHASH contract at block #2: %d != 65", stemStateDiff.SuffixDiffs[0].Suffix) - } - if stemStateDiff.SuffixDiffs[0].NewValue == nil { - t.Fatalf("missing post state value for BLOCKHASH contract at block #2") - } - if *stemStateDiff.SuffixDiffs[0].NewValue != chain[0].Hash() { - t.Fatalf("invalid post state value for BLOCKHASH contract at block #2: %x != %x", chain[0].Hash(), (*stemStateDiff.SuffixDiffs[0].NewValue)[:]) - } - } else if suffixDiff.Suffix > 4 { - t.Fatalf("invalid suffix diff found for %x in block #2: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) - } - } - } -} - -func verkleTestGenesis(config *params.ChainConfig) *Genesis { - var ( - coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") - account1 = common.HexToAddress("0x687704DB07e902e9A8B3754031D168D46E3D586e") - account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") - ) - return &Genesis{ - Config: config, - Alloc: GenesisAlloc{ - coinbase: GenesisAccount{ - Balance: big.NewInt(1000000000000000000), // 1 ether - Nonce: 0, - }, - account1: GenesisAccount{ - Balance: big.NewInt(1000000000000000000), // 1 ether - Nonce: 0, - }, - account2: GenesisAccount{ - Balance: big.NewInt(1000000000000000000), // 1 ether - Nonce: 3, - }, - params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0}, - params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0}, - params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0}, - params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0}, - }, - } -} - -// TestProcessVerkleContractWithEmptyCode checks that the witness contains all valid -// entries, if the initcode returns an empty code. -func TestProcessVerkleContractWithEmptyCode(t *testing.T) { - // The test txs were taken from a secondary testnet with chain id 69421 - config := *testKaustinenLikeChainConfig - config.ChainID = new(big.Int).SetUint64(69421) - gspec := verkleTestGenesis(&config) - - genesisH, _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { - gen.SetPoS() - var tx types.Transaction - // a transaction that does some PUSH1n but returns a 0-sized contract - txpayload := common.Hex2Bytes("02f8db83010f2d03843b9aca008444cf6a05830186a08080b8807fdfbbb59f2371a76485ce557fd0de00c298d3ede52a3eab56d35af674eb49ec5860335260826053536001605453604c60555360f3605653606060575360446058536096605953600c605a5360df605b5360f3605c5360fb605d53600c605e53609a605f53607f60605360fe606153603d60625360f4606353604b60645360cac001a0486b6dc55b8a311568b7239a2cae1d77e7446dba71df61eaafd53f73820a138fa010bd48a45e56133ac4c5645142c2ea48950d40eb35050e9510b6bad9e15c5865") - if err := tx.UnmarshalBinary(txpayload); err != nil { - t.Fatal(err) - } - gen.AddTx(&tx) - }) - - eip2935Stem := utils.GetTreeKey(params.HistoryStorageAddress[:], uint256.NewInt(0), 0) - eip2935Stem = eip2935Stem[:31] - - for _, stemStateDiff := range statediffs[0] { - // Handle the case of the history contract: make sure only the correct - // slots are added to the witness. - if bytes.Equal(stemStateDiff.Stem[:], eip2935Stem) { - // BLOCKHASH contract stem - if len(stemStateDiff.SuffixDiffs) > 1 { - t.Fatalf("invalid suffix diff count found for BLOCKHASH contract: %d != 1", len(stemStateDiff.SuffixDiffs)) - } - if stemStateDiff.SuffixDiffs[0].Suffix != 64 { - t.Fatalf("invalid suffix diff value found for BLOCKHASH contract: %d != 64", stemStateDiff.SuffixDiffs[0].Suffix) - } - // check that the "current value" is nil and that the new value isn't. - if stemStateDiff.SuffixDiffs[0].CurrentValue != nil { - t.Fatalf("non-nil current value in BLOCKHASH contract insert: %x", stemStateDiff.SuffixDiffs[0].CurrentValue) - } - if stemStateDiff.SuffixDiffs[0].NewValue == nil { - t.Fatalf("nil new value in BLOCKHASH contract insert") - } - if *stemStateDiff.SuffixDiffs[0].NewValue != genesisH { - t.Fatalf("invalid BLOCKHASH value: %x != %x", *stemStateDiff.SuffixDiffs[0].NewValue, genesisH) - } - } else { - for _, suffixDiff := range stemStateDiff.SuffixDiffs { - if suffixDiff.Suffix > 2 { - // if d8898012c484fb48610ecb7963886339207dab004bce968b007b616ffa18e0 shows up, it means that the PUSHn - // in the transaction above added entries into the witness, when they should not have since they are - // part of a contract deployment. - t.Fatalf("invalid suffix diff found for %x in block #1: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) - } - } - } - } -} - -// TestProcessVerkleExtCodeHashOpcode verifies that calling EXTCODEHASH on another -// deployed contract, creates all the right entries in the witness. -func TestProcessVerkleExtCodeHashOpcode(t *testing.T) { - // The test txs were taken from a secondary testnet with chain id 69421 - config := *testKaustinenLikeChainConfig - config.ChainID = new(big.Int).SetUint64(69421) - - var ( - signer = types.LatestSigner(&config) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - gspec = verkleTestGenesis(&config) - ) - dummyContract := []byte{ - byte(vm.PUSH1), 2, - byte(vm.PUSH1), 12, - byte(vm.PUSH1), 0x00, - byte(vm.CODECOPY), - - byte(vm.PUSH1), 2, - byte(vm.PUSH1), 0x00, - byte(vm.RETURN), - - byte(vm.PUSH1), 42, - } - deployer := crypto.PubkeyToAddress(testKey.PublicKey) - dummyContractAddr := crypto.CreateAddress(deployer, 0) - - // contract that calls EXTCODEHASH on the dummy contract - extCodeHashContract := []byte{ - byte(vm.PUSH1), 22, - byte(vm.PUSH1), 12, - byte(vm.PUSH1), 0x00, - byte(vm.CODECOPY), - - byte(vm.PUSH1), 22, - byte(vm.PUSH1), 0x00, - byte(vm.RETURN), - - byte(vm.PUSH20), - 0x3a, 0x22, 0x0f, 0x35, 0x12, 0x52, 0x08, 0x9d, 0x38, 0x5b, 0x29, 0xbe, 0xca, 0x14, 0xe2, 0x7f, 0x20, 0x4c, 0x29, 0x6a, - byte(vm.EXTCODEHASH), - } - extCodeHashContractAddr := crypto.CreateAddress(deployer, 1) - - _, _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { - gen.SetPoS() - - if i == 0 { - // Create dummy contract. - tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, - Value: big.NewInt(0), - Gas: 100_000, - GasPrice: big.NewInt(875000000), - Data: dummyContract, - }) - gen.AddTx(tx) - - // Create contract with EXTCODEHASH opcode. - tx, _ = types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 1, - Value: big.NewInt(0), - Gas: 100_000, - GasPrice: big.NewInt(875000000), - Data: extCodeHashContract}) - gen.AddTx(tx) - } else { - tx, _ := types.SignTx(types.NewTransaction(2, extCodeHashContractAddr, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey) - gen.AddTx(tx) - } - }) - - contractKeccakTreeKey := utils.CodeHashKey(dummyContractAddr[:]) - - var stateDiffIdx = -1 - for i, stemStateDiff := range statediffs[1] { - if bytes.Equal(stemStateDiff.Stem[:], contractKeccakTreeKey[:31]) { - stateDiffIdx = i - break - } - } - if stateDiffIdx == -1 { - t.Fatalf("no state diff found for stem") - } - - codeHashStateDiff := statediffs[1][stateDiffIdx].SuffixDiffs[0] - // Check location of code hash was accessed - if codeHashStateDiff.Suffix != utils.CodeHashLeafKey { - t.Fatalf("code hash invalid suffix") - } - // check the code hash wasn't present in the prestate, as - // the contract was deployed in this block. - if codeHashStateDiff.CurrentValue == nil { - t.Fatalf("codeHash.CurrentValue must not be empty") - } - // check the poststate value corresponds to the code hash - // of the deployed contract. - expCodeHash := crypto.Keccak256Hash(dummyContract[12:]) - if *codeHashStateDiff.CurrentValue != expCodeHash { - t.Fatalf("codeHash.CurrentValue unexpected code hash") - } - if codeHashStateDiff.NewValue != nil { - t.Fatalf("codeHash.NewValue must be nil") - } -} - -// TestProcessVerkleBalanceOpcode checks that calling balance -// on another contract will add the correct entries to the witness. -func TestProcessVerkleBalanceOpcode(t *testing.T) { - // The test txs were taken from a secondary testnet with chain id 69421 - config := *testKaustinenLikeChainConfig - config.ChainID = new(big.Int).SetUint64(69421) - - var ( - signer = types.LatestSigner(&config) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") - gspec = verkleTestGenesis(&config) - ) - _, _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { - gen.SetPoS() - txData := slices.Concat( - []byte{byte(vm.PUSH20)}, - common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d").Bytes(), - []byte{byte(vm.BALANCE)}) - - tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, - Value: big.NewInt(0), - Gas: 100_000, - GasPrice: big.NewInt(875000000), - Data: txData}) - gen.AddTx(tx) - }) - - account2BalanceTreeKey := utils.BasicDataKey(account2[:]) - - var stateDiffIdx = -1 - for i, stemStateDiff := range statediffs[0] { - if bytes.Equal(stemStateDiff.Stem[:], account2BalanceTreeKey[:31]) { - stateDiffIdx = i - break - } - } - if stateDiffIdx == -1 { - t.Fatalf("no state diff found for stem") - } - - var zero [32]byte - balanceStateDiff := statediffs[0][stateDiffIdx].SuffixDiffs[0] - if balanceStateDiff.Suffix != utils.BasicDataLeafKey { - t.Fatalf("invalid suffix diff") - } - // check the prestate balance wasn't 0 or missing - if balanceStateDiff.CurrentValue == nil || *balanceStateDiff.CurrentValue == zero { - t.Fatalf("invalid current value %v", *balanceStateDiff.CurrentValue) - } - // check that the poststate witness value for the balance is nil, - // meaning that it didn't get updated. - if balanceStateDiff.NewValue != nil { - t.Fatalf("invalid new value") - } -} - -// TestProcessVerkleSelfDestructInSeparateTx controls the contents of the witness after -// a non-eip6780-compliant selfdestruct occurs. -func TestProcessVerkleSelfDestructInSeparateTx(t *testing.T) { - // The test txs were taken from a secondary testnet with chain id 69421 - config := *testKaustinenLikeChainConfig - config.ChainID = new(big.Int).SetUint64(69421) - - var ( - signer = types.LatestSigner(&config) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") - gspec = verkleTestGenesis(&config) - ) - - // runtime code: selfdestruct ( 0x6177843db3138ae69679A54b95cf345ED759450d ) - runtimeCode := slices.Concat( - []byte{byte(vm.PUSH20)}, - account2.Bytes(), - []byte{byte(vm.SELFDESTRUCT)}) - - //The goal of this test is to test SELFDESTRUCT that happens in a contract - // execution which is created in a previous transaction. - selfDestructContract := slices.Concat([]byte{ - byte(vm.PUSH1), byte(len(runtimeCode)), - byte(vm.PUSH1), 12, - byte(vm.PUSH1), 0x00, - byte(vm.CODECOPY), // Codecopy( to-offset: 0, code offset: 12, length: 22 ) - - byte(vm.PUSH1), byte(len(runtimeCode)), - byte(vm.PUSH1), 0x00, - byte(vm.RETURN), // Return ( 0 : len(runtimecode) - }, - runtimeCode) - - deployer := crypto.PubkeyToAddress(testKey.PublicKey) - contract := crypto.CreateAddress(deployer, 0) - - _, _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { - gen.SetPoS() - - if i == 0 { - // Create selfdestruct contract, sending 42 wei. - tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, - Value: big.NewInt(42), - Gas: 100_000, - GasPrice: big.NewInt(875000000), - Data: selfDestructContract, - }) - gen.AddTx(tx) - } else { - // Call it. - tx, _ := types.SignTx(types.NewTransaction(1, contract, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey) - gen.AddTx(tx) - } - }) - - var zero [32]byte - { // Check self-destructed contract in the witness - selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) - - var stateDiffIdx = -1 - for i, stemStateDiff := range statediffs[1] { - if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { - stateDiffIdx = i - break - } - } - if stateDiffIdx == -1 { - t.Fatalf("no state diff found for stem") - } - - balanceStateDiff := statediffs[1][stateDiffIdx].SuffixDiffs[0] - if balanceStateDiff.Suffix != utils.BasicDataLeafKey { - t.Fatalf("balance invalid suffix") - } - - // The original balance was 42. - var oldBalance [16]byte - oldBalance[15] = 42 - if !bytes.Equal((*balanceStateDiff.CurrentValue)[utils.BasicDataBalanceOffset:], oldBalance[:]) { - t.Fatalf("the pre-state balance before self-destruct must be %x, got %x", oldBalance, *balanceStateDiff.CurrentValue) - } - - // The new balance must be 0. - if !bytes.Equal((*balanceStateDiff.NewValue)[utils.BasicDataBalanceOffset:], zero[utils.BasicDataBalanceOffset:]) { - t.Fatalf("the post-state balance after self-destruct must be 0") - } - } - { // Check self-destructed target in the witness. - selfDestructTargetTreeKey := utils.CodeHashKey(account2[:]) - - var stateDiffIdx = -1 - for i, stemStateDiff := range statediffs[1] { - if bytes.Equal(stemStateDiff.Stem[:], selfDestructTargetTreeKey[:31]) { - stateDiffIdx = i - break - } - } - if stateDiffIdx == -1 { - t.Fatalf("no state diff found for stem") - } - - balanceStateDiff := statediffs[1][stateDiffIdx].SuffixDiffs[0] - if balanceStateDiff.Suffix != utils.BasicDataLeafKey { - t.Fatalf("balance invalid suffix") - } - if balanceStateDiff.CurrentValue == nil { - t.Fatalf("codeHash.CurrentValue must not be empty") - } - if balanceStateDiff.NewValue == nil { - t.Fatalf("codeHash.NewValue must not be empty") - } - preStateBalance := binary.BigEndian.Uint64(balanceStateDiff.CurrentValue[utils.BasicDataBalanceOffset+8:]) - postStateBalance := binary.BigEndian.Uint64(balanceStateDiff.NewValue[utils.BasicDataBalanceOffset+8:]) - if postStateBalance-preStateBalance != 42 { - t.Fatalf("the post-state balance after self-destruct must be 42, got %d-%d=%d", postStateBalance, preStateBalance, postStateBalance-preStateBalance) - } - } -} - -// TestProcessVerkleSelfDestructInSameTx controls the contents of the witness after -// a eip6780-compliant selfdestruct occurs. -func TestProcessVerkleSelfDestructInSameTx(t *testing.T) { - // The test txs were taken from a secondary testnet with chain id 69421 - config := *testKaustinenLikeChainConfig - config.ChainID = new(big.Int).SetUint64(69421) - - var ( - signer = types.LatestSigner(&config) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") - gspec = verkleTestGenesis(&config) - ) - - // The goal of this test is to test SELFDESTRUCT that happens in a contract - // execution which is created in **the same** transaction sending the remaining - // balance to an external (i.e: not itself) account. - - selfDestructContract := slices.Concat( - []byte{byte(vm.PUSH20)}, - account2.Bytes(), - []byte{byte(vm.SELFDESTRUCT)}) - deployer := crypto.PubkeyToAddress(testKey.PublicKey) - contract := crypto.CreateAddress(deployer, 0) - - _, _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { - gen.SetPoS() - tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, - Value: big.NewInt(42), - Gas: 100_000, - GasPrice: big.NewInt(875000000), - Data: selfDestructContract, - }) - gen.AddTx(tx) - }) - - { // Check self-destructed contract in the witness - selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) - - var stateDiffIdx = -1 - for i, stemStateDiff := range statediffs[0] { - if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { - stateDiffIdx = i - break - } - } - if stateDiffIdx == -1 { - t.Fatalf("no state diff found for stem") - } - - balanceStateDiff := statediffs[0][stateDiffIdx].SuffixDiffs[0] - if balanceStateDiff.Suffix != utils.BasicDataLeafKey { - t.Fatalf("balance invalid suffix") - } - - if balanceStateDiff.CurrentValue != nil { - t.Fatalf("the pre-state balance before must be nil, since the contract didn't exist") - } - - if balanceStateDiff.NewValue != nil { - t.Fatalf("the post-state balance after self-destruct must be nil since the contract shouldn't be created at all") - } - } - { // Check self-destructed target in the witness. - selfDestructTargetTreeKey := utils.CodeHashKey(account2[:]) - - var stateDiffIdx = -1 - for i, stemStateDiff := range statediffs[0] { - if bytes.Equal(stemStateDiff.Stem[:], selfDestructTargetTreeKey[:31]) { - stateDiffIdx = i - break - } - } - if stateDiffIdx == -1 { - t.Fatalf("no state diff found for stem") - } - - balanceStateDiff := statediffs[0][stateDiffIdx].SuffixDiffs[0] - if balanceStateDiff.Suffix != utils.BasicDataLeafKey { - t.Fatalf("balance invalid suffix") - } - if balanceStateDiff.CurrentValue == nil { - t.Fatalf("codeHash.CurrentValue must not be empty") - } - if balanceStateDiff.NewValue == nil { - t.Fatalf("codeHash.NewValue must not be empty") - } - preStateBalance := binary.BigEndian.Uint64(balanceStateDiff.CurrentValue[utils.BasicDataBalanceOffset+8:]) - postStateBalance := binary.BigEndian.Uint64(balanceStateDiff.NewValue[utils.BasicDataBalanceOffset+8:]) - if postStateBalance-preStateBalance != 42 { - t.Fatalf("the post-state balance after self-destruct must be 42. got %d", postStateBalance) - } - } -} - -// TestProcessVerkleSelfDestructInSeparateTxWithSelfBeneficiary checks the content of the witness -// if a selfdestruct occurs in a different tx than the one that created it, but the beneficiary -// is the selfdestructed account. -func TestProcessVerkleSelfDestructInSeparateTxWithSelfBeneficiary(t *testing.T) { - // The test txs were taken from a secondary testnet with chain id 69421 - config := *testKaustinenLikeChainConfig - config.ChainID = new(big.Int).SetUint64(69421) - - var ( - signer = types.LatestSigner(&config) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - gspec = verkleTestGenesis(&config) - ) - // The goal of this test is to test SELFDESTRUCT that happens in a contract - // execution which is created in a *previous* transaction sending the remaining - // balance to itself. - selfDestructContract := []byte{ - byte(vm.PUSH1), 2, // PUSH1 2 - byte(vm.PUSH1), 10, // PUSH1 12 - byte(vm.PUSH0), // PUSH0 - byte(vm.CODECOPY), // Codecopy ( to offset 0, code@offset: 10, length: 2) - - byte(vm.PUSH1), 22, - byte(vm.PUSH0), - byte(vm.RETURN), // RETURN( memory[0:2] ) - - // Deployed code - byte(vm.ADDRESS), - byte(vm.SELFDESTRUCT), - } - deployer := crypto.PubkeyToAddress(testKey.PublicKey) - contract := crypto.CreateAddress(deployer, 0) - - _, _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { - gen.SetPoS() - if i == 0 { - // Create self-destruct contract, sending 42 wei. - tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, - Value: big.NewInt(42), - Gas: 100_000, - GasPrice: big.NewInt(875000000), - Data: selfDestructContract, - }) - gen.AddTx(tx) - } else { - // Call it. - tx, _ := types.SignTx(types.NewTransaction(1, contract, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey) - gen.AddTx(tx) - } - }) - - { - // Check self-destructed contract in the witness. - // The way 6780 is implemented today, it always SubBalance from the self-destructed contract, and AddBalance - // to the beneficiary. In this case both addresses are the same, thus this might be optimizable from a gas - // perspective. But until that happens, we need to honor this "balance reading" adding it to the witness. - - selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) - - var stateDiffIdx = -1 - for i, stemStateDiff := range statediffs[1] { - if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { - stateDiffIdx = i - break - } - } - if stateDiffIdx == -1 { - t.Fatal("no state diff found for stem") - } - - balanceStateDiff := statediffs[1][stateDiffIdx].SuffixDiffs[0] - if balanceStateDiff.Suffix != utils.BasicDataLeafKey { - t.Fatal("balance invalid suffix") - } - - // The original balance was 42. - var oldBalance [16]byte - oldBalance[15] = 42 - if !bytes.Equal((*balanceStateDiff.CurrentValue)[utils.BasicDataBalanceOffset:], oldBalance[:]) { - t.Fatal("the pre-state balance before self-destruct must be 42") - } - - // Note that the SubBalance+AddBalance net effect is a 0 change, so NewValue - // must be nil. - if balanceStateDiff.NewValue != nil { - t.Fatal("the post-state balance after self-destruct must be empty") - } - } -} - -// TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiary checks the content of the witness -// if a selfdestruct occurs in the same tx as the one that created it, but the beneficiary -// is the selfdestructed account. -func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiary(t *testing.T) { - // The test txs were taken from a secondary testnet with chain id 69421 - config := *testKaustinenLikeChainConfig - config.ChainID = new(big.Int).SetUint64(69421) - - var ( - signer = types.LatestSigner(&config) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - gspec = verkleTestGenesis(&config) - deployer = crypto.PubkeyToAddress(testKey.PublicKey) - contract = crypto.CreateAddress(deployer, 0) - ) - - // The goal of this test is to test SELFDESTRUCT that happens while executing - // the init code of a contract creation, that occurs in **the same** transaction. - // The balance is sent to itself. - t.Logf("Contract: %v", contract.String()) - - selfDestructContract := []byte{byte(vm.ADDRESS), byte(vm.SELFDESTRUCT)} - - _, _, _, _, _, stateDiffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { - gen.SetPoS() - tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, - Value: big.NewInt(42), - Gas: 100_000, - GasPrice: big.NewInt(875000000), - Data: selfDestructContract, - }) - gen.AddTx(tx) - }) - stateDiff := stateDiffs[0] // state difference of block 1 - - { // Check self-destructed contract in the witness - selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) - - var stateDiffIdx = -1 - for i, stemStateDiff := range stateDiff { - if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { - stateDiffIdx = i - break - } - } - if stateDiffIdx == -1 { - t.Fatal("no state diff found for stem") - } - balanceStateDiff := stateDiff[stateDiffIdx].SuffixDiffs[0] - if balanceStateDiff.Suffix != utils.BasicDataLeafKey { - t.Fatal("balance invalid suffix") - } - if balanceStateDiff.CurrentValue != nil { - t.Fatal("the pre-state balance before must be nil, since the contract didn't exist") - } - // Ensure that the value is burnt, and therefore that the balance of the self-destructed - // contract isn't modified (it should remain missing from the state) - if balanceStateDiff.NewValue != nil { - t.Fatal("the post-state balance after self-destruct must be nil since the contract shouldn't be created at all") - } - } -} - -// TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiaryAndPrefundedAccount checks the -// content of the witness if a selfdestruct occurs in the same tx as the one that created it, -// it, but the beneficiary is the selfdestructed account. The difference with the test above, -// is that the created account is prefunded and so the final value should be 0. -func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiaryAndPrefundedAccount(t *testing.T) { - // The test txs were taken from a secondary testnet with chain id 69421 - config := *testKaustinenLikeChainConfig - config.ChainID = new(big.Int).SetUint64(69421) - - var ( - signer = types.LatestSigner(&config) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - gspec = verkleTestGenesis(&config) - deployer = crypto.PubkeyToAddress(testKey.PublicKey) - contract = crypto.CreateAddress(deployer, 0) - ) - // Prefund the account, at an address that the contract will be deployed at, - // before it selfdestrucs. We can therefore check that the account itseld is - // NOT destroyed, which is what the current version of the spec requires. - // TODO(gballet) revisit after the spec has been modified. - gspec.Alloc[contract] = types.Account{ - Balance: big.NewInt(100), - } - - selfDestructContract := []byte{byte(vm.ADDRESS), byte(vm.SELFDESTRUCT)} - - _, _, _, _, _, stateDiffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { - gen.SetPoS() - tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, - Value: big.NewInt(42), - Gas: 100_000, - GasPrice: big.NewInt(875000000), - Data: selfDestructContract, - }) - gen.AddTx(tx) - }) - stateDiff := stateDiffs[0] // state difference of block 1 - - { // Check self-destructed contract in the witness - selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) - - var stateDiffIdx = -1 - for i, stemStateDiff := range stateDiff { - if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { - stateDiffIdx = i - break - } - } - if stateDiffIdx == -1 { - t.Fatal("no state diff found for stem") - } - balanceStateDiff := stateDiff[stateDiffIdx].SuffixDiffs[0] - if balanceStateDiff.Suffix != utils.BasicDataLeafKey { - t.Fatal("balance invalid suffix") - } - expected, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000064") - if balanceStateDiff.CurrentValue == nil || !bytes.Equal(balanceStateDiff.CurrentValue[:], expected) { - t.Fatalf("incorrect prestate balance: %x != %x", *balanceStateDiff.CurrentValue, expected) - } - // Ensure that the value is burnt, and therefore that the balance of the self-destructed - // contract isn't modified (it should remain missing from the state) - expected = make([]byte, 32) - if balanceStateDiff.NewValue == nil { - t.Fatal("incorrect nil poststate balance") - } - if !bytes.Equal(balanceStateDiff.NewValue[:], expected[:]) { - t.Fatalf("incorrect poststate balance: %x != %x", *balanceStateDiff.NewValue, expected[:]) - } - } -} diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 3b88753b1c82..2ced18787a24 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -117,19 +117,20 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t return UnsupportedForkError{t.json.Network} } // import pre accounts & construct test genesis block & state root + // Commit genesis state var ( + gspec = t.genesis(config) db = rawdb.NewMemoryDatabase() tconf = &triedb.Config{ Preimages: true, + IsVerkle: gspec.Config.VerkleTime != nil && *gspec.Config.VerkleTime <= gspec.Timestamp, } ) - if scheme == rawdb.PathScheme { + if scheme == rawdb.PathScheme || tconf.IsVerkle { tconf.PathDB = pathdb.Defaults } else { tconf.HashDB = hashdb.Defaults } - // Commit genesis state - gspec := t.genesis(config) // if ttd is not specified, set an arbitrary huge value if gspec.Config.TerminalTotalDifficulty == nil { diff --git a/tests/init.go b/tests/init.go index 705e929ae9d7..d10b47986cd4 100644 --- a/tests/init.go +++ b/tests/init.go @@ -720,6 +720,25 @@ var Forks = map[string]*params.ChainConfig{ BPO4: params.DefaultBPO4BlobConfig, }, }, + "Verkle": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + VerkleTime: u64(0), + }, } var bpo1BlobConfig = ¶ms.BlobConfig{ diff --git a/trie/bintrie/binary_node.go b/trie/bintrie/binary_node.go index 1c003a6c8fdf..690489b2aa16 100644 --- a/trie/bintrie/binary_node.go +++ b/trie/bintrie/binary_node.go @@ -31,8 +31,11 @@ type ( var zero [32]byte const ( - NodeWidth = 256 // Number of child per leaf node - StemSize = 31 // Number of bytes to travel before reaching a group of leaves + StemNodeWidth = 256 // Number of child per leaf node + StemSize = 31 // Number of bytes to travel before reaching a group of leaves + NodeTypeBytes = 1 // Size of node type prefix in serialization + HashSize = 32 // Size of a hash in bytes + BitmapSize = 32 // Size of the bitmap in a stem node ) const ( @@ -58,25 +61,28 @@ type BinaryNode interface { func SerializeNode(node BinaryNode) []byte { switch n := (node).(type) { case *InternalNode: - var serialized [65]byte + // InternalNode: 1 byte type + 32 bytes left hash + 32 bytes right hash + var serialized [NodeTypeBytes + HashSize + HashSize]byte serialized[0] = nodeTypeInternal copy(serialized[1:33], n.left.Hash().Bytes()) copy(serialized[33:65], n.right.Hash().Bytes()) return serialized[:] case *StemNode: - var serialized [32 + 32 + 256*32]byte + // StemNode: 1 byte type + 31 bytes stem + 32 bytes bitmap + 256*32 bytes values + var serialized [NodeTypeBytes + StemSize + BitmapSize + StemNodeWidth*HashSize]byte serialized[0] = nodeTypeStem - copy(serialized[1:32], node.(*StemNode).Stem) - bitmap := serialized[32:64] - offset := 64 - for i, v := range node.(*StemNode).Values { + copy(serialized[NodeTypeBytes:NodeTypeBytes+StemSize], n.Stem) + bitmap := serialized[NodeTypeBytes+StemSize : NodeTypeBytes+StemSize+BitmapSize] + offset := NodeTypeBytes + StemSize + BitmapSize + for i, v := range n.Values { if v != nil { bitmap[i/8] |= 1 << (7 - (i % 8)) - copy(serialized[offset:offset+32], v) - offset += 32 + copy(serialized[offset:offset+HashSize], v) + offset += HashSize } } - return serialized[:] + // Only return the actual data, not the entire array + return serialized[:offset] default: panic("invalid node type") } @@ -104,21 +110,21 @@ func DeserializeNode(serialized []byte, depth int) (BinaryNode, error) { if len(serialized) < 64 { return nil, invalidSerializedLength } - var values [256][]byte - bitmap := serialized[32:64] - offset := 64 + var values [StemNodeWidth][]byte + bitmap := serialized[NodeTypeBytes+StemSize : NodeTypeBytes+StemSize+BitmapSize] + offset := NodeTypeBytes + StemSize + BitmapSize - for i := range 256 { + for i := range StemNodeWidth { if bitmap[i/8]>>(7-(i%8))&1 == 1 { - if len(serialized) < offset+32 { + if len(serialized) < offset+HashSize { return nil, invalidSerializedLength } - values[i] = serialized[offset : offset+32] - offset += 32 + values[i] = serialized[offset : offset+HashSize] + offset += HashSize } } return &StemNode{ - Stem: serialized[1:32], + Stem: serialized[NodeTypeBytes : NodeTypeBytes+StemSize], Values: values[:], depth: depth, }, nil diff --git a/trie/bintrie/binary_node_test.go b/trie/bintrie/binary_node_test.go index b21daaab6974..242743ba53bb 100644 --- a/trie/bintrie/binary_node_test.go +++ b/trie/bintrie/binary_node_test.go @@ -77,12 +77,12 @@ func TestSerializeDeserializeInternalNode(t *testing.T) { // TestSerializeDeserializeStemNode tests serialization and deserialization of StemNode func TestSerializeDeserializeStemNode(t *testing.T) { // Create a stem node with some values - stem := make([]byte, 31) + stem := make([]byte, StemSize) for i := range stem { stem[i] = byte(i) } - var values [256][]byte + var values [StemNodeWidth][]byte // Add some values at different indices values[0] = common.HexToHash("0x0101010101010101010101010101010101010101010101010101010101010101").Bytes() values[10] = common.HexToHash("0x0202020202020202020202020202020202020202020202020202020202020202").Bytes() @@ -103,7 +103,7 @@ func TestSerializeDeserializeStemNode(t *testing.T) { } // Check the stem is correctly serialized - if !bytes.Equal(serialized[1:32], stem) { + if !bytes.Equal(serialized[1:1+StemSize], stem) { t.Errorf("Stem mismatch in serialized data") } @@ -136,7 +136,7 @@ func TestSerializeDeserializeStemNode(t *testing.T) { } // Check that other values are nil - for i := range NodeWidth { + for i := range StemNodeWidth { if i == 0 || i == 10 || i == 255 { continue } @@ -218,15 +218,15 @@ func TestKeyToPath(t *testing.T) { }, { name: "max valid depth", - depth: 31 * 8, - key: make([]byte, 32), - expected: make([]byte, 31*8+1), + depth: StemSize * 8, + key: make([]byte, HashSize), + expected: make([]byte, StemSize*8+1), wantErr: false, }, { name: "depth too large", - depth: 31*8 + 1, - key: make([]byte, 32), + depth: StemSize*8 + 1, + key: make([]byte, HashSize), wantErr: true, }, } diff --git a/trie/bintrie/hashed_node.go b/trie/bintrie/hashed_node.go index 8f9fd66a59a0..e4d8c2e7ac7d 100644 --- a/trie/bintrie/hashed_node.go +++ b/trie/bintrie/hashed_node.go @@ -46,8 +46,31 @@ func (h HashedNode) GetValuesAtStem(_ []byte, _ NodeResolverFn) ([][]byte, error return nil, errors.New("attempted to get values from an unresolved node") } -func (h HashedNode) InsertValuesAtStem(key []byte, values [][]byte, resolver NodeResolverFn, depth int) (BinaryNode, error) { - return nil, errors.New("insertValuesAtStem not implemented for hashed node") +func (h HashedNode) InsertValuesAtStem(stem []byte, values [][]byte, resolver NodeResolverFn, depth int) (BinaryNode, error) { + // Step 1: Generate the path for this node's position in the tree + path, err := keyToPath(depth, stem) + if err != nil { + return nil, fmt.Errorf("InsertValuesAtStem path generation error: %w", err) + } + + if resolver == nil { + return nil, errors.New("InsertValuesAtStem resolve error: resolver is nil") + } + + // Step 2: Resolve the hashed node to get the actual node data + data, err := resolver(path, common.Hash(h)) + if err != nil { + return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err) + } + + // Step 3: Deserialize the resolved data into a concrete node + node, err := DeserializeNode(data, depth) + if err != nil { + return nil, fmt.Errorf("InsertValuesAtStem node deserialization error: %w", err) + } + + // Step 4: Call InsertValuesAtStem on the resolved concrete node + return node.InsertValuesAtStem(stem, values, resolver, depth) } func (h HashedNode) toDot(parent string, path string) string { @@ -58,7 +81,8 @@ func (h HashedNode) toDot(parent string, path string) string { } func (h HashedNode) CollectNodes([]byte, NodeFlushFn) error { - return errors.New("collectNodes not implemented for hashed node") + // HashedNodes are already persisted in the database and don't need to be collected. + return nil } func (h HashedNode) GetHeight() int { diff --git a/trie/bintrie/hashed_node_test.go b/trie/bintrie/hashed_node_test.go index 0c19ae0c57d4..f9e69848884a 100644 --- a/trie/bintrie/hashed_node_test.go +++ b/trie/bintrie/hashed_node_test.go @@ -17,6 +17,7 @@ package bintrie import ( + "bytes" "testing" "github.com/ethereum/go-ethereum/common" @@ -59,8 +60,8 @@ func TestHashedNodeCopy(t *testing.T) { func TestHashedNodeInsert(t *testing.T) { node := HashedNode(common.HexToHash("0x1234")) - key := make([]byte, 32) - value := make([]byte, 32) + key := make([]byte, HashSize) + value := make([]byte, HashSize) _, err := node.Insert(key, value, nil, 0) if err == nil { @@ -76,7 +77,7 @@ func TestHashedNodeInsert(t *testing.T) { func TestHashedNodeGetValuesAtStem(t *testing.T) { node := HashedNode(common.HexToHash("0x1234")) - stem := make([]byte, 31) + stem := make([]byte, StemSize) _, err := node.GetValuesAtStem(stem, nil) if err == nil { t.Fatal("Expected error for GetValuesAtStem on HashedNode") @@ -91,17 +92,85 @@ func TestHashedNodeGetValuesAtStem(t *testing.T) { func TestHashedNodeInsertValuesAtStem(t *testing.T) { node := HashedNode(common.HexToHash("0x1234")) - stem := make([]byte, 31) - values := make([][]byte, 256) + stem := make([]byte, StemSize) + values := make([][]byte, StemNodeWidth) + // Test 1: nil resolver should return an error _, err := node.InsertValuesAtStem(stem, values, nil, 0) if err == nil { - t.Fatal("Expected error for InsertValuesAtStem on HashedNode") + t.Fatal("Expected error for InsertValuesAtStem on HashedNode with nil resolver") } - if err.Error() != "insertValuesAtStem not implemented for hashed node" { + if err.Error() != "InsertValuesAtStem resolve error: resolver is nil" { t.Errorf("Unexpected error message: %v", err) } + + // Test 2: mock resolver returning invalid data should return deserialization error + mockResolver := func(path []byte, hash common.Hash) ([]byte, error) { + // Return invalid/nonsense data that cannot be deserialized + return []byte{0xff, 0xff, 0xff}, nil + } + + _, err = node.InsertValuesAtStem(stem, values, mockResolver, 0) + if err == nil { + t.Fatal("Expected error for InsertValuesAtStem on HashedNode with invalid resolver data") + } + + expectedPrefix := "InsertValuesAtStem node deserialization error:" + if len(err.Error()) < len(expectedPrefix) || err.Error()[:len(expectedPrefix)] != expectedPrefix { + t.Errorf("Expected deserialization error, got: %v", err) + } + + // Test 3: mock resolver returning valid serialized node should succeed + stem = make([]byte, StemSize) + stem[0] = 0xaa + var originalValues [StemNodeWidth][]byte + originalValues[0] = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111").Bytes() + originalValues[1] = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222").Bytes() + + originalNode := &StemNode{ + Stem: stem, + Values: originalValues[:], + depth: 0, + } + + // Serialize the node + serialized := SerializeNode(originalNode) + + // Create a mock resolver that returns the serialized node + validResolver := func(path []byte, hash common.Hash) ([]byte, error) { + return serialized, nil + } + + var newValues [StemNodeWidth][]byte + newValues[2] = common.HexToHash("0x3333333333333333333333333333333333333333333333333333333333333333").Bytes() + + resolvedNode, err := node.InsertValuesAtStem(stem, newValues[:], validResolver, 0) + if err != nil { + t.Fatalf("Expected successful resolution and insertion, got error: %v", err) + } + + resultStem, ok := resolvedNode.(*StemNode) + if !ok { + t.Fatalf("Expected resolved node to be *StemNode, got %T", resolvedNode) + } + + if !bytes.Equal(resultStem.Stem, stem) { + t.Errorf("Stem mismatch: expected %x, got %x", stem, resultStem.Stem) + } + + // Verify the original values are preserved + if !bytes.Equal(resultStem.Values[0], originalValues[0]) { + t.Errorf("Original value at index 0 not preserved: expected %x, got %x", originalValues[0], resultStem.Values[0]) + } + if !bytes.Equal(resultStem.Values[1], originalValues[1]) { + t.Errorf("Original value at index 1 not preserved: expected %x, got %x", originalValues[1], resultStem.Values[1]) + } + + // Verify the new value was inserted + if !bytes.Equal(resultStem.Values[2], newValues[2]) { + t.Errorf("New value at index 2 not inserted correctly: expected %x, got %x", newValues[2], resultStem.Values[2]) + } } // TestHashedNodeToDot tests the toDot method for visualization diff --git a/trie/bintrie/internal_node.go b/trie/bintrie/internal_node.go index f3ddd1aab023..0a7bece521fd 100644 --- a/trie/bintrie/internal_node.go +++ b/trie/bintrie/internal_node.go @@ -49,14 +49,26 @@ func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([ } bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1 - var child *BinaryNode if bit == 0 { - child = &bt.left - } else { - child = &bt.right + if hn, ok := bt.left.(HashedNode); ok { + path, err := keyToPath(bt.depth, stem) + if err != nil { + return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err) + } + data, err := resolver(path, common.Hash(hn)) + if err != nil { + return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err) + } + node, err := DeserializeNode(data, bt.depth+1) + if err != nil { + return nil, fmt.Errorf("GetValuesAtStem node deserialization error: %w", err) + } + bt.left = node + } + return bt.left.GetValuesAtStem(stem, resolver) } - if hn, ok := (*child).(HashedNode); ok { + if hn, ok := bt.right.(HashedNode); ok { path, err := keyToPath(bt.depth, stem) if err != nil { return nil, fmt.Errorf("GetValuesAtStem resolve error: %w", err) @@ -69,9 +81,9 @@ func (bt *InternalNode) GetValuesAtStem(stem []byte, resolver NodeResolverFn) ([ if err != nil { return nil, fmt.Errorf("GetValuesAtStem node deserialization error: %w", err) } - *child = node + bt.right = node } - return (*child).GetValuesAtStem(stem, resolver) + return bt.right.GetValuesAtStem(stem, resolver) } // Get retrieves the value for the given key. @@ -80,6 +92,9 @@ func (bt *InternalNode) Get(key []byte, resolver NodeResolverFn) ([]byte, error) if err != nil { return nil, fmt.Errorf("get error: %w", err) } + if values == nil { + return nil, nil + } return values[key[31]], nil } @@ -118,17 +133,54 @@ func (bt *InternalNode) Hash() common.Hash { // InsertValuesAtStem inserts a full value group at the given stem in the internal node. // Already-existing values will be overwritten. func (bt *InternalNode) InsertValuesAtStem(stem []byte, values [][]byte, resolver NodeResolverFn, depth int) (BinaryNode, error) { - var ( - child *BinaryNode - err error - ) + var err error bit := stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1 if bit == 0 { - child = &bt.left - } else { - child = &bt.right + if bt.left == nil { + bt.left = Empty{} + } + + if hn, ok := bt.left.(HashedNode); ok { + path, err := keyToPath(bt.depth, stem) + if err != nil { + return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err) + } + data, err := resolver(path, common.Hash(hn)) + if err != nil { + return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err) + } + node, err := DeserializeNode(data, bt.depth+1) + if err != nil { + return nil, fmt.Errorf("InsertValuesAtStem node deserialization error: %w", err) + } + bt.left = node + } + + bt.left, err = bt.left.InsertValuesAtStem(stem, values, resolver, depth+1) + return bt, err + } + + if bt.right == nil { + bt.right = Empty{} } - *child, err = (*child).InsertValuesAtStem(stem, values, resolver, depth+1) + + if hn, ok := bt.right.(HashedNode); ok { + path, err := keyToPath(bt.depth, stem) + if err != nil { + return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err) + } + data, err := resolver(path, common.Hash(hn)) + if err != nil { + return nil, fmt.Errorf("InsertValuesAtStem resolve error: %w", err) + } + node, err := DeserializeNode(data, bt.depth+1) + if err != nil { + return nil, fmt.Errorf("InsertValuesAtStem node deserialization error: %w", err) + } + bt.right = node + } + + bt.right, err = bt.right.InsertValuesAtStem(stem, values, resolver, depth+1) return bt, err } diff --git a/trie/bintrie/iterator.go b/trie/bintrie/iterator.go index a6bab2bcfa9f..9b863ed1e3f8 100644 --- a/trie/bintrie/iterator.go +++ b/trie/bintrie/iterator.go @@ -108,6 +108,11 @@ func (it *binaryNodeIterator) Next(descend bool) bool { } // go back to parent to get the next leaf + // Check if we're at the root before popping + if len(it.stack) == 1 { + it.lastErr = errIteratorEnd + return false + } it.stack = it.stack[:len(it.stack)-1] it.current = it.stack[len(it.stack)-1].Node it.stack[len(it.stack)-1].Index++ @@ -183,9 +188,31 @@ func (it *binaryNodeIterator) NodeBlob() []byte { } // Leaf returns true iff the current node is a leaf node. +// In a Binary Trie, a StemNode contains up to 256 leaf values. +// The iterator is only considered to be "at a leaf" when it's positioned +// at a specific non-nil value within the StemNode, not just at the StemNode itself. func (it *binaryNodeIterator) Leaf() bool { - _, ok := it.current.(*StemNode) - return ok + sn, ok := it.current.(*StemNode) + if !ok { + return false + } + + // Check if we have a valid stack position + if len(it.stack) == 0 { + return false + } + + // The Index in the stack state points to the NEXT position after the current value. + // So if Index is 0, we haven't started iterating through the values yet. + // If Index is 5, we're currently at value[4] (the 5th value, 0-indexed). + idx := it.stack[len(it.stack)-1].Index + if idx == 0 || idx > 256 { + return false + } + + // Check if there's actually a value at the current position + currentValueIndex := idx - 1 + return sn.Values[currentValueIndex] != nil } // LeafKey returns the key of the leaf. The method panics if the iterator is not @@ -219,7 +246,7 @@ func (it *binaryNodeIterator) LeafProof() [][]byte { panic("LeafProof() called on an binary node iterator not at a leaf location") } - proof := make([][]byte, 0, len(it.stack)+NodeWidth) + proof := make([][]byte, 0, len(it.stack)+StemNodeWidth) // Build proof by walking up the stack and collecting sibling hashes for i := range it.stack[:len(it.stack)-2] { diff --git a/trie/bintrie/iterator_test.go b/trie/bintrie/iterator_test.go deleted file mode 100644 index 8773e9e0c54c..000000000000 --- a/trie/bintrie/iterator_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// 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 . - -package bintrie - -import ( - "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/ethdb" - "github.com/ethereum/go-ethereum/triedb" - "github.com/ethereum/go-ethereum/triedb/hashdb" - "github.com/ethereum/go-ethereum/triedb/pathdb" - "github.com/holiman/uint256" -) - -func newTestDatabase(diskdb ethdb.Database, scheme string) *triedb.Database { - config := &triedb.Config{Preimages: true} - if scheme == rawdb.HashScheme { - config.HashDB = &hashdb.Config{CleanCacheSize: 0} - } else { - config.PathDB = &pathdb.Config{TrieCleanSize: 0, StateCleanSize: 0} - } - return triedb.NewDatabase(diskdb, config) -} - -func TestBinaryIterator(t *testing.T) { - trie, err := NewBinaryTrie(types.EmptyVerkleHash, newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme)) - if err != nil { - t.Fatal(err) - } - account0 := &types.StateAccount{ - Nonce: 1, - Balance: uint256.NewInt(2), - Root: types.EmptyRootHash, - CodeHash: nil, - } - // NOTE: the code size isn't written to the trie via TryUpdateAccount - // so it will be missing from the test nodes. - trie.UpdateAccount(common.Address{}, account0, 0) - account1 := &types.StateAccount{ - Nonce: 1337, - Balance: uint256.NewInt(2000), - Root: types.EmptyRootHash, - CodeHash: nil, - } - // This address is meant to hash to a value that has the same first byte as 0xbf - var clash = common.HexToAddress("69fd8034cdb20934dedffa7dccb4fb3b8062a8be") - trie.UpdateAccount(clash, account1, 0) - - // Manually go over every node to check that we get all - // the correct nodes. - it, err := trie.NodeIterator(nil) - if err != nil { - t.Fatal(err) - } - var leafcount int - for it.Next(true) { - t.Logf("Node: %x", it.Path()) - if it.Leaf() { - leafcount++ - t.Logf("\tLeaf: %x", it.LeafKey()) - } - } - if leafcount != 2 { - t.Fatalf("invalid leaf count: %d != 6", leafcount) - } -} diff --git a/trie/bintrie/key_encoding.go b/trie/bintrie/key_encoding.go index 13c20573710b..cda797521a66 100644 --- a/trie/bintrie/key_encoding.go +++ b/trie/bintrie/key_encoding.go @@ -47,6 +47,12 @@ func GetBinaryTreeKey(addr common.Address, key []byte) []byte { return k } +func GetBinaryTreeKeyBasicData(addr common.Address) []byte { + var k [32]byte + k[31] = BasicDataLeafKey + return GetBinaryTreeKey(addr, k[:]) +} + func GetBinaryTreeKeyCodeHash(addr common.Address) []byte { var k [32]byte k[31] = CodeHashLeafKey diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go index 50c06c9761e7..60856b42ce60 100644 --- a/trie/bintrie/stem_node.go +++ b/trie/bintrie/stem_node.go @@ -28,7 +28,7 @@ import ( // StemNode represents a group of `NodeWith` values sharing the same stem. type StemNode struct { - Stem []byte // Stem path to get to 256 values + Stem []byte // Stem path to get to StemNodeWidth values Values [][]byte // All values, indexed by the last byte of the key. depth int // Depth of the node } @@ -40,7 +40,7 @@ func (bt *StemNode) Get(key []byte, _ NodeResolverFn) ([]byte, error) { // Insert inserts a new key-value pair into the node. func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn, depth int) (BinaryNode, error) { - if !bytes.Equal(bt.Stem, key[:31]) { + if !bytes.Equal(bt.Stem, key[:StemSize]) { bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1 n := &InternalNode{depth: bt.depth} @@ -65,26 +65,26 @@ func (bt *StemNode) Insert(key []byte, value []byte, _ NodeResolverFn, depth int } *other = Empty{} } else { - var values [256][]byte - values[key[31]] = value + var values [StemNodeWidth][]byte + values[key[StemSize]] = value *other = &StemNode{ - Stem: slices.Clone(key[:31]), + Stem: slices.Clone(key[:StemSize]), Values: values[:], depth: depth + 1, } } return n, nil } - if len(value) != 32 { + if len(value) != HashSize { return bt, errors.New("invalid insertion: value length") } - bt.Values[key[31]] = value + bt.Values[key[StemSize]] = value return bt, nil } // Copy creates a deep copy of the node. func (bt *StemNode) Copy() BinaryNode { - var values [256][]byte + var values [StemNodeWidth][]byte for i, v := range bt.Values { values[i] = slices.Clone(v) } @@ -102,7 +102,7 @@ func (bt *StemNode) GetHeight() int { // Hash returns the hash of the node. func (bt *StemNode) Hash() common.Hash { - var data [NodeWidth]common.Hash + var data [StemNodeWidth]common.Hash for i, v := range bt.Values { if v != nil { h := sha256.Sum256(v) @@ -112,7 +112,7 @@ func (bt *StemNode) Hash() common.Hash { h := sha256.New() for level := 1; level <= 8; level++ { - for i := range NodeWidth / (1 << level) { + for i := range StemNodeWidth / (1 << level) { h.Reset() if data[i*2] == (common.Hash{}) && data[i*2+1] == (common.Hash{}) { @@ -141,14 +141,17 @@ func (bt *StemNode) CollectNodes(path []byte, flush NodeFlushFn) error { } // GetValuesAtStem retrieves the group of values located at the given stem key. -func (bt *StemNode) GetValuesAtStem(_ []byte, _ NodeResolverFn) ([][]byte, error) { +func (bt *StemNode) GetValuesAtStem(stem []byte, _ NodeResolverFn) ([][]byte, error) { + if !bytes.Equal(bt.Stem, stem) { + return nil, nil + } return bt.Values[:], nil } // InsertValuesAtStem inserts a full value group at the given stem in the internal node. // Already-existing values will be overwritten. func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolverFn, depth int) (BinaryNode, error) { - if !bytes.Equal(bt.Stem, key[:31]) { + if !bytes.Equal(bt.Stem, key[:StemSize]) { bitStem := bt.Stem[bt.depth/8] >> (7 - (bt.depth % 8)) & 1 n := &InternalNode{depth: bt.depth} @@ -174,7 +177,7 @@ func (bt *StemNode) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolv *other = Empty{} } else { *other = &StemNode{ - Stem: slices.Clone(key[:31]), + Stem: slices.Clone(key[:StemSize]), Values: values, depth: n.depth + 1, } @@ -206,7 +209,7 @@ func (bt *StemNode) toDot(parent, path string) string { // Key returns the full key for the given index. func (bt *StemNode) Key(i int) []byte { - var ret [32]byte + var ret [HashSize]byte copy(ret[:], bt.Stem) ret[StemSize] = byte(i) return ret[:] diff --git a/trie/bintrie/stem_node_test.go b/trie/bintrie/stem_node_test.go index e0ffd5c3c844..d8d6844427de 100644 --- a/trie/bintrie/stem_node_test.go +++ b/trie/bintrie/stem_node_test.go @@ -251,27 +251,23 @@ func TestStemNodeGetValuesAtStem(t *testing.T) { } // Check that all values match - for i := 0; i < 256; i++ { + for i := range 256 { if !bytes.Equal(retrievedValues[i], values[i]) { t.Errorf("Value mismatch at index %d", i) } } - // GetValuesAtStem with different stem also returns the same values - // (implementation ignores the stem parameter) + // GetValuesAtStem with different stem should return nil differentStem := make([]byte, 31) differentStem[0] = 0xFF - retrievedValues2, err := node.GetValuesAtStem(differentStem, nil) + shouldBeNil, err := node.GetValuesAtStem(differentStem, nil) if err != nil { t.Fatalf("Failed to get values with different stem: %v", err) } - // Should still return the same values (stem is ignored) - for i := 0; i < 256; i++ { - if !bytes.Equal(retrievedValues2[i], values[i]) { - t.Errorf("Value mismatch at index %d with different stem", i) - } + if shouldBeNil != nil { + t.Error("Expected nil for different stem, got non-nil") } } diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go index 0a8bd325f58d..a7ee342b7452 100644 --- a/trie/bintrie/trie.go +++ b/trie/bintrie/trie.go @@ -33,6 +33,84 @@ import ( var errInvalidRootType = errors.New("invalid root type") +// ChunkedCode represents a sequence of HashSize-byte chunks of code (StemSize bytes of which +// are actual code, and NodeTypeBytes byte is the pushdata offset). +type ChunkedCode []byte + +// Copy the values here so as to avoid an import cycle +const ( + PUSH1 = byte(0x60) + PUSH32 = byte(0x7f) +) + +// ChunkifyCode generates the chunked version of an array representing EVM bytecode +// according to EIP-7864 specification. +// +// The code is divided into HashSize-byte chunks, where each chunk contains: +// - Byte 0: Metadata byte indicating the number of leading bytes that are PUSHDATA (0-StemSize) +// - Bytes 1-StemSize: Actual code bytes +// +// This format enables stateless clients to validate jump destinations within a chunk +// without requiring additional context. When a PUSH instruction's data spans multiple +// chunks, the metadata byte tells us how many bytes at the start of the chunk are +// part of the previous chunk's PUSH instruction data. +// +// For example: +// - If a chunk starts with regular code: metadata byte = 0 +// - If a PUSH32 instruction starts at byte 30 of chunk N: +// - Chunk N: normal, contains PUSH32 opcode + 1 byte of data +// - Chunk N+1: metadata = StemSize (entire chunk is PUSH data) +// - Chunk N+2: metadata = 1 (first byte is PUSH data, then normal code resumes) +// +// This chunking approach ensures that jump destination validity can be determined +// by examining only the chunk containing the potential JUMPDEST, making it ideal +// for stateless execution and verkle/binary tries. +// +// Reference: https://eips.ethereum.org/EIPS/eip-7864 +func ChunkifyCode(code []byte) ChunkedCode { + var ( + chunkOffset = 0 // offset in the chunk + chunkCount = len(code) / StemSize + codeOffset = 0 // offset in the code + ) + if len(code)%StemSize != 0 { + chunkCount++ + } + chunks := make([]byte, chunkCount*HashSize) + for i := 0; i < chunkCount; i++ { + // number of bytes to copy, StemSize unless the end of the code has been reached. + end := StemSize * (i + 1) + if len(code) < end { + end = len(code) + } + copy(chunks[i*HashSize+1:], code[StemSize*i:end]) // copy the code itself + + // chunk offset = taken from the last chunk. + if chunkOffset > StemSize { + // skip offset calculation if push data covers the whole chunk + chunks[i*HashSize] = StemSize + chunkOffset = 1 + continue + } + chunks[HashSize*i] = byte(chunkOffset) + chunkOffset = 0 + + // Check each instruction and update the offset it should be 0 unless + // a PUSH-N overflows. + for ; codeOffset < end; codeOffset++ { + if code[codeOffset] >= PUSH1 && code[codeOffset] <= PUSH32 { + codeOffset += int(code[codeOffset] - PUSH1 + 1) + if codeOffset+1 >= StemSize*(i+1) { + codeOffset++ + chunkOffset = codeOffset - StemSize*(i+1) + break + } + } + } + } + return chunks +} + // NewBinaryNode creates a new empty binary trie func NewBinaryNode() BinaryNode { return Empty{} @@ -114,7 +192,7 @@ func (t *BinaryTrie) GetAccount(addr common.Address) (*types.StateAccount, error ) switch r := t.root.(type) { case *InternalNode: - values, err = r.GetValuesAtStem(key[:31], t.nodeResolver) + values, err = r.GetValuesAtStem(key[:StemSize], t.nodeResolver) case *StemNode: values = r.Values case Empty: @@ -168,8 +246,8 @@ func (t *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error { var ( err error - basicData [32]byte - values = make([][]byte, NodeWidth) + basicData [HashSize]byte + values = make([][]byte, StemNodeWidth) stem = GetBinaryTreeKey(addr, zero[:]) ) binary.BigEndian.PutUint32(basicData[BasicDataCodeSizeOffset-1:], uint32(codeLen)) @@ -177,14 +255,14 @@ func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, // Because the balance is a max of 16 bytes, truncate // the extra values. This happens in devmode, where - // 0xff**32 is allocated to the developer account. + // 0xff**HashSize is allocated to the developer account. balanceBytes := acc.Balance.Bytes() // TODO: reduce the size of the allocation in devmode, then panic instead // of truncating. if len(balanceBytes) > 16 { balanceBytes = balanceBytes[16:] } - copy(basicData[32-len(balanceBytes):], balanceBytes[:]) + copy(basicData[HashSize-len(balanceBytes):], balanceBytes[:]) values[BasicDataLeafKey] = basicData[:] values[CodeHashLeafKey] = acc.CodeHash[:] @@ -205,11 +283,11 @@ func (t *BinaryTrie) UpdateStem(key []byte, values [][]byte) error { // database, a trie.MissingNodeError is returned. func (t *BinaryTrie) UpdateStorage(address common.Address, key, value []byte) error { k := GetBinaryTreeKeyStorageSlot(address, key) - var v [32]byte - if len(value) >= 32 { - copy(v[:], value[:32]) + var v [HashSize]byte + if len(value) >= HashSize { + copy(v[:], value[:HashSize]) } else { - copy(v[32-len(value):], value[:]) + copy(v[HashSize-len(value):], value[:]) } root, err := t.root.Insert(k, v[:], t.nodeResolver, 0) if err != nil { @@ -228,7 +306,7 @@ func (t *BinaryTrie) DeleteAccount(addr common.Address) error { // found in the database, a trie.MissingNodeError is returned. func (t *BinaryTrie) DeleteStorage(addr common.Address, key []byte) error { k := GetBinaryTreeKey(addr, key) - var zero [32]byte + var zero [HashSize]byte root, err := t.root.Insert(k, zero[:], t.nodeResolver, 0) if err != nil { return fmt.Errorf("DeleteStorage (%x) error: %v", addr, err) @@ -246,12 +324,12 @@ func (t *BinaryTrie) Hash() common.Hash { // Commit writes all nodes to the trie's memory database, tracking the internal // and external (for account tries) references. func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) { - root := t.root.(*InternalNode) nodeset := trienode.NewNodeSet(common.Hash{}) - err := root.CollectNodes(nil, func(path []byte, node BinaryNode) { + // The root can be any type of BinaryNode (InternalNode, StemNode, etc.) + err := t.root.CollectNodes(nil, func(path []byte, node BinaryNode) { serialized := SerializeNode(node) - nodeset.AddNode(path, trienode.NewNodeWithPrev(common.Hash{}, serialized, t.tracer.Get(path))) + nodeset.AddNode(path, trienode.NewNodeWithPrev(node.Hash(), serialized, t.tracer.Get(path))) }) if err != nil { panic(fmt.Errorf("CollectNodes failed: %v", err)) @@ -299,23 +377,23 @@ func (t *BinaryTrie) IsVerkle() bool { // Note: the basic data leaf needs to have been previously created for this to work func (t *BinaryTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error { var ( - chunks = trie.ChunkifyCode(code) + chunks = ChunkifyCode(code) values [][]byte key []byte err error ) - for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 { - groupOffset := (chunknr + 128) % 256 + for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+HashSize, chunknr+1 { + groupOffset := (chunknr + 128) % StemNodeWidth if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ { - values = make([][]byte, NodeWidth) - var offset [32]byte + values = make([][]byte, StemNodeWidth) + var offset [HashSize]byte binary.LittleEndian.PutUint64(offset[24:], chunknr+128) key = GetBinaryTreeKey(addr, offset[:]) } - values[groupOffset] = chunks[i : i+32] + values[groupOffset] = chunks[i : i+HashSize] - if groupOffset == 255 || len(chunks)-i <= 32 { - err = t.UpdateStem(key[:31], values) + if groupOffset == StemNodeWidth-1 || len(chunks)-i <= HashSize { + err = t.UpdateStem(key[:StemSize], values) if err != nil { return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err) diff --git a/trie/bintrie/trie_test.go b/trie/bintrie/trie_test.go index 84f76895494a..ca02cfaa1f3a 100644 --- a/trie/bintrie/trie_test.go +++ b/trie/bintrie/trie_test.go @@ -25,7 +25,7 @@ import ( ) var ( - zeroKey = [32]byte{} + zeroKey = [HashSize]byte{} oneKey = common.HexToHash("0101010101010101010101010101010101010101010101010101010101010101") twoKey = common.HexToHash("0202020202020202020202020202020202020202020202020202020202020202") threeKey = common.HexToHash("0303030303030303030303030303030303030303030303030303030303030303") @@ -158,8 +158,8 @@ func TestInsertDuplicateKey(t *testing.T) { func TestLargeNumberOfEntries(t *testing.T) { var err error tree := NewBinaryNode() - for i := range 256 { - var key [32]byte + for i := range StemNodeWidth { + var key [HashSize]byte key[0] = byte(i) tree, err = tree.Insert(key[:], ffKey[:], nil, 0) if err != nil { @@ -182,7 +182,7 @@ func TestMerkleizeMultipleEntries(t *testing.T) { common.HexToHash("8100000000000000000000000000000000000000000000000000000000000000").Bytes(), } for i, key := range keys { - var v [32]byte + var v [HashSize]byte binary.LittleEndian.PutUint64(v[:8], uint64(i)) tree, err = tree.Insert(key, v[:], nil, 0) if err != nil { diff --git a/trie/transition.go b/trie/transitiontrie/transition.go similarity index 87% rename from trie/transition.go rename to trie/transitiontrie/transition.go index c6eecd39376d..4c730220821f 100644 --- a/trie/transition.go +++ b/trie/transitiontrie/transition.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package trie +package transitiontrie import ( "fmt" @@ -22,8 +22,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/trie/trienode" - "github.com/ethereum/go-verkle" ) // TransitionTrie is a trie that implements a façade design pattern, presenting @@ -31,13 +32,16 @@ import ( // first from the overlay trie, and falls back to the base trie if the key isn't // found. All writes go to the overlay trie. type TransitionTrie struct { - overlay *VerkleTrie - base *SecureTrie + overlay *bintrie.BinaryTrie + base *trie.SecureTrie storage bool } // NewTransitionTrie creates a new TransitionTrie. -func NewTransitionTrie(base *SecureTrie, overlay *VerkleTrie, st bool) *TransitionTrie { +// Note: base can be nil when using TransitionTrie as a wrapper for BinaryTrie +// to work around import cycles. This is a temporary hack that should be +// refactored in future PRs (see core/state/reader.go for details). +func NewTransitionTrie(base *trie.SecureTrie, overlay *bintrie.BinaryTrie, st bool) *TransitionTrie { return &TransitionTrie{ overlay: overlay, base: base, @@ -46,12 +50,12 @@ func NewTransitionTrie(base *SecureTrie, overlay *VerkleTrie, st bool) *Transiti } // Base returns the base trie. -func (t *TransitionTrie) Base() *SecureTrie { +func (t *TransitionTrie) Base() *trie.SecureTrie { return t.base } // Overlay returns the overlay trie. -func (t *TransitionTrie) Overlay() *VerkleTrie { +func (t *TransitionTrie) Overlay() *bintrie.BinaryTrie { return t.overlay } @@ -61,7 +65,10 @@ func (t *TransitionTrie) GetKey(key []byte) []byte { if key := t.overlay.GetKey(key); key != nil { return key } - return t.base.GetKey(key) + if t.base != nil { + return t.base.GetKey(key) + } + return nil } // GetStorage returns the value for key stored in the trie. The value bytes must @@ -74,8 +81,11 @@ func (t *TransitionTrie) GetStorage(addr common.Address, key []byte) ([]byte, er if len(val) != 0 { return val, nil } - // TODO also insert value into overlay - return t.base.GetStorage(addr, key) + if t.base != nil { + // TODO also insert value into overlay + return t.base.GetStorage(addr, key) + } + return nil, nil } // PrefetchStorage attempts to resolve specific storage slots from the database @@ -102,7 +112,10 @@ func (t *TransitionTrie) GetAccount(address common.Address) (*types.StateAccount if data != nil { return data, nil } - return t.base.GetAccount(address) + if t.base != nil { + return t.base.GetAccount(address) + } + return nil, nil } // PrefetchAccount attempts to resolve specific accounts from the database @@ -174,7 +187,7 @@ func (t *TransitionTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSe // NodeIterator returns an iterator that returns nodes of the trie. Iteration // starts at the key after the given start key. -func (t *TransitionTrie) NodeIterator(startKey []byte) (NodeIterator, error) { +func (t *TransitionTrie) NodeIterator(startKey []byte) (trie.NodeIterator, error) { panic("not implemented") // TODO: Implement } @@ -197,14 +210,10 @@ func (t *TransitionTrie) IsVerkle() bool { // UpdateStem updates a group of values, given the stem they are using. If // a value already exists, it is overwritten. +// TODO: This is Verkle-specific and requires access to private fields. +// Not currently used in the codebase. func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error { - trie := t.overlay - switch root := trie.root.(type) { - case *verkle.InternalNode: - return root.InsertValuesAtStem(key, values, t.overlay.nodeResolver) - default: - panic("invalid root type") - } + panic("UpdateStem is not implemented for TransitionTrie") } // Copy creates a deep copy of the transition trie. diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go index dea210c0465a..2e42477b8d9e 100644 --- a/trie/utils/verkle.go +++ b/trie/utils/verkle.go @@ -45,6 +45,10 @@ var ( verkleNodeWidth = uint256.NewInt(256) codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset) mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(1), 248-uint(verkleNodeWidthLog2)) + CodeOffset = uint256.NewInt(128) + VerkleNodeWidth = uint256.NewInt(256) + HeaderStorageOffset = uint256.NewInt(64) + VerkleNodeWidthLog2 = 8 index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64] @@ -200,6 +204,22 @@ func CodeChunkKey(address []byte, chunk *uint256.Int) []byte { return GetTreeKey(address, treeIndex, subIndex) } +func GetTreeKeyCodeChunkIndices(chunk *uint256.Int) (*uint256.Int, byte) { + chunkOffset := new(uint256.Int).Add(CodeOffset, chunk) + treeIndex := new(uint256.Int).Div(chunkOffset, VerkleNodeWidth) + subIndexMod := new(uint256.Int).Mod(chunkOffset, VerkleNodeWidth) + var subIndex byte + if len(subIndexMod) != 0 { + subIndex = byte(subIndexMod[0]) + } + return treeIndex, subIndex +} + +func GetTreeKeyCodeChunk(address []byte, chunk *uint256.Int) []byte { + treeIndex, subIndex := GetTreeKeyCodeChunkIndices(chunk) + return GetTreeKey(address, treeIndex, subIndex) +} + func StorageIndex(storageKey []byte) (*uint256.Int, byte) { // If the storage slot is in the header, we need to add the header offset. var key uint256.Int @@ -297,3 +317,97 @@ func evaluateAddressPoint(address []byte) *verkle.Point { ret.Add(ret, index0Point) return ret } + +func EvaluateAddressPoint(address []byte) *verkle.Point { + if len(address) < 32 { + var aligned [32]byte + address = append(aligned[:32-len(address)], address...) + } + var poly [3]fr.Element + + poly[0].SetZero() + + // 32-byte address, interpreted as two little endian + // 16-byte numbers. + verkle.FromLEBytes(&poly[1], address[:16]) + verkle.FromLEBytes(&poly[2], address[16:]) + + cfg := verkle.GetConfig() + ret := cfg.CommitToPoly(poly[:], 0) + + // add a constant point + ret.Add(ret, index0Point) + + return ret +} + +func GetTreeKeyStorageSlotWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte { + treeIndex, subIndex := GetTreeKeyStorageSlotTreeIndexes(storageKey) + return GetTreeKeyWithEvaluatedAddess(evaluated, treeIndex, subIndex) +} + +func GetTreeKeyStorageSlotTreeIndexes(storageKey []byte) (*uint256.Int, byte) { + var pos uint256.Int + pos.SetBytes(storageKey) + + // If the storage slot is in the header, we need to add the header offset. + if pos.Cmp(codeStorageDelta) < 0 { + // This addition is always safe; it can't ever overflow since pos Date: Sat, 15 Nov 2025 16:04:21 +0200 Subject: [PATCH 209/228] ethapi: deref gasUsed pointer in eth_simulate log (#33192) Show the actual gas used in the block limit error so RPC clients see useful numbers. --- internal/ethapi/simulate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 0d1a59b371a4..e0732c327aa2 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -377,7 +377,7 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state vm.StateDB, head call.Gas = (*hexutil.Uint64)(&remaining) } if *gasUsed+uint64(*call.Gas) > blockContext.GasLimit { - return &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", gasUsed, blockContext.GasLimit)} + return &blockGasLimitReachedError{fmt.Sprintf("block gas limit reached: %d >= %d", *gasUsed, blockContext.GasLimit)} } if err := call.CallDefaults(sim.gp.Gas(), header.BaseFee, sim.chainConfig.ChainID); err != nil { return err From 05c26af90f6d837e7abae80664b8a05817025bd2 Mon Sep 17 00:00:00 2001 From: maradini77 <140460067+maradini77@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:54:53 +0100 Subject: [PATCH 210/228] eth: fix panic in randomDuration when min equals max (#33193) Fixes a potential panic in `randomDuration` when `min == max` by handling the edge case explicitly. --- eth/dropper.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eth/dropper.go b/eth/dropper.go index 51f2a7a95a3f..dada5d07c04f 100644 --- a/eth/dropper.go +++ b/eth/dropper.go @@ -145,6 +145,9 @@ func randomDuration(min, max time.Duration) time.Duration { if min > max { panic("min duration must be less than or equal to max duration") } + if min == max { + return min + } return time.Duration(mrand.Int63n(int64(max-min)) + int64(min)) } From 38cb879295bb9d71fe402a04f9c850458b721ab4 Mon Sep 17 00:00:00 2001 From: wit liu Date: Thu, 20 Nov 2025 02:00:31 +0800 Subject: [PATCH 211/228] core: initialize tracer before DAO fork logic (#33214) `StateDB` lacks recording functionality, so it has been replaced with `tractStateDB` and advanced --- core/state_processor.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index b66046f50178..b4b22e4318b4 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -69,9 +69,14 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg gp = new(GasPool).AddGas(block.GasLimit()) ) + var tracingStateDB = vm.StateDB(statedb) + if hooks := cfg.Tracer; hooks != nil { + tracingStateDB = state.NewHookedState(statedb, hooks) + } + // Mutate the block and state according to any hard-fork specs if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 { - misc.ApplyDAOHardFork(statedb) + misc.ApplyDAOHardFork(tracingStateDB) } var ( context vm.BlockContext @@ -79,10 +84,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg ) // Apply pre-execution system calls. - var tracingStateDB = vm.StateDB(statedb) - if hooks := cfg.Tracer; hooks != nil { - tracingStateDB = state.NewHookedState(statedb, hooks) - } context = NewEVMBlockContext(header, p.chain, nil) evm := vm.NewEVM(context, tracingStateDB, config, cfg) From fb72d88fa9cf1602cd8fb756142860253ef381ad Mon Sep 17 00:00:00 2001 From: phrwlk Date: Mon, 24 Nov 2025 15:01:00 +0200 Subject: [PATCH 212/228] cmd/utils: make datadir.minfreedisk an IntFlag (#33252) --- cmd/utils/flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 5a7e40767cfb..c95efd9fd7d2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -114,7 +114,7 @@ var ( Usage: "Root directory for era1 history (default = inside ancient/chain)", Category: flags.EthCategory, } - MinFreeDiskSpaceFlag = &flags.DirectoryFlag{ + MinFreeDiskSpaceFlag = &cli.IntFlag{ Name: "datadir.minfreedisk", Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)", Category: flags.EthCategory, From 333f3087f80a60d361f9d4504fb010c3c686ed2b Mon Sep 17 00:00:00 2001 From: Cedrick AH Date: Mon, 24 Nov 2025 14:50:48 +0100 Subject: [PATCH 213/228] core, cmd: removed tablewriter from the dependencies (#33218) Fix #33212. This PR remove `github.com/olekukonko/tablewriter` from dependencies and use a naive stub implementation. `github.com/olekukonko/tablewriter` is used to format database inspection output neatly. However, it requires custom adjustments for TinyGo and is incompatible with the latest version. --------- Co-authored-by: MariusVanDerWijden --- cmd/geth/dbcmd.go | 3 +- cmd/keeper/go.mod | 3 -- cmd/keeper/go.sum | 7 ---- core/rawdb/database.go | 2 +- ...iter_tinygo.go => database_tablewriter.go} | 17 +++++----- ...o_test.go => database_tablewriter_test.go} | 13 +++----- core/rawdb/database_tablewriter_unix.go | 33 ------------------- go.mod | 1 - go.sum | 3 -- 9 files changed, 16 insertions(+), 66 deletions(-) rename core/rawdb/{database_tablewriter_tinygo.go => database_tablewriter.go} (93%) rename core/rawdb/{database_tablewriter_tinygo_test.go => database_tablewriter_test.go} (94%) delete mode 100644 core/rawdb/database_tablewriter_unix.go diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index c57add065634..fb688793e3ec 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -41,7 +41,6 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/triedb" - "github.com/olekukonko/tablewriter" "github.com/urfave/cli/v2" ) @@ -760,7 +759,7 @@ func showMetaData(ctx *cli.Context) error { data = append(data, []string{"headHeader.Root", fmt.Sprintf("%v", h.Root)}) data = append(data, []string{"headHeader.Number", fmt.Sprintf("%d (%#x)", h.Number, h.Number)}) } - table := tablewriter.NewWriter(os.Stdout) + table := rawdb.NewTableWriter(os.Stdout) table.SetHeader([]string{"Field", "Value"}) table.AppendBulk(data) table.Render() diff --git a/cmd/keeper/go.mod b/cmd/keeper/go.mod index 9486347b1fac..f47dc54c0660 100644 --- a/cmd/keeper/go.mod +++ b/cmd/keeper/go.mod @@ -28,11 +28,8 @@ require ( github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect github.com/minio/sha256-simd v1.0.0 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/rivo/uniseg v0.2.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect diff --git a/cmd/keeper/go.sum b/cmd/keeper/go.sum index ad4c98c4b3b9..5744ae209300 100644 --- a/cmd/keeper/go.sum +++ b/cmd/keeper/go.sum @@ -80,17 +80,12 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -105,8 +100,6 @@ github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJf github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= diff --git a/core/rawdb/database.go b/core/rawdb/database.go index d5c0f0aab295..131e370650bc 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -650,7 +650,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { total.Add(uint64(ancient.size())) } - table := newTableWriter(os.Stdout) + table := NewTableWriter(os.Stdout) table.SetHeader([]string{"Database", "Category", "Size", "Items"}) table.SetFooter([]string{"", "Total", common.StorageSize(total.Load()).String(), fmt.Sprintf("%d", count.Load())}) table.AppendBulk(stats) diff --git a/core/rawdb/database_tablewriter_tinygo.go b/core/rawdb/database_tablewriter.go similarity index 93% rename from core/rawdb/database_tablewriter_tinygo.go rename to core/rawdb/database_tablewriter.go index 2f8e456fd51d..e1cda5c93fa5 100644 --- a/core/rawdb/database_tablewriter_tinygo.go +++ b/core/rawdb/database_tablewriter.go @@ -14,10 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// TODO: naive stub implementation for tablewriter - -//go:build tinygo -// +build tinygo +// Naive stub implementation for tablewriter package rawdb @@ -40,7 +37,7 @@ type Table struct { rows [][]string } -func newTableWriter(w io.Writer) *Table { +func NewTableWriter(w io.Writer) *Table { return &Table{out: w} } @@ -89,6 +86,7 @@ func (t *Table) render() error { rowSeparator := t.buildRowSeparator(widths) if len(t.headers) > 0 { + fmt.Fprintln(t.out, rowSeparator) t.printRow(t.headers, widths) fmt.Fprintln(t.out, rowSeparator) } @@ -100,6 +98,7 @@ func (t *Table) render() error { if len(t.footer) > 0 { fmt.Fprintln(t.out, rowSeparator) t.printRow(t.footer, widths) + fmt.Fprintln(t.out, rowSeparator) } return nil @@ -172,21 +171,22 @@ func (t *Table) calculateColumnWidths() []int { // // It generates a string with dashes (-) for each column width, joined by plus signs (+). // -// Example output: "----------+--------+-----------" +// Example output: "+----------+--------+-----------+" func (t *Table) buildRowSeparator(widths []int) string { parts := make([]string, len(widths)) for i, w := range widths { parts[i] = strings.Repeat("-", w) } - return strings.Join(parts, "+") + return "+" + strings.Join(parts, "+") + "+" } // printRow outputs a single row to the table writer. // // Each cell is padded with spaces and separated by pipe characters (|). // -// Example output: " Database | Size | Items " +// Example output: "| Database | Size | Items |" func (t *Table) printRow(row []string, widths []int) { + fmt.Fprintf(t.out, "|") for i, cell := range row { if i > 0 { fmt.Fprint(t.out, "|") @@ -204,5 +204,6 @@ func (t *Table) printRow(row []string, widths []int) { fmt.Fprintf(t.out, "%s%s%s", leftPadding, cell, rightPadding) } + fmt.Fprintf(t.out, "|") fmt.Fprintln(t.out) } diff --git a/core/rawdb/database_tablewriter_tinygo_test.go b/core/rawdb/database_tablewriter_test.go similarity index 94% rename from core/rawdb/database_tablewriter_tinygo_test.go rename to core/rawdb/database_tablewriter_test.go index 3bcf93832b46..e9de5d8ce864 100644 --- a/core/rawdb/database_tablewriter_tinygo_test.go +++ b/core/rawdb/database_tablewriter_test.go @@ -14,9 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -//go:build tinygo -// +build tinygo - package rawdb import ( @@ -27,7 +24,7 @@ import ( func TestTableWriterTinyGo(t *testing.T) { var buf bytes.Buffer - table := newTableWriter(&buf) + table := NewTableWriter(&buf) headers := []string{"Database", "Size", "Items", "Status"} rows := [][]string{ @@ -51,7 +48,7 @@ func TestTableWriterValidationErrors(t *testing.T) { // Test missing headers t.Run("MissingHeaders", func(t *testing.T) { var buf bytes.Buffer - table := newTableWriter(&buf) + table := NewTableWriter(&buf) rows := [][]string{{"x", "y", "z"}} @@ -66,7 +63,7 @@ func TestTableWriterValidationErrors(t *testing.T) { t.Run("NotEnoughRowColumns", func(t *testing.T) { var buf bytes.Buffer - table := newTableWriter(&buf) + table := NewTableWriter(&buf) headers := []string{"A", "B", "C"} badRows := [][]string{ @@ -85,7 +82,7 @@ func TestTableWriterValidationErrors(t *testing.T) { t.Run("TooManyRowColumns", func(t *testing.T) { var buf bytes.Buffer - table := newTableWriter(&buf) + table := NewTableWriter(&buf) headers := []string{"A", "B", "C"} badRows := [][]string{ @@ -105,7 +102,7 @@ func TestTableWriterValidationErrors(t *testing.T) { // Test mismatched footer columns t.Run("MismatchedFooterColumns", func(t *testing.T) { var buf bytes.Buffer - table := newTableWriter(&buf) + table := NewTableWriter(&buf) headers := []string{"A", "B", "C"} rows := [][]string{{"x", "y", "z"}} diff --git a/core/rawdb/database_tablewriter_unix.go b/core/rawdb/database_tablewriter_unix.go deleted file mode 100644 index 8bec5396e876..000000000000 --- a/core/rawdb/database_tablewriter_unix.go +++ /dev/null @@ -1,33 +0,0 @@ -// 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 . - -//go:build !tinygo -// +build !tinygo - -package rawdb - -import ( - "io" - - "github.com/olekukonko/tablewriter" -) - -// Re-export the real tablewriter types and functions -type Table = tablewriter.Table - -func newTableWriter(w io.Writer) *Table { - return tablewriter.NewWriter(w) -} diff --git a/go.mod b/go.mod index 3590a5492932..9c9e873a5e72 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,6 @@ require ( github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.20 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 - github.com/olekukonko/tablewriter v0.0.5 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 github.com/pion/stun/v2 v2.0.0 github.com/protolambda/bls12-381-util v0.1.0 diff --git a/go.sum b/go.sum index 6ecb0b7ec348..89dd9af52a0e 100644 --- a/go.sum +++ b/go.sum @@ -257,7 +257,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= @@ -274,8 +273,6 @@ github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcou github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= From 3680ece97e04a7ac819be87a1d3e48f2985918f8 Mon Sep 17 00:00:00 2001 From: georgehao Date: Mon, 24 Nov 2025 22:02:13 +0800 Subject: [PATCH 214/228] eth/catalyst: remove the outdated comments of ForkchoiceUpdatedV1 (#33251) --- eth/catalyst/api.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 75b263bf6b60..f88acb5cff78 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -163,9 +163,6 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI { // // We try to set our blockchain to the headBlock. // -// If the method is called with an empty head block: we return success, which can be used -// to check if the engine API is enabled. -// // If the total difficulty was not reached: we return INVALID. // // If the finalizedBlockHash is set: we check if we have the finalizedBlockHash in our db, From 906e7c863b4f323e4bcfa8fbd26c5cd32fcd414c Mon Sep 17 00:00:00 2001 From: Rizky Ikwan Date: Mon, 24 Nov 2025 15:30:44 +0100 Subject: [PATCH 215/228] consensus/beacon: fix blob gas error message formatting (#33201) --- consensus/beacon/consensus.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 84926c3d0b67..dbba73947f32 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -258,11 +258,11 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa if !cancun { switch { case header.ExcessBlobGas != nil: - return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas) + return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", *header.ExcessBlobGas) case header.BlobGasUsed != nil: - return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed) + return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", *header.BlobGasUsed) case header.ParentBeaconRoot != nil: - return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot) + return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", *header.ParentBeaconRoot) } } else { if header.ParentBeaconRoot == nil { From 4730e130277eebf5eaa46b4c834a1246a0a44a99 Mon Sep 17 00:00:00 2001 From: sashass1315 Date: Tue, 25 Nov 2025 04:23:30 +0200 Subject: [PATCH 216/228] core/txpool/blobpool: drain and signal pending conversion tasks on shutdown (#33260) --- core/txpool/blobpool/conversion.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/txpool/blobpool/conversion.go b/core/txpool/blobpool/conversion.go index 95828d83b2c6..80b97af5d7ae 100644 --- a/core/txpool/blobpool/conversion.go +++ b/core/txpool/blobpool/conversion.go @@ -183,6 +183,15 @@ func (q *conversionQueue) loop() { log.Debug("Waiting for blobpool billy conversion to exit") <-q.billyTaskDone } + // Signal any tasks that were queued for the next batch but never started + // so callers blocked in convert() receive an error instead of hanging. + for _, t := range txTasks { + // Best-effort notify; t.done is a buffered channel of size 1 + // created by convert(), and we send exactly once per task. + t.done <- errors.New("conversion queue closed") + } + // Drop references to allow GC of the backing array. + txTasks = txTasks[:0] return } } From 9b967140b0211a22a55eabef58714f0f814024bc Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Tue, 25 Nov 2025 04:24:17 +0200 Subject: [PATCH 217/228] core/rawdb: fix underflow in freezer inspect for empty ancients (#33203) --- core/rawdb/ancient_utils.go | 18 ++++++++++++------ core/rawdb/database.go | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/core/rawdb/ancient_utils.go b/core/rawdb/ancient_utils.go index b940d910402c..7af3d2e19762 100644 --- a/core/rawdb/ancient_utils.go +++ b/core/rawdb/ancient_utils.go @@ -34,14 +34,10 @@ type freezerInfo struct { name string // The identifier of freezer head uint64 // The number of last stored item in the freezer tail uint64 // The number of first stored item in the freezer + count uint64 // The number of stored items in the freezer sizes []tableSize // The storage size per table } -// count returns the number of stored items in the freezer. -func (info *freezerInfo) count() uint64 { - return info.head - info.tail + 1 -} - // size returns the storage size of the entire freezer. func (info *freezerInfo) size() common.StorageSize { var total common.StorageSize @@ -65,7 +61,11 @@ func inspect(name string, order map[string]freezerTableConfig, reader ethdb.Anci if err != nil { return freezerInfo{}, err } - info.head = ancients - 1 + if ancients > 0 { + info.head = ancients - 1 + } else { + info.head = 0 + } // Retrieve the number of first stored item tail, err := reader.Tail() @@ -73,6 +73,12 @@ func inspect(name string, order map[string]freezerTableConfig, reader ethdb.Anci return freezerInfo{}, err } info.tail = tail + + if ancients == 0 { + info.count = 0 + } else { + info.count = info.head - info.tail + 1 + } return info, nil } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 131e370650bc..826039180252 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -644,7 +644,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { fmt.Sprintf("Ancient store (%s)", strings.Title(ancient.name)), strings.Title(table.name), table.size.String(), - fmt.Sprintf("%d", ancient.count()), + fmt.Sprintf("%d", ancient.count), }) } total.Add(uint64(ancient.size())) From c91391347bd4a8244819e9646807e0b87eecfe7c Mon Sep 17 00:00:00 2001 From: Bashmunta Date: Tue, 25 Nov 2025 14:34:58 +0200 Subject: [PATCH 218/228] cmd/geth: skip resolver for zero-commitment verkle children (#33265) --- cmd/geth/verkle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/geth/verkle.go b/cmd/geth/verkle.go index 67dc7257c011..c064d70ababc 100644 --- a/cmd/geth/verkle.go +++ b/cmd/geth/verkle.go @@ -76,10 +76,10 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error for i, child := range node.Children() { childC := child.Commit().Bytes() - childS, err := resolver(childC[:]) if bytes.Equal(childC[:], zero[:]) { continue } + childS, err := resolver(childC[:]) if err != nil { return fmt.Errorf("could not find child %x in db: %w", childC, err) } From ac0355163da5607b0a716a2a267d2229e242dfbd Mon Sep 17 00:00:00 2001 From: Rizky Ikwan Date: Wed, 26 Nov 2025 09:05:30 +0100 Subject: [PATCH 219/228] consensus/misc: fix blob gas error message formatting (#33275) Dereference the `header.BlobGasUsed` pointer when formatting the error message in `VerifyEIP4844Header`. --- consensus/misc/eip4844/eip4844.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index c1a21195e320..47b54d2e8529 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -115,7 +115,7 @@ func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Heade return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, bcfg.maxBlobGas()) } if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 { - return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob) + return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", *header.BlobGasUsed, params.BlobTxBlobGasPerBlob) } // Verify the excessBlobGas is correct based on the parent header From 99044e57a734f09eac2a187649404395703b5f46 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 26 Nov 2025 16:07:16 +0800 Subject: [PATCH 220/228] triedb/pathdb: implement iterator of history index (#32981) This change introduces an iterator for the history index in the pathdb. It provides sequential access to historical entries, enabling efficient scanning and future features built on top of historical state traversal. --- triedb/pathdb/history_index.go | 43 +-- triedb/pathdb/history_index_block.go | 54 +-- triedb/pathdb/history_index_iterator.go | 359 +++++++++++++++++++ triedb/pathdb/history_index_iterator_test.go | 297 +++++++++++++++ 4 files changed, 684 insertions(+), 69 deletions(-) create mode 100644 triedb/pathdb/history_index_iterator.go create mode 100644 triedb/pathdb/history_index_iterator_test.go diff --git a/triedb/pathdb/history_index.go b/triedb/pathdb/history_index.go index 5b4c91d7e61e..87b6e377afe2 100644 --- a/triedb/pathdb/history_index.go +++ b/triedb/pathdb/history_index.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" "math" - "sort" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" @@ -119,30 +118,34 @@ func (r *indexReader) refresh() error { return nil } +// newIterator creates an iterator for traversing the index entries. +func (r *indexReader) newIterator() *indexIterator { + return newIndexIterator(r.descList, func(id uint32) (*blockReader, error) { + br, ok := r.readers[id] + if !ok { + var err error + br, err = newBlockReader(readStateIndexBlock(r.state, r.db, id)) + if err != nil { + return nil, err + } + r.readers[id] = br + } + return br, nil + }) +} + // readGreaterThan locates the first element that is greater than the specified // id. If no such element is found, MaxUint64 is returned. func (r *indexReader) readGreaterThan(id uint64) (uint64, error) { - index := sort.Search(len(r.descList), func(i int) bool { - return id < r.descList[i].max - }) - if index == len(r.descList) { - return math.MaxUint64, nil + it := r.newIterator() + found := it.SeekGT(id) + if err := it.Error(); err != nil { + return 0, err } - desc := r.descList[index] - - br, ok := r.readers[desc.id] - if !ok { - var err error - blob := readStateIndexBlock(r.state, r.db, desc.id) - br, err = newBlockReader(blob) - if err != nil { - return 0, err - } - r.readers[desc.id] = br + if !found { + return math.MaxUint64, nil } - // The supplied ID is not greater than block.max, ensuring that an element - // satisfying the condition can be found. - return br.readGreaterThan(id) + return it.ID(), nil } // indexWriter is responsible for writing index data for a specific state (either diff --git a/triedb/pathdb/history_index_block.go b/triedb/pathdb/history_index_block.go index 5abdee682ad6..7b59c8e88215 100644 --- a/triedb/pathdb/history_index_block.go +++ b/triedb/pathdb/history_index_block.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "math" - "sort" ) const ( @@ -164,58 +163,15 @@ func newBlockReader(blob []byte) (*blockReader, error) { // readGreaterThan locates the first element in the block that is greater than // the specified value. If no such element is found, MaxUint64 is returned. func (br *blockReader) readGreaterThan(id uint64) (uint64, error) { - var err error - index := sort.Search(len(br.restarts), func(i int) bool { - item, n := binary.Uvarint(br.data[br.restarts[i]:]) - if n <= 0 { - err = fmt.Errorf("failed to decode item at restart %d", br.restarts[i]) - } - return item > id - }) - if err != nil { + it := newBlockIterator(br.data, br.restarts) + found := it.SeekGT(id) + if err := it.Error(); err != nil { return 0, err } - if index == 0 { - item, _ := binary.Uvarint(br.data[br.restarts[0]:]) - return item, nil - } - var ( - start int - limit int - result uint64 - ) - if index == len(br.restarts) { - // The element being searched falls within the last restart section, - // there is no guarantee such element can be found. - start = int(br.restarts[len(br.restarts)-1]) - limit = len(br.data) - } else { - // The element being searched falls within the non-last restart section, - // such element can be found for sure. - start = int(br.restarts[index-1]) - limit = int(br.restarts[index]) - } - pos := start - for pos < limit { - x, n := binary.Uvarint(br.data[pos:]) - if pos == start { - result = x - } else { - result += x - } - if result > id { - return result, nil - } - pos += n - } - // The element which is greater than specified id is not found. - if index == len(br.restarts) { + if !found { return math.MaxUint64, nil } - // The element which is the first one greater than the specified id - // is exactly the one located at the restart point. - item, _ := binary.Uvarint(br.data[br.restarts[index]:]) - return item, nil + return it.ID(), nil } type blockWriter struct { diff --git a/triedb/pathdb/history_index_iterator.go b/triedb/pathdb/history_index_iterator.go new file mode 100644 index 000000000000..1ccb39ad0970 --- /dev/null +++ b/triedb/pathdb/history_index_iterator.go @@ -0,0 +1,359 @@ +// 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 id + }) + if err != nil { + it.setErr(err) + return false + } + if index == 0 { + item, n := binary.Uvarint(it.data[it.restarts[0]:]) + + // If the restart size is 1, then the restart pointer shouldn't be 0. + // It's not practical and should be denied in the first place. + it.set(int(it.restarts[0])+n, 0, item) + return true + } + var ( + start int + limit int + restartIndex int // The restart section being searched below + ) + if index == len(it.restarts) { + // The element being searched falls within the last restart section, + // there is no guarantee such element can be found. + start = int(it.restarts[len(it.restarts)-1]) + limit = len(it.data) + restartIndex = len(it.restarts) - 1 + } else { + // The element being searched falls within the non-last restart section, + // such element can be found for sure. + start = int(it.restarts[index-1]) + limit = int(it.restarts[index]) + restartIndex = index - 1 + } + var ( + result uint64 + pos = start + ) + for pos < limit { + x, n := binary.Uvarint(it.data[pos:]) + if n <= 0 { + it.setErr(fmt.Errorf("failed to decode item at pos %d", pos)) + return false + } + if pos == start { + result = x + } else { + result += x + } + pos += n + + if result > id { + if pos == limit { + it.set(pos, restartIndex+1, result) + } else { + it.set(pos, restartIndex, result) + } + return true + } + } + // The element which is greater than specified id is not found. + if index == len(it.restarts) { + it.reset() + return false + } + // The element which is the first one greater than the specified id + // is exactly the one located at the restart point. + item, n := binary.Uvarint(it.data[it.restarts[index]:]) + it.set(int(it.restarts[index])+n, index, item) + return true +} + +func (it *blockIterator) init() { + if it.dataPtr != -1 { + return + } + it.dataPtr = 0 + it.restartPtr = 0 +} + +// Next implements the HistoryIndexIterator, moving the iterator to the next +// element. If the iterator has been exhausted, and boolean with false should +// be returned. +func (it *blockIterator) Next() bool { + if it.exhausted || it.err != nil { + return false + } + it.init() + + // Decode the next element pointed by the iterator + v, n := binary.Uvarint(it.data[it.dataPtr:]) + if n <= 0 { + it.setErr(fmt.Errorf("failed to decode item at pos %d", it.dataPtr)) + return false + } + + var val uint64 + if it.dataPtr == int(it.restarts[it.restartPtr]) { + val = v + } else { + val = it.id + v + } + + // Move to the next restart section if the data pointer crosses the boundary + nextRestartPtr := it.restartPtr + if it.restartPtr < len(it.restarts)-1 && it.dataPtr+n == int(it.restarts[it.restartPtr+1]) { + nextRestartPtr = it.restartPtr + 1 + } + it.set(it.dataPtr+n, nextRestartPtr, val) + + return true +} + +// ID implements HistoryIndexIterator, returning the id of the element where the +// iterator is positioned at. +func (it *blockIterator) ID() uint64 { + return it.id +} + +// Error implements HistoryIndexIterator, returning any accumulated error. +// Exhausting all the elements is not considered to be an error. +func (it *blockIterator) Error() error { return it.err } + +// blockLoader defines the method to retrieve the specific block for reading. +type blockLoader func(id uint32) (*blockReader, error) + +// indexIterator is an iterator to traverse the history indices belonging to the +// specific state entry. +type indexIterator struct { + // immutable fields + descList []*indexBlockDesc + loader blockLoader + + // mutable fields + blockIt *blockIterator + blockPtr int + exhausted bool + err error +} + +func newIndexIterator(descList []*indexBlockDesc, loader blockLoader) *indexIterator { + it := &indexIterator{ + descList: descList, + loader: loader, + } + it.reset() + return it +} + +func (it *indexIterator) setErr(err error) { + if it.err != nil { + return + } + it.err = err +} + +func (it *indexIterator) reset() { + it.blockIt = nil + it.blockPtr = -1 + it.exhausted = false + it.err = nil + + if len(it.descList) == 0 { + it.exhausted = true + } +} + +func (it *indexIterator) open(blockPtr int) error { + id := it.descList[blockPtr].id + br, err := it.loader(id) + if err != nil { + return err + } + it.blockIt = newBlockIterator(br.data, br.restarts) + it.blockPtr = blockPtr + return nil +} + +// SeekGT moves the iterator to the first element whose id is greater than the +// given number. It returns whether such element exists. +// +// Note, this operation will unset the exhausted status and subsequent traversal +// is allowed. +func (it *indexIterator) SeekGT(id uint64) bool { + if it.err != nil { + return false + } + index := sort.Search(len(it.descList), func(i int) bool { + return id < it.descList[i].max + }) + if index == len(it.descList) { + return false + } + it.exhausted = false + + if it.blockIt == nil || it.blockPtr != index { + if err := it.open(index); err != nil { + it.setErr(err) + return false + } + } + return it.blockIt.SeekGT(id) +} + +func (it *indexIterator) init() error { + if it.blockIt != nil { + return nil + } + return it.open(0) +} + +// Next implements the HistoryIndexIterator, moving the iterator to the next +// element. If the iterator has been exhausted, and boolean with false should +// be returned. +func (it *indexIterator) Next() bool { + if it.exhausted || it.err != nil { + return false + } + if err := it.init(); err != nil { + it.setErr(err) + return false + } + + if it.blockIt.Next() { + return true + } + if it.blockPtr == len(it.descList)-1 { + it.exhausted = true + return false + } + if err := it.open(it.blockPtr + 1); err != nil { + it.setErr(err) + return false + } + return it.blockIt.Next() +} + +// Error implements HistoryIndexIterator, returning any accumulated error. +// Exhausting all the elements is not considered to be an error. +func (it *indexIterator) Error() error { + if it.err != nil { + return it.err + } + if it.blockIt != nil { + return it.blockIt.Error() + } + return nil +} + +// ID implements HistoryIndexIterator, returning the id of the element where the +// iterator is positioned at. +func (it *indexIterator) ID() uint64 { + return it.blockIt.ID() +} diff --git a/triedb/pathdb/history_index_iterator_test.go b/triedb/pathdb/history_index_iterator_test.go new file mode 100644 index 000000000000..da60dc6e8f28 --- /dev/null +++ b/triedb/pathdb/history_index_iterator_test.go @@ -0,0 +1,297 @@ +// 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 input + }) + var ( + exp bool + expVal uint64 + remains []uint64 + ) + if index == len(elements) { + exp = false + } else { + exp = true + expVal = elements[index] + if index < len(elements) { + remains = elements[index+1:] + } + } + if err := checkSeekGT(it, input, exp, expVal); err != nil { + t.Fatal(err) + } + if exp { + if err := checkNext(it, remains); err != nil { + t.Fatal(err) + } + } + } +} + +func TestIndexIteratorSeekGT(t *testing.T) { + ident := newAccountIdent(common.Hash{0x1}) + + dbA := rawdb.NewMemoryDatabase() + testIndexIterator(t, ident, dbA, makeTestIndexBlocks(dbA, ident, 1)) + + dbB := rawdb.NewMemoryDatabase() + testIndexIterator(t, ident, dbB, makeTestIndexBlocks(dbB, ident, 3*indexBlockEntriesCap)) + + dbC := rawdb.NewMemoryDatabase() + testIndexIterator(t, ident, dbC, makeTestIndexBlocks(dbC, ident, indexBlockEntriesCap-1)) + + dbD := rawdb.NewMemoryDatabase() + testIndexIterator(t, ident, dbD, makeTestIndexBlocks(dbD, ident, indexBlockEntriesCap+1)) +} + +func testIndexIterator(t *testing.T, stateIdent stateIdent, db ethdb.Database, elements []uint64) { + ir, err := newIndexReader(db, stateIdent) + if err != nil { + t.Fatalf("Failed to open the index reader, %v", err) + } + it := newIndexIterator(ir.descList, func(id uint32) (*blockReader, error) { + return newBlockReader(readStateIndexBlock(stateIdent, db, id)) + }) + + for i := 0; i < 128; i++ { + var input uint64 + if rand.Intn(2) == 0 { + input = elements[rand.Intn(len(elements))] + } else { + input = uint64(rand.Uint32()) + } + index := sort.Search(len(elements), func(i int) bool { + return elements[i] > input + }) + var ( + exp bool + expVal uint64 + remains []uint64 + ) + if index == len(elements) { + exp = false + } else { + exp = true + expVal = elements[index] + if index < len(elements) { + remains = elements[index+1:] + } + } + if err := checkSeekGT(it, input, exp, expVal); err != nil { + t.Fatal(err) + } + if exp { + if err := checkNext(it, remains); err != nil { + t.Fatal(err) + } + } + } +} + +func TestBlockIteratorTraversal(t *testing.T) { + /* 0-size index block is not allowed + + data, elements := makeTestIndexBlock(0) + testBlockIterator(t, data, elements) + */ + + data, elements := makeTestIndexBlock(1) + testBlockIteratorTraversal(t, data, elements) + + data, elements = makeTestIndexBlock(indexBlockRestartLen) + testBlockIteratorTraversal(t, data, elements) + + data, elements = makeTestIndexBlock(3 * indexBlockRestartLen) + testBlockIteratorTraversal(t, data, elements) + + data, elements = makeTestIndexBlock(indexBlockEntriesCap) + testBlockIteratorTraversal(t, data, elements) +} + +func testBlockIteratorTraversal(t *testing.T, data []byte, elements []uint64) { + br, err := newBlockReader(data) + if err != nil { + t.Fatalf("Failed to open the block for reading, %v", err) + } + it := newBlockIterator(br.data, br.restarts) + + if err := checkNext(it, elements); err != nil { + t.Fatal(err) + } +} + +func TestIndexIteratorTraversal(t *testing.T) { + ident := newAccountIdent(common.Hash{0x1}) + + dbA := rawdb.NewMemoryDatabase() + testIndexIteratorTraversal(t, ident, dbA, makeTestIndexBlocks(dbA, ident, 1)) + + dbB := rawdb.NewMemoryDatabase() + testIndexIteratorTraversal(t, ident, dbB, makeTestIndexBlocks(dbB, ident, 3*indexBlockEntriesCap)) + + dbC := rawdb.NewMemoryDatabase() + testIndexIteratorTraversal(t, ident, dbC, makeTestIndexBlocks(dbC, ident, indexBlockEntriesCap-1)) + + dbD := rawdb.NewMemoryDatabase() + testIndexIteratorTraversal(t, ident, dbD, makeTestIndexBlocks(dbD, ident, indexBlockEntriesCap+1)) +} + +func testIndexIteratorTraversal(t *testing.T, stateIdent stateIdent, db ethdb.KeyValueReader, elements []uint64) { + ir, err := newIndexReader(db, stateIdent) + if err != nil { + t.Fatalf("Failed to open the index reader, %v", err) + } + it := newIndexIterator(ir.descList, func(id uint32) (*blockReader, error) { + return newBlockReader(readStateIndexBlock(stateIdent, db, id)) + }) + if err := checkNext(it, elements); err != nil { + t.Fatal(err) + } +} From 3690c9b94577c33e9962408b4cf6932720633e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Wed, 26 Nov 2025 11:07:56 +0100 Subject: [PATCH 221/228] beacon: update beacon light client for fusaka (#33272) This PR adds the "FULU" beacon chain config entries for all networks and fixes the select statements that choose the appropriate engine API call versions (no new version there but the "default" was always the first version; now it's the latest version so no need to change unless there is actually a new version). New beacon checkpoints are also added for mainnet, sepolia and hoodi (not for holesky because it's not finalizing at the moment). Note that though unrelated to fusaka, the log indexer checkpoints are also updated for mainnet (not for the other testnets, mainly because I only have mainnet synced here on my travel SSD; this should be fine though because the index is also reverse generated for a year by default so it does not really affect the indexing time) Links for the new checkpoints: https://beaconcha.in/slot/13108192 https://light-sepolia.beaconcha.in/slot/9032384 https://hoodi.beaconcha.in/slot/1825728 --- beacon/blsync/engineclient.go | 28 ++++++++-------- beacon/params/checkpoint_hoodi.hex | 2 +- beacon/params/checkpoint_mainnet.hex | 2 +- beacon/params/checkpoint_sepolia.hex | 2 +- beacon/params/networks.go | 41 +++++++++++++----------- core/filtermaps/checkpoints_mainnet.json | 29 ++++++++++++++++- 6 files changed, 67 insertions(+), 37 deletions(-) diff --git a/beacon/blsync/engineclient.go b/beacon/blsync/engineclient.go index f9821fc6f360..9fc6a18a5773 100644 --- a/beacon/blsync/engineclient.go +++ b/beacon/blsync/engineclient.go @@ -101,7 +101,16 @@ func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent) params = []any{execData} ) switch fork { - case "electra": + case "altair", "bellatrix": + method = "engine_newPayloadV1" + case "capella": + method = "engine_newPayloadV2" + case "deneb": + method = "engine_newPayloadV3" + parentBeaconRoot := event.BeaconHead.ParentRoot + blobHashes := collectBlobHashes(event.Block) + params = append(params, blobHashes, parentBeaconRoot) + default: // electra, fulu and above method = "engine_newPayloadV4" parentBeaconRoot := event.BeaconHead.ParentRoot blobHashes := collectBlobHashes(event.Block) @@ -110,15 +119,6 @@ func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent) hexRequests[i] = hexutil.Bytes(event.ExecRequests[i]) } params = append(params, blobHashes, parentBeaconRoot, hexRequests) - case "deneb": - method = "engine_newPayloadV3" - parentBeaconRoot := event.BeaconHead.ParentRoot - blobHashes := collectBlobHashes(event.Block) - params = append(params, blobHashes, parentBeaconRoot) - case "capella": - method = "engine_newPayloadV2" - default: - method = "engine_newPayloadV1" } ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5) @@ -145,12 +145,12 @@ func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHead var method string switch fork { - case "deneb", "electra": - method = "engine_forkchoiceUpdatedV3" + case "altair", "bellatrix": + method = "engine_forkchoiceUpdatedV1" case "capella": method = "engine_forkchoiceUpdatedV2" - default: - method = "engine_forkchoiceUpdatedV1" + default: // deneb, electra, fulu and above + method = "engine_forkchoiceUpdatedV3" } ctx, cancel := context.WithTimeout(ec.rootCtx, time.Second*5) diff --git a/beacon/params/checkpoint_hoodi.hex b/beacon/params/checkpoint_hoodi.hex index 2885d7c996ef..7bac591f969e 100644 --- a/beacon/params/checkpoint_hoodi.hex +++ b/beacon/params/checkpoint_hoodi.hex @@ -1 +1 @@ -0x1bbf958008172591b6cbdb3d8d52e26998258e83d4bdb9eec10969d84519a6bd \ No newline at end of file +0xbb7a7f3c40d8ea0b450f91587db65d0f1c079669277e01a0426c8911702a863a \ No newline at end of file diff --git a/beacon/params/checkpoint_mainnet.hex b/beacon/params/checkpoint_mainnet.hex index 417e69a24bab..c2886cc564f9 100644 --- a/beacon/params/checkpoint_mainnet.hex +++ b/beacon/params/checkpoint_mainnet.hex @@ -1 +1 @@ -0x2fe39a39b6f7cbd549e0f74d259de6db486005a65bd3bd92840dd6ce21d6f4c8 \ No newline at end of file +0x2af778d703186526a1b6304b423f338f11556206f618643c3f7fa0d7b1ef5c9b \ No newline at end of file diff --git a/beacon/params/checkpoint_sepolia.hex b/beacon/params/checkpoint_sepolia.hex index 02faf7218778..55842f8ac023 100644 --- a/beacon/params/checkpoint_sepolia.hex +++ b/beacon/params/checkpoint_sepolia.hex @@ -1 +1 @@ -0x86686b2b366e24134e0e3969a9c5f3759f92e5d2b04785b42e22cc7d468c2107 \ No newline at end of file +0x48a89c9ea7ba19de2931797974cf8722344ab231c0edada278b108ef74125478 \ No newline at end of file diff --git a/beacon/params/networks.go b/beacon/params/networks.go index b35db34fd64e..5dcf08cc5de1 100644 --- a/beacon/params/networks.go +++ b/beacon/params/networks.go @@ -40,36 +40,39 @@ var ( GenesisTime: 1606824023, Checkpoint: common.HexToHash(checkpointMainnet), }). - AddFork("GENESIS", 0, []byte{0, 0, 0, 0}). - AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}). - AddFork("BELLATRIX", 144896, []byte{2, 0, 0, 0}). - AddFork("CAPELLA", 194048, []byte{3, 0, 0, 0}). - AddFork("DENEB", 269568, []byte{4, 0, 0, 0}). - AddFork("ELECTRA", 364032, []byte{5, 0, 0, 0}) + AddFork("GENESIS", 0, common.FromHex("0x00000000")). + AddFork("ALTAIR", 74240, common.FromHex("0x01000000")). + AddFork("BELLATRIX", 144896, common.FromHex("0x02000000")). + AddFork("CAPELLA", 194048, common.FromHex("0x03000000")). + AddFork("DENEB", 269568, common.FromHex("0x04000000")). + AddFork("ELECTRA", 364032, common.FromHex("0x05000000")). + AddFork("FULU", 411392, common.FromHex("0x06000000")) SepoliaLightConfig = (&ChainConfig{ GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"), GenesisTime: 1655733600, Checkpoint: common.HexToHash(checkpointSepolia), }). - AddFork("GENESIS", 0, []byte{144, 0, 0, 105}). - AddFork("ALTAIR", 50, []byte{144, 0, 0, 112}). - AddFork("BELLATRIX", 100, []byte{144, 0, 0, 113}). - AddFork("CAPELLA", 56832, []byte{144, 0, 0, 114}). - AddFork("DENEB", 132608, []byte{144, 0, 0, 115}). - AddFork("ELECTRA", 222464, []byte{144, 0, 0, 116}) + AddFork("GENESIS", 0, common.FromHex("0x90000069")). + AddFork("ALTAIR", 50, common.FromHex("0x90000070")). + AddFork("BELLATRIX", 100, common.FromHex("0x90000071")). + AddFork("CAPELLA", 56832, common.FromHex("0x90000072")). + AddFork("DENEB", 132608, common.FromHex("0x90000073")). + AddFork("ELECTRA", 222464, common.FromHex("0x90000074")). + AddFork("FULU", 272640, common.FromHex("0x90000075")) HoleskyLightConfig = (&ChainConfig{ GenesisValidatorsRoot: common.HexToHash("0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1"), GenesisTime: 1695902400, Checkpoint: common.HexToHash(checkpointHolesky), }). - AddFork("GENESIS", 0, []byte{1, 1, 112, 0}). - AddFork("ALTAIR", 0, []byte{2, 1, 112, 0}). - AddFork("BELLATRIX", 0, []byte{3, 1, 112, 0}). - AddFork("CAPELLA", 256, []byte{4, 1, 112, 0}). - AddFork("DENEB", 29696, []byte{5, 1, 112, 0}). - AddFork("ELECTRA", 115968, []byte{6, 1, 112, 0}) + AddFork("GENESIS", 0, common.FromHex("0x01017000")). + AddFork("ALTAIR", 0, common.FromHex("0x02017000")). + AddFork("BELLATRIX", 0, common.FromHex("0x03017000")). + AddFork("CAPELLA", 256, common.FromHex("0x04017000")). + AddFork("DENEB", 29696, common.FromHex("0x05017000")). + AddFork("ELECTRA", 115968, common.FromHex("0x06017000")). + AddFork("FULU", 165120, common.FromHex("0x07017000")) HoodiLightConfig = (&ChainConfig{ GenesisValidatorsRoot: common.HexToHash("0x212f13fc4df078b6cb7db228f1c8307566dcecf900867401a92023d7ba99cb5f"), @@ -82,5 +85,5 @@ var ( AddFork("CAPELLA", 0, common.FromHex("0x40000910")). AddFork("DENEB", 0, common.FromHex("0x50000910")). AddFork("ELECTRA", 2048, common.FromHex("0x60000910")). - AddFork("FULU", 18446744073709551615, common.FromHex("0x70000910")) + AddFork("FULU", 50688, common.FromHex("0x70000910")) ) diff --git a/core/filtermaps/checkpoints_mainnet.json b/core/filtermaps/checkpoints_mainnet.json index 2ea065ddb76b..795967405d31 100644 --- a/core/filtermaps/checkpoints_mainnet.json +++ b/core/filtermaps/checkpoints_mainnet.json @@ -288,5 +288,32 @@ {"blockNumber": 22958100, "blockId": "0xe38e0ff7b0c4065ca42ea577bc32f2566ca46f2ddeedcc4bc1f8fb00e7f26329", "firstIndex": 19260242424}, {"blockNumber": 22988600, "blockId": "0x04ca74758b22e0ea54b8c992022ff21c16a2af9c45144c3b0f80de921a7eee82", "firstIndex": 19327351273}, {"blockNumber": 23018392, "blockId": "0x61cc979b00bc97b48356f986a5b9ec997d674bc904c2a2e4b0f17de08e50b3bb", "firstIndex": 19394459627}, -{"blockNumber": 23048524, "blockId": "0x489de15d95739ede4ab15e8b5151d80d4dc85ae10e7be800b1a4723094a678df", "firstIndex": 19461570073} +{"blockNumber": 23048524, "blockId": "0x489de15d95739ede4ab15e8b5151d80d4dc85ae10e7be800b1a4723094a678df", "firstIndex": 19461570073}, +{"blockNumber": 23078983, "blockId": "0xd64da4fe45a0a349101b8ce1a6336fb099d8b00cc274d0eb59356e134190b8f2", "firstIndex": 19528679292}, +{"blockNumber": 23109224, "blockId": "0xbb6c29f91820fcf6caef881bdfe61eb690f9796f8f139c56eaf27aa601fe5ed2", "firstIndex": 19595787850}, +{"blockNumber": 23136690, "blockId": "0xc1915739edff731d469ec1500ad05102c0d68cc1ef062411d2e9741b8ebdc571", "firstIndex": 19662896969}, +{"blockNumber": 23164324, "blockId": "0x97d0078f2a22a8fbde4660d5f11846d00a669c606e42291df55e863190914a9f", "firstIndex": 19730004997}, +{"blockNumber": 23192975, "blockId": "0x8463749ec09f55fccdd962498f93d6468d744faedc35da8097642cd4609e08f2", "firstIndex": 19797111604}, +{"blockNumber": 23219805, "blockId": "0x76ca51d01b5724f1b0f6c3acb50dfcdb00e1648a475dd7599174b301c25e5517", "firstIndex": 19864222312}, +{"blockNumber": 23236331, "blockId": "0x388f9e36b3ec2120d2b4adfdcdb0c0b8de139eef107e327670dd77fc581c2c5f", "firstIndex": 19931331831}, +{"blockNumber": 23260849, "blockId": "0xe4a467164dbc8f9beebf0c8478a9e7ee16dfce65e2da423b1048601831222ba7", "firstIndex": 19998441286}, +{"blockNumber": 23282795, "blockId": "0x74ad210aa1bfdd4bcf61298ebff622da758c36c38d62d001f2440d09e73ef6c7", "firstIndex": 20065548083}, +{"blockNumber": 23300759, "blockId": "0xa405f5ea21a5207d3cde718a1e3fb7f0ce3dd87ac6040a0db52da0e9488e63f6", "firstIndex": 20132623334}, +{"blockNumber": 23319772, "blockId": "0xafae645dd057af450eddf69c7604bde0136524abc5b7d6697426427ef2d30724", "firstIndex": 20199767615}, +{"blockNumber": 23342113, "blockId": "0x8482c4be13294cfd862d500051c8b4efb95b50f4a79717da6aeabb2a6ff3e199", "firstIndex": 20266874986}, +{"blockNumber": 23366974, "blockId": "0xf6047cafea5da7aaf20691df700d759fe84f5aa2176d9dd42c6ae138899a29ea", "firstIndex": 20333983351}, +{"blockNumber": 23397579, "blockId": "0xe94815fe0278659a26e15b023c0c3877abf6f1265710c5dfddf161ee8af01b40", "firstIndex": 20401094335}, +{"blockNumber": 23425940, "blockId": "0xa98d6d48b93d9ef848c340b47cf9d191f36e76375dd747d8adcb39d72251822d", "firstIndex": 20468200506}, +{"blockNumber": 23452402, "blockId": "0x5f26ff308c0883a04f4a92411bcd05e50538d95a96532452f43b71fde4af908d", "firstIndex": 20535311178}, +{"blockNumber": 23478884, "blockId": "0x23a81186c21218f7034ee81dcd72db71edcbc0e97da26703b8026b6d4a81f693", "firstIndex": 20602384106}, +{"blockNumber": 23507136, "blockId": "0xb1d4a2bd836c5d78b678f0bfffd651bdfeae8b7965639b640f8745193b142323", "firstIndex": 20669529935}, +{"blockNumber": 23535463, "blockId": "0x1886a719a6376352753242ec5093c434938b9a90356facdbdaafd2670da97d82", "firstIndex": 20736637374}, +{"blockNumber": 23564278, "blockId": "0xcdc5a1349f45430771fa2080555f20b80b2d84f575728dbb8bd3b633fbb0a00b", "firstIndex": 20803747399}, +{"blockNumber": 23594711, "blockId": "0xbbf41e407367feeb865b305e38aee8bef84ba62b66efbd6fcd3b16a5d50cc055", "firstIndex": 20870854523}, +{"blockNumber": 23627417, "blockId": "0x1b3c6bd39c5aa7c73101e7e946964b3d7afecf3f253efda8de5610dd99cbee82", "firstIndex": 20937963176}, +{"blockNumber": 23660347, "blockId": "0xadc8ac88c281f50c3aaf1d08149100e8f91a16bbe09b28ac86665bcea681d41b", "firstIndex": 21005072865}, +{"blockNumber": 23692228, "blockId": "0xeb4040468161d9d5b6863eb62e4f21f72a1944f910ecfa95ee6a9dbc39e92ef0", "firstIndex": 21072181753}, +{"blockNumber": 23722331, "blockId": "0xdfc9be1b43488148868da0c8ac56e99e0ffde18bcc418755c4765e745fe74024", "firstIndex": 21139291342}, +{"blockNumber": 23752866, "blockId": "0xb9cf0dfee429a1450f5fb7a237729cf7d0c49e7e35c00dc3709f197c091f8b39", "firstIndex": 21206399901}, +{"blockNumber": 23784485, "blockId": "0x92f5f119078b4ef7ad99273e2ae6f874cfb663e80d741a821e0bb7c25c0369c7", "firstIndex": 21273505141} ] From bf41c073fdf720a633f7f644b411183f9739e0ed Mon Sep 17 00:00:00 2001 From: radik878 Date: Wed, 26 Nov 2025 12:30:05 +0200 Subject: [PATCH 222/228] rlp: finalize listIterator on parse error to prevent non-advancing loops (#33245) The list iterator previously returned true on parse errors without advancing the input, which could lead to non-advancing infinite loops for callers that do not check Err() inside the loop; to make iteration safe while preserving error visibility, Next() now marks the iterator as finished when readKind fails, returning true for the error step so existing users that check Err() can handle it, and then false on subsequent calls, and the function comment was updated to document this behavior and the need to check Err(). --- rlp/iterator.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/rlp/iterator.go b/rlp/iterator.go index 95bd3f258208..05567fa05b90 100644 --- a/rlp/iterator.go +++ b/rlp/iterator.go @@ -37,15 +37,24 @@ func NewListIterator(data RawValue) (*listIterator, error) { return it, nil } -// Next forwards the iterator one step, returns true if it was not at end yet +// Next forwards the iterator one step. +// Returns true if there is a next item or an error occurred on this step (check Err()). +// On parse error, the iterator is marked finished and subsequent calls return false. func (it *listIterator) Next() bool { if len(it.data) == 0 { return false } _, t, c, err := readKind(it.data) + if err != nil { + it.next = nil + it.err = err + // Mark iteration as finished to avoid potential infinite loops on subsequent Next calls. + it.data = nil + return true + } it.next = it.data[:t+c] it.data = it.data[t+c:] - it.err = err + it.err = nil return true } From 8f17a97bc40c58e638b02127e43ae50b23a6ffeb Mon Sep 17 00:00:00 2001 From: Lucia Date: Thu, 27 Nov 2025 02:58:15 +1300 Subject: [PATCH 223/228] accounts/usbwallet: fix double hashing in SignTextWithPassphrase (#33138) SignTextWithPassphrase calls SignText, which already performs TextHash. --- accounts/usbwallet/wallet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 0fd0415a9ef8..f1597ca1a702 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -632,7 +632,7 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID // data is not supported for Ledger wallets, so this method will always return // an error. func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { - return w.SignText(account, accounts.TextHash(text)) + return w.SignText(account, text) } // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given From 8a406661d693c92272a7491f5f1242c1c3128889 Mon Sep 17 00:00:00 2001 From: oxBoni Date: Wed, 26 Nov 2025 15:34:11 +0100 Subject: [PATCH 224/228] p2p/discover/v5wire: remove redundant bytes clone in WHOAREYOU encoding (#33180) head.AuthData is assigned later in the function, so the earlier assignment can safely be removed. --- p2p/discover/v5wire/encoding.go | 1 - 1 file changed, 1 deletion(-) diff --git a/p2p/discover/v5wire/encoding.go b/p2p/discover/v5wire/encoding.go index 08292a70bad3..d6a30a17ca87 100644 --- a/p2p/discover/v5wire/encoding.go +++ b/p2p/discover/v5wire/encoding.go @@ -326,7 +326,6 @@ func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error // Create header. head := c.makeHeader(toID, flagWhoareyou, 0) - head.AuthData = slices.Clone(c.buf.Bytes()) head.Nonce = packet.Nonce // Encode auth data. From cf820ff4b2d27ecf4455bcb975de3ccaaba7e215 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 26 Nov 2025 15:53:03 +0100 Subject: [PATCH 225/228] miner: add --miner.maxblobs flag (#33129) Adds a flag to specify how many blobs a node is willing to include in their locally build block as specified in https://eips.ethereum.org/EIPS/eip-7872 I deviated from the EIP in one case, I allowed for specifying 0 as the minimum blobs/block --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 9 +++++++++ miner/miner.go | 1 + miner/worker.go | 16 +++++++++++++--- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 109b36836a04..851ae1ce0bbf 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -118,6 +118,7 @@ var ( utils.MinerGasPriceFlag, utils.MinerEtherbaseFlag, // deprecated utils.MinerExtraDataFlag, + utils.MinerMaxBlobsFlag, utils.MinerRecommitIntervalFlag, utils.MinerPendingFeeRecipientFlag, utils.MinerNewPayloadTimeoutFlag, // deprecated diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c95efd9fd7d2..536f8c9e657f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -553,6 +553,11 @@ var ( Usage: "0x prefixed public address for the pending block producer (not used for actual block production)", Category: flags.MinerCategory, } + MinerMaxBlobsFlag = &cli.IntFlag{ + Name: "miner.maxblobs", + Usage: "Maximum number of blobs per block (falls back to protocol maximum if unspecified)", + Category: flags.MinerCategory, + } // Account settings PasswordFileFlag = &cli.PathFlag{ @@ -1571,6 +1576,10 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) { log.Warn("The flag --miner.newpayload-timeout is deprecated and will be removed, please use --miner.recommit") cfg.Recommit = ctx.Duration(MinerNewPayloadTimeoutFlag.Name) } + if ctx.IsSet(MinerMaxBlobsFlag.Name) { + maxBlobs := ctx.Int(MinerMaxBlobsFlag.Name) + cfg.MaxBlobsPerBlock = &maxBlobs + } } func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) { diff --git a/miner/miner.go b/miner/miner.go index 810cc20a6c61..4c40b0c4f8f2 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -48,6 +48,7 @@ type Config struct { GasCeil uint64 // Target gas ceiling for mined blocks. GasPrice *big.Int // Minimum gas price for mining a transaction Recommit time.Duration // The time interval for miner to re-create mining work. + MaxBlobsPerBlock *int // Maximum number of blobs per block (unset uses protocol default) } // DefaultConfig contains default settings for miner. diff --git a/miner/worker.go b/miner/worker.go index c0574eac2380..e0dcdca456be 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -43,6 +43,16 @@ var ( errBlockInterruptedByTimeout = errors.New("timeout while building block") ) +// maxBlobsPerBlock returns the maximum number of blobs per block. +// Users can specify the maximum number of blobs per block if necessary. +func (miner *Miner) maxBlobsPerBlock(time uint64) int { + maxBlobs := eip4844.MaxBlobsPerBlock(miner.chainConfig, time) + if miner.config.MaxBlobsPerBlock != nil { + maxBlobs = *miner.config.MaxBlobsPerBlock + } + return maxBlobs +} + // environment is the worker's current environment and holds all // information of the sealing block generation. type environment struct { @@ -309,7 +319,7 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio // isn't really a better place right now. The blob gas limit is checked at block validation time // and not during execution. This means core.ApplyTransaction will not return an error if the // tx has too many blobs. So we have to explicitly check it here. - maxBlobs := eip4844.MaxBlobsPerBlock(miner.chainConfig, env.header.Time) + maxBlobs := miner.maxBlobsPerBlock(env.header.Time) if env.blobs+len(sc.Blobs) > maxBlobs { return errors.New("max data blobs reached") } @@ -364,7 +374,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran } // If we don't have enough blob space for any further blob transactions, // skip that list altogether - if !blobTxs.Empty() && env.blobs >= eip4844.MaxBlobsPerBlock(miner.chainConfig, env.header.Time) { + if !blobTxs.Empty() && env.blobs >= miner.maxBlobsPerBlock(env.header.Time) { log.Trace("Not enough blob space for further blob transactions") blobTxs.Clear() // Fall though to pick up any plain txs @@ -403,7 +413,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran // blobs or not, however the max check panics when called on a chain without // a defined schedule, so we need to verify it's safe to call. if isCancun { - left := eip4844.MaxBlobsPerBlock(miner.chainConfig, env.header.Time) - env.blobs + left := miner.maxBlobsPerBlock(env.header.Time) - env.blobs if left < int(ltx.BlobGas/params.BlobTxBlobGasPerBlob) { log.Trace("Not enough blob space left for transaction", "hash", ltx.Hash, "left", left, "needed", ltx.BlobGas/params.BlobTxBlobGasPerBlob) txs.Pop() From ad3a93f8e219f81317117175d3cf4fe4ac7f1a48 Mon Sep 17 00:00:00 2001 From: Jonny Rhea <5555162+jrhea@users.noreply.github.com> Date: Wed, 26 Nov 2025 08:58:59 -0600 Subject: [PATCH 226/228] core/vm: implement EIP-8024 (#33095) EIP-8024: Backward compatible SWAPN, DUPN, EXCHANGE Introduces additional instructions for manipulating the stack which allow accessing the stack at higher depths. This is an initial implementation of the EIP, which is still in Review stage. --- core/vm/eips.go | 23 +++++ core/vm/instructions.go | 109 ++++++++++++++++++++++++ core/vm/instructions_test.go | 161 +++++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+) diff --git a/core/vm/eips.go b/core/vm/eips.go index d7ed18648e6f..dfcac4b93029 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -42,6 +42,7 @@ var activators = map[int]func(*JumpTable){ 4762: enable4762, 7702: enable7702, 7939: enable7939, + 8024: enable8024, } // EnableEIP enables the given EIP on the config. @@ -342,6 +343,28 @@ func enable6780(jt *JumpTable) { } } +// enable8024 applies EIP-8024 (DUPN, SWAPN, EXCHANGE) +func enable8024(jt *JumpTable) { + jt[DUPN] = &operation{ + execute: opDupN, + constantGas: GasFastestStep, + minStack: minStack(1, 0), + maxStack: maxStack(0, 1), + } + jt[SWAPN] = &operation{ + execute: opSwapN, + constantGas: GasFastestStep, + minStack: minStack(2, 0), + maxStack: maxStack(0, 0), + } + jt[EXCHANGE] = &operation{ + execute: opExchange, + constantGas: GasFastestStep, + minStack: minStack(2, 0), + maxStack: maxStack(0, 0), + } +} + func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { var ( stack = scope.Stack diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 44d3e81a9cfd..29f1f79c4991 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -920,6 +920,115 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro return nil, errStopToken } +func decodeSingle(x byte) int { + if x <= 90 { + return int(x) + 17 + } + return int(x) - 20 +} + +func decodePair(x byte) (int, int) { + var k int + if x <= 79 { + k = int(x) + } else { + k = int(x) - 48 + } + q, r := k/16, k%16 + if q < r { + return q + 1, r + 1 + } + return r + 1, 29 - q +} + +func opDupN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { + code := scope.Contract.Code + i := *pc + 1 + + // Ensure an immediate byte exists after DUPN + if i >= uint64(len(code)) { + return nil, &ErrInvalidOpCode{opcode: INVALID} + } + x := code[i] + + // This range is excluded to preserve compatibility with existing opcodes. + if x > 90 && x < 128 { + return nil, &ErrInvalidOpCode{opcode: OpCode(x)} + } + n := decodeSingle(x) + + // DUPN duplicates the n'th stack item, so the stack must contain at least n elements. + if scope.Stack.len() < n { + return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: n} + } + + //The n‘th stack item is duplicated at the top of the stack. + scope.Stack.push(scope.Stack.Back(n - 1)) + *pc += 2 + return nil, nil +} + +func opSwapN(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { + code := scope.Contract.Code + i := *pc + 1 + + // Ensure an immediate byte exists after SWAPN + if i >= uint64(len(code)) { + return nil, &ErrInvalidOpCode{opcode: INVALID} + } + x := code[i] + + // This range is excluded to preserve compatibility with existing opcodes. + if x > 90 && x < 128 { + return nil, &ErrInvalidOpCode{opcode: OpCode(x)} + } + n := decodeSingle(x) + + // SWAPN operates on the top and n+1 stack items, so the stack must contain at least n+1 elements. + if scope.Stack.len() < n+1 { + return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: n + 1} + } + + // The (n+1)‘th stack item is swapped with the top of the stack. + indexTop := scope.Stack.len() - 1 + indexN := scope.Stack.len() - 1 - n + scope.Stack.data[indexTop], scope.Stack.data[indexN] = scope.Stack.data[indexN], scope.Stack.data[indexTop] + *pc += 2 + return nil, nil +} + +func opExchange(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { + code := scope.Contract.Code + i := *pc + 1 + + // Ensure an immediate byte exists after EXCHANGE + if i >= uint64(len(code)) { + return nil, &ErrInvalidOpCode{opcode: INVALID} + } + x := code[i] + + // This range is excluded both to preserve compatibility with existing opcodes + // and to keep decode_pair’s 16-aligned arithmetic mapping valid (0–79, 128–255). + if x > 79 && x < 128 { + return nil, &ErrInvalidOpCode{opcode: OpCode(x)} + } + n, m := decodePair(x) + need := max(n, m) + 1 + + // EXCHANGE operates on the (n+1)'th and (m+1)'th stack items, + // so the stack must contain at least max(n, m)+1 elements. + if scope.Stack.len() < need { + return nil, &ErrStackUnderflow{stackLen: scope.Stack.len(), required: need} + } + + // The (n+1)‘th stack item is swapped with the (m+1)‘th stack item. + indexN := scope.Stack.len() - 1 - n + indexM := scope.Stack.len() - 1 - m + scope.Stack.data[indexN], scope.Stack.data[indexM] = scope.Stack.data[indexM], scope.Stack.data[indexN] + *pc += 2 + return nil, nil +} + // following functions are used by the instruction jump table // make log instruction function diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 72f561f4bf3f..0f91a205f5c3 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -1008,3 +1008,164 @@ func TestOpCLZ(t *testing.T) { } } } + +func TestEIP8024_Execution(t *testing.T) { + evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) + + tests := []struct { + name string + codeHex string + wantErr bool + wantVals []uint64 + }{ + { + name: "DUPN", + codeHex: "60016000808080808080808080808080808080e600", + wantVals: []uint64{ + 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, + }, + }, + { + name: "SWAPN", + codeHex: "600160008080808080808080808080808080806002e700", + wantVals: []uint64{ + 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, + }, + }, + { + name: "EXCHANGE", + codeHex: "600060016002e801", + wantVals: []uint64{2, 0, 1}, + }, + { + name: "INVALID_SWAPN_LOW", + codeHex: "e75b", + wantErr: true, + }, + { + name: "JUMP over INVALID_DUPN", + codeHex: "600456e65b", + wantErr: false, + }, + // Additional test cases + { + name: "INVALID_DUPN_LOW", + codeHex: "e65b", + wantErr: true, + }, + { + name: "INVALID_EXCHANGE_LOW", + codeHex: "e850", + wantErr: true, + }, + { + name: "INVALID_DUPN_HIGH", + codeHex: "e67f", + wantErr: true, + }, + { + name: "INVALID_SWAPN_HIGH", + codeHex: "e77f", + wantErr: true, + }, + { + name: "INVALID_EXCHANGE_HIGH", + codeHex: "e87f", + wantErr: true, + }, + { + name: "UNDERFLOW_DUPN", + codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe600", // (n=17, need 17 items, have 16) + wantErr: true, + }, + { + name: "UNDERFLOW_SWAPN", + codeHex: "5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5fe700", // (n=17, need 18 items, have 17) + wantErr: true, + }, + { + name: "UNDERFLOW_EXCHANGE", + codeHex: "60016002e801", // (n,m)=(1,2), need 3 items, have 2 + wantErr: true, + }, + { + name: "MISSING_IMMEDIATE_DUPN", + codeHex: "e6", // no operand + wantErr: true, + }, + { + name: "MISSING_IMMEDIATE_SWAPN", + codeHex: "e7", // no operand + wantErr: true, + }, + { + name: "MISSING_IMMEDIATE_EXCHANGE", + codeHex: "e8", // no operand + wantErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + code := common.FromHex(tc.codeHex) + stack := newstack() + pc := uint64(0) + scope := &ScopeContext{Stack: stack, Contract: &Contract{Code: code}} + var err error + for pc < uint64(len(code)) && err == nil { + op := code[pc] + switch op { + case 0x00: + return + case 0x60: + _, err = opPush1(&pc, evm, scope) + pc++ + case 0x80: + dup1 := makeDup(1) + _, err = dup1(&pc, evm, scope) + pc++ + case 0x56: + _, err = opJump(&pc, evm, scope) + pc++ + case 0x5b: + _, err = opJumpdest(&pc, evm, scope) + pc++ + case 0xe6: + _, err = opDupN(&pc, evm, scope) + case 0xe7: + _, err = opSwapN(&pc, evm, scope) + case 0xe8: + _, err = opExchange(&pc, evm, scope) + default: + err = &ErrInvalidOpCode{opcode: OpCode(op)} + } + } + if tc.wantErr { + if err == nil { + t.Fatalf("expected error, got nil") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + got := make([]uint64, 0, stack.len()) + for i := stack.len() - 1; i >= 0; i-- { + got = append(got, stack.data[i].Uint64()) + } + if len(got) != len(tc.wantVals) { + t.Fatalf("stack len=%d; want %d", len(got), len(tc.wantVals)) + } + for i := range got { + if got[i] != tc.wantVals[i] { + t.Fatalf("[%s] stack[%d]=%d; want %d\nstack=%v", + tc.name, i, got[i], tc.wantVals[i], got) + } + } + }) + } +} From a8d26b2e92540a02f7213a38ecade86060ee153f Mon Sep 17 00:00:00 2001 From: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:04:35 +0100 Subject: [PATCH 227/228] Update wallet.go --- accounts/usbwallet/wallet.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index f1597ca1a702..5997a2a6765a 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -338,14 +338,15 @@ func (w *wallet) selfDerive() { } // Device lock obtained, derive the next batch of accounts var ( - accs []accounts.Account - paths []accounts.DerivationPath - - nextPaths = append([]accounts.DerivationPath{}, w.deriveNextPaths...) - nextAddrs = append([]common.Address{}, w.deriveNextAddrs...) - + accs []accounts.Account + paths []accounts.DerivationPath context = context.Background() + nextAddrs = append([]common.Address{}, w.deriveNextAddrs...) ) + nextPaths := make([]accounts.DerivationPath, len(w.deriveNextPaths)) + for i, path := range w.deriveNextPaths { + nextPaths[i] = append(accounts.DerivationPath(nil), path...) + } for i := 0; i < len(nextAddrs); i++ { for empty := false; !empty; { // Retrieve the next derived Ethereum account @@ -632,7 +633,7 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID // data is not supported for Ledger wallets, so this method will always return // an error. func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { - return w.SignText(account, text) + return w.SignText(account, accounts.TextHash(text)) } // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given From f4d3df62aa5fb754790ca70ab5a87add44c42872 Mon Sep 17 00:00:00 2001 From: Alvarez <140459501+prestoalvarez@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:23:20 +0100 Subject: [PATCH 228/228] Update wallet.go --- accounts/usbwallet/wallet.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 5997a2a6765a..04e8b6051749 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -343,6 +343,8 @@ func (w *wallet) selfDerive() { context = context.Background() nextAddrs = append([]common.Address{}, w.deriveNextAddrs...) ) + // Deep-copy derivation paths so updates inside this section don't mutate + // w.deriveNextPaths while we only hold stateLock.RLock. nextPaths := make([]accounts.DerivationPath, len(w.deriveNextPaths)) for i, path := range w.deriveNextPaths { nextPaths[i] = append(accounts.DerivationPath(nil), path...)