Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add network simulator #10

Merged
merged 25 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@ testnet.g
**/coverage.txt

dist/
/docker/networksimulator/docker-compose.yaml
/docker/networksimulator/prometheus/prometheus.yml
13 changes: 13 additions & 0 deletions docker/networksimulator/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM ubuntu:22.04

RUN apt update -y \
&& apt install -y golang wget git make curl

COPY .. /src/

RUN cd /src \
&& make x1 \
&& mv ./build/x1 /usr/local/bin/ \
&& rm -rf /src

entrypoint ["/usr/local/bin/x1"]
117 changes: 117 additions & 0 deletions docker/networksimulator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Simulate a Network

> install deps
```shell
# be in the docker/networksimulator directory
cd docker/networksimulator

pip3 install -r requirements.txt
```

> run the simulated network. Replace NUM_OF_NODES with the number of nodes you want to simulate.

```shell
# Generate the docker compose file
# Replace NUM_OF_NODES with the number of nodes you want to simulate.
python3 generate.py NUM_OF_NODES

# Run the network
# use --build to rebuild the docker image from source ex: docker-compose up --build
docker-compose up

# Example: build and run a network of 10 nodes
python3 generate.py 10 && docker compose up --build
```

Give it a few minutes for all the nodes to start up.

> Test the RPC endpoint
```shell
curl -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}' http://127.0.0.1:8545
```

See test.py for an example of how to interact with the network.

## Lachesis Base

This is a hacky way to include a custom Lachesis base inside the docker build context.

> Rsync your lachesis base files into the `lachesis-base` directory inside go-x1. This will be copied into the docker build context.
```shell
rsync -rav --exclude .git ../lachesis-base/ lachesis-base/
```

> Update the go.mod file to point to the local lachesis-base directory
```shell
echo "replace github.com/Fantom-foundation/lachesis-base => ./lachesis-base" >> go.mod
```

> Now build the docker image as usual
```shell
cd docker/networksimulator

# Example: build and run a network of 10 nodes
python3 generate.py 10 && docker compose up --build
```

## Include a Custom SFC contract

The custom SFC contract will be built and included in the genesis block of the simulated network.

Note: The genesis will no longer be compatible with the testnet genesis.

```shell
# clone the sfc contract repo into the parent directory of go-x1
# so both repos are in the same directory
cd ../../..
git clone [email protected]:nibty/x1-sfc.git

cd x1-sfc
# make any changes to the SFC contract here

# run go generate ./... inside the go-x1 directory
# This will build the SFC contract and generate the go bindings.
# This requires Docker, takes a long time, and will not exit with ctrl-c, so be careful.
cd ../go-x1
go generate ./...
```

> Now build the docker image as usual
```shell
cd docker/networksimulator

# Example: build and run a network of 10 nodes
python3 generate.py 10 && docker compose up --build
```

## Metrics and Monitoring

Prometheus and Granfana are included in the docker-compose file.

- Prometheus server at http://localhost:9090/targets
- Grafana dashboards at http://localhost:3000

## Test Script

The test.py script is an example of how to interact with the simulated network. It uses the web3.py library to send transactions to the network.

> send 5 transactions to the network using validator 1's private key
```shell
python3 test.py 5
```

> send 100 transactions and don't wait for a receipt from validator 2
```shell
python3 test.py 5 --no-wait --validator 2
```

> send 1000 transactions and don't wait for a receipt from 10 validators concurrently
```shell
python3 test.py 5 --no-wait --validator 2
```

> see all the options
```shell
python3 test.py --help
```

49 changes: 49 additions & 0 deletions docker/networksimulator/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import jinja2
import ipaddress
import argparse

DEFAULT_IP_BASE = '172.16.239.0'
DEFAULT_NUM_NODES = 3
DEFAULT_STARTING_PORT = 7050
DEFAULT_FLAGS = "--testnet --metrics --metrics.addr 0.0.0.0 --http --http.port 8545 --http.addr 0.0.0.0 --http.vhosts '*' --http.corsdomain '*' --ws --ws.addr 0.0.0.0 --ws.port 8546 --ws.origins '*' --verbosity 3 "
BOOT_NODE_KEY = "4b20a091f6389ca9ee1492187cc2d775511fa1e0801bf1f787b3a14f961530b1"
BOOT_NODE_PUB_KEY = "d06482f636e5c68586215f9ab9dfda270d38bf468195fc2e767d5d74b5fc7ab4faffc46028aa360b723ce53ded022949a8a6b1c96013d8ec1771f4ed448518b4"


argparser = argparse.ArgumentParser()
argparser.add_argument(type=int, dest="num_nodes", default=DEFAULT_NUM_NODES)
argparser.add_argument('--ip-base', type=str, default=DEFAULT_IP_BASE)
argparser.add_argument('--flags', type=str, default=DEFAULT_FLAGS)
argparser.add_argument('--starting-port', type=int, default=DEFAULT_STARTING_PORT)
args = argparser.parse_args()

nodes = []
for i in range(1, args.num_nodes + 1):
nodes.append({
"id": i,
"ip": ipaddress.ip_address(args.ip_base) + i,
"p2p_port": args.starting_port + i,
"metrics_port": args.starting_port + i + 1000,
"rpc_port": args.starting_port + i + 2000,
"ws_port": args.starting_port + i + 3000,
})

environment = jinja2.Environment()
environment.filters['ip_address'] = ipaddress.ip_address
environment.loader = jinja2.FileSystemLoader('templates')

template = environment.get_template('docker-compose.yaml.j2')
template.stream({
"num_nodes": args.num_nodes,
"ip_base": args.ip_base,
"starting_port": args.starting_port,
"flags": args.flags,
"bootnode_key": BOOT_NODE_KEY,
"bootnode_pub_key": BOOT_NODE_PUB_KEY,
"nodes": nodes,
}).dump('docker-compose.yaml')

prometheus = environment.get_template('prometheus.yml.j2')
prometheus.stream({
"nodes": nodes,
}).dump('prometheus/prometheus.yml')
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: 1

providers:
- name: "Dashboard provider"
orgId: 1
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: false
editable: true
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: 1

datasources:
- name: Prometheus
type: prometheus
url: http://prometheus:9090
isDefault: true
access: proxy
editable: true
Loading
Loading