Compare commits

...

24 Commits

Author SHA1 Message Date
Neeraj
8ca78a91bf Get network name by extracting it from deployment.yml 2024-09-12 11:11:49 +05:30
Neeraj
c493363214 Remove skip container build instruction 2024-09-12 10:54:02 +05:30
Neeraj
32b0b9ee52 Add comments for values in bridge-vars file 2024-09-12 09:20:36 +05:30
Neeraj
98c502a882 Update steps to modify output directory in setup-vars file 2024-09-11 12:12:52 +05:30
Neeraj
5c79a71851 Add steps to make optimism deployment on remote host 2024-09-11 11:26:25 +05:30
Neeraj
edc4f743d6 Add steps to make nitro contract and bridge deployment on remote host 2024-09-11 11:01:37 +05:30
3b6dd2b2c6 Update prerequisites and port mappings 2024-09-11 10:16:50 +05:30
Neeraj
5649d8fd6d Fetch and use peer id and state channel address 2024-09-11 09:27:10 +05:30
Neeraj
d71a481db3 Add steps to deploy l1 nitro contracts 2024-09-11 09:27:10 +05:30
db67b2fe3b Add details for users Nitro account 2024-09-10 16:20:51 +05:30
7067208d2a Add instructions to bridge funds on L2 2024-09-09 12:19:29 +05:30
cdf26a0014 Add troubleshooting steps 2024-09-05 17:47:39 +05:30
2039ad645d Add instructions for users to create ledger channel 2024-09-05 17:08:25 +05:30
776f91468e Add steps to create the required config for Nitro nodes 2024-09-05 13:12:09 +05:30
2c7077d2f7 Fill in values for L2 deployment config 2024-09-05 12:36:41 +05:30
1bf0066234 Update instructions L2 and bridge setup 2024-09-05 09:10:54 +05:30
ed0b13ce31 Add instructions to setup and run Nitro bridge deployment 2024-09-03 19:34:08 +05:30
5ad781d258 Fix L2 config file path in instructions 2024-09-03 15:40:29 +05:30
21bafbfd4b Add instructions for running a testnet Nitro node 2024-09-03 14:57:22 +05:30
Neeraj
aa656e828d Add README to run nitro node 2024-09-03 11:57:57 +05:30
Shreerang Kale
a6cfdf5325 Add instructions to setup and run L2 deployment from scratch 2024-09-03 11:12:06 +05:30
8c672c0966 Add a script to generate stage1 participants and allocations with given validators (#25)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)
Requires cerc-io/fixturenet-laconicd-stack#14

Reviewed-on: cerc-io/testnet-laconicd-stack#25
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-08-19 10:12:29 +00:00
98e882bf28 Add instructions to run stage1 with slashing module (#23)
Part of [laconicd testnet validator enrollment](cerc-io/testnet-laconicd-stack#19)
Requires cerc-io/laconicd#53 and cerc-io/fixturenet-laconicd-stack#13

Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Reviewed-on: cerc-io/testnet-laconicd-stack#23
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-08-16 14:52:45 +00:00
5ce343566e Add a script to get subscribed and onboarded laconicd accounts (#22)
Part of [laconicd testnet validator enrollment](cerc-io/testnet-laconicd-stack#19)

The script:
- Fetches onboarded participants using the configured laconicd GQL endpoint
- Reads a given CSV file with subscribers data
- Fetches all the onboarding txs using given laconicd RPC endpoint
- For each subscriber registered as a participant, maps the laconic pubkey and latest onboarding tx height
- Outputs the result in a CSV

Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Reviewed-on: cerc-io/testnet-laconicd-stack#22
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2024-08-16 04:11:54 +00:00
17 changed files with 121316 additions and 40 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*-deployment
*-spec.yml

View File

@ -15,3 +15,7 @@ Stacks to run a node for laconic testnet
## Join LORO testnet
Follow steps in [testnet-onboarding-validator.md](./testnet-onboarding-validator.md) to onboard your participant and join as a validator on the LORO testnet
## Run Testnet Nitro Node
Follow steps in [testnet-nitro-node.md](./testnet-nitro-node.md) to run you Nitro node for the testnet

View File

@ -10,6 +10,517 @@
cd /srv
```
## Prerequisites
* Local:
* Clone the `cerc-io/testnet-ops` repository:
```bash
git clone git@git.vdb.to:cerc-io/testnet-ops.git
```
* Ansible: see [installation](https://git.vdb.to/cerc-io/testnet-ops#installation)
* On deployments VM(s):
* laconic-so: see [installation](https://git.vdb.to/cerc-io/testnet-ops/src/branch/main/stack-orchestrator-setup/README.md)
<details open>
<summary>L2 Optimism</summary>
## L2 Optimism
* Stack: <https://git.vdb.to/cerc-io/fixturenet-optimism-stack>
* Source repos:
* <github.com/ethereum-optimism/optimism@v1.7.7>
* <github.com/ethereum-optimism/op-geth@v1.101315.2>
* Target dir: `/srv/op-sepolia/optimism-deployment`
* Cleanup an existing deployment on VM if required:
```bash
cd /srv/op-sepolia
# Stop the deployment
laconic-so deployment --dir optimism-deployment stop --delete-volumes
# Remove the deployment dir
sudo rm -rf optimism-deployment
```
### Setup
* Switch to `testnet-ops/l2-setup` directory on your local machine:
```bash
cd testnet-ops/l2-setup
```
* Copy the `l2-vars-example.yml` vars file:
```bash
cp l2-vars-example.yml l2-vars.yml
```
* Edit `l2-vars.yml` with required values:
```bash
# L1 chain ID (Sepolia: 11155111)
l1_chain_id: "11155111"
# L1 RPC endpoint
l1_rpc: "http://host.docker.internal:8545"
# L1 RPC endpoint host or IP address
l1_host: "host.docker.internal"
# L1 RPC endpoint port number
l1_port: "8545"
# L1 Beacon endpoint
l1_beacon: "http://host.docker.internal:8001"
# Account credentials for the Admin account
# Used for Optimism contracts deployment and funding other generated accounts
l1_address: ""
l1_priv_key: ""
```
* Update the target dir in `setup-vars.yml`:
```bash
sed -i 's|^l2_directory:.*|l2_directory: /srv/op-sepolia|' setup-vars.yml
# Will create deployment at /srv/op-sepolia/optimism-deployment
```
### Run
* Set up and run L2 on remote host by executing `run-optimism.yml` Ansible playbook on your local machine:
* Create a new `hosts.ini` file:
```bash
cp ../hosts.example.ini hosts.ini
```
* Edit the [`hosts.ini`](./hosts.ini) file to run the playbook on a remote machine:
```ini
[l2_host]
<host_name> ansible_host=<target_ip> ansible_user=<ssh_user> ansible_ssh_common_args='-o ForwardAgent=yes'
```
- Replace `<host_name>` with the alias of your choice
- Replace `<target_ip>` with the IP address or hostname of the target machine
- Replace `<ssh_user>` with the SSH username (e.g., dev, ubuntu)
* Verify that you are able to connect to the host using the following command:
```bash
ansible all -m ping -i hosts.ini -k
# Expected output:
# <host_name> | SUCCESS => {
# "ansible_facts": {
# "discovered_interpreter_python": "/usr/bin/python3.10"
# },
# "changed": false,
# "ping": "pong"
# }
```
* Execute the `run-optimism.yml` Ansible playbook for remote deployment:
```bash
LANG=en_US.utf8 ansible-playbook -i hosts.ini run-optimism.yml --extra-vars='{ "target_host": "l2_host"}' --user $USER -kK
```
* Bridge funds on L2:
* On the deployment VM, set the following variables:
```bash
cd /srv/op-sepolia
L1_RPC=http://host.docker.internal:8545
L2_RPC=http://host.docker.internal:9545
NETWORK=$(grep 'cluster-id' optimism-deployment/deployment.yml | sed 's/cluster-id: //')_default
DEPLOYMENT_CONTEXT=11155111
ACCOUNT=<admin-account-address>
```
* Read the bridge contract address from the L1 deployment records in the `op-node` container:
```bash
BRIDGE=$(laconic-so deployment --dir optimism-deployment exec op-node "cat /l1-deployment/$DEPLOYMENT_CONTEXT-deploy.json" | jq -r .L1StandardBridgeProxy)
# Get the funded account's pk
ACCOUNT_PK=$(laconic-so deployment --dir optimism-deployment exec op-node "jq -r '.AdminKey' /l2-accounts/accounts.json")
```
* Check that the starting balance for account on L2 is 0:
```bash
docker run --rm --network $NETWORK cerc/optimism-contracts:local "cast balance $ACCOUNT --rpc-url $L2_RPC"
# 0
```
* Use cast to send ETH to the bridge contract:
```bash
docker run --rm cerc/optimism-contracts:local "cast send --from $ACCOUNT --value 1ether $BRIDGE --rpc-url $L1_RPC --private-key $ACCOUNT_PK"
```
* Allow a couple minutes for the bridge to complete
* Check balance on L2:
```bash
docker run --rm --network $NETWORK cerc/optimism-contracts:local "cast balance $ACCOUNT --rpc-url $L2_RPC"
# 100000000000000000
```
</details>
<details open>
<summary>L1 Nitro Contracts Deployment</summary>
## L1 Nitro Contracts Deployment
* Stack: <https://git.vdb.to/cerc-io/nitro-stack/src/branch/main/stack-orchestrator/stacks/nitro-contracts>
* Source repo: <https://github.com/cerc-io/go-nitro>
* Target dir: `/srv/bridge/nitro-contracts-deployment`
* Cleanup an existing deployment on VM if required:
```bash
cd /srv/bridge
# Stop the deployment
laconic-so deployment --dir nitro-contracts-deployment stop --delete-volumes
# Remove the deployment dir
sudo rm -rf nitro-contracts-deployment
### Setup
* Switch to `testnet-ops/nitro-contracts-setup` directory on your local machine:
```bash
cd testnet-ops/nitro-contracts-setup
```
* Copy the `contract-vars-example.yml` vars file:
```bash
cp contract-vars.example.yml contract-vars.yml
```
* Edit [`contract-vars.yml`](./contract-vars.yml) and fill in the following values:
```bash
# L1 RPC endpoint
geth_url: "https://sepolia.laconic.com"
# L1 chain ID (Sepolia: 11155111)
geth_chain_id: "11155111"
# Private key for a funded account on L1 to use for contracts deployment on L1
geth_deployer_pk: ""
# Custom L1 token to be deployed
token_name: ""
token_symbol: ""
intial_token_supply: ""
```
* Update the target dir in `setup-vars.yml`:
```bash
sed -i 's|^nitro_directory:.*|nitro_directory: /srv/bridge|' setup-vars.yml
# Will create deployment at /srv/bridge/nitro-contracts-deployment
```
### Run
* Deploy nitro contracts on remote host by executing `deploy-contracts.yml` Ansible playbook on your local machine:
* Create a new `hosts.ini` file:
```bash
cp ../hosts.example.ini hosts.ini
```
* Edit the [`hosts.ini`](./hosts.ini) file to run the playbook on a remote machine:
```ini
[nitro_host]
<host_name> ansible_host=<target_ip> ansible_user=<ssh_user> ansible_ssh_common_args='-o ForwardAgent=yes'
```
- Replace `<host_name>` with the alias of your choice
- Replace `<target_ip>` with the IP address or hostname of the target machine
- Replace `<ssh_user>` with the SSH username (e.g., dev, ubuntu)
* Verify that you are able to connect to the host using the following command:
```bash
ansible all -m ping -i hosts.ini -k
# Expected output:
# <host_name> | SUCCESS => {
# "ansible_facts": {
# "discovered_interpreter_python": "/usr/bin/python3.10"
# },
# "changed": false,
# "ping": "pong"
# }
```
* Execute the `deploy-contracts.yml` Ansible playbook for remote deployment:
```bash
LANG=en_US.utf8 ansible-playbook -i hosts.ini deploy-contracts.yml --extra-vars='{ "target_host": "nitro_host"}' --user $USER -kK
```
* Check logs for deployment on the virtual machine:
```bash
cd /srv/bridge
# Check the l1 nitro contract deployments
laconic-so deployment --dir nitro-contracts-deployment logs nitro-contracts -f
```
</details>
<details open>
<summary>Nitro Bridge</summary>
## Nitro Bridge
* Stack: <https://git.vdb.to/cerc-io/nitro-stack/src/branch/main/stack-orchestrator/stacks/bridge>
* Source repo: <https://github.com/cerc-io/go-nitro>
* Target dir: `/srv/bridge/bridge-deployment`
* Cleanup an existing deployment on VM if required:
```bash
cd /srv/bridge
# Stop the deployment
laconic-so deployment --dir bridge-deployment stop --delete-volumes
# Remove the deployment dir
sudo rm -rf bridge-deployment
```
### Setup
* Execute the command on the deployment VM to get the deployed L1 Nitro contract addresses along with the L1 asset address:
```bash
cd /srv/bridge
laconic-so deployment --dir nitro-contracts-deployment exec nitro-contracts "cat /app/deployment/nitro-addresses.json"
# Expected output:
# {
# "11155111": [
# {
# "name": "geth",
# "chainId": "11155111",
# "contracts": {
# "ConsensusApp": {
# "address": "0xC98aD0B41B9224dad0605be32A9241dB9c67E2e8"
# },
# "NitroAdjudicator": {
# "address": "0x7C22fdA703Cdf09eB8D3B5Adc81F723526713D0e"
# },
# "VirtualPaymentApp": {
# "address": "0x778e4e6297E8BF04C67a20Ec989618d72eB4a19E"
# },
# "Token": {
# "address": "0x02ebfB2706527C7310F2a7d9098b2BC61014C5F2"
# }
# }
# }
# ]
# }
```
* Switch to `testnet-ops/nitro-bridge-setup` directory on your local machine:
```bash
cd testnet-ops/nitro-bridge-setup
```
* Create the required vars file:
```bash
cp bridge-vars.example.yml bridge-vars.yml
```
* Edit `bridge-vars.yml` with required values:
```bash
# L1 WS endpoint
nitro_l1_chain_url: "wss://sepolia.laconic.com"
# L2 WS endpoint
nitro_l2_chain_url: "wss://optimism.laconic.com"
# Private key for bridge Nitro address
nitro_sc_pk: ""
# Private key for a funded account on L1
# This account should have L1 tokens for funding Nitro channels
nitro_chain_pk: ""
# L2 chain ID (Optimism: 42069)
optimism_chain_id: "42069"
# L2 RPC endpoint
optimism_url: "https://optimism.laconic.com"
# Private key for a funded account for L2 contracts deployment
# Use the same account for L1 and L2 deployments
optimism_deployer_pk: ""
# Custom L2 token to be deployed
token_name: ""
token_symbol: ""
intial_token_supply: ""
# Deployed L1 Nitro contract addresses
na_address: ""
vpa_address: ""
ca_address: ""
# Deployed L1 token address
l1_asset_address: ""
```
* Update the target dir in `setup-vars.yml`:
```bash
sed -i 's|^nitro_directory:.*|nitro_directory: /srv/bridge|' setup-vars.yml
# Will create deployment at /srv/bridge/nitro-contracts-deployment and /srv/bridge/bridge-deployment
```
### Run
* Deploy L2 contracts and start the bridge on remote host by executing `run-nitro-bridge.yml` Ansible playbook on your local machine:
* Create a new `hosts.ini` file:
```bash
cp ../hosts.example.ini hosts.ini
```
* Edit the [`hosts.ini`](./hosts.ini) file to run the playbook on a remote machine:
```ini
[nitro_host]
<host_name> ansible_host=<target_ip> ansible_user=<ssh_user> ansible_ssh_common_args='-o ForwardAgent=yes'
```
- Replace `<host_name>` with the alias of your choice
- Replace `<target_ip>` with the IP address or hostname of the target machine
- Replace `<ssh_user>` with the SSH username (e.g., dev, ubuntu)
* Verify that you are able to connect to the host using the following command:
```bash
ansible all -m ping -i hosts.ini -k
# Expected output:
# <host_name> | SUCCESS => {
# "ansible_facts": {
# "discovered_interpreter_python": "/usr/bin/python3.10"
# },
# "changed": false,
# "ping": "pong"
# }
```
* Execute the `run-nitro-bridge.yml` Ansible playbook for remote deployment:
```bash
LANG=en_US.utf8 ansible-playbook -i hosts.ini run-nitro-bridge.yml --extra-vars='{ "target_host": "nitro_host"}' --user $USER -kK
```
* Check logs for deployments on the virtual machine:
```bash
cd /srv/bridge
# Check the l2 nitro contract deployments
laconic-so deployment --dir bridge-deployment logs l2-nitro-contracts -f
# Check bridge logs, ensure that the node is running
laconic-so deployment --dir bridge-deployment logs nitro-bridge -f
```
* Create Nitro node config for users:
```bash
cd /srv/bridge
# Create required variables
GETH_CHAIN_ID="11155111"
OPTIMISM_CHAIN_ID="42069"
export NA_ADDRESS=$(laconic-so deployment --dir nitro-contracts-deployment exec nitro-contracts "jq -r '.\"$GETH_CHAIN_ID\"[0].contracts.NitroAdjudicator.address' /app/deployment/nitro-addresses.json")
export CA_ADDRESS=$(laconic-so deployment --dir nitro-contracts-deployment exec nitro-contracts "jq -r '.\"$GETH_CHAIN_ID\"[0].contracts.ConsensusApp.address' /app/deployment/nitro-addresses.json")
export VPA_ADDRESS=$(laconic-so deployment --dir nitro-contracts-deployment exec nitro-contracts "jq -r '.\"$GETH_CHAIN_ID\"[0].contracts.VirtualPaymentApp.address' /app/deployment/nitro-addresses.json")
export L1_ASSET_ADDRESS=$(laconic-so deployment --dir nitro-contracts-deployment exec nitro-contracts "jq -r '.\"$GETH_CHAIN_ID\"[0].contracts.Token.address' /app/deployment/nitro-addresses.json")
export BRIDGE_CONTRACT_ADDRESS=$(laconic-so deployment --dir bridge-deployment exec nitro-bridge "jq -r '.\"$OPTIMISM_CHAIN_ID\"[0].contracts.Bridge.address' /app/deployment/nitro-addresses.json")
export BRIDGE_NITRO_ADDRESS=$(laconic-so deployment --dir bridge-deployment exec nitro-rpc-client "nitro-rpc-client get-node-info -p 4006 -h nitro-bridge" | jq -r '.SCAddress')
export BRIDGE_PEER_ID=$(laconic-so deployment --dir bridge-deployment exec nitro-rpc-client "nitro-rpc-client get-node-info -p 4006 -h nitro-bridge" | jq -r '.MessageServicePeerId')
export L1_BRIDGE_MULTIADDR="/dns4/bridge.laconic.com/tcp/3005/p2p/$BRIDGE_PEER_ID"
export L2_BRIDGE_MULTIADDR="/dns4/bridge.laconic.com/tcp/3006/p2p/$BRIDGE_PEER_ID"
# Create the required config file
cat <<EOF > nitro-node-config.yml
nitro_l1_chain_url: "wss://sepolia.laconic.com"
nitro_l2_chain_url: "wss://optimism.laconic.com"
na_address: "$NA_ADDRESS"
ca_address: "$CA_ADDRESS"
vpa_address: "$VPA_ADDRESS"
l1_asset_address: "${L1_ASSET_ADDRESS}"
bridge_contract_address: "$BRIDGE_CONTRACT_ADDRESS"
bridge_nitro_address: "$BRIDGE_NITRO_ADDRESS"
nitro_l1_bridge_multiaddr: "$L1_BRIDGE_MULTIADDR"
nitro_l2_bridge_multiaddr: "$L2_BRIDGE_MULTIADDR"
EOF
```
The required config file should be generated at `/srv/bridge/nitro-node-config.yml`
Check in the file at location `ops/stage2/nitro-node-config.yml`
</details>
<details open>
<summary>stage0 laconicd</summary>
@ -99,7 +610,7 @@
cat <<EOF > stage0-deployment/config.env
# Set to true to enable adding participants functionality of the onboarding module
ONBOARDING_ENABLED=true
# A custom human readable name for this node
MONIKER=LaconicStage0
EOF
@ -625,7 +1136,7 @@
```bash
network:
ports:
laconic-console:
console:
- '127.0.0.1:4001:80'
```
@ -669,10 +1180,22 @@
## Domains / Port Mappings
```bash
laconicd.laconic.com -> 26657
laconicd.laconic.com/api -> 9473/api
faucet.laconic.com -> 4000
loro-signup.laconic.com -> 3000
wallet.laconic.com -> 5000
loro-console.laconic.com -> 4001
# Machine 1
https://laconicd.laconic.com -> 26657
https://laconicd.laconic.com/api -> 9473/api
https://faucet.laconic.com -> 4000
https://loro-signup.laconic.com -> 3000
https://wallet.laconic.com -> 5000
https://loro-console.laconic.com -> 4001
# Machine 2
https://sepolia.laconic.com -> 8545
wss://sepolia.laconic.com -> 8546
https://optimism.laconic.com -> 9545
wss://optimism.laconic.com -> 9546
bridge.laconic.com
Open ports:
3005 (L1 side)
3006 (L2 side)
```

View File

@ -22,12 +22,6 @@ Once all the participants have completed their onboarding, stage0 laconicd chain
laconic-so deployment --dir stage0-deployment logs laconicd -f --tail 30
```
* List the participants on stage0:
```bash
laconic-so deployment --dir stage0-deployment exec laconicd "laconicd query onboarding list"
```
* Stop the stage0 deployment:
```bash
@ -36,37 +30,29 @@ Once all the participants have completed their onboarding, stage0 laconicd chain
## Start stage1
* Use the scripts in fixturenet-laconicd stack to generate genesis file for stage1 using onboarding participants from stage0 chain with token allocations:
* Rebuild laconicd container with `>=v0.1.7` to enable `slashing` module:
```bash
# laconicd source
cd ~/cerc/laconicd
# Pull latest changes
git pull
# Confirm the latest commit hash
git log
# Rebuild the containers
cd /srv/laconicd
# Set current working dir path in a variable
DEPLOYMENTS_DIR=$(pwd)
cd ~/cerc/fixturenet-laconicd-stack/stack-orchestrator/stacks/fixturenet-laconicd
# Generate the genesis file
# Participant allocation: 1000000000000 (10^12)
# Validator allocation: 2000000000000000 (10^15)
./scripts/generate-stage1-genesis-using-allocations.sh $DEPLOYMENTS_DIR/stage0-deployment 1000000000000 2000000000000000
# Expected output:
# Genesis file for stage1 written to output/genesis.json
# Remove the temporary data directory
sudo rm -rf stage1-genesis
# Go back to the directory where deployments are created
cd $DEPLOYMENTS_DIR
laconic-so --stack ~/cerc/fixturenet-laconicd-stack/stack-orchestrator/stacks/fixturenet-laconicd build-containers --force-rebuild
```
* Copy over the generated genesis file (`.json`) containing the onboarding module state with funded participants to data directory in stage1 deployment (`stage1-deployment/data/genesis-config`):
* Fetch the generated genesis file with stage1 participants and token allocations:
```bash
cd /srv/laconicd
cp ~/cerc/fixturenet-laconicd-stack/stack-orchestrator/stacks/fixturenet-laconicd/output/genesis.json stage1-deployment/data/genesis-config/genesis.json
# Place in stage1 deployment directory
wget -O /srv/laconicd/stage1-deployment/data/genesis-config/genesis.json https://git.vdb.to/cerc-io/testnet-laconicd-stack/raw/branch/main/ops/stage1/genesis-accounts.json
```
* Start the stage1 deployment:
@ -108,3 +94,78 @@ Once all the participants have completed their onboarding, stage0 laconicd chain
```
* Now users can follow the steps to [Join as a validator on stage1](https://git.vdb.to/cerc-io/testnet-laconicd-stack/src/branch/main/testnet-onboarding-validator.md#join-as-a-validator-on-stage1)
## Bank Transfer
* Transfer tokens to an address:
```bash
cd /srv/laconicd
RECEIVER_ADDRESS=
AMOUNT=
laconic-so deployment --dir stage1-deployment exec laconicd "laconicd tx bank send alice ${RECEIVER_ADDRESS} ${AMOUNT}alnt --from alice --fees 1000000alnt"
```
* Check balance:
```bash
laconic-so deployment --dir stage1-deployment exec laconicd "laconicd query bank balances ${RECEIVER_ADDRESS}"
```
---
## Generating stage1 genesis
* Following steps to be run on a local machine
* Clone repos:
```bash
git clone git@git.vdb.to:cerc-io/testnet-laconicd-stack.git
git clone git@git.vdb.to:cerc-io/fixturenet-laconicd-stack.git
```
* Create stage1 participants and allocations using provided validators list:
* Prerequisite: `validators.csv` file with list of laconic addresses, example:
```csv
laconic13ftz0c6cg6ttfda7ct4r6pf2j976zsey7l4wmj
laconic1he4wjpfm5atwfvqurpg57ctp8chmxt9swf02dx
laconic1wpsdkwz0t4ejdm7gcl7kn8989z88dd6wwy04np
...
```
* Build
```bash
# Change to scripts dir
cd testnet-laconicd-stack/scripts
# Install dependencies and build
yarn && yarn build
```
* Run script
```bash
yarn participants-with-filtered-validators --validators-csv ./validators.csv --participant-alloc 200000000000 --validator-alloc 1000200000000000 --output stage1-participants-$(date +"%Y-%m-%dT%H%M%S").json --output-allocs stage1-allocs-$(date +"%Y-%m-%dT%H%M%S").json
# This should create two json files with stage1 participants and allocations
```
* Create stage1 genesis file:
```bash
# Change to fixturenet-laconicd stack dir
cd fixturenet-laconicd-stack/stack-orchestrator/stacks/fixturenet-laconicd
# Generate genesis file
# Provide absolute paths to generated stage1-participants and stage1-allocs files
./scripts/generate-stage1-genesis-from-json.sh /path/to/testnet-laconicd-stack/scripts/stage1-participants.json /path/to/testnet-laconicd-stack/scripts/stage1-allocs.json
# This should generate the required genesis file at output/genesis.json
```

File diff suppressed because it is too large Load Diff

8
scripts/.env.example Normal file
View File

@ -0,0 +1,8 @@
# Default: https://laconicd.laconic.com/api
LACONICD_GQL_ENDPOINT=
# Default: https://laconicd.laconic.com
LACONICD_RPC_ENDPOINT=
# Default: laconic_9000-1
LACONICD_CHAIN_ID=

3
scripts/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules
dist
.env

1
scripts/.npmrc Normal file
View File

@ -0,0 +1 @@
@cerc-io:registry=https://git.vdb.to/api/packages/cerc-io/npm/

45
scripts/README.md Normal file
View File

@ -0,0 +1,45 @@
# scripts
## Prerequisites
- NodeJS >= `v18.17.x`
## Instructions
- Change to scripts dir:
```bash
cd scripts
```
- Install dependencies and build:
```bash
yarn && yarn build
```
- Create required env configuration:
```bash
# Update the values as required
# By default, live laconicd testnet (laconicd.laconic.com) endpoint is configured
cp .env.example .env
```
- Generate a list of onboarded participants and allocations with given list of validators:
```bash
yarn participants-with-filtered-validators --validators-csv <validators-csv-file> --participant-alloc <participant-alloc-amount> --validator-alloc <validator-alloc-amount> --output <output-json-file> --output-allocs <output-allocs-json-file>
# Example:
# yarn participants-with-filtered-validators --validators-csv ./validators.csv --participant-alloc 200000000000 --validator-alloc 1000200000000000 --output stage1-participants-$(date +"%Y-%m-%dT%H%M%S").json --output-allocs stage1-allocs-$(date +"%Y-%m-%dT%H%M%S").json
```
- Map subscribers to onboarded participants:
```bash
yarn map-subscribers-to-participants --subscribers-csv <subscribers-csv-file> --output <output-csv-file>
# Example:
# yarn map-subscribers-to-participants --subscribers-csv subscribers.csv --output result-$(date +"%Y-%m-%dT%H%M%S").csv
```

29
scripts/package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "testnet-laconicd-stack",
"version": "0.1.0",
"main": "index.js",
"repository": "git@git.vdb.to:cerc-io/testnet-laconicd-stack.git",
"license": "UNLICENSED",
"private": true,
"devDependencies": {
"@types/cli-progress": "^3.11.6",
"@types/node": "^22.2.0",
"@types/yargs": "^17.0.33",
"typescript": "^5.5.4"
},
"dependencies": {
"@cerc-io/registry-sdk": "^0.2.6",
"@cosmjs/stargate": "^0.32.4",
"csv-parse": "^5.5.6",
"csv-parser": "^3.0.0",
"csv-writer": "^1.6.0",
"dotenv": "^16.4.5",
"yargs": "^17.7.2"
},
"scripts": {
"build": "tsc",
"map-subscribers-to-participants": "node dist/map-subscribers-to-participants.js",
"participants-with-filtered-validators": "node dist/participants-with-filtered-validators.js"
},
"packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
}

View File

@ -0,0 +1,171 @@
import * as fs from 'fs';
import * as crypto from 'crypto';
import * as path from 'path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { parse as csvParse } from 'csv-parse';
import * as csvWriter from 'csv-writer';
import dotenv from 'dotenv';
import { StargateClient } from '@cosmjs/stargate';
import { Registry } from '@cerc-io/registry-sdk';
import { decodeTxRaw, decodePubkey } from '@cosmjs/proto-signing';
dotenv.config();
const LACONICD_GQL_ENDPOINT = process.env.LACONICD_GQL_ENDPOINT || 'https://laconicd.laconic.com/api';
const LACONICD_RPC_ENDPOINT = process.env.LACONICD_RPC_ENDPOINT || 'https://laconicd.laconic.com';
const LACONICD_CHAIN_ID = process.env.LACONICD_CHAIN_ID || 'laconic_9000-1';
async function main(): Promise<void> {
const argv = _getArgv();
const registry = new Registry(LACONICD_GQL_ENDPOINT, LACONICD_RPC_ENDPOINT, LACONICD_CHAIN_ID);
const client = await StargateClient.connect(LACONICD_RPC_ENDPOINT);
console.time('time_taken_getParticipants');
const participants = await registry.getParticipants();
console.timeEnd('time_taken_getParticipants');
const subscribers = await readSubscribers(argv.subscribersCsv);
console.log('Read subscribers, count:', subscribers.length);
await processSubscribers(client, participants, subscribers, argv.output);
}
async function readSubscribers(subscribersCsvPath: string): Promise<any> {
const fileContent = fs.readFileSync(path.resolve(subscribersCsvPath), { encoding: 'utf-8' });
const headers = ['subscriber_id', 'email', 'status', 'premium?', 'created_at', 'api_subscription_id'];
return csvParse(fileContent, { delimiter: ',', columns: headers }).toArray();
}
function hashSubscriberId(subscriberId: string): string {
return '0x' + crypto.createHash('sha256').update(subscriberId).digest('hex');
}
async function processSubscribers(client: StargateClient, participants: any[], subscribers: any[], outputPath: string) {
// Map kyc_id to participant data
const kycMap: Record<string, any> = {};
participants.forEach((participant: any) => {
kycMap[participant.kycId] = participant;
});
const onboardingTxsHeightMap: Record<string, { txHeight: number, pubkey: string }> = {};
console.time('time_taken_searchTx');
const onboardingTxs = await client.searchTx(`message.action='/cerc.onboarding.v1.MsgOnboardParticipant'`);
console.timeEnd('time_taken_searchTx');
console.log('Fetched onboardingTxs, count:', onboardingTxs.length);
console.time('time_taken_decodingTxs');
onboardingTxs.forEach(onboardingTx => {
const rawPubkey = decodeTxRaw(onboardingTx.tx).authInfo.signerInfos[0].publicKey;
if (!rawPubkey) {
console.error('pubkey not found in tx', onboardingTx.hash);
return;
}
const pubkey = decodePubkey(rawPubkey).value;
// Determine sender
const onboardParticipantEvent = onboardingTx.events.find(event => event.type === 'onboard_participant');
if (!onboardParticipantEvent) {
console.error('onboard_participant event not found in tx', onboardingTx.hash);
return;
}
const sender = onboardParticipantEvent.attributes.find(attr => attr.key === 'signer')?.value;
if (!sender) {
console.error('sender not found in onboard_participant event for tx', onboardingTx.hash)
return;
}
// Update if already exists
let latesTxHeight = onboardingTx.height;
if (onboardingTxsHeightMap[sender]) {
latesTxHeight = latesTxHeight > onboardingTxsHeightMap[sender].txHeight ? latesTxHeight : onboardingTxsHeightMap[sender].txHeight;
}
onboardingTxsHeightMap[sender] = { txHeight: latesTxHeight, pubkey };
});
console.timeEnd('time_taken_decodingTxs');
const onboardedSubscribers: any[] = [];
for (let i = 0; i < subscribers.length; i++) {
const subscriber = subscribers[i];
const hashedSubscriberId = hashSubscriberId(subscriber['subscriber_id']);
const participant = kycMap[hashedSubscriberId];
if (!participant) {
continue;
}
const participantAddresss = participant['cosmosAddress'];
// Skip participant if an onboarding tx not found
if (!onboardingTxsHeightMap[participantAddresss]) {
continue;
}
const onboardedSubscriber = {
subscriber_id: subscriber['subscriber_id'],
email: subscriber['email'],
status: subscriber['status'],
'premium?': subscriber['premium?'],
created_at: subscriber['created_at'],
laconic_address: participantAddresss,
nitro_address: participant['nitroAddress'],
role: participant['role'],
hashed_subscriber_id: participant['kycId'],
laconic_pubkey: onboardingTxsHeightMap[participantAddresss].pubkey,
onboarding_height: onboardingTxsHeightMap[participantAddresss].txHeight
};
onboardedSubscribers.push(onboardedSubscriber);
}
const writer = csvWriter.createObjectCsvWriter({
path: path.resolve(outputPath),
header: [
{ id: 'subscriber_id', title: 'subscriber_id' },
{ id: 'email', title: 'email' },
{ id: 'status', title: 'status' },
{ id: 'premium?', title: 'premium?' },
{ id: 'created_at', title: 'created_at' },
{ id: 'laconic_address', title: 'laconic_address' },
{ id: 'nitro_address', title: 'nitro_address' },
{ id: 'role', title: 'role' },
{ id: 'hashed_subscriber_id', title: 'hashed_subscriber_id' },
{ id: 'laconic_pubkey', title: 'laconic_pubkey' },
{ id: 'onboarding_height', title: 'onboarding_height' },
],
alwaysQuote: true
});
await writer.writeRecords(onboardedSubscribers);
console.log(`Data has been written to ${path.resolve(outputPath)}`);
}
function _getArgv (): any {
return yargs(hideBin(process.argv))
.option('subscribersCsv', {
alias: 's',
type: 'string',
demandOption: true,
describe: 'Path to the subscribers CSV file',
})
.option('output', {
alias: 'o',
type: 'string',
demandOption: true,
describe: 'Path to the output CSV file',
})
.help()
.argv;
}
main().catch(err => {
console.log(err);
});

View File

@ -0,0 +1,114 @@
import * as fs from 'fs';
import * as path from 'path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import dotenv from 'dotenv';
import { Registry } from '@cerc-io/registry-sdk';
dotenv.config();
const LACONICD_GQL_ENDPOINT = process.env.LACONICD_GQL_ENDPOINT || 'https://laconicd.laconic.com/api';
const LACONICD_RPC_ENDPOINT = process.env.LACONICD_RPC_ENDPOINT || 'https://laconicd.laconic.com';
const LACONICD_CHAIN_ID = process.env.LACONICD_CHAIN_ID || 'laconic_9000-1';
async function main(): Promise<void> {
const argv = _getArgv();
const registry = new Registry(LACONICD_GQL_ENDPOINT, LACONICD_RPC_ENDPOINT, LACONICD_CHAIN_ID);
console.time('time_taken_getParticipants');
const participants = await registry.getParticipants();
console.log('Fetched participants, count:', participants.length);
console.timeEnd('time_taken_getParticipants');
let validators: Array<string> = await readValidators(argv.validatorsCsv);
console.log('Read validators, count:', validators.length);
let stage1Allocations: Array<{ 'cosmos_address': string, balance: string }> = [];
const stage1Participants = participants.map((participant: any) => {
const outputParticipant: any = {
'cosmos_address': participant.cosmosAddress,
'nitro_address': participant.nitroAddress,
'kyc_id': participant.kycId
};
if (validators.includes(participant.cosmosAddress)) {
outputParticipant.role = 'validator';
stage1Allocations.push({
cosmos_address: participant.cosmosAddress,
balance: argv.validatorAlloc
});
// Remove processed participant from validators list
validators = validators.filter(val => val !== participant.cosmosAddress);
} else {
outputParticipant.role = 'participant';
stage1Allocations.push({
cosmos_address: participant.cosmosAddress,
balance: argv.participantAlloc
});
}
return outputParticipant;
});
// Provide allocs for remaining validators
validators.forEach(val => {
stage1Allocations.push({
cosmos_address: val,
balance: argv.validatorAlloc
});
});
const participantsOutputFilePath = path.resolve(argv.output);
fs.writeFileSync(participantsOutputFilePath, JSON.stringify(stage1Participants, null, 2));
console.log(`Onboarded participants with filtered validators written to ${participantsOutputFilePath}`);
const allocsOutputFilePath = path.resolve(argv.outputAllocs);
fs.writeFileSync(allocsOutputFilePath, JSON.stringify(stage1Allocations, null, 2));
console.log(`Stage1 allocations written to ${allocsOutputFilePath}`);
}
async function readValidators(subscribersCsvPath: string): Promise<any> {
const fileContent = fs.readFileSync(path.resolve(subscribersCsvPath), { encoding: 'utf-8' });
return fileContent.split('\r\n').map(address => address.trim());
}
function _getArgv (): any {
return yargs(hideBin(process.argv))
.option('validatorsCsv', {
type: 'string',
demandOption: true,
describe: 'Path to a CSV file with validators list',
})
.option('participantAlloc', {
type: 'string',
demandOption: true,
describe: 'Participant stage1 balance allocation',
})
.option('validatorAlloc', {
type: 'string',
demandOption: true,
describe: 'Validator stage1 balance allocation',
})
.option('output', {
type: 'string',
demandOption: true,
describe: 'Path to the output JSON file',
})
.option('outputAllocs', {
type: 'string',
demandOption: true,
describe: 'Path to the output JSON file with allocs',
})
.help()
.argv;
}
main().catch(err => {
console.log(err);
});

110
scripts/tsconfig.json Normal file
View File

@ -0,0 +1,110 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["src"],
"exclude": ["dist"],
}

1999
scripts/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ services:
registry:
rpcEndpoint: ${CERC_LACONICD_RPC_ENDPOINT}
gqlEndpoint: ${CERC_LACONICD_GQL_ENDPOINT}
userKey: ${CERC_LACONICD_USER_KEY}
userKey: "${CERC_LACONICD_USER_KEY}"
bondId: ${CERC_LACONICD_BOND_ID}
chainId: ${CERC_LACONICD_CHAIN_ID}
gas: ${CERC_LACONICD_GAS}

View File

@ -266,7 +266,7 @@ laconic-so deployment --dir laconic-console-deployment start
# services:
# registry:
# ...
# userKey: <your-private-key>
# userKey: "<your-private-key>"
# ...
# Note: any changes made to the config will be lost when the cli Docker container is brought down
@ -277,7 +277,7 @@ laconic-so deployment --dir laconic-console-deployment start
```bash
# Example
laconic-so deployment --dir laconic-console-deployment exec cli "laconic registry bond create --type photon --quantity 1000000000000"
laconic-so deployment --dir laconic-console-deployment exec cli "laconic registry bond create --type alnt --quantity 1000000000000"
```
## Clean up

259
testnet-nitro-node.md Normal file
View File

@ -0,0 +1,259 @@
# testnet-nitro-node
## Prerequisites
* laconic-so: see [installation](https://git.vdb.to/cerc-io/stack-orchestrator#install)
* Ansible: see [installation](https://git.vdb.to/cerc-io/testnet-ops#installation)
* yq: see [installation](https://github.com/mikefarah/yq#install)
* Check versions to verify installation:
```bash
laconic-so version
ansible --version
yq --version
```
## Setup
* Clone the `cerc-io/testnet-ops` repository:
```bash
git clone git@git.vdb.to:cerc-io/testnet-ops.git
cd testnet-ops/nitro-node-setup
```
* Fetch the required Nitro node config:
```bash
wget -O nitro-vars.yml https://git.vdb.to/cerc-io/testnet-laconicd-stack/raw/branch/main/ops/stage2/nitro-node-config.yml
# Expected variables in the fetched config file:
# nitro_l1_chain_url: ""
# nitro_l2_chain_url: ""
# na_address: ""
# ca_address: ""
# vpa_address: ""
# l1_asset_address: ""
# bridge_contract_address: ""
# bridge_nitro_address: ""
# nitro_l1_bridge_multiaddr: ""
# nitro_l2_bridge_multiaddr: ""
```
* TODO: Get L1 tokens on your address
* Edit `nitro-vars.yml` and add the following variables:
```bash
# Private key for your Nitro account (same as the one used in stage0 onboarding)
# Export the key from Laconic wallet (https://wallet.laconic.com)
nitro_sc_pk: ""
# Private key for a funded account on L1
# This account should have L1 tokens for funding your Nitro channels
nitro_chain_pk: ""
# Multiaddr with publically accessible IP address / DNS for your L1 nitro node
# Use port 3007
# Example: "/ip4/192.168.x.y/tcp/3007"
# Example: "/dns4/example.com/tcp/3007"
nitro_l1_ext_multiaddr: ""
# Multiaddr with publically accessible IP address / DNS for your L2 nitro node
# Use port 3009
# Example: "/ip4/192.168.x.y/tcp/3009"
# Example: "/dns4/example.com/tcp/3009"
nitro_l2_ext_multiaddr: ""
```
* Update the target dir in `setup-vars.yml`:
```bash
# Set path to desired deployments dir
DEPLOYMENTS_DIR=<path-to-deployments-dir>
sed -i "s|^nitro_directory:.*|nitro_directory: $DEPLOYMENTS_DIR/nitro-node|" setup-vars.yml
# Will create deployments at $DEPLOYMENTS_DIR/nitro-node/l1-nitro-deployment and $DEPLOYMENTS_DIR/nitro-node/l2-nitro-deployment
```
## Run
* Setup and run a Nitro node (L1+L2) by executing the `run-nitro-nodes.yml` Ansible playbook:
```bash
LANG=en_US.utf8 ansible-playbook -i localhost, --connection=local run-nitro-nodes.yml --extra-vars='{ "target_host": "localhost"}' --user $USER
```
* Check status:
```bash
cd $DEPLOYMENTS_DIR/nitro-node
# Check the logs, ensure that the nodes are running
laconic-so deployment --dir l1-nitro-deployment logs nitro-node -f
laconic-so deployment --dir l2-nitro-deployment logs nitro-node -f
```
## Create Channels
* Create a ledger channel with the bridge on L1 that gets mirrored on L2
* Set required variables:
```bash
cd $DEPLOYMENTS_DIR/nitro-node
# Fetch the required Nitro node config
wget https://git.vdb.to/cerc-io/testnet-laconicd-stack/raw/branch/main/ops/stage2/nitro-node-config.yml
export BRIDGE_NITRO_ADDRESS=$(yq eval '.bridge_nitro_address' nitro-node-config.yml)
export L1_ASSET_ADDRESS=$(yq eval '.l1_asset_address' nitro-node-config.yml)
```
* Check that check that you have no existing channels on L1 or L2:
```bash
laconic-so deployment --dir l1-nitro-deployment exec nitro-rpc-client "nitro-rpc-client get-all-ledger-channels -p 4005 -h nitro-node"
laconic-so deployment --dir l2-nitro-deployment exec nitro-rpc-client "nitro-rpc-client get-all-ledger-channels -p 4005 -h nitro-node"
# Expected output:
# []
```
* Create a ledger channel between your L1 Nitro node and Bridge with custom asset:
```bash
# Set amount to ledger
LEDGER_AMOUNT=1000000
laconic-so deployment --dir l1-nitro-deployment exec nitro-rpc-client "nitro-rpc-client direct-fund $BRIDGE_NITRO_ADDRESS --assetAddress $L1_ASSET_ADDRESS --alphaAmount $LEDGER_AMOUNT --betaAmount $LEDGER_AMOUNT -p 4005 -h nitro-node"
# Follow your L1 Nitro node logs for progress
# Expected Output:
# Objective started DirectFunding-0x161d289a50222caa781db215bb82a3ede4f557217742245525b8e8cbff04ec21
# Channel Open 0x161d289a50222caa781db215bb82a3ede4f557217742245525b8e8cbff04ec21
# Set the resulting ledger channel id in a variable
export LEDGER_CHANNEL_ID=
```
* Check the [Troubleshooting](#troubleshooting) section if command to create a ledger channel fails or gets stuck
* Once direct-fund objective is complete, the bridge will create a mirrored channel on L2
* Check L2 Nitro node's logs to see that a bridged-fund objective completed:
```bash
laconic-so deployment --dir l2-nitro-deployment logs nitro-node -f --tail 30
# Expected Output:
# nitro-node-1 | 5:01AM INF INFO Objective cranked address=0xaaa6628ec44a8a742987ef3a114ddfe2d4f7adce objective-id=bridgedfunding-0x6a9f5ccf1fa802525d794f4a899897f947615f6acc7141e61e056a8bfca29179 waiting-for=WaitingForNothing
# nitro-node-1 | 5:01AM INF INFO Objective is complete & returned to API address=0xaaa6628ec44a8a742987ef3a114ddfe2d4f7adce objective-id=bridgedfunding-0x6a9f5ccf1fa802525d794f4a899897f947615f6acc7141e61e056a8bfca29179
```
* Check status of L1 ledger channel with the bridge using channel id:
```bash
laconic-so deployment --dir l1-nitro-deployment exec nitro-rpc-client "nitro-rpc-client get-ledger-channel $LEDGER_CHANNEL_ID -p 4005 -h nitro-node"
# Expected output:
# {
# ID: '0x161d289a50222caa781db215bb82a3ede4f557217742245525b8e8cbff04ec21',
# Status: 'Open',
# Balance: {
# AssetAddress: '<l1-asset-address>',
# Me: '<your-nitro-address>',
# Them: '<bridge-nitro-address>',
# MyBalance: <ledger-amount>n,
# TheirBalance: <ledger-amount>n
# },
# ChannelMode: 'Open'
# }
```
* Check status of the mirrored channel on L2:
```bash
laconic-so deployment --dir l2-nitro-deployment exec nitro-rpc-client "nitro-rpc-client get-all-ledger-channels -p 4005 -h nitro-node"
# Expected output:
# [
# {
# "ID": "0x6a9f5ccf1fa802525d794f4a899897f947615f6acc7141e61e056a8bfca29179",
# "Status": "Open",
# "Balance": {
# "AssetAddress": "<l2-asset-address>",
# "Me": "<your-nitro-address>",
# "Them": "<bridge-nitro-address>",
# "MyBalance": <ledger-amount>n,
# "TheirBalance": <ledger-amount>n
# },
# "ChannelMode": "Open"
# }
# ]
```
## Clean up
* Switch to deployments dir:
```bash
cd $DEPLOYMENTS_DIR/nitro-node
```
* Stop all Nitro services running in the background:
```bash
laconic-so deployment --dir l1-nitro-deployment stop
laconic-so deployment --dir l2-nitro-deployment stop
```
* To stop all services and also delete data:
```bash
laconic-so deployment --dir l1-nitro-deployment stop --delete-volumes
laconic-so deployment --dir l2-nitro-deployment stop --delete-volumes
# Remove deployment directories (deployments will have to be recreated for a re-run)
sudo rm -r l1-nitro-deployment
sudo rm -r l2-nitro-deployment
```
## Troubleshooting
* Stop (`Ctrl+C`) the direct-fund command if it is stuck
* Restart the L1 Nitro node:
* Stop the deployment:
```bash
cd $DEPLOYMENTS_DIR/nitro-node
laconic-so deployment --dir l1-nitro-deployment stop
```
* Reset the node's durable store:
```bash
sudo rm -rf l1-nitro-deployment/data/nitro_node_data
mkdir l1-nitro-deployment/data/nitro_node_data
```
* Restart the deployment:
```bash
laconic-so deployment --dir l1-nitro-deployment start
```
* Retry the ledger channel creation command