Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ var (
utils.RollupInteropRPCFlag,
utils.RollupInteropMempoolFilteringFlag,
utils.RollupDisableTxPoolGossipFlag,
utils.RollupNetrestrictTxPoolGossipFlag,
utils.RollupTxPoolTrustedPeersOnlyFlag,
utils.RollupEnableTxPoolAdmissionFlag,
utils.RollupComputePendingBlock,
utils.RollupHaltOnIncompatibleProtocolVersionFlag,
Expand Down
12 changes: 12 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,16 @@ var (
Usage: "Disable transaction pool gossip.",
Category: flags.RollupCategory,
}
RollupNetrestrictTxPoolGossipFlag = &cli.StringFlag{
Name: "rollup.netrestricttxpoolgossip",
Usage: "Restricts transaction pool gossip to the given IP networks (CIDR masks)",
Category: flags.RollupCategory,
}
RollupTxPoolTrustedPeersOnlyFlag = &cli.BoolFlag{
Name: "rollup.txpooltrustedpeersonly",
Usage: "Restricts transaction pool gossip and acceptance to trusted peers only",
Category: flags.RollupCategory,
}
RollupEnableTxPoolAdmissionFlag = &cli.BoolFlag{
Name: "rollup.enabletxpooladmission",
Usage: "Add RPC-submitted transactions to the txpool (on by default if --rollup.sequencerhttp is not set).",
Expand Down Expand Up @@ -1924,6 +1934,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.InteropMempoolFiltering = ctx.Bool(RollupInteropMempoolFilteringFlag.Name)
}
cfg.RollupDisableTxPoolGossip = ctx.Bool(RollupDisableTxPoolGossipFlag.Name)
cfg.RollupNetrestrictTxPoolGossip = ctx.String(RollupNetrestrictTxPoolGossipFlag.Name)
cfg.RollupTxPoolTrustedPeersOnly = ctx.Bool(RollupTxPoolTrustedPeersOnlyFlag.Name)
cfg.RollupDisableTxPoolAdmission = cfg.RollupSequencerHTTP != "" && !ctx.Bool(RollupEnableTxPoolAdmissionFlag.Name)
cfg.RollupHaltOnIncompatibleProtocolVersion = ctx.String(RollupHaltOnIncompatibleProtocolVersionFlag.Name)
cfg.ApplySuperchainUpgrades = ctx.Bool(RollupSuperchainUpgradesFlag.Name)
Expand Down
3 changes: 2 additions & 1 deletion core/txpool/subpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ type PendingFilter struct {
OnlyPlainTxs bool // Return only plain EVM transactions (peer-join announces, block space filling)
OnlyBlobTxs bool // Return only blob transactions (block blob-space filling)

// OP stack addition: Maximum l1 data size allowed for an included transaction (for throttling
// OP Stack additions
// Maximum l1 data size allowed for an included transaction (for throttling
// when batcher is backlogged). Ignored if nil.
MaxDATxSize *big.Int
}
Expand Down
24 changes: 23 additions & 1 deletion eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import (
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/dnsdisc"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
Expand Down Expand Up @@ -379,6 +380,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
stack.RegisterLifecycle(pj)
}

txGossipNetRestrict, err := parseTxGossipNetRestrict(config.RollupNetrestrictTxPoolGossip)
if err != nil {
return nil, err
}

// Permit the downloader to use the trie cache allowance during fast sync
cacheLimit := options.TrieCleanLimit + options.TrieDirtyLimit + options.SnapshotLimit
if eth.handler, err = newHandler(&handlerConfig{
Expand All @@ -391,7 +397,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
BloomCache: uint64(cacheLimit),
EventMux: eth.eventMux,
RequiredBlocks: config.RequiredBlocks,
NoTxGossip: config.RollupDisableTxPoolGossip,

// OP Stack additions
NoTxGossip: config.RollupDisableTxPoolGossip,
TxGossipNetRestrict: txGossipNetRestrict,
TxGossipTrustedPeersOnly: config.RollupTxPoolTrustedPeersOnly,
}); err != nil {
return nil, err
}
Expand Down Expand Up @@ -750,3 +760,15 @@ func (s *Ethereum) HandleRequiredProtocolVersion(required params.ProtocolVersion
}
return nil
}

// parseTxGossipNetRestrict parses the netrestrict string for txpool gossip
func parseTxGossipNetRestrict(netrestrict string) (*netutil.Netlist, error) {
if netrestrict == "" {
return nil, nil
}
list, err := netutil.ParseNetlist(netrestrict)
if err != nil {
return nil, fmt.Errorf("invalid txpool gossip netrestrict list: %w", err)
}
return list, nil
}
2 changes: 2 additions & 0 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ type Config struct {
RollupHistoricalRPC string
RollupHistoricalRPCTimeout time.Duration
RollupDisableTxPoolGossip bool
RollupNetrestrictTxPoolGossip string `toml:",omitempty"` // Netrestrict for transaction gossip
RollupTxPoolTrustedPeersOnly bool // Restrict tx pool gossip to trusted peers only
RollupDisableTxPoolAdmission bool
RollupHaltOnIncompatibleProtocolVersion string

Expand Down
12 changes: 12 additions & 0 deletions eth/ethconfig/gen_config.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 14 additions & 3 deletions eth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/netutil"
)

