@@ -11,6 +11,8 @@ import (
11
11
"io"
12
12
"os"
13
13
"path/filepath"
14
+ "strconv"
15
+ "strings"
14
16
"time"
15
17
16
18
cfg "github.com/ava-labs/avalanchego/config"
@@ -56,6 +58,22 @@ func (ln *LocalNetwork) GetNodes() []testnet.Node {
56
58
return nodes
57
59
}
58
60
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
+
59
77
// Starts a new network stored under the provided root dir. Required
60
78
// configuration will be defaulted if not provided.
61
79
func StartNetwork (
@@ -110,8 +128,7 @@ func StopNetwork(dir string) error {
110
128
if err != nil {
111
129
return err
112
130
}
113
- network .Stop ()
114
- return nil
131
+ return network .Stop ()
115
132
}
116
133
117
134
// Ensure the network has the configuration it needs to start.
@@ -342,12 +359,20 @@ func (ln *LocalNetwork) GetURIs() []string {
342
359
}
343
360
344
361
// Stop all nodes in the network.
345
- func (ln * LocalNetwork ) Stop () {
362
+ func (ln * LocalNetwork ) Stop () error {
346
363
// Assume the nodes are loaded and the pids are current
364
+ errs := []error {}
347
365
for _ , node := range ln .Nodes {
348
- node .Stop ()
366
+ if err := node .Stop (); err != nil {
367
+ errs = append (errs , err )
368
+ }
349
369
}
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
351
376
}
352
377
353
378
func (ln * LocalNetwork ) GetGenesisPath () string {
@@ -569,3 +594,90 @@ func (ln *LocalNetwork) ReadAll() error {
569
594
}
570
595
return ln .ReadNodes ()
571
596
}
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