Skip to content

Commit d401af1

Browse files
nonsensepcw109550
andauthored
eth, cmd: add RollupNetrestrictTxPoolGossipFlag and RollupTxPoolTrustedPeersOnlyFlag (#706)
* eth, cmd: add RollupNetrestrictTxPoolGossipFlag * fork.yaml: description of fork changes for tx gossip netrestrict * eth: h.syncTransactions behind check for netrestrict for txpool gossip * eth, eth/protocols: propagate netrestrict to Peer and limit incoming/outgoing message. * eth, cmd: add RollupNetrestrictTxPoolGossipFlag PoC using `NilPool` (#713) * Make peer struct not aware of ip net restrict * Propagate Peer ip info to txpool * fix * cleanup * eth/handler_test: unit test for TxGossipNetRestrict and TxPool() method * eth: add short-circuit for incoming messages * eth: rely on AcceptTxs * eth: all tx propagating by default * consistent OP Stack additions comment * eth, cmd: restrict mempool only to trusted peers (#719) * address comments * eth/handler_test: modify unit test to check for trusted conn too * add txGossipAllowed * correct comments * update fork.yaml --------- Co-authored-by: Changwan Park <[email protected]>
1 parent 020b543 commit d401af1

File tree

16 files changed

+224
-35
lines changed

16 files changed

+224
-35
lines changed

cmd/geth/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,10 @@ var (
159159
utils.RollupHistoricalRPCTimeoutFlag,
160160
utils.RollupInteropRPCFlag,
161161
utils.RollupInteropMempoolFilteringFlag,
162-
utils.RollupDisableTxPoolGossipFlag,
163-
utils.RollupEnableTxPoolAdmissionFlag,
162+
utils.RollupTxPoolDisableGossipFlag,
163+
utils.RollupTxPoolNetrestrictFlag,
164+
utils.RollupTxPoolTrustedPeersOnlyFlag,
165+
utils.RollupTxPoolEnableAdmissionFlag,
164166
utils.RollupComputePendingBlock,
165167
utils.RollupHaltOnIncompatibleProtocolVersionFlag,
166168
utils.RollupSuperchainUpgradesFlag,

cmd/utils/flags.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -984,15 +984,28 @@ var (
984984
Category: flags.RollupCategory,
985985
}
986986

987-
RollupDisableTxPoolGossipFlag = &cli.BoolFlag{
988-
Name: "rollup.disabletxpoolgossip",
987+
RollupTxPoolDisableGossipFlag = &cli.BoolFlag{
988+
Name: "rollup.txpool.disable-gossip",
989989
Usage: "Disable transaction pool gossip.",
990990
Category: flags.RollupCategory,
991+
Aliases: []string{"rollup.disabletxpoolgossip"},
991992
}
992-
RollupEnableTxPoolAdmissionFlag = &cli.BoolFlag{
993-
Name: "rollup.enabletxpooladmission",
993+
RollupTxPoolNetrestrictFlag = &cli.StringFlag{
994+
Name: "rollup.txpool.netrestrict",
995+
Usage: "Restricts transaction pool gossip to the given IP networks (CIDR masks)",
996+
Category: flags.RollupCategory,
997+
}
998+
RollupTxPoolTrustedPeersOnlyFlag = &cli.BoolFlag{
999+
Name: "rollup.txpool.trusted-peers-only",
1000+
Usage: "Restricts transaction pool gossip and acceptance to trusted peers only",
1001+
Category: flags.RollupCategory,
1002+
Value: false,
1003+
}
1004+
RollupTxPoolEnableAdmissionFlag = &cli.BoolFlag{
1005+
Name: "rollup.txpool.enable-admission",
9941006
Usage: "Add RPC-submitted transactions to the txpool (on by default if --rollup.sequencerhttp is not set).",
9951007
Category: flags.RollupCategory,
1008+
Aliases: []string{"rollup.enabletxpooladmission"},
9961009
}
9971010
RollupComputePendingBlock = &cli.BoolFlag{
9981011
Name: "rollup.computependingblock",
@@ -1923,8 +1936,10 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
19231936
if ctx.IsSet(RollupInteropMempoolFilteringFlag.Name) {
19241937
cfg.InteropMempoolFiltering = ctx.Bool(RollupInteropMempoolFilteringFlag.Name)
19251938
}
1926-
cfg.RollupDisableTxPoolGossip = ctx.Bool(RollupDisableTxPoolGossipFlag.Name)
1927-
cfg.RollupDisableTxPoolAdmission = cfg.RollupSequencerHTTP != "" && !ctx.Bool(RollupEnableTxPoolAdmissionFlag.Name)
1939+
cfg.RollupDisableTxPoolGossip = ctx.Bool(RollupTxPoolDisableGossipFlag.Name)
1940+
cfg.RollupTxPoolNetrestrict = ctx.String(RollupTxPoolNetrestrictFlag.Name)
1941+
cfg.RollupTxPoolTrustedPeersOnly = ctx.Bool(RollupTxPoolTrustedPeersOnlyFlag.Name)
1942+
cfg.RollupDisableTxPoolAdmission = cfg.RollupSequencerHTTP != "" && !ctx.Bool(RollupTxPoolEnableAdmissionFlag.Name)
19281943
cfg.RollupHaltOnIncompatibleProtocolVersion = ctx.String(RollupHaltOnIncompatibleProtocolVersionFlag.Name)
19291944
cfg.ApplySuperchainUpgrades = ctx.Bool(RollupSuperchainUpgradesFlag.Name)
19301945
cfg.RollupSequencerTxConditionalEnabled = ctx.Bool(RollupSequencerTxConditionalEnabledFlag.Name)

core/txpool/subpool.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ type PendingFilter struct {
8383
OnlyPlainTxs bool // Return only plain EVM transactions (peer-join announces, block space filling)
8484
OnlyBlobTxs bool // Return only blob transactions (block blob-space filling)
8585

86-
// OP stack addition: Maximum l1 data size allowed for an included transaction (for throttling
86+
// OP Stack additions
87+
// Maximum l1 data size allowed for an included transaction (for throttling
8788
// when batcher is backlogged). Ignored if nil.
8889
MaxDATxSize *big.Int
8990
}

eth/backend.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import (
6464
"github.com/ethereum/go-ethereum/p2p"
6565
"github.com/ethereum/go-ethereum/p2p/dnsdisc"
6666
"github.com/ethereum/go-ethereum/p2p/enode"
67+
"github.com/ethereum/go-ethereum/p2p/netutil"
6768
"github.com/ethereum/go-ethereum/params"
6869
"github.com/ethereum/go-ethereum/rlp"
6970
"github.com/ethereum/go-ethereum/rpc"
@@ -379,6 +380,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
379380
stack.RegisterLifecycle(pj)
380381
}
381382

383+
txGossipNetRestrict, err := parseTxGossipNetRestrict(config.RollupTxPoolNetrestrict)
384+
if err != nil {
385+
return nil, err
386+
}
387+
382388
// Permit the downloader to use the trie cache allowance during fast sync
383389
cacheLimit := options.TrieCleanLimit + options.TrieDirtyLimit + options.SnapshotLimit
384390
if eth.handler, err = newHandler(&handlerConfig{
@@ -391,7 +397,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
391397
BloomCache: uint64(cacheLimit),
392398
EventMux: eth.eventMux,
393399
RequiredBlocks: config.RequiredBlocks,
394-
NoTxGossip: config.RollupDisableTxPoolGossip,
400+
401+
// OP Stack additions
402+
NoTxGossip: config.RollupDisableTxPoolGossip,
403+
TxGossipNetRestrict: txGossipNetRestrict,
404+
TxGossipTrustedPeersOnly: config.RollupTxPoolTrustedPeersOnly,
395405
}); err != nil {
396406
return nil, err
397407
}
@@ -750,3 +760,15 @@ func (s *Ethereum) HandleRequiredProtocolVersion(required params.ProtocolVersion
750760
}
751761
return nil
752762
}
763+
764+
// parseTxGossipNetRestrict parses the netrestrict string for txpool gossip
765+
func parseTxGossipNetRestrict(netrestrict string) (*netutil.Netlist, error) {
766+
if netrestrict == "" {
767+
return nil, nil
768+
}
769+
list, err := netutil.ParseNetlist(netrestrict)
770+
if err != nil {
771+
return nil, fmt.Errorf("invalid txpool gossip netrestrict list: %w", err)
772+
}
773+
return list, nil
774+
}

eth/ethconfig/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ type Config struct {
190190
RollupHistoricalRPC string
191191
RollupHistoricalRPCTimeout time.Duration
192192
RollupDisableTxPoolGossip bool
193+
RollupTxPoolNetrestrict string `toml:",omitempty"` // Netrestrict for transaction gossip
194+
RollupTxPoolTrustedPeersOnly bool // Restrict tx pool gossip to trusted peers only
193195
RollupDisableTxPoolAdmission bool
194196
RollupHaltOnIncompatibleProtocolVersion string
195197

eth/ethconfig/gen_config.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eth/handler.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
"github.com/ethereum/go-ethereum/metrics"
4545
"github.com/ethereum/go-ethereum/p2p"
4646
"github.com/ethereum/go-ethereum/p2p/enode"
47+
"github.com/ethereum/go-ethereum/p2p/netutil"
4748
)
4849

4950
const (
@@ -106,7 +107,11 @@ type handlerConfig struct {
106107
BloomCache uint64 // Megabytes to alloc for snap sync bloom
107108
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
108109
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
109-
NoTxGossip bool // Disable P2P transaction gossip
110+
111+
// OP Stack additions
112+
NoTxGossip bool // Disable P2P transaction gossip
113+
TxGossipNetRestrict *netutil.Netlist // Restrict tx gossip to specific IP networks
114+
TxGossipTrustedPeersOnly bool // Restrict tx gossip to trusted peers only
110115
}
111116

112117
type handler struct {
@@ -121,7 +126,9 @@ type handler struct {
121126
chain *core.BlockChain
122127
maxPeers int
123128

124-
noTxGossip bool
129+
noTxGossip bool
130+
txGossipNetRestrict *netutil.Netlist
131+
txGossipTrustedPeersOnly bool
125132

126133
downloader *downloader.Downloader
127134
txFetcher *fetcher.TxFetcher
@@ -156,14 +163,18 @@ func newHandler(config *handlerConfig) (*handler, error) {
156163
eventMux: config.EventMux,
157164
database: config.Database,
158165
txpool: config.TxPool,
159-
noTxGossip: config.NoTxGossip,
160166
chain: config.Chain,
161167
peers: newPeerSet(),
162168
txBroadcastKey: newBroadcastChoiceKey(),
163169
requiredBlocks: config.RequiredBlocks,
164170
quitSync: make(chan struct{}),
165171
handlerDoneCh: make(chan struct{}),
166172
handlerStartCh: make(chan struct{}),
173+
174+
// OP Stack additions
175+
noTxGossip: config.NoTxGossip,
176+
txGossipNetRestrict: config.TxGossipNetRestrict,
177+
txGossipTrustedPeersOnly: config.TxGossipTrustedPeersOnly,
167178
}
168179
if config.Sync == ethconfig.FullSync {
169180
// The database seems empty as the current block is the genesis. Yet the snap

eth/handler_eth.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/ethereum/go-ethereum/core/txpool"
2626
"github.com/ethereum/go-ethereum/core/types"
2727
"github.com/ethereum/go-ethereum/eth/protocols/eth"
28+
"github.com/ethereum/go-ethereum/p2p"
2829
"github.com/ethereum/go-ethereum/p2p/enode"
2930
)
3031

@@ -42,8 +43,14 @@ func (n NilPool) Get(common.Hash) *types.Transaction { return nil }
4243
func (n NilPool) GetRLP(common.Hash) []byte { return nil }
4344
func (n NilPool) GetMetadata(hash common.Hash) *txpool.TxMetadata { return nil }
4445

45-
func (h *ethHandler) TxPool() eth.TxPool {
46-
if h.noTxGossip {
46+
func (h *ethHandler) txGossipAllowed(peer *p2p.Peer) bool {
47+
return !(h.noTxGossip ||
48+
(h.txGossipTrustedPeersOnly && !peer.Trusted()) ||
49+
(h.txGossipNetRestrict != nil && !h.txGossipNetRestrict.ContainsAddr(peer.Node().IPAddr())))
50+
}
51+
52+
func (h *ethHandler) TxPool(peer *p2p.Peer) eth.TxPool {
53+
if !h.txGossipAllowed(peer) {
4754
return &NilPool{}
4855
}
4956
return h.txpool
@@ -64,8 +71,9 @@ func (h *ethHandler) PeerInfo(id enode.ID) interface{} {
6471

6572
// AcceptTxs retrieves whether transaction processing is enabled on the node
6673
// or if inbound transactions should simply be dropped.
67-
func (h *ethHandler) AcceptTxs() bool {
68-
if h.noTxGossip {
74+
func (h *ethHandler) AcceptTxs(peer *eth.Peer) bool {
75+
// Check if peer is allowed for transaction gossip
76+
if !h.txGossipAllowed(peer.Peer) {
6977
return false
7078
}
7179
return h.synced.Load()

eth/handler_eth_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ type testEthHandler struct {
4444
}
4545

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

eth/handler_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package eth
1818

1919
import (
20+
"fmt"
2021
"maps"
2122
"math/big"
2223
"math/rand"
24+
"net/netip"
2325
"sort"
2426
"sync"
2527
"testing"
@@ -37,6 +39,8 @@ import (
3739
"github.com/ethereum/go-ethereum/event"
3840
"github.com/ethereum/go-ethereum/p2p"
3941
"github.com/ethereum/go-ethereum/p2p/enode"
42+
"github.com/ethereum/go-ethereum/p2p/enr"
43+
"github.com/ethereum/go-ethereum/p2p/netutil"
4044
"github.com/ethereum/go-ethereum/params"
4145
"github.com/ethereum/go-ethereum/rlp"
4246
"github.com/holiman/uint256"
@@ -317,3 +321,100 @@ func closePeers(peers []*ethPeer) {
317321
p.Close()
318322
}
319323
}
324+
325+
// TestHandlerTxPool tests that the handler correctly assigns TxPool vs NilPool
326+
// based on the txGossipNetRestrict configuration.
327+
func TestHandlerTxPool(t *testing.T) {
328+
t.Parallel()
329+
330+
// 8 nodes with different IPs and trusted flags
331+
nodes := []struct {
332+
ip string
333+
trusted bool
334+
}{
335+
{ip: "127.0.0.1", trusted: true}, // Allowed (127.0.0.0/8)
336+
{ip: "127.0.0.2", trusted: true}, // Allowed (127.0.0.0/8)
337+
{ip: "127.0.0.3", trusted: true}, // Allowed (127.0.0.0/8)
338+
{ip: "127.0.0.4", trusted: false}, // Restricted due to trusted flag (127.0.0.0/8)
339+
{ip: "192.168.1.1", trusted: false}, // Restricted
340+
{ip: "192.168.1.2", trusted: false}, // Restricted
341+
{ip: "10.0.0.1", trusted: true}, // Restricted due to network subset
342+
{ip: "10.0.0.2", trusted: true}, // Restricted due to network subset
343+
}
344+
345+
db := rawdb.NewMemoryDatabase()
346+
gspec := &core.Genesis{
347+
Config: params.TestChainConfig,
348+
Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}},
349+
}
350+
chain, _ := core.NewBlockChain(db, gspec, ethash.NewFaker(), nil)
351+
txpool := newTestTxPool()
352+
353+
// Set up netrestrict to allow only 127.0.0.0/8 range
354+
netrestrict := new(netutil.Netlist)
355+
netrestrict.Add("127.0.0.0/8")
356+
357+
handler, err := newHandler(&handlerConfig{
358+
Database: db,
359+
Chain: chain,
360+
TxPool: txpool,
361+
TxGossipNetRestrict: netrestrict,
362+
TxGossipTrustedPeersOnly: true,
363+
})
364+
if err != nil {
365+
t.Fatalf("Failed to create handler: %v", err)
366+
}
367+
handler.Start(1000)
368+
defer handler.Stop()
369+
370+
// Test each node's IP
371+
ethHandler := (*ethHandler)(handler)
372+
373+
// Expected: first 3 nodes should get real TxPool, last 5 should get NilPool
374+
expectedTxPoolCount := 0
375+
expectedNilPoolCount := 0
376+
377+
for i, node := range nodes {
378+
ip, err := netip.ParseAddr(node.ip)
379+
if err != nil {
380+
t.Fatalf("Failed to parse IP %s: %v", node.ip, err)
381+
}
382+
383+
var r enr.Record
384+
r.Set(enr.IPv4Addr(ip))
385+
enode := enode.SignNull(&r, enode.ID{})
386+
p := p2p.NewPeerFromNode(enode, fmt.Sprintf("test-peer-%d", i), nil)
387+
p.TestSetTrusted(node.trusted)
388+
389+
txPool := ethHandler.TxPool(p)
390+
allowed := ethHandler.txGossipAllowed(p)
391+
392+
// Check if we got a real TxPool or NilPool
393+
if _, ok := txPool.(*testTxPool); ok {
394+
expectedTxPoolCount++
395+
if i >= 3 {
396+
t.Errorf("Node %d (%s) should have gotten NilPool but got real TxPool", i, node.ip)
397+
}
398+
if !allowed {
399+
t.Errorf("Node %d (%s) should have gotten allowed for gossiping but got not allowed", i, node.ip)
400+
}
401+
} else if _, ok := txPool.(*NilPool); ok {
402+
expectedNilPoolCount++
403+
if i < 3 {
404+
t.Errorf("Node %d (%s) should have gotten real TxPool but got NilPool", i, node.ip)
405+
}
406+
if allowed {
407+
t.Errorf("Node %d (%s) should have gotten not allowed for gossiping but got allowed", i, node.ip)
408+
}
409+
} else {
410+
t.Errorf("Node %d (%s) got unexpected TxPool type: %T", i, node.ip, txPool)
411+
}
412+
}
413+
414+
if expectedTxPoolCount != 3 {
415+
t.Errorf("Expected 3 nodes with real TxPool, got %d", expectedTxPoolCount)
416+
}
417+
if expectedNilPoolCount != 5 {
418+
t.Errorf("Expected 5 nodes with NilPool, got %d", expectedNilPoolCount)
419+
}
420+
}

0 commit comments

Comments
 (0)