From a10969f65061d99d47728fba14ec2b022e436233 Mon Sep 17 00:00:00 2001 From: jwasinger Date: Tue, 14 Jul 2026 08:45:16 -0400 Subject: [PATCH 1/6] accounts/abi: fix abigen v1 bindings for deployment of library dependencies (#32164) When we are deploying library dependencies, if the `TransactOpts` nonce is unset we will choose the nonce of the next deployment transaction based on the pending nonce of the sender. If a previous library deployment transaction was made but not yet accepted into the pool, the pending nonce will not be updated for the the next deployment transaction. This PR introduces a new method to the bind API `WaitAccepted` which poll until a submitted transaction hash is accepted into the pool or rejected. The bindings for v1 are updated to invoke this method after deploying each library dependency. --- accounts/abi/abigen/source.go.tpl | 10 +++++++++- accounts/abi/bind/old.go | 6 ++++++ accounts/abi/bind/v2/backend.go | 3 +++ accounts/abi/bind/v2/base.go | 6 +++--- accounts/abi/bind/v2/base_test.go | 4 ++++ accounts/abi/bind/v2/util.go | 25 +++++++++++++++++++++++++ 6 files changed, 50 insertions(+), 4 deletions(-) diff --git a/accounts/abi/abigen/source.go.tpl b/accounts/abi/abigen/source.go.tpl index c84862d03b..09bc167188 100644 --- a/accounts/abi/abigen/source.go.tpl +++ b/accounts/abi/abigen/source.go.tpl @@ -4,8 +4,10 @@ package {{.Package}} import ( + "context" "math/big" "strings" + "time" "errors" ethereum "github.com/ethereum/go-ethereum" @@ -27,6 +29,8 @@ var ( _ = types.BloomLookup _ = event.NewSubscription _ = abi.ConvertType + _ = time.Tick + _ = context.Background ) {{$structs := .Structs}} @@ -77,7 +81,11 @@ var ( return common.Address{}, nil, nil, errors.New("GetABI returned nil") } {{range $pattern, $name := .Libraries}} - {{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend) + {{decapitalise $name}}Addr, tx, _, _ := Deploy{{capitalise $name}}(auth, backend) + ctx, _ := context.WithTimeout(context.Background(), 5 * time.Second) + if err := bind.WaitAccepted(ctx, backend, tx); err != nil { + return common.Address{}, nil, nil, err + } {{$contract.Type}}Bin = strings.ReplaceAll({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:]) {{end}} address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}}) diff --git a/accounts/abi/bind/old.go b/accounts/abi/bind/old.go index 1fe1b1cca5..0cc91cc8ee 100644 --- a/accounts/abi/bind/old.go +++ b/accounts/abi/bind/old.go @@ -273,6 +273,12 @@ func (m *MetaData) GetAbi() (*abi.ABI, error) { // util.go +// WaitAccepted waits for a tx to be accepted into the pool. +// It stops waiting when the context is canceled. +func WaitAccepted(ctx context.Context, b ContractBackend, tx *types.Transaction) error { + return bind2.WaitAccepted(ctx, b, tx.Hash()) +} + // WaitMined waits for tx to be mined on the blockchain. // It stops waiting when the context is canceled. func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*types.Receipt, error) { diff --git a/accounts/abi/bind/v2/backend.go b/accounts/abi/bind/v2/backend.go index 2f5f17b31e..d7ac49867c 100644 --- a/accounts/abi/bind/v2/backend.go +++ b/accounts/abi/bind/v2/backend.go @@ -103,6 +103,9 @@ type ContractTransactor interface { // PendingNonceAt retrieves the current pending nonce associated with an account. PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) + + // TransactionByHash retrieves the transaction associated with the hash, if it exists in the pool. + TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) } // DeployBackend wraps the operations needed by WaitMined and WaitDeployed. diff --git a/accounts/abi/bind/v2/base.go b/accounts/abi/bind/v2/base.go index 862175ee32..a317cdeaa0 100644 --- a/accounts/abi/bind/v2/base.go +++ b/accounts/abi/bind/v2/base.go @@ -320,7 +320,7 @@ func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Add } } // create the transaction - nonce, err := c.getNonce(opts) + nonce, err := c.GetNonce(opts) if err != nil { return nil, err } @@ -365,7 +365,7 @@ func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Addr } } // create the transaction - nonce, err := c.getNonce(opts) + nonce, err := c.GetNonce(opts) if err != nil { return nil, err } @@ -402,7 +402,7 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad return c.transactor.EstimateGas(ensureContext(opts.Context), msg) } -func (c *BoundContract) getNonce(opts *TransactOpts) (uint64, error) { +func (c *BoundContract) GetNonce(opts *TransactOpts) (uint64, error) { if opts.Nonce == nil { return c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) } else { diff --git a/accounts/abi/bind/v2/base_test.go b/accounts/abi/bind/v2/base_test.go index 80d0f22f2c..7015d39694 100644 --- a/accounts/abi/bind/v2/base_test.go +++ b/accounts/abi/bind/v2/base_test.go @@ -75,6 +75,10 @@ func (mt *mockTransactor) SendTransaction(ctx context.Context, tx *types.Transac return nil } +func (mt *mockTransactor) TransactionByHash(ctx context.Context, hash common.Hash) (*types.Transaction, bool, error) { + return nil, false, nil +} + type mockCaller struct { codeAtBlockNumber *big.Int callContractBlockNumber *big.Int diff --git a/accounts/abi/bind/v2/util.go b/accounts/abi/bind/v2/util.go index 438848a753..0f56edbcf1 100644 --- a/accounts/abi/bind/v2/util.go +++ b/accounts/abi/bind/v2/util.go @@ -75,3 +75,28 @@ func WaitDeployed(ctx context.Context, b DeployBackend, hash common.Hash) (commo } return receipt.ContractAddress, err } + +func WaitAccepted(ctx context.Context, d ContractBackend, txHash common.Hash) error { + queryTicker := time.NewTicker(time.Second) + defer queryTicker.Stop() + logger := log.New("hash", txHash) + for { + _, _, err := d.TransactionByHash(ctx, txHash) + if err == nil { + return nil + } + + if errors.Is(err, ethereum.NotFound) { // TODO: check this is emitted + logger.Trace("Transaction not yet accepted") + } else { + logger.Trace("Transaction submission failed", "err", err) + } + + // Wait for the next round. + select { + case <-ctx.Done(): + return ctx.Err() + case <-queryTicker.C: + } + } +} From 8d84e5001a19eba43b291e5ddf607120d5cf8cd4 Mon Sep 17 00:00:00 2001 From: Chase Wright Date: Tue, 14 Jul 2026 09:05:02 -0500 Subject: [PATCH 2/6] p2p/dnsdisc, cmd/devp2p: keep invalid and unreachable nodes out of DNS trees (#35312) The DNS discovery crawler (ethereum/discv4-crawl) builds signed enrtree lists by crawling the network and running the collected records through `devp2p nodeset filter` and `devp2p dns sign`. Those records come from external nodes, and `enr.Record` keeps entries it can't decode as raw RLP, so a self-signed record with an out-of-range port (EIP-778 defines ports only as "big endian integer", not `uint16`) can round-trip verbatim into a signed tree. Consumers that decode ports strictly then fail to decode that record. Two publish-side checks: - `MakeTree` rejects a record if a present port entry (`tcp`/`tcp6`/`udp`/`udp6`/`quic`/`quic6`) does not decode as a `uint16`. This is an invariant at the signing boundary: geth won't sign a tree containing a record with an undecodable port, regardless of how the node list was produced. Absent and zero ports are unaffected. - `devp2p nodeset filter -dialable` keeps only nodes advertising a usable RLPx port (non-zero `tcp`/`tcp6`/`quic`/`quic6`), letting the crawler drop discovery-only and unreachable nodes so consumers aren't handed peers they can't connect to. The two are deliberately separate: the `MakeTree` check is a correctness guard that always applies, while `-dialable` is an opt-in selection filter for the crawler pipeline. A follow-up will add `-dialable` to discv4-crawl's `filter_list`. --- cmd/devp2p/nodesetcmd.go | 13 +++++++ cmd/devp2p/nodesetcmd_test.go | 70 +++++++++++++++++++++++++++++++++++ p2p/dnsdisc/tree.go | 27 ++++++++++++++ p2p/dnsdisc/tree_test.go | 18 +++++++++ 4 files changed, 128 insertions(+) create mode 100644 cmd/devp2p/nodesetcmd_test.go diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index d7725fadb1..4b01a21c07 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -141,6 +141,7 @@ var filterFlags = map[string]nodeFilterC{ "-eth-network": {1, ethFilter}, "-les-server": {0, lesFilter}, "-snap": {0, snapFilter}, + "-dialable": {0, dialableFilter}, } // parseFilters parses nodeFilters from args. @@ -272,3 +273,15 @@ func snapFilter(args []string) (nodeFilter, error) { } return f, nil } + +func dialableFilter(args []string) (nodeFilter, error) { + f := func(n nodeJSON) bool { + var tcp, tcp6, quic, quic6 uint16 + n.N.Load((*enr.TCP)(&tcp)) + n.N.Load((*enr.TCP6)(&tcp6)) + n.N.Load((*enr.QUIC)(&quic)) + n.N.Load((*enr.QUIC6)(&quic6)) + return tcp != 0 || tcp6 != 0 || quic != 0 || quic6 != 0 + } + return f, nil +} diff --git a/cmd/devp2p/nodesetcmd_test.go b/cmd/devp2p/nodesetcmd_test.go new file mode 100644 index 0000000000..68f3dfb053 --- /dev/null +++ b/cmd/devp2p/nodesetcmd_test.go @@ -0,0 +1,70 @@ +// Copyright 2026 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" +) + +func TestDialableFilter(t *testing.T) { + filter, err := dialableFilter(nil) + if err != nil { + t.Fatal(err) + } + + makeNode := func(entries ...enr.Entry) nodeJSON { + r := new(enr.Record) + for _, e := range entries { + r.Set(e) + } + key, err := crypto.GenerateKey() + if err != nil { + t.Fatal(err) + } + if err := enode.SignV4(r, key); err != nil { + t.Fatal(err) + } + n, err := enode.New(enode.ValidSchemes, r) + if err != nil { + t.Fatal(err) + } + return nodeJSON{N: n} + } + + tests := []struct { + name string + node nodeJSON + want bool + }{ + {"tcp", makeNode(enr.TCP(30303)), true}, + {"tcp6", makeNode(enr.TCP6(30303)), true}, + {"quic", makeNode(enr.QUIC(30303)), true}, + {"no ports", makeNode(), false}, + {"udp only", makeNode(enr.UDP(30303)), false}, + {"zero tcp", makeNode(enr.TCP(0)), false}, + {"oversized tcp", makeNode(enr.WithEntry("tcp", uint32(70000))), false}, + } + for _, tt := range tests { + if got := filter(tt.node); got != tt.want { + t.Errorf("%s: dialable = %v, want %v", tt.name, got, tt.want) + } + } +} diff --git a/p2p/dnsdisc/tree.go b/p2p/dnsdisc/tree.go index a629d6291f..55ef9b19cc 100644 --- a/p2p/dnsdisc/tree.go +++ b/p2p/dnsdisc/tree.go @@ -162,6 +162,9 @@ func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) { if len(n.Record().Signature()) == 0 { return nil, fmt.Errorf("can't add node %v: unsigned node record", n.ID()) } + if err := checkRecordPorts(n.Record()); err != nil { + return nil, fmt.Errorf("can't add node %v: %v", n.ID(), err) + } } // Create the leaf list. @@ -393,6 +396,30 @@ func parseENR(e string, validSchemes enr.IdentityScheme) (entry, error) { return &enrEntry{n}, nil } +var portKeys = []string{ + enr.TCP(0).ENRKey(), enr.TCP6(0).ENRKey(), + enr.UDP(0).ENRKey(), enr.UDP6(0).ENRKey(), + enr.QUIC(0).ENRKey(), enr.QUIC6(0).ENRKey(), +} + +// checkRecordPorts verifies that port entries, if present, decode as a uint16. +// enr.Record keeps undecodable pairs as raw RLP, so an out-of-range port in a record +// from an external source would otherwise round-trip into a signed tree and fail to +// decode for consumers that read the port strictly. +func checkRecordPorts(r *enr.Record) error { + for _, key := range portKeys { + var port uint16 + err := r.Load(enr.WithEntry(key, &port)) + if enr.IsNotFound(err) { + continue + } + if err != nil { + return err + } + } + return nil +} + func isValidHash(s string) bool { dlen := b32format.DecodedLen(len(s)) if dlen < minHashLength || dlen > 32 || strings.ContainsAny(s, "\n\r") { diff --git a/p2p/dnsdisc/tree_test.go b/p2p/dnsdisc/tree_test.go index 9ed17aa4b3..9d012cf011 100644 --- a/p2p/dnsdisc/tree_test.go +++ b/p2p/dnsdisc/tree_test.go @@ -23,6 +23,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" ) func TestParseRoot(t *testing.T) { @@ -149,3 +150,20 @@ func TestMakeTree(t *testing.T) { t.Fatal("too few TXT records in output") } } + +func TestMakeTreeRejectsUndecodablePort(t *testing.T) { + keys := testKeys(1) + + oversized := new(enr.Record) + oversized.Set(enr.WithEntry("udp", uint32(70000))) + if err := enode.SignV4(oversized, keys[0]); err != nil { + t.Fatal(err) + } + n, err := enode.New(enode.ValidSchemes, oversized) + if err != nil { + t.Fatal(err) + } + if _, err := MakeTree(1, []*enode.Node{n}, nil); err == nil { + t.Errorf("MakeTree accepted record with out-of-range port: %s", n.String()) + } +} From 6e6fcef0ba6cbf3768706b42845cec5a9e99499a Mon Sep 17 00:00:00 2001 From: cui Date: Tue, 14 Jul 2026 22:06:57 +0800 Subject: [PATCH 3/6] eth/protocols/eth: discard message before size check (#35289) - Move `msg.Discard` defer ahead of the max message size check in `handleMessage`. - Ensures oversized messages are released when the handler returns early. --- eth/protocols/eth/handler.go | 2 +- eth/protocols/snap/handler.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 154b75130c..325ffa0738 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -224,10 +224,10 @@ func handleMessage(backend Backend, peer *Peer) error { if err != nil { return err } + defer msg.Discard() if msg.Size > maxMessageSize { return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) } - defer msg.Discard() var handlers map[uint64]msgHandler switch peer.version { diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index ff5564ca65..6c2ff6149f 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -160,10 +160,10 @@ func HandleMessage(backend Backend, peer *Peer) error { if err != nil { return err } + defer msg.Discard() if msg.Size > maxMessageSize { return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize) } - defer msg.Discard() var handlers map[uint64]msgHandler switch peer.version { From 3155d3ad14ee895524ab8089f22d05a5be73c497 Mon Sep 17 00:00:00 2001 From: ozpool <151670776+ozpool@users.noreply.github.com> Date: Tue, 14 Jul 2026 20:26:22 +0530 Subject: [PATCH 4/6] core/rawdb: drop stray %d verb from freezer metadata log message (#35351) --- core/rawdb/freezer_meta.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rawdb/freezer_meta.go b/core/rawdb/freezer_meta.go index 03c26f7daf..0d28ee181f 100644 --- a/core/rawdb/freezer_meta.go +++ b/core/rawdb/freezer_meta.go @@ -112,7 +112,7 @@ func decodeV2(file *os.File) *freezerTableMeta { return nil } if o.Offset > math.MaxInt64 { - log.Error("Invalid flushOffset %d in freezer metadata", "offset", o.Offset, "file", file.Name()) + log.Error("Invalid flushOffset in freezer metadata", "offset", o.Offset, "file", file.Name()) return nil } return &freezerTableMeta{ From d5d936d76cfa90fe59d064c38fb77c97797004af Mon Sep 17 00:00:00 2001 From: Jonny Rhea <5555162+jrhea@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:24:07 -0500 Subject: [PATCH 5/6] eth/catalyst: handle bogota fork in payloadVersion (#35355) #34057 added the Bogota fork, but didn't add it to payloadVersion. This PR fixes the CI error. --- 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 7120c14501..8637d9e3cc 100644 --- a/eth/catalyst/simulated_beacon.go +++ b/eth/catalyst/simulated_beacon.go @@ -105,7 +105,7 @@ func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersi switch config.LatestFork(time) { case forks.Amsterdam: return engine.PayloadV4 - case forks.BPO5, forks.BPO4, forks.BPO3, forks.BPO2, forks.BPO1, forks.Osaka, forks.Prague, forks.Cancun: + case forks.Bogota, 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 c3f28518726a1ffe0051282f66666abb88710d18 Mon Sep 17 00:00:00 2001 From: Sina M <1591639+s1na@users.noreply.github.com> Date: Tue, 14 Jul 2026 20:00:47 +0200 Subject: [PATCH 6/6] cmd/utils: tune GOGC for large cache values (#34851) --------- Co-authored-by: jonny rhea <5555162+jrhea@users.noreply.github.com> --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 0108867f5a..b5802f51e7 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -101,6 +101,7 @@ var ( utils.CachePreimagesFlag, utils.CacheLogSizeFlag, utils.FDLimitFlag, + utils.MemoryLimitFlag, utils.CryptoKZGFlag, utils.ListenPortFlag, utils.DiscoveryPortFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 4082717930..8b06772d87 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -554,6 +554,11 @@ var ( Usage: "Raise the open file descriptor resource limit (default = system fd limit)", Category: flags.PerfCategory, } + MemoryLimitFlag = &cli.IntFlag{ + Name: "memorylimit", + Usage: "Soft memory limit for the Go runtime in megabytes (default = no limit)", + Category: flags.PerfCategory, + } CryptoKZGFlag = &cli.StringFlag{ Name: "crypto.kzg", Usage: "KZG library implementation to use; gokzg (recommended) or ckzg", @@ -1762,12 +1767,13 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { ctx.Set(CacheFlag.Name, strconv.Itoa(allowance)) } } - // Ensure Go's GC ignores the database cache for trigger percentage - cache := ctx.Int(CacheFlag.Name) - gogc := max(20, min(100, 100/(float64(cache)/1024))) - log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) - godebug.SetGCPercent(int(gogc)) + godebug.SetGCPercent(100) + if ctx.IsSet(MemoryLimitFlag.Name) { + memLimit := int64(ctx.Int(MemoryLimitFlag.Name)) * 1024 * 1024 + log.Debug("Setting Go memory limit", "MB", memLimit/1024/1024) + godebug.SetMemoryLimit(memLimit) + } if ctx.IsSet(SyncTargetFlag.Name) { cfg.SyncMode = ethconfig.FullSync // dev sync target forces full sync