2020-05-19 05:00:03 +00:00
import
2021-09-15 08:01:09 +00:00
strformat , os , osproc , net , strformat , chronicles , confutils , json ,
2022-03-01 16:12:23 +00:00
eth / p2p / discoveryv5 / enr ,
eth / keys ,
stew / shims / net as stewNet ,
2020-05-19 05:00:03 +00:00
libp2p / multiaddress ,
libp2p / crypto / crypto ,
2020-05-20 05:34:43 +00:00
libp2p / crypto / secp ,
2020-05-19 05:00:03 +00:00
libp2p / peerinfo
2020-05-20 05:34:43 +00:00
# Fix ambiguous call error
import strutils except fromHex
2020-05-20 06:21:17 +00:00
const
2022-03-01 16:12:23 +00:00
# Scenarios without discv5
# defaults ="--log-level:TRACE --metrics-logging --metrics-server --rpc"
# # Scenarios using discv5
defaults = " --log-level:TRACE --metrics-logging --metrics-server --rpc --discv5-discovery --discv5-table-ip-limit=1024 --discv5-bucket-ip-limit=16 --discv5-bits-per-hop=1 "
2020-07-13 10:08:03 +00:00
wakuNodeBin = " build " / " wakunode2 "
2020-06-15 04:06:41 +00:00
metricsDir = " metrics "
2020-05-21 04:28:57 +00:00
portOffset = 2
2022-03-01 16:12:23 +00:00
discv5ExtIpAddr = " 127.0.0.1 " # Scenarios using discv5
2020-05-20 06:21:17 +00:00
2020-05-19 05:00:03 +00:00
type
NodeInfo * = object
cmd : string
2020-05-21 04:28:57 +00:00
master : bool
2020-05-19 05:00:03 +00:00
address : string
2020-05-20 06:21:17 +00:00
shift : int
2020-05-19 05:00:03 +00:00
label : string
2022-03-01 16:12:23 +00:00
enrUri : string # Scenarios using discv5
2020-05-19 05:00:03 +00:00
2020-05-27 05:37:27 +00:00
Topology = enum
Star ,
2022-03-01 16:12:23 +00:00
FullMesh ,
BootstrapFleet # comprising one full-mesh core (fleet) and satellites (full nodes) using the fleet as bootstrap nodes
2021-09-15 08:01:09 +00:00
WakuNetworkConf * = object
topology * {.
desc : " Set the network topology. "
defaultValue : FullMesh
name : " topology " . } : Topology
amount * {.
desc : " Amount of relay nodes to be started. "
defaultValue : 16
name : " amount " . } : int
2020-05-19 05:00:03 +00:00
2022-03-01 16:12:23 +00:00
numFleetNodes * {.
desc : " Number of Fleetnodes. "
defaultValue : 3
name : " numFleetNodes " . } : int
proc debugPrintEnrURI ( enrUri : string ) =
var r : Record
if not fromURI ( r , enrUri ) :
echo " could not read ENR URI "
echo " ENR content: " , r
2020-05-27 05:37:27 +00:00
# NOTE: Don't distinguish between node types here a la full node, light node etc
2022-03-01 16:12:23 +00:00
proc initNodeCmd ( shift : int , staticNodes : seq [ string ] = @ [ ] , master = false , label : string , discv5BootStrapEnrs : seq [ string ] = @ [ ] ) : NodeInfo =
2020-05-19 05:00:03 +00:00
let
2020-07-13 10:08:03 +00:00
rng = crypto . newRng ( )
key = SkPrivateKey . random ( rng [ ] )
2020-05-20 05:34:43 +00:00
hkey = key . getBytes ( ) . toHex ( )
2020-05-26 03:55:53 +00:00
rkey = SkPrivateKey . init ( fromHex ( hkey ) ) [ ] #assumes ok
2022-03-01 16:12:23 +00:00
privKey = crypto . PrivateKey ( scheme : Secp256k1 , skkey : rkey )
2020-05-20 05:34:43 +00:00
#privKey = PrivateKey.random(Secp256k1)
2021-11-04 14:46:38 +00:00
pubkey = privKey . getPublicKey ( ) [ ] #assumes ok
2022-03-01 16:12:23 +00:00
keyPair = crypto . KeyPair ( seckey : privKey , pubkey : pubkey )
2021-11-04 14:46:38 +00:00
peerInfo = PeerInfo . new ( privKey )
2020-05-28 03:28:44 +00:00
port = 60000 + shift
2022-03-01 16:12:23 +00:00
discv5Port = 9000 + shift
2020-05-28 03:28:44 +00:00
#DefaultAddr = "/ip4/127.0.0.1/tcp/55505"
address = " /ip4/127.0.0.1/tcp/ " & $ port
2020-06-01 03:15:37 +00:00
hostAddress = MultiAddress . init ( address ) . tryGet ( )
2020-05-19 05:00:03 +00:00
2020-05-28 03:40:41 +00:00
info " Address " , address
2020-05-28 03:28:44 +00:00
# TODO: Need to port shift
2020-05-19 05:00:03 +00:00
peerInfo . addrs . add ( hostAddress )
2020-09-16 04:23:10 +00:00
let id = $ peerInfo . peerId
2020-05-20 06:21:17 +00:00
info " PeerInfo " , id = id , addrs = peerInfo . addrs
let listenStr = $ peerInfo . addrs [ 0 ] & " /p2p/ " & id
result . cmd = wakuNodeBin & " " & defaults & " "
result . cmd & = " --nodekey: " & hkey & " "
result . cmd & = " --ports-shift: " & $ shift & " "
if staticNodes . len > 0 :
for staticNode in staticNodes :
result . cmd & = " --staticnode: " & staticNode & " "
2022-03-01 16:12:23 +00:00
# Scenarios using discv5
result . cmd & = " --nat:extip: " & discv5ExtIpAddr & " "
if discv5BootStrapEnrs . len > 0 :
for enr in discv5BootStrapEnrs :
result . cmd & = " --discv5-bootstrap-node: " & enr & " "
2020-05-20 06:21:17 +00:00
result . shift = shift
result . label = label
2020-05-21 04:28:57 +00:00
result . master = master
2020-05-20 06:21:17 +00:00
result . address = listenStr
2022-03-01 16:12:23 +00:00
# Scenarios using discv5
# We have to manually build the ENR which the node ran with the currently build node command will generate,
# because we need to know it before the node starts in order to be able to provide it as bootstrap information.
# Note: the ENR built here is not exactly the same as we don't integrate the waku2 key in this test scenario.
let enr = enr . Record . init ( 1 ,
keys . PrivateKey . fromHex ( hkey ) . expect ( " could not convert priv key from hex " ) ,
some ( stewNet . ValidIpAddress . init ( discv5ExtIpAddr ) ) ,
some ( Port ( discv5Port ) ) , # tcp-port
some ( Port ( discv5Port ) ) ) # udp-port
. expect ( " Record within size limits " )
result . enrUri = " enr: " & enr . toBase64
2020-05-20 06:21:17 +00:00
info " Node command created. " , cmd = result . cmd , address = result . address
2020-05-19 05:00:03 +00:00
2022-03-01 16:12:23 +00:00
# # Scenarios without discv5
# proc starNetwork(amount: int): seq[NodeInfo] =
# let masterNode = initNodeCmd(portOffset, master = true, label = "master node")
# result.add(masterNode)
# for i in 1..<amount:
# result.add(initNodeCmd(portOffset + i, @[masterNode.address], label = "full node"))
# Scenarios using discv5
2020-05-21 04:36:30 +00:00
proc starNetwork ( amount : int ) : seq [ NodeInfo ] =
let masterNode = initNodeCmd ( portOffset , master = true , label = " master node " )
2022-03-01 16:12:23 +00:00
let bootstrapEnrList = @ [ masterNode . enrUri ]
2020-05-21 04:36:30 +00:00
result . add ( masterNode )
for i in 1 .. < amount :
2022-03-01 16:12:23 +00:00
result . add ( initNodeCmd ( portOffset + i , label = " full node " , discv5BootStrapEnrs = bootstrapEnrList ) ) # no waku bootstrap nodes; get bootstrap waku nodes via discv5
# Scenarios using discv5
proc bootstrapFleetNetwork ( amount : int , numFleetNodes : int ) : seq [ NodeInfo ] =
debug " number of fleet nodes " , numFleetNodes
debug " number of satellite nodes " , amount
2020-05-19 05:00:03 +00:00
2022-03-01 16:12:23 +00:00
var bootstrapEnrList : seq [ string ] = @ [ ] # bootstrap for discv5
var bootstrapNodeList : seq [ string ] = @ [ ] # bootstrap for waku relay
for i in 0 .. < numFleetNodes :
for fleetNode in result :
bootstrapEnrList . add ( fleetNode . enrUri )
bootstrapNodeList . add ( fleetNode . address )
result . add ( initNodeCmd ( portOffset + i , staticNodes = bootstrapNodeList , label = " fleet node " , discv5BootStrapEnrs = bootstrapEnrList ) )
for i in 0 .. < amount :
result . add ( initNodeCmd ( portOffset + numFleetNodes + i , label = " full node " , discv5BootStrapEnrs = bootstrapEnrList ) ) # no waku bootstrap nodes; get bootstrap waku nodes via discv5
# Scenarios without discv5
2020-05-27 05:37:27 +00:00
proc fullMeshNetwork ( amount : int ) : seq [ NodeInfo ] =
debug " amount " , amount
for i in 0 .. < amount :
var staticnodes : seq [ string ]
for item in result :
staticnodes . add ( item . address )
2020-05-28 02:58:37 +00:00
result . add ( initNodeCmd ( portOffset + i , staticnodes , label = " full node " ) )
2020-05-27 05:37:27 +00:00
2020-06-15 04:06:41 +00:00
proc generatePrometheusConfig ( nodes : seq [ NodeInfo ] , outputFile : string ) =
var config = """
global :
scrape_interval : 1 s
scrape_configs :
- job_name : " wakusim "
static_configs : """
var count = 0
for node in nodes :
let port = 8008 + node . shift
config & = & """
- targets : [ ' 1 2 7 . 0 . 0 . 1 : { p o r t } ' ]
labels :
node : ' { c o u n t } ' """
count + = 1
var ( path , file ) = splitPath ( outputFile )
createDir ( path )
writeFile ( outputFile , config )
proc proccessGrafanaDashboard ( nodes : int , inputFile : string ,
outputFile : string ) =
# from https://github.com/status-im/nim-beacon-chain/blob/master/tests/simulation/process_dashboard.nim
var
inputData = parseFile ( inputFile )
panels = inputData [ " panels " ] . copy ( )
numPanels = len ( panels )
gridHeight = 0
outputData = inputData
for panel in panels :
if panel [ " gridPos " ] [ " x " ] . getInt ( ) = = 0 :
gridHeight + = panel [ " gridPos " ] [ " h " ] . getInt ( )
outputData [ " panels " ] = % * [ ]
for nodeNum in 0 .. ( nodes - 1 ) :
var
nodePanels = panels . copy ( )
panelIndex = 0
for panel in nodePanels . mitems :
panel [ " title " ] = % * replace ( panel [ " title " ] . getStr ( ) , " #0 " , " # " & $ nodeNum )
panel [ " id " ] = % * ( panelIndex + ( nodeNum * numPanels ) )
panel [ " gridPos " ] [ " y " ] = % * ( panel [ " gridPos " ] [ " y " ] . getInt ( ) + ( nodeNum * gridHeight ) )
var targets = panel [ " targets " ]
for target in targets . mitems :
target [ " expr " ] = % * replace ( target [ " expr " ] . getStr ( ) , " {node= \" 0 \" } " , " {node= \" " & $ nodeNum & " \" } " )
outputData [ " panels " ] . add ( panel )
panelIndex . inc ( )
outputData [ " uid " ] = % * ( outputData [ " uid " ] . getStr ( ) & " a " )
outputData [ " title " ] = % * ( outputData [ " title " ] . getStr ( ) & " (all nodes) " )
writeFile ( outputFile , pretty ( outputData ) )
2022-03-01 16:12:23 +00:00
2020-05-21 04:36:30 +00:00
when isMainModule :
2021-09-15 08:01:09 +00:00
let conf = WakuNetworkConf . load ( )
2020-05-21 04:36:30 +00:00
# TODO: WakuNetworkConf
var nodes : seq [ NodeInfo ]
2021-09-15 08:01:09 +00:00
let topology = conf . topology
2020-06-15 03:06:08 +00:00
# Scenario xx2 14
2021-09-15 08:01:09 +00:00
let amount = conf . amount
2022-03-01 16:12:23 +00:00
let numFleetNodes = conf . numFleetNodes
2020-05-27 05:37:27 +00:00
case topology :
of Star :
nodes = starNetwork ( amount )
of FullMesh :
nodes = fullMeshNetwork ( amount )
2022-03-01 16:12:23 +00:00
of BootstrapFleet :
nodes = bootstrapFleetNetwork ( amount , numFleetNodes )
2020-05-27 05:37:27 +00:00
2021-09-15 08:01:09 +00:00
# var staticnodes: seq[string]
# for i in 0..<amount:
# # TODO: could also select nodes randomly
# staticnodes.add(nodes[i].address)
2020-05-28 03:28:44 +00:00
2020-06-15 03:06:08 +00:00
# Scenario xx1 - 16 full nodes, one app topic, full mesh, gossip
# Scenario xx2 - 14 full nodes, two edge nodes, one app topic, full mesh, gossip
2020-06-02 03:18:55 +00:00
# NOTE: Only connecting to one node here
2020-06-15 03:06:08 +00:00
#var nodesubseta: seq[string]
#var nodesubsetb: seq[string]
#nodesubseta.add(staticnodes[0])
#nodesubsetb.add(staticnodes[amount-1])
## XXX: Let's turn them into normal nodes
#nodes.add(initNodeCmd(0, nodesubseta, label = "edge node (A)"))
#nodes.add(initNodeCmd(1, nodesubsetb, label = "edge node (B)"))
2020-05-21 04:36:30 +00:00
var commandStr = " multitail -s 2 -M 0 -x \" Waku Simulation \" "
var count = 0
var sleepDuration = 0
for node in nodes :
2020-05-28 03:28:44 +00:00
if topology in { Star } : #DiscoveryBased
sleepDuration = if node . master : 0
else : 1
2020-05-21 04:36:30 +00:00
commandStr & = & " -cT ansi -t ' node #{count} {node.label} ' -l ' sleep {sleepDuration}; {node.cmd}; echo [node execution completed]; while true; do sleep 100; done ' "
2020-05-28 03:28:44 +00:00
if topology = = FullMesh :
sleepDuration + = 1
count + = 1
2020-05-21 04:36:30 +00:00
2022-03-01 16:12:23 +00:00
2020-06-15 04:06:41 +00:00
generatePrometheusConfig ( nodes , metricsDir / " prometheus " / " prometheus.yml " )
proccessGrafanaDashboard ( nodes . len ,
metricsDir / " waku-grafana-dashboard.json " ,
metricsDir / " waku-sim-all-nodes-grafana-dashboard.json " )
2020-05-21 04:36:30 +00:00
let errorCode = execCmd ( commandStr )
if errorCode ! = 0 :
error " launch command failed " , command = commandStr