diff --git a/config/config.go b/config/config.go index 5fc82c3e345..457344ccc85 100644 --- a/config/config.go +++ b/config/config.go @@ -62,6 +62,7 @@ const ( var ( // Deprecated key --> deprecation message (i.e. which key replaces it) + // TODO: deprecate "BootstrapIDsKey" and "BootstrapIPsKey" deprecatedKeys = map[string]string{ NetworkCompressionEnabledKey: fmt.Sprintf("use --%s instead", NetworkCompressionTypeKey), GenesisConfigFileKey: fmt.Sprintf("use --%s instead", GenesisFileKey), @@ -532,6 +533,8 @@ func getBootstrapConfig(v *viper.Viper, networkID uint32) (node.BootstrapConfig, BootstrapAncestorsMaxContainersReceived: int(v.GetUint(BootstrapAncestorsMaxContainersReceivedKey)), } + // TODO: Add a "BootstrappersKey" flag to more clearly enforce ID and IP + // length equality. ipsSet := v.IsSet(BootstrapIPsKey) idsSet := v.IsSet(BootstrapIDsKey) if ipsSet && !idsSet { @@ -540,40 +543,49 @@ func getBootstrapConfig(v *viper.Viper, networkID uint32) (node.BootstrapConfig, if !ipsSet && idsSet { return node.BootstrapConfig{}, fmt.Errorf("set %q but didn't set %q", BootstrapIDsKey, BootstrapIPsKey) } - - bootstrapIPs, bootstrapIDs := genesis.SampleBeacons(networkID, 5) - if ipsSet { - bootstrapIPs = strings.Split(v.GetString(BootstrapIPsKey), ",") + if !ipsSet && !idsSet { + config.Bootstrappers = genesis.SampleBootstrappers(networkID, 5) + return config, nil } - for _, ip := range bootstrapIPs { + + bootstrapIPs := strings.Split(v.GetString(BootstrapIPsKey), ",") + config.Bootstrappers = make([]genesis.Bootstrapper, 0, len(bootstrapIPs)) + for _, bootstrapIP := range bootstrapIPs { + ip := strings.TrimSpace(bootstrapIP) if ip == "" { continue } + addr, err := ips.ToIPPort(ip) if err != nil { return node.BootstrapConfig{}, fmt.Errorf("couldn't parse bootstrap ip %s: %w", ip, err) } - config.BootstrapIPs = append(config.BootstrapIPs, addr) + config.Bootstrappers = append(config.Bootstrappers, genesis.Bootstrapper{ + // ID is populated below + IP: ips.IPDesc(addr), + }) } - if idsSet { - bootstrapIDs = strings.Split(v.GetString(BootstrapIDsKey), ",") - } - for _, id := range bootstrapIDs { + bootstrapIDs := strings.Split(v.GetString(BootstrapIDsKey), ",") + bootstrapNodeIDs := make([]ids.NodeID, 0, len(bootstrapIDs)) + for _, bootstrapID := range bootstrapIDs { + id := strings.TrimSpace(bootstrapID) if id == "" { continue } + nodeID, err := ids.NodeIDFromString(id) if err != nil { return node.BootstrapConfig{}, fmt.Errorf("couldn't parse bootstrap peer id %s: %w", id, err) } - config.BootstrapIDs = append(config.BootstrapIDs, nodeID) + bootstrapNodeIDs = append(bootstrapNodeIDs, nodeID) } - lenIPs := len(config.BootstrapIPs) - lenIDs := len(config.BootstrapIDs) - if lenIPs != lenIDs { - return node.BootstrapConfig{}, fmt.Errorf("expected the number of bootstrapIPs (%d) to match the number of bootstrapIDs (%d)", lenIPs, lenIDs) + if len(config.Bootstrappers) != len(bootstrapNodeIDs) { + return node.BootstrapConfig{}, fmt.Errorf("expected the number of bootstrapIPs (%d) to match the number of bootstrapIDs (%d)", len(config.Bootstrappers), len(bootstrapNodeIDs)) + } + for i, nodeID := range bootstrapNodeIDs { + config.Bootstrappers[i].ID = nodeID } return config, nil diff --git a/config/flags.go b/config/flags.go index 1d257eb7f2a..8665184d3a1 100644 --- a/config/flags.go +++ b/config/flags.go @@ -297,6 +297,7 @@ func addNodeFlags(fs *pflag.FlagSet) { fs.String(StateSyncIDsKey, "", "Comma separated list of state sync peer ids to connect to. Example: NodeID-JR4dVmy6ffUGAKCBDkyCbeZbyHQBeDsET,NodeID-8CrVPQZ4VSqgL8zTdvL14G8HqAfrBr4z") // Bootstrapping + // TODO: combine "BootstrapIPsKey" and "BootstrapIDsKey" into one flag fs.String(BootstrapIPsKey, "", "Comma separated list of bootstrap peer ips to connect to. Example: 127.0.0.1:9630,127.0.0.1:9631") fs.String(BootstrapIDsKey, "", "Comma separated list of bootstrap peer ids to connect to. Example: NodeID-JR4dVmy6ffUGAKCBDkyCbeZbyHQBeDsET,NodeID-8CrVPQZ4VSqgL8zTdvL14G8HqAfrBr4z") fs.Bool(RetryBootstrapKey, true, "Specifies whether bootstrap should be retried") diff --git a/genesis/beacons.go b/genesis/beacons.go deleted file mode 100644 index b911cb4a2e4..00000000000 --- a/genesis/beacons.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package genesis - -import ( - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/sampler" -) - -// getIPs returns the beacon IPs for each network -func getIPs(networkID uint32) []string { - switch networkID { - case constants.MainnetID: - return []string{ - "54.94.43.49:9651", - "52.79.47.77:9651", - "18.229.206.191:9651", - "3.34.221.73:9651", - "13.244.155.170:9651", - "13.244.47.224:9651", - "122.248.200.212:9651", - "52.30.9.211:9651", - "122.248.199.127:9651", - "18.202.190.40:9651", - "15.206.182.45:9651", - "15.207.11.193:9651", - "44.226.118.72:9651", - "54.185.87.50:9651", - "18.158.15.12:9651", - "3.21.38.33:9651", - "54.93.182.129:9651", - "3.128.138.36:9651", - "3.104.107.241:9651", - "3.106.25.139:9651", - "18.162.129.129:9651", - "18.162.161.230:9651", - "52.47.181.114:9651", - "15.188.9.42:9651", - } - case constants.FujiID: - return []string{ - "18.192.93.241:9651", - "3.76.143.200:9651", - "16.163.84.252:9651", - "13.237.111.196:9651", - "18.167.242.179:9651", - "54.207.25.7:9651", - "15.184.214.136:9651", - "13.55.124.229:9651", - "3.99.55.15:9651", - "157.241.59.198:9651", - "52.29.72.46:9651", - "16.163.75.62:9651", - "34.248.69.195:9651", - "54.66.120.144:9651", - "3.97.132.61:9651", - "18.230.111.83:9651", - "52.67.156.131:9651", - "176.34.80.199:9651", - "176.34.97.64:9651", - "3.97.255.92:9651", - "15.184.142.50:9651", - } - default: - return nil - } -} - -// getNodeIDs returns the beacon node IDs for each network -func getNodeIDs(networkID uint32) []string { - switch networkID { - case constants.MainnetID: - return []string{ - "NodeID-A6onFGyJjA37EZ7kYHANMR1PFRT8NmXrF", - "NodeID-6SwnPJLH8cWfrJ162JjZekbmzaFpjPcf", - "NodeID-GSgaA47umS1px2ohVjodW9621Ks63xDxD", - "NodeID-BQEo5Fy1FRKLbX51ejqDd14cuSXJKArH2", - "NodeID-Drv1Qh7iJvW3zGBBeRnYfCzk56VCRM2GQ", - "NodeID-DAtCoXfLT6Y83dgJ7FmQg8eR53hz37J79", - "NodeID-FGRoKnyYKFWYFMb6Xbocf4hKuyCBENgWM", - "NodeID-Dw7tuwxpAmcpvVGp9JzaHAR3REPoJ8f2R", - "NodeID-4kCLS16Wy73nt1Zm54jFZsL7Msrv3UCeJ", - "NodeID-9T7NXBFpp8LWCyc58YdKNoowDipdVKAWz", - "NodeID-6ghBh6yof5ouMCya2n9fHzhpWouiZFVVj", - "NodeID-HiFv1DpKXkAAfJ1NHWVqQoojjznibZXHP", - "NodeID-Fv3t2shrpkmvLnvNzcv1rqRKbDAYFnUor", - "NodeID-AaxT2P4uuPAHb7vAD8mNvjQ3jgyaV7tu9", - "NodeID-kZNuQMHhydefgnwjYX1fhHMpRNAs9my1", - "NodeID-A7GwTSd47AcDVqpTVj7YtxtjHREM33EJw", - "NodeID-Hr78Fy8uDYiRYocRYHXp4eLCYeb8x5UuM", - "NodeID-9CkG9MBNavnw7EVSRsuFr7ws9gascDQy3", - "NodeID-A8jypu63CWp76STwKdqP6e9hjL675kdiG", - "NodeID-HsBEx3L71EHWSXaE6gvk2VsNntFEZsxqc", - "NodeID-Nr584bLpGgbCUbZFSBaBz3Xum5wpca9Ym", - "NodeID-QKGoUvqcgormCoMj6yPw9isY7DX9H4mdd", - "NodeID-HCw7S2TVbFPDWNBo1GnFWqJ47f9rDJtt1", - "NodeID-FYv1Lb29SqMpywYXH7yNkcFAzRF2jvm3K", - } - case constants.FujiID: - return []string{ - "NodeID-2m38qc95mhHXtrhjyGbe7r2NhniqHHJRB", - "NodeID-JjvzhxnLHLUQ5HjVRkvG827ivbLXPwA9u", - "NodeID-LegbVf6qaMKcsXPnLStkdc1JVktmmiDxy", - "NodeID-HGZ8ae74J3odT8ESreAdCtdnvWG1J4X5n", - "NodeID-CYKruAjwH1BmV3m37sXNuprbr7dGQuJwG", - "NodeID-4KXitMCoE9p2BHA6VzXtaTxLoEjNDo2Pt", - "NodeID-LQwRLm4cbJ7T2kxcxp4uXCU5XD8DFrE1C", - "NodeID-4CWTbdvgXHY1CLXqQNAp22nJDo5nAmts6", - "NodeID-4QBwET5o8kUhvt9xArhir4d3R25CtmZho", - "NodeID-JyE4P8f4cTryNV8DCz2M81bMtGhFFHexG", - "NodeID-EDESh4DfZFC15i613pMtWniQ9arbBZRnL", - "NodeID-BFa1padLXBj7VHa2JYvYGzcTBPQGjPhUy", - "NodeID-CZmZ9xpCzkWqjAyS7L4htzh5Lg6kf1k18", - "NodeID-FesGqwKq7z5nPFHa5iwZctHE5EZV9Lpdq", - "NodeID-84KbQHSDnojroCVY7vQ7u9Tx7pUonPaS", - "NodeID-CTtkcXvVdhpNp6f97LEUXPwsRD3A2ZHqP", - "NodeID-hArafGhY2HFTbwaaVh1CSCUCUCiJ2Vfb", - "NodeID-4B4rc5vdD1758JSBYL1xyvE5NHGzz6xzH", - "NodeID-EzGaipqomyK9UKx9DBHV6Ky3y68hoknrF", - "NodeID-NpagUxt6KQiwPch9Sd4osv8kD1TZnkjdk", - "NodeID-3VWnZNViBP2b56QBY7pNJSLzN2rkTyqnK", - } - default: - return nil - } -} - -// SampleBeacons returns the some beacons this node should connect to -func SampleBeacons(networkID uint32, count int) ([]string, []string) { - ips := getIPs(networkID) - ids := getNodeIDs(networkID) - - if numIPs := len(ips); numIPs < count { - count = numIPs - } - - sampledIPs := make([]string, 0, count) - sampledIDs := make([]string, 0, count) - - s := sampler.NewUniform() - s.Initialize(uint64(len(ips))) - indices, _ := s.Sample(count) - for _, index := range indices { - sampledIPs = append(sampledIPs, ips[int(index)]) - sampledIDs = append(sampledIDs, ids[int(index)]) - } - - return sampledIPs, sampledIDs -} diff --git a/genesis/bootstrappers.go b/genesis/bootstrappers.go new file mode 100644 index 00000000000..4aec33f9c3d --- /dev/null +++ b/genesis/bootstrappers.go @@ -0,0 +1,54 @@ +// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package genesis + +import ( + "encoding/json" + "fmt" + + _ "embed" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/ips" + "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/utils/sampler" +) + +var ( + //go:embed bootstrappers.json + bootstrappersPerNetworkJSON []byte + + bootstrappersPerNetwork map[string][]Bootstrapper +) + +func init() { + if err := json.Unmarshal(bootstrappersPerNetworkJSON, &bootstrappersPerNetwork); err != nil { + panic(fmt.Sprintf("failed to decode bootstrappers.json %v", err)) + } +} + +// Represents the relationship between the nodeID and the nodeIP. +// The bootstrapper is sometimes called "anchor" or "beacon" node. +type Bootstrapper struct { + ID ids.NodeID `json:"id"` + IP ips.IPDesc `json:"ip"` +} + +// SampleBootstrappers returns the some beacons this node should connect to +func SampleBootstrappers(networkID uint32, count int) []Bootstrapper { + networkName := constants.NetworkIDToNetworkName[networkID] + bootstrappers := bootstrappersPerNetwork[networkName] + count = math.Min(count, len(bootstrappers)) + + s := sampler.NewUniform() + s.Initialize(uint64(len(bootstrappers))) + indices, _ := s.Sample(count) + + sampled := make([]Bootstrapper, 0, len(indices)) + for _, index := range indices { + sampled = append(sampled, bootstrappers[int(index)]) + } + return sampled +} diff --git a/genesis/bootstrappers.json b/genesis/bootstrappers.json new file mode 100644 index 00000000000..62784fc7b4d --- /dev/null +++ b/genesis/bootstrappers.json @@ -0,0 +1,186 @@ +{ + "mainnet": [ + { + "id": "NodeID-A6onFGyJjA37EZ7kYHANMR1PFRT8NmXrF", + "ip": "54.94.43.49:9651" + }, + { + "id": "NodeID-6SwnPJLH8cWfrJ162JjZekbmzaFpjPcf", + "ip": "52.79.47.77:9651" + }, + { + "id": "NodeID-GSgaA47umS1px2ohVjodW9621Ks63xDxD", + "ip": "18.229.206.191:9651" + }, + { + "id": "NodeID-BQEo5Fy1FRKLbX51ejqDd14cuSXJKArH2", + "ip": "3.34.221.73:9651" + }, + { + "id": "NodeID-Drv1Qh7iJvW3zGBBeRnYfCzk56VCRM2GQ", + "ip": "13.244.155.170:9651" + }, + { + "id": "NodeID-DAtCoXfLT6Y83dgJ7FmQg8eR53hz37J79", + "ip": "13.244.47.224:9651" + }, + { + "id": "NodeID-FGRoKnyYKFWYFMb6Xbocf4hKuyCBENgWM", + "ip": "122.248.200.212:9651" + }, + { + "id": "NodeID-Dw7tuwxpAmcpvVGp9JzaHAR3REPoJ8f2R", + "ip": "52.30.9.211:9651" + }, + { + "id": "NodeID-4kCLS16Wy73nt1Zm54jFZsL7Msrv3UCeJ", + "ip": "122.248.199.127:9651" + }, + { + "id": "NodeID-9T7NXBFpp8LWCyc58YdKNoowDipdVKAWz", + "ip": "18.202.190.40:9651" + }, + { + "id": "NodeID-6ghBh6yof5ouMCya2n9fHzhpWouiZFVVj", + "ip": "15.206.182.45:9651" + }, + { + "id": "NodeID-HiFv1DpKXkAAfJ1NHWVqQoojjznibZXHP", + "ip": "15.207.11.193:9651" + }, + { + "id": "NodeID-Fv3t2shrpkmvLnvNzcv1rqRKbDAYFnUor", + "ip": "44.226.118.72:9651" + }, + { + "id": "NodeID-AaxT2P4uuPAHb7vAD8mNvjQ3jgyaV7tu9", + "ip": "54.185.87.50:9651" + }, + { + "id": "NodeID-kZNuQMHhydefgnwjYX1fhHMpRNAs9my1", + "ip": "18.158.15.12:9651" + }, + { + "id": "NodeID-A7GwTSd47AcDVqpTVj7YtxtjHREM33EJw", + "ip": "3.21.38.33:9651" + }, + { + "id": "NodeID-Hr78Fy8uDYiRYocRYHXp4eLCYeb8x5UuM", + "ip": "54.93.182.129:9651" + }, + { + "id": "NodeID-9CkG9MBNavnw7EVSRsuFr7ws9gascDQy3", + "ip": "3.128.138.36:9651" + }, + { + "id": "NodeID-A8jypu63CWp76STwKdqP6e9hjL675kdiG", + "ip": "3.104.107.241:9651" + }, + { + "id": "NodeID-HsBEx3L71EHWSXaE6gvk2VsNntFEZsxqc", + "ip": "3.106.25.139:9651" + }, + { + "id": "NodeID-Nr584bLpGgbCUbZFSBaBz3Xum5wpca9Ym", + "ip": "18.162.129.129:9651" + }, + { + "id": "NodeID-QKGoUvqcgormCoMj6yPw9isY7DX9H4mdd", + "ip": "18.162.161.230:9651" + }, + { + "id": "NodeID-HCw7S2TVbFPDWNBo1GnFWqJ47f9rDJtt1", + "ip": "52.47.181.114:9651" + }, + { + "id": "NodeID-FYv1Lb29SqMpywYXH7yNkcFAzRF2jvm3K", + "ip": "15.188.9.42:9651" + } + ], + "fuji": [ + { + "id": "NodeID-2m38qc95mhHXtrhjyGbe7r2NhniqHHJRB", + "ip": "18.192.93.241:9651" + }, + { + "id": "NodeID-JjvzhxnLHLUQ5HjVRkvG827ivbLXPwA9u", + "ip": "3.76.143.200:9651" + }, + { + "id": "NodeID-LegbVf6qaMKcsXPnLStkdc1JVktmmiDxy", + "ip": "16.163.84.252:9651" + }, + { + "id": "NodeID-HGZ8ae74J3odT8ESreAdCtdnvWG1J4X5n", + "ip": "13.237.111.196:9651" + }, + { + "id": "NodeID-CYKruAjwH1BmV3m37sXNuprbr7dGQuJwG", + "ip": "18.167.242.179:9651" + }, + { + "id": "NodeID-4KXitMCoE9p2BHA6VzXtaTxLoEjNDo2Pt", + "ip": "54.207.25.7:9651" + }, + { + "id": "NodeID-LQwRLm4cbJ7T2kxcxp4uXCU5XD8DFrE1C", + "ip": "15.184.214.136:9651" + }, + { + "id": "NodeID-4CWTbdvgXHY1CLXqQNAp22nJDo5nAmts6", + "ip": "13.55.124.229:9651" + }, + { + "id": "NodeID-4QBwET5o8kUhvt9xArhir4d3R25CtmZho", + "ip": "3.99.55.15:9651" + }, + { + "id": "NodeID-JyE4P8f4cTryNV8DCz2M81bMtGhFFHexG", + "ip": "157.241.59.198:9651" + }, + { + "id": "NodeID-EDESh4DfZFC15i613pMtWniQ9arbBZRnL", + "ip": "52.29.72.46:9651" + }, + { + "id": "NodeID-BFa1padLXBj7VHa2JYvYGzcTBPQGjPhUy", + "ip": "16.163.75.62:9651" + }, + { + "id": "NodeID-CZmZ9xpCzkWqjAyS7L4htzh5Lg6kf1k18", + "ip": "34.248.69.195:9651" + }, + { + "id": "NodeID-FesGqwKq7z5nPFHa5iwZctHE5EZV9Lpdq", + "ip": "54.66.120.144:9651" + }, + { + "id": "NodeID-84KbQHSDnojroCVY7vQ7u9Tx7pUonPaS", + "ip": "3.97.132.61:9651" + }, + { + "id": "NodeID-CTtkcXvVdhpNp6f97LEUXPwsRD3A2ZHqP", + "ip": "18.230.111.83:9651" + }, + { + "id": "NodeID-hArafGhY2HFTbwaaVh1CSCUCUCiJ2Vfb", + "ip": "52.67.156.131:9651" + }, + { + "id": "NodeID-4B4rc5vdD1758JSBYL1xyvE5NHGzz6xzH", + "ip": "176.34.80.199:9651" + }, + { + "id": "NodeID-EzGaipqomyK9UKx9DBHV6Ky3y68hoknrF", + "ip": "176.34.97.64:9651" + }, + { + "id": "NodeID-NpagUxt6KQiwPch9Sd4osv8kD1TZnkjdk", + "ip": "3.97.255.92:9651" + }, + { + "id": "NodeID-3VWnZNViBP2b56QBY7pNJSLzN2rkTyqnK", + "ip": "15.184.142.50:9651" + } + ] +} diff --git a/genesis/bootstrappers_test.go b/genesis/bootstrappers_test.go new file mode 100644 index 00000000000..d14eebbb559 --- /dev/null +++ b/genesis/bootstrappers_test.go @@ -0,0 +1,26 @@ +// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package genesis + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/utils/constants" +) + +func TestSampleBootstrappers(t *testing.T) { + require := require.New(t) + + for networkID, networkName := range constants.NetworkIDToNetworkName { + length := 10 + bootstrappers := SampleBootstrappers(networkID, length) + t.Logf("%s bootstrappers: %+v", networkName, bootstrappers) + + if networkID == constants.MainnetID || networkID == constants.FujiID { + require.Len(bootstrappers, length) + } + } +} diff --git a/network/example_test.go b/network/example_test.go index 9f942eeaa62..ca059a391d7 100644 --- a/network/example_test.go +++ b/network/example_test.go @@ -108,30 +108,9 @@ func ExampleNewTestNetwork() { // We need to initially connect to some nodes in the network before peer // gossip will enable connecting to all the remaining nodes in the network. - beaconIPs, beaconIDs := genesis.SampleBeacons(constants.FujiID, 5) - for i, beaconIDStr := range beaconIDs { - beaconID, err := ids.NodeIDFromString(beaconIDStr) - if err != nil { - log.Fatal( - "failed to parse beaconID", - zap.String("beaconID", beaconIDStr), - zap.Error(err), - ) - return - } - - beaconIPStr := beaconIPs[i] - ipPort, err := ips.ToIPPort(beaconIPStr) - if err != nil { - log.Fatal( - "failed to parse beaconIP", - zap.String("beaconIP", beaconIPStr), - zap.Error(err), - ) - return - } - - network.ManuallyTrack(beaconID, ipPort) + bootstrappers := genesis.SampleBootstrappers(constants.FujiID, 5) + for _, bootstrapper := range bootstrappers { + network.ManuallyTrack(bootstrapper.ID, ips.IPPort(bootstrapper.IP)) } // Typically network.StartClose() should be called based on receiving a diff --git a/node/config.go b/node/config.go index 5ff04a76a4d..d6ad4f6bc84 100644 --- a/node/config.go +++ b/node/config.go @@ -119,8 +119,7 @@ type BootstrapConfig struct { // ancestors while responding to a GetAncestors message BootstrapMaxTimeGetAncestors time.Duration `json:"bootstrapMaxTimeGetAncestors"` - BootstrapIDs []ids.NodeID `json:"bootstrapIDs"` - BootstrapIPs []ips.IPPort `json:"bootstrapIPs"` + Bootstrappers []genesis.Bootstrapper `json:"bootstrappers"` } type DatabaseConfig struct { diff --git a/node/node.go b/node/node.go index 9265d734da5..9b8686b3add 100644 --- a/node/node.go +++ b/node/node.go @@ -149,7 +149,7 @@ type Node struct { tlsKeyLogWriterCloser io.WriteCloser // this node's initial connections to the network - beacons validators.Set + bootstrappers validators.Set // current validators of the network vdrs validators.Manager @@ -284,8 +284,8 @@ func (n *Node) initNetworking(primaryNetVdrs validators.Set) error { } } - numBeacons := n.beacons.Len() - requiredConns := (3*numBeacons + 3) / 4 + numBootstrappers := n.bootstrappers.Len() + requiredConns := (3*numBootstrappers + 3) / 4 if requiredConns > 0 { // Set a timer that will fire after a given timeout unless we connect @@ -295,7 +295,7 @@ func (n *Node) initNetworking(primaryNetVdrs validators.Set) error { // If the timeout fires and we're already shutting down, nothing to do. if !n.shuttingDown.Get() { n.Log.Warn("failed to connect to bootstrap nodes", - zap.Stringer("beacons", n.beacons), + zap.Stringer("bootstrappers", n.bootstrappers), zap.Duration("duration", n.Config.BootstrapBeaconConnectionTimeout), ) } @@ -307,7 +307,7 @@ func (n *Node) initNetworking(primaryNetVdrs validators.Set) error { consensusRouter = &beaconManager{ Router: consensusRouter, timer: timer, - beacons: n.beacons, + beacons: n.bootstrappers, requiredConns: int64(requiredConns), } } @@ -330,7 +330,7 @@ func (n *Node) initNetworking(primaryNetVdrs validators.Set) error { n.Config.NetworkConfig.MyIPPort = n.Config.IPPort n.Config.NetworkConfig.NetworkID = n.Config.NetworkID n.Config.NetworkConfig.Validators = n.vdrs - n.Config.NetworkConfig.Beacons = n.beacons + n.Config.NetworkConfig.Beacons = n.bootstrappers n.Config.NetworkConfig.TLSConfig = tlsConfig n.Config.NetworkConfig.TLSKey = tlsKey n.Config.NetworkConfig.TrackedSubnets = n.Config.TrackedSubnets @@ -386,8 +386,8 @@ func (n *Node) Dispatch() error { } // Add bootstrap nodes to the peer network - for i, peerIP := range n.Config.BootstrapIPs { - n.Net.ManuallyTrack(n.Config.BootstrapIDs[i], peerIP) + for _, bootstrapper := range n.Config.Bootstrappers { + n.Net.ManuallyTrack(bootstrapper.ID, ips.IPPort(bootstrapper.IP)) } // Start P2P connections @@ -481,13 +481,13 @@ func (n *Node) initDatabase() error { } // Set the node IDs of the peers this node should first connect to -func (n *Node) initBeacons() error { - n.beacons = validators.NewSet() - for _, peerID := range n.Config.BootstrapIDs { +func (n *Node) initBootstrappers() error { + n.bootstrappers = validators.NewSet() + for _, bootstrapper := range n.Config.Bootstrappers { // Note: The beacon connection manager will treat all beaconIDs as // equal. // Invariant: We never use the TxID or BLS keys populated here. - if err := n.beacons.Add(peerID, nil, ids.Empty, 1); err != nil { + if err := n.bootstrappers.Add(bootstrapper.ID, nil, ids.Empty, 1); err != nil { return err } } @@ -565,7 +565,7 @@ func (n *Node) initChains(genesisBytes []byte) error { SubnetID: constants.PrimaryNetworkID, GenesisData: genesisBytes, // Specifies other chains to create VMID: constants.PlatformVMID, - CustomBeacons: n.beacons, + CustomBeacons: n.bootstrappers, } // Start the chain creator with the Platform Chain @@ -1269,7 +1269,7 @@ func (n *Node) Initialize( n.VMManager = vms.NewManager(n.VMFactoryLog, config.VMAliaser) - if err := n.initBeacons(); err != nil { // Configure the beacons + if err := n.initBootstrappers(); err != nil { // Configure the bootstrappers return fmt.Errorf("problem initializing node beacons: %w", err) } diff --git a/utils/ips/ip_port.go b/utils/ips/ip_port.go index ba0e74aff84..472b5c372a2 100644 --- a/utils/ips/ip_port.go +++ b/utils/ips/ip_port.go @@ -12,7 +12,44 @@ import ( "github.com/ava-labs/avalanchego/utils/wrappers" ) -var errBadIP = errors.New("bad ip format") +const nullStr = "null" + +var ( + errMissingQuotes = errors.New("first and last characters should be quotes") + errBadIP = errors.New("bad ip format") +) + +type IPDesc IPPort + +func (ipDesc IPDesc) String() string { + return IPPort(ipDesc).String() +} + +func (ipDesc IPDesc) MarshalJSON() ([]byte, error) { + return []byte(`"` + ipDesc.String() + `"`), nil +} + +func (ipDesc *IPDesc) UnmarshalJSON(b []byte) error { + str := string(b) + if str == nullStr { // If "null", do nothing + return nil + } else if len(str) < 2 { + return errMissingQuotes + } + + lastIndex := len(str) - 1 + if str[0] != '"' || str[lastIndex] != '"' { + return errMissingQuotes + } + + ipPort, err := ToIPPort(str[1:lastIndex]) + if err != nil { + return fmt.Errorf("couldn't decode to IPPort: %w", err) + } + *ipDesc = IPDesc(ipPort) + + return nil +} // An IP and a port. type IPPort struct { diff --git a/utils/ips/ip_test.go b/utils/ips/ip_test.go index 14853ea7791..d454d65acd5 100644 --- a/utils/ips/ip_test.go +++ b/utils/ips/ip_test.go @@ -4,29 +4,36 @@ package ips import ( + "encoding/json" "fmt" "net" "testing" + + "github.com/stretchr/testify/require" ) func TestIPPortEqual(t *testing.T) { tests := []struct { + ipPort string ipPort1 IPPort ipPort2 IPPort result bool }{ // Expected equal { + `"127.0.0.1:0"`, IPPort{net.ParseIP("127.0.0.1"), 0}, IPPort{net.ParseIP("127.0.0.1"), 0}, true, }, { + `"[::1]:0"`, IPPort{net.ParseIP("::1"), 0}, IPPort{net.ParseIP("::1"), 0}, true, }, { + `"127.0.0.1:0"`, IPPort{net.ParseIP("127.0.0.1"), 0}, IPPort{net.ParseIP("::ffff:127.0.0.1"), 0}, true, @@ -34,16 +41,19 @@ func TestIPPortEqual(t *testing.T) { // Expected unequal { + `"127.0.0.1:0"`, IPPort{net.ParseIP("127.0.0.1"), 0}, IPPort{net.ParseIP("1.2.3.4"), 0}, false, }, { + `"[::1]:0"`, IPPort{net.ParseIP("::1"), 0}, IPPort{net.ParseIP("2001::1"), 0}, false, }, { + `"127.0.0.1:0"`, IPPort{net.ParseIP("127.0.0.1"), 0}, IPPort{net.ParseIP("127.0.0.1"), 1}, false, @@ -51,18 +61,17 @@ func TestIPPortEqual(t *testing.T) { } for i, tt := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - if tt.ipPort1.IP == nil { - t.Error("ipPort1 nil") - } else if tt.ipPort2.IP == nil { - t.Error("ipPort2 nil") - } - result := tt.ipPort1.Equal(tt.ipPort2) - if result && result != tt.result { - t.Error("Expected IPPort to be equal, but they were not") - } - if !result && result != tt.result { - t.Error("Expected IPPort to be unequal, but they were equal") - } + require := require.New(t) + + ipPort := IPDesc{} + require.NoError(ipPort.UnmarshalJSON([]byte(tt.ipPort))) + require.Equal(tt.ipPort1, IPPort(ipPort)) + + ipPortJSON, err := json.Marshal(ipPort) + require.NoError(err) + require.Equal(tt.ipPort, string(ipPortJSON)) + + require.Equal(tt.result, tt.ipPort1.Equal(tt.ipPort2)) }) } }