Skip to content

Commit

Permalink
netdeploy: support hybrid p2p scenarios
Browse files Browse the repository at this point in the history
  • Loading branch information
algorandskiy committed Jun 28, 2024
1 parent 3f3b132 commit 5d82ec4
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 30 deletions.
10 changes: 10 additions & 0 deletions netdeploy/remote/deployedNetwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,16 @@ func createHostSpec(host HostConfig, template cloudHost) (hostSpec cloudHostSpec
ports[port] = true
portList = append(portList, strconv.Itoa(port))
}
if node.P2PNetAddress != "" {
port, err = extractPublicPort(node.P2PNetAddress)
if err != nil {
return

Check warning on line 1010 in netdeploy/remote/deployedNetwork.go

View check run for this annotation

Codecov / codecov/patch

netdeploy/remote/deployedNetwork.go#L1007-L1010

Added lines #L1007 - L1010 were not covered by tests
}
if !ports[port] {
ports[port] = true
portList = append(portList, strconv.Itoa(port))

Check warning on line 1014 in netdeploy/remote/deployedNetwork.go

View check run for this annotation

Codecov / codecov/patch

netdeploy/remote/deployedNetwork.go#L1012-L1014

Added lines #L1012 - L1014 were not covered by tests
}
}
}

// See if the APIEndpoint is open to the public, and if so add it
Expand Down
2 changes: 2 additions & 0 deletions netdeploy/remote/nodeConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type NodeConfig struct {
DeadlockOverride int `json:",omitempty"` // -1 = Disable deadlock detection, 0 = Use Default for build, 1 = Enable
ConfigJSONOverride string `json:",omitempty"` // Raw json to merge into config.json after other modifications are complete
P2PBootstrap bool // True if this node should be a p2p bootstrap node and registered in DNS
P2PNetAddress string `json:",omitempty"`
PublicAddress bool

// NodeNameMatchRegex is tested against Name in generated configs and if matched the rest of the configs in this record are applied as a template
NodeNameMatchRegex string `json:",omitempty"`
Expand Down
11 changes: 10 additions & 1 deletion netdeploy/remote/nodecfg/nodeConfigurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ func (nc *nodeConfigurator) apply(rootConfigDir, rootNodeDir string) (err error)

nc.genesisFile = filepath.Join(rootConfigDir, "genesisdata", config.GenesisJSONFile)
nc.genesisData, err = bookkeeping.LoadGenesisFromFile(nc.genesisFile)
if err != nil {
return fmt.Errorf("error loading genesis from '%s': %v", nc.genesisFile, err)

}
nodeDirs, err := nc.prepareNodeDirs(nc.config.Nodes, rootConfigDir, rootNodeDir)
if err != nil {
return fmt.Errorf("error preparing node directories: %v", err)
Expand Down Expand Up @@ -198,6 +202,11 @@ func (nc *nodeConfigurator) prepareNodeDirs(configs []remote.NodeConfig, rootCon
return
}

// getHostName creates a DNS name for a host
func (nc *nodeConfigurator) getNetworkHostName() string {
return nc.config.Name + "." + string(nc.genesisData.Network) + ".algodev.network"
}

func (nc *nodeConfigurator) registerDNSRecords() (err error) {
cfZoneID, cfToken, err := getClouldflareCredentials()
if err != nil {
Expand All @@ -215,7 +224,7 @@ func (nc *nodeConfigurator) registerDNSRecords() (err error) {
// If we need to register anything, first register a DNS entry
// to map our network DNS name to our public name (or IP) provided to nodecfg
// Network HostName = eg r1.testnet.algodev.network
networkHostName := nc.config.Name + "." + string(nc.genesisData.Network) + ".algodev.network"
networkHostName := nc.getNetworkHostName()
isIP := net.ParseIP(nc.dnsName) != nil
var recordType string
if isIP {
Expand Down
36 changes: 33 additions & 3 deletions netdeploy/remote/nodecfg/nodeDir.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ func (nd *nodeDir) configure() (err error) {
return
}

if err = nd.configurePublicAddress(nd.PublicAddress); err != nil {
fmt.Fprintf(os.Stdout, "Error during configurePublicAddress: %s\n", err)
return
}

if err = nd.configureP2PDNSBootstrap(nd.P2PBootstrap); err != nil {
fmt.Fprintf(os.Stdout, "Error during configureP2PDNSBootstrap: %s\n", err)
return
Expand Down Expand Up @@ -160,10 +165,35 @@ func (nd *nodeDir) configureNetAddress() (err error) {
nd.configurator.addRelaySrv(bootstrapRecord.PrimarySRVBootstrap, nd.NetAddress)
}
}
if nd.P2PNetAddress != "" {
fmt.Fprintf(os.Stdout, " - Assigning P2PNetAddress: %s\n", nd.P2PNetAddress)
nd.config.P2PNetAddress = nd.P2PNetAddress
}
err = nd.saveConfig()
return
}

func (nd *nodeDir) configurePublicAddress(publicAddress bool) error {
if !publicAddress {
return nil
}

if err := nd.ensureConfig(); err != nil {
return err
}

if !nd.IsRelay() {
return errors.New("publicAddress is only valid for relay nodes")
}

if nd.NetAddress[0] == ':' {
networkHostName := nd.configurator.getNetworkHostName() + nd.NetAddress
nd.config.PublicAddress = networkHostName
}

return nil
}

func (nd *nodeDir) configureP2PDNSBootstrap(p2pBootstrap bool) error {
if !p2pBootstrap {
return nil
Expand All @@ -179,16 +209,16 @@ func (nd *nodeDir) configureP2PDNSBootstrap(p2pBootstrap bool) error {
if !nd.config.EnableP2P && !nd.config.EnableP2PHybridMode {
return errors.New("p2p bootstrap requires EnableP2P or EnableP2PHybridMode to be set")
}
if nd.NetAddress == "" && nd.config.P2PNetAddress == "" {
if nd.NetAddress == "" && nd.P2PNetAddress == "" {
return errors.New("p2p bootstrap requires NetAddress or P2PNetAddress to be set")
}
if !nd.config.EnableGossipService {
return errors.New("p2p bootstrap requires EnableGossipService to be set")
}

netAddress := nd.NetAddress
if nd.config.P2PNetAddress != "" {
netAddress = nd.config.P2PNetAddress
if nd.P2PNetAddress != "" {
netAddress = nd.P2PNetAddress
}

key, err := p2p.GetPrivKey(config.Local{P2PPersistPeerID: true}, nd.dataDir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ PARAMS=-w 20 -R 8 -N 20 -n 20 --npn-algod-nodes 10 --node-template node.json --r

.PHONY: clean all

HYBRID ?= no

all: net.json genesis.json topology.json

node.json nonPartNode.json relay.json:
python3 copy-node-configs.py
node.json nonPartNode.json relay.json: copy-node-configs.py
python3 copy-node-configs.py --hybrid=${HYBRID}

net.json: node.json nonPartNode.json relay.json ${GOPATH}/bin/netgoal Makefile
net.json: node.json nonPartNode.json relay.json Makefile
netgoal generate -t net -r /tmp/wat -o net.json ${PARAMS}

genesis.json: ${GOPATH}/bin/netgoal Makefile
genesis.json: Makefile
netgoal generate -t genesis -r /tmp/wat -o genesis.l.json ${PARAMS}
jq '.LastPartKeyRound=5000|.NetworkName="s1s-p2p"|.ConsensusProtocol="future"' < genesis.l.json > genesis.json
rm genesis.l.json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ This is a copy of scenario1s with the following changes in nodes configuration:
## Build

```sh
export GOPATH=~/go
make
```

If want to configure a hybrid net, set the `HYBRID` mode parameter to:
- `p2p` meaning all nodes are p2pnet and 50% of them are hybrid
- `ws` meaning all nodes are wsnet and 50% of them are hybrid

```sh
make -D HYBRID=p2p
```

## Run

Run as usual cluster test scenario with algonet.
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,132 @@
3. Set DNSSecurityFlags: 0 to all configs
"""

import argparse
import copy
import json
import os

CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
SCENARIO1S_DIR = os.path.join(CURRENT_DIR, "..", "scenario1s")

def make_p2p_net(*args):
"""convert config to a pure p2p network"""
for config in args:
override_json = json.loads(config.get("ConfigJSONOverride", "{}"))
override_json["EnableP2P"] = True
override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC
config["ConfigJSONOverride"] = json.dumps(override_json)

net_address = config.get("NetAddress")
if net_address:
config["P2PBootstrap"] = True
altconfigs = config.get("AltConfigs", [])
if altconfigs:
for i, altconfig in enumerate(altconfigs):
override_json = json.loads(altconfig.get("ConfigJSONOverride", "{}"))
override_json["EnableP2P"] = True
override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC
altconfigs[i]["ConfigJSONOverride"] = json.dumps(override_json)
config["AltConfigs"] = altconfigs


def make_hybrid_p2p_net(*args):
"""convert config to a hybrid p2p network:
- half of relays become hybrid and receive public address
- half of non-relay nodes become hybrid
- AltConfigs are used for hybrid nodes with FractionApply=0.5
- Only one AltConfigs is supported and its FractionApply is forced to 0.5
"""
for config in args:
override_json = json.loads(config.get("ConfigJSONOverride", "{}"))
override_json["EnableP2P"] = True
override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC
config["ConfigJSONOverride"] = json.dumps(override_json)

net_address = config.get("NetAddress")
altconfigs = config.get("AltConfigs")
altconfig = None
if altconfigs:
altconfig = altconfigs[0]
else:
altconfig = copy.deepcopy(config)

override_json = json.loads(altconfig.get("ConfigJSONOverride", "{}"))
override_json["EnableP2PHybridMode"] = True
override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC
altconfig["ConfigJSONOverride"] = json.dumps(override_json)
if net_address: # relay, set public address
altconfig["P2PBootstrap"] = True
altconfig["P2PNetAddress"] = "{{NetworkPort1}}"
altconfig["PublicAddress"] = True
altconfig['FractionApply'] = 0.5

altconfigs = [altconfig]
config["AltConfigs"] = altconfigs


def make_hybrid_ws_net(*args):
"""convert config to a hybrid ws network:
- half of relays become hybrid and receive public address
- half of non-relay nodes become hybrid
- AltConfigs are used for hybrid nodes with FractionApply=0.5
- Only one AltConfigs is supported and its FractionApply is forced to 0.5
"""
for config in args:
override_json = json.loads(config.get("ConfigJSONOverride", "{}"))
override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC
config["ConfigJSONOverride"] = json.dumps(override_json)

net_address = config.get("NetAddress")
altconfigs = config.get("AltConfigs")
altconfig = None
if altconfigs:
altconfig = altconfigs[0]
else:
altconfig = copy.deepcopy(config)

override_json = json.loads(altconfig.get("ConfigJSONOverride", "{}"))
override_json["EnableP2PHybridMode"] = True
override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC
altconfig["ConfigJSONOverride"] = json.dumps(override_json)
if net_address: # relay, set public address
altconfig["P2PBootstrap"] = True
altconfig["P2PNetAddress"] = "{{NetworkPort1}}"
altconfig["PublicAddress"] = True
altconfig['FractionApply'] = 0.5

altconfigs = [altconfig]
config["AltConfigs"] = altconfigs


def main():
"""main"""
ap = argparse.ArgumentParser()
ap.add_argument('--hybrid', type=str, help='Hybrid mode: p2p, ws')
args = ap.parse_args()

hybrid_mode = args.hybrid
if hybrid_mode not in ("p2p", "ws"):
hybrid_mode = None

print('Hybrid mode:', hybrid_mode)

with open(os.path.join(SCENARIO1S_DIR, "node.json"), "r") as f:
node = json.load(f)
with open(os.path.join(SCENARIO1S_DIR, "relay.json"), "r") as f:
relay = json.load(f)
with open(os.path.join(SCENARIO1S_DIR, "nonPartNode.json"), "r") as f:
non_part_node = json.load(f)

# make all relays P2PBootstrap'able
relay["P2PBootstrap"] = True

# enable P2P for all configs
for config in (node, relay, non_part_node):
override = config.get("ConfigJSONOverride")
if override:
override_json = json.loads(override)
override_json["EnableP2P"] = True
override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC
config["ConfigJSONOverride"] = json.dumps(override_json)
altconfigs = config.get("AltConfigs", [])
if altconfigs:
for i, altconfig in enumerate(altconfigs):
override = altconfig.get("ConfigJSONOverride")
if override:
override_json = json.loads(override)
override_json["EnableP2P"] = True
override_json["DNSSecurityFlags"] = 0x8000 # set to some unused value otherwise 0 would be migrated to default that enables DNSSEC
altconfigs[i]["ConfigJSONOverride"] = json.dumps(override_json)
config["AltConfigs"] = altconfigs
# in p2p-only mode all relays are P2PBootstrap-able
if not hybrid_mode:
make_p2p_net(node, relay, non_part_node)
elif hybrid_mode == 'p2p':
make_hybrid_p2p_net(node, relay, non_part_node)
elif hybrid_mode == 'ws':
make_hybrid_ws_net(node, relay, non_part_node)
else:
raise ValueError(f"Invalid hybrid mode: { hybrid_mode }")

with open("node.json", "w") as f:
json.dump(node, f, indent=4)
Expand Down

0 comments on commit 5d82ec4

Please sign in to comment.