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: + } + } +} 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/cmd/geth/main.go b/cmd/geth/main.go index 2c3c4fbfd1..4aadb5fb87 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -102,6 +102,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 cec743f3f7..f264ed58e8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -561,6 +561,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", @@ -1772,12 +1777,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 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{ 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 diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index dd78d560a5..83a1275f46 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -258,10 +258,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 { 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()) + } +}