const (
Expand Down Expand Up @@ -106,7 +107,11 @@ type handlerConfig struct {
BloomCache uint64 // Megabytes to alloc for snap sync bloom
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
NoTxGossip bool // Disable P2P transaction gossip

// OP Stack additions
NoTxGossip bool // Disable P2P transaction gossip
TxGossipNetRestrict *netutil.Netlist // Restrict tx gossip to specific IP networks
TxGossipTrustedPeersOnly bool // Restrict tx gossip to trusted peers only
}

type handler struct {
Expand All @@ -121,7 +126,9 @@ type handler struct {
chain *core.BlockChain
maxPeers int

noTxGossip bool
noTxGossip bool
txGossipNetRestrict *netutil.Netlist
txGossipTrustedPeersOnly bool

downloader *downloader.Downloader
txFetcher *fetcher.TxFetcher
Expand Down Expand Up @@ -156,14 +163,18 @@ func newHandler(config *handlerConfig) (*handler, error) {
eventMux: config.EventMux,
database: config.Database,
txpool: config.TxPool,
noTxGossip: config.NoTxGossip,
chain: config.Chain,
peers: newPeerSet(),
txBroadcastKey: newBroadcastChoiceKey(),
requiredBlocks: config.RequiredBlocks,
quitSync: make(chan struct{}),
handlerDoneCh: make(chan struct{}),
handlerStartCh: make(chan struct{}),

// OP Stack additions
noTxGossip: config.NoTxGossip,
txGossipNetRestrict: config.TxGossipNetRestrict,
txGossipTrustedPeersOnly: config.TxGossipTrustedPeersOnly,
}
if config.Sync == ethconfig.FullSync {
// The database seems empty as the current block is the genesis. Yet the snap
Expand Down
16 changes: 10 additions & 6 deletions eth/handler_eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
)

Expand All @@ -42,11 +43,13 @@ func (n NilPool) Get(common.Hash) *types.Transaction { return nil }
func (n NilPool) GetRLP(common.Hash) []byte { return nil }
func (n NilPool) GetMetadata(hash common.Hash) *txpool.TxMetadata { return nil }

func (h *ethHandler) TxPool() eth.TxPool {
if h.noTxGossip {
return &NilPool{}
func (h *ethHandler) TxPool(peer *p2p.Peer) (eth.TxPool, bool) {
if h.noTxGossip ||
(h.txGossipTrustedPeersOnly && !peer.Trusted()) ||
(h.txGossipNetRestrict != nil && !h.txGossipNetRestrict.ContainsAddr(peer.Node().IPAddr())) {
return &NilPool{}, false
}
return h.txpool
return h.txpool, true
}

// RunPeer is invoked when a peer joins on the `eth` protocol.
Expand All @@ -64,8 +67,9 @@ func (h *ethHandler) PeerInfo(id enode.ID) interface{} {

// AcceptTxs retrieves whether transaction processing is enabled on the node
// or if inbound transactions should simply be dropped.
func (h *ethHandler) AcceptTxs() bool {
if h.noTxGossip {
func (h *ethHandler) AcceptTxs(peer *eth.Peer) bool {
// Check if peer is allowed for transaction gossip
if !peer.IsAllowedForTxGossip() {
return false
}
return h.synced.Load()
Expand Down
4 changes: 2 additions & 2 deletions eth/handler_eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ type testEthHandler struct {
}

func (h *testEthHandler) Chain() *core.BlockChain { panic("no backing chain") }
func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") }
func (h *testEthHandler) AcceptTxs() bool { return true }
func (h *testEthHandler) TxPool(*p2p.Peer) (eth.TxPool, bool) { panic("no backing tx pool") }
func (h *testEthHandler) AcceptTxs(*eth.Peer) bool { return true }
func (h *testEthHandler) RunPeer(*eth.Peer, eth.Handler) error { panic("not used in tests") }
func (h *testEthHandler) PeerInfo(enode.ID) interface{} { panic("not used in tests") }

Expand Down
98 changes: 98 additions & 0 deletions eth/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package eth

import (
"fmt"
"maps"
"math/big"
"math/rand"
"net/netip"
"sort"
"sync"
"testing"
Expand All @@ -37,6 +39,8 @@ import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
Expand Down Expand Up @@ -317,3 +321,97 @@ func closePeers(peers []*ethPeer) {
p.Close()
}
}

// TestHandlerTxPool tests that the handler correctly assigns TxPool vs NilPool
// based on the txGossipNetRestrict configuration.
func TestHandlerTxPool(t *testing.T) {
t.Parallel()

// 8 nodes with different IPs - 4 in allowed range, 4 in restricted range
nodes := []struct {
ip string
}{
{ip: "127.0.0.1"}, // Allowed (127.0.0.0/8)
{ip: "127.0.0.2"}, // Allowed (127.0.0.0/8)
{ip: "127.0.0.3"}, // Allowed (127.0.0.0/8)
{ip: "127.0.0.4"}, // Allowed (127.0.0.0/8)
{ip: "192.168.1.1"}, // Restricted
{ip: "192.168.1.2"}, // Restricted
{ip: "10.0.0.1"}, // Restricted
{ip: "10.0.0.2"}, // Restricted
}

db := rawdb.NewMemoryDatabase()
gspec := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}},
}
chain, _ := core.NewBlockChain(db, gspec, ethash.NewFaker(), nil)
txpool := newTestTxPool()

// Set up netrestrict to allow only 127.0.0.0/8 range
netrestrict := new(netutil.Netlist)
netrestrict.Add("127.0.0.0/8")

handler, err := newHandler(&handlerConfig{
Database: db,
Chain: chain,
TxPool: txpool,
TxGossipNetRestrict: netrestrict,
})
if err != nil {
t.Fatalf("Failed to create handler: %v", err)
}
handler.Start(1000)
defer handler.Stop()

// Test each node's IP
ethHandler := (*ethHandler)(handler)

// Expected: first 4 nodes should get real TxPool, last 4 should get NilPool
expectedTxPoolCount := 0
expectedNilPoolCount := 0

for i, node := range nodes {
ip, err := netip.ParseAddr(node.ip)
if err != nil {
t.Fatalf("Failed to parse IP %s: %v", node.ip, err)
}

var r enr.Record
r.Set(enr.IPv4Addr(ip))
enode := enode.SignNull(&r, enode.ID{})
p := p2p.NewPeerFromNode(enode, fmt.Sprintf("test-peer-%d", i), nil)

txPool, allowed := ethHandler.TxPool(p)

// Check if we got a real TxPool or NilPool
if _, ok := txPool.(*testTxPool); ok {
expectedTxPoolCount++
if i >= 4 {
t.Errorf("Node %d (%s) should have gotten NilPool but got real TxPool", i, node.ip)
}
if !allowed {
t.Errorf("Node %d (%s) should have gotten allowed for gossiping but got not allowed", i, node.ip)
}
} else if _, ok := txPool.(*NilPool); ok {
expectedNilPoolCount++
if i < 4 {
t.Errorf("Node %d (%s) should have gotten real TxPool but got NilPool", i, node.ip)
}
if allowed {
t.Errorf("Node %d (%s) should have gotten not allowed for gossiping but got allowed", i, node.ip)
}
} else {
t.Errorf("Node %d (%s) got unexpected TxPool type: %T", i, node.ip, txPool)
}
}

// Verify we got exactly 4 of each type
if expectedTxPoolCount != 4 {
t.Errorf("Expected 4 nodes with real TxPool, got %d", expectedTxPoolCount)
}
if expectedNilPoolCount != 4 {
t.Errorf("Expected 4 nodes with NilPool, got %d", expectedNilPoolCount)
}
}
11 changes: 7 additions & 4 deletions eth/protocols/eth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,13 @@ type Backend interface {
// Chain retrieves the blockchain object to serve data.
Chain() *core.BlockChain

// TxPool retrieves the transaction pool object to serve data.
TxPool() TxPool
// TxPool retrieves the transaction pool object to serve data, depending
// on the IP address of the peer, if txpool gossip filtering is enabled.
TxPool(peer *p2p.Peer) (TxPool, bool)

// AcceptTxs retrieves whether transaction processing is enabled on the node
// or if inbound transactions should simply be dropped.
AcceptTxs() bool
AcceptTxs(peer *Peer) bool

// RunPeer is invoked when a peer joins on the `eth` protocol. The handler
// should do any peer maintenance work, handshakes and validations. If all
Expand Down Expand Up @@ -106,7 +107,9 @@ func MakeProtocols(backend Backend, network uint64, disc enode.Iterator) []p2p.P
Version: version,
Length: protocolLengths[version],
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
peer := NewPeer(version, p, rw, backend.TxPool())
txpool, allowed := backend.TxPool(p)
peer := NewPeer(version, p, rw, txpool)
peer.SetAllowedForTxGossip(allowed)
defer peer.Close()

return backend.RunPeer(peer, func(peer *Peer) error {
Expand Down
Loading