From abd42d3ce01ff45905d356b37c5841f62c443758 Mon Sep 17 00:00:00 2001 From: Sun Hyuk Ahn Date: Tue, 22 Jul 2025 15:14:03 +0900 Subject: [PATCH] feat: quic configurations feat: quic flags --- cmd/bootnode/main.go | 1 + cmd/config/default.go | 3 ++ cmd/config/flags.go | 25 ++++++++++++ cmd/harmony/main.go | 6 +++ internal/configs/harmony/harmony.go | 6 +++ internal/configs/node/network.go | 6 +++ p2p/host.go | 62 ++++++++++++++++++++++++++--- 7 files changed, 104 insertions(+), 5 deletions(-) diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index 470ac8a467..0cdfc63e44 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -101,6 +101,7 @@ func printVersion(me string) { fmt.Fprintf(os.Stderr, "Harmony (C) %d. %v, version %v-%v (%v %v)\n", currentYear, path.Base(me), version, commit, builtBy, builtAt) } +// todo(sun): quic should be enabled but not prioritized? func main() { timestamp := time.Now().Format("20060102150405") defUserAgent := fmt.Sprintf("bootnode-%s", timestamp) diff --git a/cmd/config/default.go b/cmd/config/default.go index e2d2b8eb86..aa2982c38f 100644 --- a/cmd/config/default.go +++ b/cmd/config/default.go @@ -49,6 +49,9 @@ var defaultConfig = harmonyconfig.HarmonyConfig{ DialTimeout: nodeconfig.DefaultDialTimeout, Muxer: nodeconfig.DefaultMuxer, NoRelay: nodeconfig.DefaultNoRelay, + EnableQuic: nodeconfig.DefaultEnableQuic, + QuicPort: nodeconfig.DefaultQuicPort, + QuicPriority: nodeconfig.DefaultQuicPriority, }, HTTP: harmonyconfig.HttpConfig{ Enabled: true, diff --git a/cmd/config/flags.go b/cmd/config/flags.go index 00dda4d61d..7c43bf0ed4 100644 --- a/cmd/config/flags.go +++ b/cmd/config/flags.go @@ -701,6 +701,21 @@ var ( Usage: "no relay services, direct connections between peers only", DefValue: defaultConfig.P2P.NoRelay, } + enableQuicFlag = cli.BoolFlag{ + Name: "p2p.enable-quic", + Usage: "enable QUIC transport", + DefValue: defaultConfig.P2P.EnableQuic, + } + quicPortFlag = cli.IntFlag{ + Name: "p2p.quic-port", + Usage: "port to listen for QUIC protocols", + DefValue: defaultConfig.P2P.QuicPort, + } + quicPriorityFlag = cli.BoolFlag{ + Name: "p2p.quic-priority", + Usage: "prioritize QUIC transport over Tcp", + DefValue: defaultConfig.P2P.QuicPriority, + } ) func applyP2PFlags(cmd *cobra.Command, config *harmonyconfig.HarmonyConfig) { @@ -780,6 +795,16 @@ func applyP2PFlags(cmd *cobra.Command, config *harmonyconfig.HarmonyConfig) { if cli.IsFlagChanged(cmd, noRelayFlag) { config.P2P.NoRelay = cli.GetBoolFlagValue(cmd, noRelayFlag) } + + if cli.IsFlagChanged(cmd, enableQuicFlag) { + config.P2P.EnableQuic = cli.GetBoolFlagValue(cmd, enableQuicFlag) + } + if cli.IsFlagChanged(cmd, quicPortFlag) { + config.P2P.QuicPort = cli.GetIntFlagValue(cmd, quicPortFlag) + } + if cli.IsFlagChanged(cmd, quicPriorityFlag) { + config.P2P.QuicPriority = cli.GetBoolFlagValue(cmd, quicPriorityFlag) + } } // http flags diff --git a/cmd/harmony/main.go b/cmd/harmony/main.go index 01a0674430..07dc22f710 100644 --- a/cmd/harmony/main.go +++ b/cmd/harmony/main.go @@ -599,6 +599,7 @@ func createGlobalConfig(hc harmonyconfig.HarmonyConfig) (*nodeconfig.ConfigType, forceReachabilityPublic = true } + // todo(sun): quic myHost, err = p2p.NewHost(p2p.HostConfig{ Self: &selfPeer, BLSKey: nodeConfig.P2PPriKey, @@ -622,6 +623,11 @@ func createGlobalConfig(hc harmonyconfig.HarmonyConfig) (*nodeconfig.ConfigType, DialTimeout: hc.P2P.DialTimeout, Muxer: hc.P2P.Muxer, NoRelay: hc.P2P.NoRelay, + QuicConfig: p2p.QuicConfig{ + Enabled: hc.P2P.EnableQuic, + Port: strconv.Itoa(hc.P2P.QuicPort), + Priority: hc.P2P.QuicPriority, + }, }) if err != nil { return nil, errors.Wrap(err, "cannot create P2P network host") diff --git a/internal/configs/harmony/harmony.go b/internal/configs/harmony/harmony.go index cf48bd72ef..e391091173 100644 --- a/internal/configs/harmony/harmony.go +++ b/internal/configs/harmony/harmony.go @@ -146,6 +146,12 @@ type P2pConfig struct { Muxer string // No relay services, direct connections between peers only NoRelay bool + // EnableQuic enables QUIC transport + EnableQuic bool + // QuicPort is the port to listen for QUIC protocols + QuicPort int + // QuicPriority prioritizes QUIC transport over TCP + QuicPriority bool } type GeneralConfig struct { diff --git a/internal/configs/node/network.go b/internal/configs/node/network.go index a5b42dbf33..4bdfab0ba8 100644 --- a/internal/configs/node/network.go +++ b/internal/configs/node/network.go @@ -97,6 +97,12 @@ const ( DefaultMuxer = "yamux, mplexC6" // DefaultNoRelay disables p2p host relay DefaultNoRelay = true + // DefaultEnableQuic enables QUIC transport + DefaultEnableQuic = false + // DefaultQuicPort is the default port for QUIC transport + DefaultQuicPort = 9001 + // DefaultQuicPriority prioritizes QUIC transport over TCP + DefaultQuicPriority = false ) const ( diff --git a/p2p/host.go b/p2p/host.go index 534f292809..eb38d28438 100644 --- a/p2p/host.go +++ b/p2p/host.go @@ -43,11 +43,13 @@ import ( "github.com/libp2p/go-libp2p/p2p/net/connmgr" noise "github.com/libp2p/go-libp2p/p2p/security/noise" tls "github.com/libp2p/go-libp2p/p2p/security/tls" + libp2p_quic "github.com/libp2p/go-libp2p/p2p/transport/quic" "github.com/libp2p/go-libp2p/p2p/transport/tcp" "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr" madns "github.com/multiformats/go-multiaddr-dns" "github.com/pkg/errors" + quic "github.com/quic-go/quic-go" "github.com/rs/zerolog" ) @@ -84,6 +86,8 @@ type Host interface { } // Peer is the object for a p2p peer (node) +// todo(sun): does it need to differentiate between tcp and quic ports? +// todo(sun): for self.peer, if QUIC is enabled, the port should be which port? type Peer struct { IP string // IP address of the peer Port string // Port number of the peer @@ -127,6 +131,13 @@ type HostConfig struct { DialTimeout time.Duration Muxer string NoRelay bool + QuicConfig QuicConfig +} + +type QuicConfig struct { + Enabled bool + Port string + Priority bool } func init() { @@ -144,12 +155,16 @@ func init() { } // NewHost .. +// todo(sun): implement dialer prioritization +// todo(sun): log the quic address +// todo(sun): upon connection, log the transport type (conn.RemoteMultiaddr()) func NewHost(cfg HostConfig) (Host, error) { var ( - self = cfg.Self - key = cfg.BLSKey - pub = cfg.BLSKey.GetPublic() - psPath = cfg.DataStoreFile + self = cfg.Self + key = cfg.BLSKey + pub = cfg.BLSKey.GetPublic() + psPath = cfg.DataStoreFile + quicConfig = cfg.QuicConfig ) pubKey := key.GetPublic() @@ -163,6 +178,7 @@ func NewHost(cfg HostConfig) (Host, error) { listenAddr := libp2p.ListenAddrStrings( addr, // regular tcp connections ) + datastore, err := createDatastore(psPath) if err != nil { return nil, fmt.Errorf("failed to create data store: %w", err) @@ -198,9 +214,11 @@ func NewHost(cfg HostConfig) (Host, error) { connGtr = gating.AddMetering(connGtr) // transporters + // todo: tcp.WithConnectionTimeout is the dial timeout, should be replaced with idle timeout tcpTransport := libp2p.Transport( tcp.NewTCPTransport, - tcp.WithConnectionTimeout(time.Minute*60)) // break unused connections + tcp.WithConnectionTimeout(time.Minute*60), // break unused connections + ) // create NAT Manager; it takes care of setting NAT port mappings, and discovering external addresses var nat libp2p_config.NATManagerC // disabled if nil @@ -232,6 +250,13 @@ func NewHost(cfg HostConfig) (Host, error) { if dto <= 0 { dto = time.Minute } + + // enable quic + var quicOpts libp2p.Option + if quicConfig.Enabled { + quicOpts = createQuicOption(self.IP, quicConfig) + } + // prepare host options p2pHostConfig := []libp2p.Option{ libp2p.Identity(key), @@ -266,6 +291,8 @@ func NewHost(cfg HostConfig) (Host, error) { libp2p.EnableNATService(), // NAT Rate Limiter libp2p.AutoNATServiceRateLimit(10, 5, time.Second*60), + // quic connections; nil if not enabled + quicOpts, } if cfg.ResourceMgrEnabled { rmgr, err := makeResourceMgr(false, cfg.ResourceMgrMemoryLimitBytes, cfg.ResourceMgrFileDescriptorsLimit, cfg.ConnManagerHighWatermark) @@ -430,6 +457,27 @@ func NewHost(cfg HostConfig) (Host, error) { return h, nil } +// createQuicOption creates a quic option for the libp2p host +func createQuicOption(ip string, quicCfg QuicConfig) libp2p.Option { + // todo(sun): derive the QUIC port from the base port ONLY when a legacy flag is used (port + 1) + // quic multiaddr + listenAddr := libp2p.ListenAddrStrings( + fmt.Sprintf("ip4/%s/udp/%s/quic-v1", ip, quicCfg.Port), + ) + + // quic transport + transport := libp2p.Transport( + libp2p_quic.NewTransport, + quic.Config{ + MaxIdleTimeout: time.Minute * 60, + MaxIncomingStreams: 256, + KeepAlivePeriod: time.Minute * 30, + }, + ) + + return libp2p.ChainOptions(listenAddr, transport) +} + func createDatastore(peerstorePath *string) (ds.Batching, error) { var err error var store ds.Batching @@ -606,6 +654,7 @@ func (host *HostV2) SendMessageToGroups(groups []nodeconfig.GroupID, msg []byte) return err } +// todo(sun): add quic support // AddPeer add p2p.Peer into Peerstore func (host *HostV2) AddPeer(p *Peer) error { if p.PeerID != "" && len(p.Addrs) != 0 { @@ -644,11 +693,13 @@ func (host *HostV2) AddTrustedNodes() { } } +// todo(sun): add quic support func (host *HostV2) AddPeerByIP(IP string, port string) error { addr := fmt.Sprintf("/ip4/%s/tcp/%s", IP, port) return host.AddPeerByAddress(addr) } +// todo(sun): add quic support // AddPeerByAddress adds a peer by address (ex: /ip4/127.0.0.1/tcp/9000/p2p/QmSomePeerID) func (host *HostV2) AddPeerByAddress(addrStr string) error { peerAddr, err := multiaddr.NewMultiaddr(addrStr) @@ -749,6 +800,7 @@ func (host *HostV2) Network() libp2p_network.Network { return host.h.Network() } +// todo(sun): add quic support // ConnectHostPeer connects to peer host func (host *HostV2) ConnectHostPeer(peer Peer) error { ctx := context.Background()