@@ -11,6 +11,8 @@ import (
1111 "io"
1212 "os"
1313 "path/filepath"
14+ "strconv"
15+ "strings"
1416 "time"
1517
1618 cfg "github.com/ava-labs/avalanchego/config"
@@ -56,6 +58,22 @@ func (ln *LocalNetwork) GetNodes() []testnet.Node {
5658 return nodes
5759}
5860
61+ // Adds a backend-agnostic node to the network
62+ func (ln * LocalNetwork ) AddNode (w io.Writer , flags testnet.FlagsMap , waitForHealth bool ) (testnet.Node , error ) {
63+ if flags == nil {
64+ flags = testnet.FlagsMap {}
65+ }
66+ node , err := ln .AddLocalNode (w , & LocalNode {
67+ NodeConfig : testnet.NodeConfig {
68+ Flags : flags ,
69+ },
70+ }, waitForHealth )
71+ if err != nil {
72+ return nil , err
73+ }
74+ return node , nil
75+ }
76+
5977// Starts a new network stored under the provided root dir. Required
6078// configuration will be defaulted if not provided.
6179func StartNetwork (
@@ -110,8 +128,7 @@ func StopNetwork(dir string) error {
110128 if err != nil {
111129 return err
112130 }
113- network .Stop ()
114- return nil
131+ return network .Stop ()
115132}
116133
117134// Ensure the network has the configuration it needs to start.
@@ -342,12 +359,20 @@ func (ln *LocalNetwork) GetURIs() []string {
342359}
343360
344361// Stop all nodes in the network.
345- func (ln * LocalNetwork ) Stop () {
362+ func (ln * LocalNetwork ) Stop () error {
346363 // Assume the nodes are loaded and the pids are current
364+ errs := []error {}
347365 for _ , node := range ln .Nodes {
348- node .Stop ()
366+ if err := node .Stop (); err != nil {
367+ errs = append (errs , err )
368+ }
349369 }
350- // TODO(marun) Collect and return errors
370+ if len (errs ) > 0 {
371+ // TODO(marun) Update to use errors.Join once 1.20 becomes the
372+ // minimum golang version
373+ return fmt .Errorf ("failed to stop all nodes: %v" , errs )
374+ }
375+ return nil
351376}
352377
353378func (ln * LocalNetwork ) GetGenesisPath () string {
@@ -569,3 +594,90 @@ func (ln *LocalNetwork) ReadAll() error {
569594 }
570595 return ln .ReadNodes ()
571596}
597+
598+ func (ln * LocalNetwork ) AddLocalNode (w io.Writer , node * LocalNode , waitForHealth bool ) (* LocalNode , error ) {
599+ // Assume network configuration has been written to disk and is current in memory
600+
601+ if err := ln .ReadNodes (); err != nil {
602+ return nil , err
603+ }
604+
605+ maxPort := 0
606+ bootstrapIPs := []string {}
607+ bootstrapIDs := []string {}
608+ for _ , node := range ln .Nodes {
609+ if len (node .StakingAddress ) == 0 {
610+ // Node is not running
611+ continue
612+ }
613+
614+ bootstrapIPs = append (bootstrapIPs , node .StakingAddress )
615+ bootstrapIDs = append (bootstrapIDs , node .NodeID .String ())
616+
617+ // Find the maximum port if using static ports
618+ if ln .UseStaticPorts {
619+ addressParts := strings .Split (node .StakingAddress , ":" )
620+ rawPort := addressParts [len (addressParts )- 1 ]
621+ port , err := strconv .Atoi (rawPort )
622+ if err != nil {
623+ return nil , err
624+ }
625+ if port > maxPort {
626+ maxPort = port
627+ }
628+ }
629+ }
630+
631+ if len (bootstrapIDs ) == 0 {
632+ // TODO(marun) Make sure to relax this for the initial node
633+ // when building a network node-by-node
634+ return nil , errors .New ("failed to add node due to missing bootstrap nodes" )
635+ }
636+
637+ if node == nil {
638+ // TODO(marun) Simplify this instantiation
639+ node = & LocalNode {
640+ NodeConfig : * testnet .NewNodeConfig (),
641+ }
642+ }
643+
644+ err := ln .PopulateNodeConfig (node )
645+ if err != nil {
646+ return nil , err
647+ }
648+
649+ // Configure the provided node for this network
650+ httpPort := 0
651+ stakingPort := 0
652+ if ln .UseStaticPorts {
653+ httpPort = maxPort
654+ stakingPort = maxPort + 1
655+ }
656+ node .SetNetworkingConfig (httpPort , stakingPort , bootstrapIDs , bootstrapIPs )
657+
658+ err = node .WriteConfig ()
659+ if err != nil {
660+ return nil , err
661+ }
662+ process , err := node .Start (w , ln .ExecPath , ln .UseStaticPorts )
663+ if err != nil {
664+ return nil , err
665+ }
666+
667+ if waitForHealth {
668+ // TODO(marun) Use a timeout
669+ ctx := context .Background ()
670+
671+ // Health check
672+ for {
673+ if healthy , err := isHealthy (ctx , process ); err != nil {
674+ return nil , err
675+ } else if healthy {
676+ break
677+ }
678+ time .Sleep (time .Millisecond * 500 )
679+ }
680+ }
681+
682+ return node , nil
683+ }
0 commit comments