diff --git a/CHANGELOG.md b/CHANGELOG.md index 342c7d6809..cef1a27d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Replace persistent P2P connection gater with a no-op variant so stale blocklist entries can no longer prevent peers from connecting after a restart. The new `p2p.disable_connection_gater` flag (default `true`) can be set to `false` to re-enable peer filtering when experiencing P2P flooding [#3273](https://github.com/evstack/ev-node/pull/3273) - Raft HA production hardening: leader fencing on SIGTERM, FSM data race, follower restart crash, log compaction config, and election timeout validation [#3230](https://github.com/evstack/ev-node/pull/3230) ### Changes diff --git a/pkg/config/config.go b/pkg/config/config.go index 850f098dc7..87e12f5597 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -102,6 +102,9 @@ const ( FlagP2PBlockedPeers = FlagPrefixEvnode + "p2p.blocked_peers" // FlagP2PAllowedPeers is a flag for specifying the P2P allowed peers FlagP2PAllowedPeers = FlagPrefixEvnode + "p2p.allowed_peers" + // FlagP2PDisableConnectionGater disables the P2P connection gater (no-op mode). + // Enabled by default; set to false to activate peer filtering when experiencing P2P flooding. + FlagP2PDisableConnectionGater = FlagPrefixEvnode + "p2p.disable_connection_gater" // Instrumentation configuration flags @@ -313,10 +316,11 @@ type LogConfig struct { // P2PConfig contains all peer-to-peer networking configuration parameters type P2PConfig struct { - ListenAddress string `mapstructure:"listen_address" yaml:"listen_address" comment:"Address to listen for incoming connections (host:port)"` - Peers string `mapstructure:"peers" yaml:"peers" comment:"Comma-separated list of peers to connect to"` - BlockedPeers string `mapstructure:"blocked_peers" yaml:"blocked_peers" comment:"Comma-separated list of peer IDs to block from connecting"` - AllowedPeers string `mapstructure:"allowed_peers" yaml:"allowed_peers" comment:"Comma-separated list of peer IDs to allow connections from"` + ListenAddress string `mapstructure:"listen_address" yaml:"listen_address" comment:"Address to listen for incoming connections (host:port)"` + Peers string `mapstructure:"peers" yaml:"peers" comment:"Comma-separated list of peers to connect to"` + BlockedPeers string `mapstructure:"blocked_peers" yaml:"blocked_peers" comment:"Comma-separated list of peer IDs to block from connecting"` + AllowedPeers string `mapstructure:"allowed_peers" yaml:"allowed_peers" comment:"Comma-separated list of peer IDs to allow connections from"` + DisableConnectionGater bool `mapstructure:"disable_connection_gater" yaml:"disable_connection_gater" comment:"Disable the P2P connection gater (no-op mode). Set to false to enforce peer filtering when experiencing P2P flooding."` } // SignerConfig contains all signer configuration parameters @@ -621,6 +625,7 @@ func AddFlags(cmd *cobra.Command) { cmd.Flags().String(FlagP2PPeers, def.P2P.Peers, "Comma separated list of seed nodes to connect to") cmd.Flags().String(FlagP2PBlockedPeers, def.P2P.BlockedPeers, "Comma separated list of nodes to ignore") cmd.Flags().String(FlagP2PAllowedPeers, def.P2P.AllowedPeers, "Comma separated list of nodes to whitelist") + cmd.Flags().Bool(FlagP2PDisableConnectionGater, def.P2P.DisableConnectionGater, "Disable P2P connection gater (no-op mode); set to false to enforce peer filtering when experiencing P2P flooding") // RPC configuration flags cmd.Flags().String(FlagRPCAddress, def.RPC.Address, "RPC server address (host:port)") diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 2cb4792189..02040ebba9 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -84,6 +84,7 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagP2PPeers, DefaultConfig().P2P.Peers) assertFlagValue(t, flags, FlagP2PBlockedPeers, DefaultConfig().P2P.BlockedPeers) assertFlagValue(t, flags, FlagP2PAllowedPeers, DefaultConfig().P2P.AllowedPeers) + assertFlagValue(t, flags, FlagP2PDisableConnectionGater, DefaultConfig().P2P.DisableConnectionGater) // Instrumentation flags instrDef := DefaultInstrumentationConfig() @@ -143,7 +144,7 @@ func TestAddFlags(t *testing.T) { assertFlagValue(t, flags, FlagPruningInterval, DefaultConfig().Pruning.Interval.Duration) // Count the number of flags we're explicitly checking - expectedFlagCount := 81 // Update this number if you add more flag checks above + expectedFlagCount := 82 // Update this number if you add more flag checks above // Get the actual number of flags (both regular and persistent) actualFlagCount := 0 diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 2a8d2b4129..233585e0c5 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -57,8 +57,9 @@ func DefaultConfig() Config { RootDir: DefaultRootDir, DBPath: "data", P2P: P2PConfig{ - ListenAddress: "/ip4/0.0.0.0/tcp/7676", - Peers: "", + ListenAddress: "/ip4/0.0.0.0/tcp/7676", + Peers: "", + DisableConnectionGater: true, }, Node: NodeConfig{ Aggregator: false, diff --git a/pkg/p2p/client.go b/pkg/p2p/client.go index e89b96b30b..0cc5116778 100644 --- a/pkg/p2p/client.go +++ b/pkg/p2p/client.go @@ -82,7 +82,17 @@ func NewClient( metrics *Metrics, ) (*Client, error) { - gater, err := conngater.NewBasicConnectionGater(ds) + // When DisableConnectionGater is true (default) the gater is a no-op: it + // uses an ephemeral in-memory store, is not registered with the libp2p host, + // and never blocks any peer. The instance is kept only because go-header's + // Exchange requires a *conngater.BasicConnectionGater parameter. + // Set DisableConnectionGater=false to activate peer filtering (e.g. when + // experiencing P2P flooding). + var gaterDS datastore.Datastore + if !conf.DisableConnectionGater { + gaterDS = datastore.NewMapDatastore() + } + gater, err := conngater.NewBasicConnectionGater(gaterDS) if err != nil { return nil, fmt.Errorf("failed to create connection gater: %w", err) } @@ -156,14 +166,15 @@ func (c *Client) startWithHost(ctx context.Context, h host.Host) error { c.logger.Info().Str("address", fmt.Sprintf("%s/p2p/%s", a, c.host.ID())).Msg("listening on address") } - c.logger.Debug().Str("blacklist", c.conf.BlockedPeers).Msg("blocking blacklisted peers") - if err := c.setupBlockedPeers(c.parseAddrInfoList(c.conf.BlockedPeers)); err != nil { - return err - } - - c.logger.Debug().Str("whitelist", c.conf.AllowedPeers).Msg("allowing whitelisted peers") - if err := c.setupAllowedPeers(c.parseAddrInfoList(c.conf.AllowedPeers)); err != nil { - return err + if !c.conf.DisableConnectionGater { + c.logger.Debug().Str("blacklist", c.conf.BlockedPeers).Msg("blocking blacklisted peers") + if err := c.setupBlockedPeers(c.parseAddrInfoList(c.conf.BlockedPeers)); err != nil { + return err + } + c.logger.Debug().Str("whitelist", c.conf.AllowedPeers).Msg("allowing whitelisted peers") + if err := c.setupAllowedPeers(c.parseAddrInfoList(c.conf.AllowedPeers)); err != nil { + return err + } } c.logger.Debug().Msg("setting up gossiping") @@ -340,7 +351,11 @@ func (c *Client) listen() (host.Host, error) { return nil, err } - return libp2p.New(libp2p.ListenAddrs(maddr), libp2p.Identity(c.privKey), libp2p.ConnectionGater(c.gater)) + opts := []libp2p.Option{libp2p.ListenAddrs(maddr), libp2p.Identity(c.privKey)} + if !c.conf.DisableConnectionGater { + opts = append(opts, libp2p.ConnectionGater(c.gater)) + } + return libp2p.New(opts...) } func (c *Client) setupDHT(ctx context.Context) error {