From c56422030d03c7878b6246f2f09b7f4a2349cdaa Mon Sep 17 00:00:00 2001 From: Parth Date: Mon, 4 Nov 2024 19:27:12 +0400 Subject: [PATCH] Add bridge testing environment (#737) * add bridge testing environment * change artifact directory to be inside tmp * Check NodeJS version * Check for go version --- .prettierignore | 3 + test/configs/zombieDancelightBridge.json | 68 +++++ test/moonwall.config.json | 21 ++ test/scripts/bridge-build-artifacts.sh | 15 ++ test/scripts/bridge/assets/beacon-relay.json | 26 ++ test/scripts/bridge/assets/beefy-relay.json | 19 ++ test/scripts/bridge/assets/genesis.json | 61 +++++ test/scripts/bridge/assets/jwtsecret | 1 + test/scripts/bridge/build-eth-contracts.sh | 14 + test/scripts/bridge/build-ethereum-node.sh | 60 +++++ test/scripts/bridge/build-relayer.sh | 35 +++ test/scripts/bridge/cleanup.sh | 45 ++++ .../bridge/deploy-ethereum-contracts.sh | 22 ++ .../bridge/generate-beefy-checkpoint.sh | 18 ++ test/scripts/bridge/generate-contract-info.sh | 13 + test/scripts/bridge/set-env.sh | 242 ++++++++++++++++++ test/scripts/bridge/setup-relayer.sh | 68 +++++ test/scripts/bridge/start-ethereum-node.sh | 108 ++++++++ test/scripts/bridge/start-relayer.sh | 50 ++++ .../test-consensus-bridge.ts | 156 +++++++++++ 20 files changed, 1045 insertions(+) create mode 100644 test/configs/zombieDancelightBridge.json create mode 100755 test/scripts/bridge-build-artifacts.sh create mode 100644 test/scripts/bridge/assets/beacon-relay.json create mode 100644 test/scripts/bridge/assets/beefy-relay.json create mode 100644 test/scripts/bridge/assets/genesis.json create mode 100644 test/scripts/bridge/assets/jwtsecret create mode 100755 test/scripts/bridge/build-eth-contracts.sh create mode 100755 test/scripts/bridge/build-ethereum-node.sh create mode 100755 test/scripts/bridge/build-relayer.sh create mode 100755 test/scripts/bridge/cleanup.sh create mode 100755 test/scripts/bridge/deploy-ethereum-contracts.sh create mode 100755 test/scripts/bridge/generate-beefy-checkpoint.sh create mode 100755 test/scripts/bridge/generate-contract-info.sh create mode 100755 test/scripts/bridge/set-env.sh create mode 100755 test/scripts/bridge/setup-relayer.sh create mode 100755 test/scripts/bridge/start-ethereum-node.sh create mode 100755 test/scripts/bridge/start-relayer.sh create mode 100644 test/suites/zombie-tanssi-relay-eth-bridge/test-consensus-bridge.ts diff --git a/.prettierignore b/.prettierignore index ba128e3e0..39a05fcd8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,6 +10,9 @@ **/.yarn test/tsconfig.json +# Test data +tmp + # Spec/Wasm build directory **/build/ diff --git a/test/configs/zombieDancelightBridge.json b/test/configs/zombieDancelightBridge.json new file mode 100644 index 000000000..599940da0 --- /dev/null +++ b/test/configs/zombieDancelightBridge.json @@ -0,0 +1,68 @@ +{ + "settings": { + "timeout": 1000, + "provider": "native" + }, + "relaychain": { + "chain": "dancelight-local", + "default_command": "../target/release/tanssi-relay", + "default_args": [ + "--no-hardware-benchmarks", + "-lparachain=debug", + "--database=paritydb", + "--enable-offchain-indexing true" + ], + "genesis": { + "runtimeGenesis": { + "patch": { + "configuration": { + "config": { + "async_backing_params": { + "allowed_ancestry_len": 2, + "max_candidate_depth": 3 + }, + "scheduler_params": { + "scheduling_lookahead": 2, + "num_cores": 4 + } + } + } + } + } + }, + "nodes": [ + { + "name": "alice", + "ws_port": "9947", + "validator": true + }, + { + "name": "bob", + "validator": true + } + ] + }, + "parachains": [ + { + "id": 2000, + "chain": "dev", + "collators": [ + { + "name": "FullNode-2000", + "validator": false, + "command": "../target/release/container-chain-simple-node", + "args": ["--no-hardware-benchmarks", "--database=paritydb", "--wasmtime-precompiled=wasm"], + "ws_port": 9949, + "p2p_port": 33049 + } + ] + } + ], + "types": { + "Header": { + "number": "u64", + "parent_hash": "Hash", + "post_state": "Hash" + } + } +} diff --git a/test/moonwall.config.json b/test/moonwall.config.json index a63bdf5ef..8e8ed4cef 100644 --- a/test/moonwall.config.json +++ b/test/moonwall.config.json @@ -770,6 +770,27 @@ } ] }, + { + "name": "zombie_tanssi_relay_eth_bridge", + "timeout": 600000, + "envVars": ["RUST_BACKTRACE=1"], + "testFileDir": ["suites/zombie-tanssi-relay-eth-bridge"], + "runScripts": ["bridge-build-artifacts.sh"], + "foundation": { + "type": "zombie", + "zombieSpec": { + "configPath": "./configs/zombieDancelightBridge.json", + "skipBlockCheck": ["Tanssi-relay"] + } + }, + "connections": [ + { + "name": "Tanssi-relay", + "type": "polkadotJs", + "endpoints": ["ws://127.0.0.1:9947"] + } + ] + }, { "name": "zombie_data_preservers", "testFileDir": ["suites/data-preservers"], diff --git a/test/scripts/bridge-build-artifacts.sh b/test/scripts/bridge-build-artifacts.sh new file mode 100755 index 000000000..8d45ac490 --- /dev/null +++ b/test/scripts/bridge-build-artifacts.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Exit on any error +set -e + +bridge_scripts=$(realpath ./scripts/bridge) +source $bridge_scripts/set-env.sh + +check_tool + +# Install sszgen +GOBIN=$output_bin_dir go install github.com/ferranbt/fastssz/sszgen@v0.1.4 + +$bridge_scripts/build-ethereum-node.sh +$bridge_scripts/build-relayer.sh diff --git a/test/scripts/bridge/assets/beacon-relay.json b/test/scripts/bridge/assets/beacon-relay.json new file mode 100644 index 000000000..4bfb2ca22 --- /dev/null +++ b/test/scripts/bridge/assets/beacon-relay.json @@ -0,0 +1,26 @@ +{ + "source": { + "beacon": { + "endpoint": "http://127.0.0.1:9596", + "stateEndpoint": "http://127.0.0.1:9596", + "spec": { + "syncCommitteeSize": 512, + "slotsInEpoch": 32, + "epochsPerSyncCommitteePeriod": 256, + "denebForkedEpoch": 0 + }, + "datastore": { + "location": "", + "maxEntries": 100 + } + } + }, + "sink": { + "parachain": { + "endpoint": "", + "maxWatchedExtrinsics": 8, + "headerRedundancy": 20 + }, + "updateSlotInterval": 30 + } +} diff --git a/test/scripts/bridge/assets/beefy-relay.json b/test/scripts/bridge/assets/beefy-relay.json new file mode 100644 index 000000000..4bce31791 --- /dev/null +++ b/test/scripts/bridge/assets/beefy-relay.json @@ -0,0 +1,19 @@ +{ + "source": { + "polkadot": { + "endpoint": "" + }, + "fast-forward-depth": 20, + "update-period": 0 + }, + "sink": { + "ethereum": { + "endpoint": "ws://127.0.0.1:8546", + "gas-limit": null + }, + "descendants-until-final": 3, + "contracts": { + "BeefyClient": null + } + } +} diff --git a/test/scripts/bridge/assets/genesis.json b/test/scripts/bridge/assets/genesis.json new file mode 100644 index 000000000..4af3fe436 --- /dev/null +++ b/test/scripts/bridge/assets/genesis.json @@ -0,0 +1,61 @@ +{ + "config": { + "chainId": 11155111, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "ethash": {}, + "terminalTotalDifficulty": 0, + "ShanghaiTime": 0, + "CancunTime": null, + "terminalTotalDifficultyPassed": true + }, + "difficulty": "0x9FFE0", + "gasLimit": "80000000", + "alloc": { + "90A987B944Cb1dCcE5564e5FDeCD7a54D3de27Fe": { + "balance": "10000000000000000000000" + }, + "Be68fC2d8249eb60bfCf0e71D5A0d2F2e292c4eD": { + "balance": "100000000000000000000" + }, + "89b4AB1eF20763630df9743ACF155865600daFF2": { + "balance": "100000000000000000000" + }, + "04E00e6D2e9Ea1E2AF553De02A5172120BFA5c3e": { + "balance": "100000000000000000000" + }, + "a255dC78C1510e2c1332fBAC2de848058f479CEE": { + "balance": "100000000000000000000" + }, + "ACbd24742b87c34dED607FB87b22401B2Ede167E": { + "balance": "100000000000000000000" + }, + "01F6749035e02205768f97e6f1d394Fb6769EC20": { + "balance": "100000000000000000000" + }, + "8b66D5499F52D6F1857084A61743dFCB9a712859": { + "balance": "100000000000000000000" + }, + "13e16C4e5787f878f98a610EB321170512b134D4": { + "balance": "100000000000000000000" + }, + "eEBFA6B9242A19f91a0463291A937a20e3355681": { + "balance": "100000000000000000000" + }, + "87D987206180B8f3807Dd90455606eEa85cdB87a": { + "balance": "100000000000000000000" + }, + "0xACbd24742b87c34dED607FB87b22401B2Ede167E": { + "balance": "100000000000000000000" + } + } +} diff --git a/test/scripts/bridge/assets/jwtsecret b/test/scripts/bridge/assets/jwtsecret new file mode 100644 index 000000000..583b13cc5 --- /dev/null +++ b/test/scripts/bridge/assets/jwtsecret @@ -0,0 +1 @@ +0xdc6457099f127cf0bac78de8b297df04951281909db4f58b43def7c7151e765d \ No newline at end of file diff --git a/test/scripts/bridge/build-eth-contracts.sh b/test/scripts/bridge/build-eth-contracts.sh new file mode 100755 index 000000000..b64ad4662 --- /dev/null +++ b/test/scripts/bridge/build-eth-contracts.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Exit on any error +set -e + +scripts_path="$(realpath ./scripts/bridge)" + +source $scripts_path/set-env.sh + +echo "Building contracts" + +pushd $contract_dir +forge build +popd diff --git a/test/scripts/bridge/build-ethereum-node.sh b/test/scripts/bridge/build-ethereum-node.sh new file mode 100755 index 000000000..781903222 --- /dev/null +++ b/test/scripts/bridge/build-ethereum-node.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Exit on any error +set -e + +scripts_path="$(realpath ./scripts/bridge)" + +source $scripts_path/set-env.sh + +echo "Building lodestar Snowfork fork" + + +set_slot_time() { + local new_value=$1 + echo "Hack lodestar for faster slot time" + local preset_mainnet_config_file="$artifacts_dir/lodestar/packages/config/src/chainConfig/configs/mainnet.ts" + if [[ "$(uname)" == "Darwin" && -z "${IN_NIX_SHELL:-}" ]]; then + gsed -i "s/SECONDS_PER_SLOT: .*/SECONDS_PER_SLOT: $new_value,/g" $preset_mainnet_config_file + else + sed -i "s/SECONDS_PER_SLOT: .*/SECONDS_PER_SLOT: $new_value,/g" $preset_mainnet_config_file + fi +} + +echo "Downloading lodestar" + +if [ -d "$lodestar_dir" ]; +then + echo "Lodestar seems to be already downloaded. Skipping downloading again" +else + git clone https://github.com/ChainSafe/lodestar $lodestar_dir + pushd $lodestar_dir + git fetch && git checkout $LODESTAR_TAG + set_slot_time 1 + popd +fi + +echo "Building lodestar" +pushd $lodestar_dir +yarn install && yarn run build +popd + + +echo "Downloading geth" + +if [ -d "$geth_dir" ]; +then + echo "Geth seems to be already downloaded. Skipping downloading" +else + git clone https://github.com/ethereum/go-ethereum.git $geth_dir + pushd $geth_dir + git fetch && git checkout $GETH_TAG + popd +fi + +echo "Building Geth" +pushd $geth_dir +GOBIN=$output_bin_dir go install ./cmd/geth +GOBIN=$output_bin_dir go install ./cmd/abigen +popd + diff --git a/test/scripts/bridge/build-relayer.sh b/test/scripts/bridge/build-relayer.sh new file mode 100755 index 000000000..db11ec12f --- /dev/null +++ b/test/scripts/bridge/build-relayer.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Exit on any error +set -e + +scripts_path="$(realpath ./scripts/bridge)" + +source $scripts_path/set-env.sh + +# Can be done independently put relayer binary in output directory + +echo "Checkout Snowbridge relayer" + +if [ -d "$relayer_root_dir" ]; +then + echo "Relayer seems to be already setup. Skipping git fetch" +else + git clone https://github.com/Snowfork/snowbridge $relayer_root_dir + pushd $relayer_root_dir + git fetch && git checkout $RELAYER_TAG + popd +fi + +$scripts_path/build-eth-contracts.sh + +echo "Building Relayer" +pushd $relayer_root_dir +cd relayer && mage build +popd + + +pushd $test_helpers_dir +pnpm install node-gyp +pnpm install +popd diff --git a/test/scripts/bridge/cleanup.sh b/test/scripts/bridge/cleanup.sh new file mode 100755 index 000000000..65c586c9c --- /dev/null +++ b/test/scripts/bridge/cleanup.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -e + +scripts_path="$(realpath ./scripts/bridge)" +source $scripts_path/set-env.sh + +if [ -z "${1}" ]; then + echo "No arguments supplied. You can supply: o (removes output dir), ol (removes output and log dir), ole (removes output, log and ethereum_data dir), olep (removes output, log, ethereum_data dir and terminate any leftover processes)" + exit 1 +fi + +if [ $1 = "o" ]; then + rm -rf $output_dir +elif [ $1 = "ol" ]; then + rm -rf $logs_dir + rm -rf $output_dir +elif [ $1 = "ole" ]; then + rm -rf $logs_dir + rm -rf $output_dir + rm -rf $ethereum_data_dir +elif [ $1 = "olep" ]; then + rm -rf $logs_dir + rm -rf $output_dir + rm -rf $ethereum_data_dir + + beacon_relay="" + beefy_relay="" + + # Source daemons.pid if it exists + source $artifacts_dir/daemons.pid 2> /dev/null || true + + # Using interrupt instead of kill signal for process to cleanup + kill -s INT $beacon_relay 2> /dev/null || true + kill -s INT $beefy_relay 2> /dev/null || true + + # Brute force to remove other process spawned by lodestar and geth, if any + echo "Warning: Terminating any process containing lodestar or geth word in the full command line (executable + argument)" + pkill -f "lodestar" + pkill -f "geth" + + # Always remove this to prevent us for terminating any other process for which the PID was reused + rm $artifacts_dir/daemons.pid 2> /dev/null || true +fi + diff --git a/test/scripts/bridge/deploy-ethereum-contracts.sh b/test/scripts/bridge/deploy-ethereum-contracts.sh new file mode 100755 index 000000000..544c1824d --- /dev/null +++ b/test/scripts/bridge/deploy-ethereum-contracts.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -eu + +scripts_path="$(realpath ./scripts/bridge)" +source $scripts_path/set-env.sh + +deploy_command() { + local deploy_script=$1 + + pushd "$contract_dir" + forge script \ + --rpc-url $eth_endpoint_http \ + --legacy \ + --broadcast \ + -vvv \ + $deploy_script + popd +} + +echo "Deploying contracts" +deploy_command scripts/DeployLocal.sol:DeployLocal diff --git a/test/scripts/bridge/generate-beefy-checkpoint.sh b/test/scripts/bridge/generate-beefy-checkpoint.sh new file mode 100755 index 000000000..75e2d2250 --- /dev/null +++ b/test/scripts/bridge/generate-beefy-checkpoint.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -eu + +scripts_path="$(realpath ./scripts/bridge)" +source $scripts_path/set-env.sh + +generate_beefy_checkpoint() +{ + pushd "$test_helpers_dir" + pnpm up "@polkadot/*@14.0.1" + pnpm generateBeefyCheckpoint + popd +} + +echo "generate beefy checkpoint!" +generate_beefy_checkpoint +wait diff --git a/test/scripts/bridge/generate-contract-info.sh b/test/scripts/bridge/generate-contract-info.sh new file mode 100755 index 000000000..e7c422bc2 --- /dev/null +++ b/test/scripts/bridge/generate-contract-info.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -eu + +scripts_path="$(realpath ./scripts/bridge)" +source $scripts_path/set-env.sh + +pushd "$test_helpers_dir" > /dev/null +pnpm generateContracts "$output_dir/contracts.json" > /dev/null +popd > /dev/null + +# Output the file so that invoker can read it +cat "$output_dir/contracts.json" diff --git a/test/scripts/bridge/set-env.sh b/test/scripts/bridge/set-env.sh new file mode 100755 index 000000000..9a9c7033d --- /dev/null +++ b/test/scripts/bridge/set-env.sh @@ -0,0 +1,242 @@ +root_dir="$(realpath .)" + +scripts_root_dir="$root_dir/scripts/bridge" +assets_dir="$scripts_root_dir/assets" + +artifacts_dir="$root_dir/tmp/bridge" +mkdir -p $artifacts_dir + +logs_dir="$artifacts_dir/logs" +ethereum_data_dir="$artifacts_dir/ethereum_data" +export output_dir="$artifacts_dir/output" +zombienet_data_dir="$output_dir/zombienet" +export output_bin_dir="$output_dir/bin" +mkdir -p $output_bin_dir +export PATH="$output_bin_dir:$PATH" + +relayer_root_dir="$artifacts_dir/relayer" +web_dir="$relayer_root_dir/web" +export contract_dir="$relayer_root_dir/contracts" +test_helpers_dir="$web_dir/packages/test-helpers" +relay_dir="$relayer_root_dir/relayer" +relay_bin="$relay_dir/build/snowbridge-relay" + +RELAYER_TAG="relayer-v1.0.30" # we will need to investigate if this is right +GETH_TAG="v1.14.11" # We will need to investigate if this is right +LODESTAR_TAG="v1.19.0" + +lodestar_dir=$artifacts_dir/lodestar + +geth_dir=$artifacts_dir/geth + + +export polkadot_sdk_dir="${POLKADOT_SDK_DIR:-../polkadot-sdk}" + +eth_network="${ETH_NETWORK:-localhost}" +eth_endpoint_http="${ETH_RPC_ENDPOINT:-http://127.0.0.1:8545}/${INFURA_PROJECT_ID:-}" +eth_endpoint_ws="${ETH_WS_ENDPOINT:-ws://127.0.0.1:8546}/${INFURA_PROJECT_ID:-}" +eth_writer_endpoint="${ETH_WRITER_ENDPOINT:-http://127.0.0.1:8545}/${INFURA_PROJECT_ID:-}" +eth_gas_limit="${ETH_GAS_LIMIT:-5000000}" +eth_chain_id="${ETH_NETWORK_ID:-15}" +etherscan_api_key="${ETHERSCAN_API_KEY:-}" +rebuild_lodestar="${REBUILD_LODESTAR:-true}" + +beefy_relay_eth_key="${BEEFY_RELAY_ETH_KEY:-0x935b65c833ced92c43ef9de6bff30703d941bd92a2637cb00cfad389f5862109}" + +# Parachain accounts for which the relayer will relay messages over the basic channel. +# These IDs are for the test accounts Alice, Bob, Charlie, Dave, Eve and Ferdie, in order +basic_parachain_account_ids="${BASIC_PARACHAIN_ACCOUNT_IDS:-0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d,0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48,0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22,0x306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20,0xe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e,0x1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c}" +# Ethereum addresses for which the relayer will relay messages over the basic channel. +# This address is for the default eth account used in the E2E tests, taken from test/src/ethclient/index.js. +basic_eth_addresses="${BASIC_ETH_ADDRESSES:-0x89b4ab1ef20763630df9743acf155865600daff2}" +beacon_endpoint_http="${BEACON_HTTP_ENDPOINT:-http://127.0.0.1:9596}" +export BRIDGE_HUB_PARAID="${BRIDGE_HUB_PARAID:-1002}" +export BRIDGE_HUB_AGENT_ID="${BRIDGE_HUB_AGENT_ID:-0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314}" +relaychain_ws_url="${RELAYCHAIN_WS_URL:-ws://127.0.0.1:9944}" +relaychain_sudo_seed="${RELAYCHAIN_SUDO_SEED:-//Alice}" + +export ASSET_HUB_PARAID="${ASSET_HUB_PARAID:-1000}" +export ASSET_HUB_AGENT_ID="${ASSET_HUB_AGENT_ID:-0x81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79}" + +# Token decimal of the relaychain(KSM|ROC:12,DOT:10) +export FOREIGN_TOKEN_DECIMALS=12 + +## Important accounts + +# Useful tool to get these account values: https://www.shawntabrizi.com/substrate-js-utilities/ +# Beacon relay account (//BeaconRelay 5GWFwdZb6JyU46e6ZiLxjGxogAHe8SenX76btfq8vGNAaq8c in testnet) +beacon_relayer_pub_key="${BEACON_RELAYER_PUB_KEY:-0xc46e141b5083721ad5f5056ba1cded69dce4a65f027ed3362357605b1687986a}" + + +# Config for deploying contracts + +## Deployment key +export PRIVATE_KEY="${DEPLOYER_ETH_KEY:-0x4e9444a6efd6d42725a250b650a781da2737ea308c839eaccb0f7f3dbd2fea77}" +export ETHERSCAN_API_KEY="${ETHERSCAN_API_KEY:-0x0}" + +## BeefyClient +# For max safety delay should be MAX_SEED_LOOKAHEAD=4 epochs=4*8*6=192s +# but for rococo-local each session is only 20 slots=120s +# so relax somehow here just for quick test +# for production deployment ETH_RANDAO_DELAY should be configured in a more reasonable sense +export RANDAO_COMMIT_DELAY="${ETH_RANDAO_DELAY:-3}" +export RANDAO_COMMIT_EXP="${ETH_RANDAO_EXP:-3}" +export MINIMUM_REQUIRED_SIGNATURES="${MINIMUM_REQUIRED_SIGNATURES:-16}" + +export REJECT_OUTBOUND_MESSAGES="${REJECT_OUTBOUND_MESSAGES:-false}" + +## Fee +export REGISTER_TOKEN_FEE="${REGISTER_TOKEN_FEE:-200000000000000000}" +export CREATE_ASSET_FEE="${CREATE_ASSET_FEE:-100000000000}" +export RESERVE_TRANSFER_FEE="${RESERVE_TRANSFER_FEE:-100000000000}" +export RESERVE_TRANSFER_MAX_DESTINATION_FEE="${RESERVE_TRANSFER_MAX_DESTINATION_FEE:-10000000000000}" + +## Pricing Parameters +export EXCHANGE_RATE="${EXCHANGE_RATE:-2500000000000000}" +export DELIVERY_COST="${DELIVERY_COST:-10000000000}" +export FEE_MULTIPLIER="${FEE_MULTIPLIER:-1000000000000000000}" + + +## Vault +export BRIDGE_HUB_INITIAL_DEPOSIT="${ETH_BRIDGE_HUB_INITIAL_DEPOSIT:-10000000000000000000}" + + +address_for() { + jq -r ".contracts.${1}.address" "$output_dir/contracts.json" +} + +kill_all() { + trap - SIGTERM + kill 0 +} + +cleanup() { + echo "Cleaning resource" + rm -rf "$output_dir" + mkdir "$output_dir" + mkdir "$output_bin_dir" + mkdir "$ethereum_data_dir" +} + +check_node_version() { + local expected_version=$1 + + if ! [ -x "$(command -v node)" ]; then + echo 'Error: NodeJS is not installed.' + exit 1 + fi + + node_version=$(node -v) # This does not seem to work in Git Bash on Windows. + # "node -v" outputs version in the format "v18.12.1" + node_version=${node_version:1} # Remove 'v' at the beginning + node_version=${node_version%\.*} # Remove trailing ".*". + node_version=${node_version%\.*} # Remove trailing ".*". + node_version=$(($node_version)) # Convert the NodeJS version number from a string to an integer. + if [ $node_version -lt "$expected_version" ] + then + echo "NodeJS version is lower than $expected_version (it is $node_version), Please update your node installation!" + exit 1 + fi +} + +vercomp() { + if [[ $1 == $2 ]] + then + echo "Equal" + return + fi + local IFS=. + local i ver1=($1) ver2=($2) + # fill empty fields in ver1 with zeros + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)) + do + ver1[i]=0 + done + for ((i=0; i<${#ver1[@]}; i++)) + do + if ((10#${ver1[i]:=0} > 10#${ver2[i]:=0})) + then + echo "Greater" + return + fi + if ((10#${ver1[i]} < 10#${ver2[i]})) + then + echo "Less" + return + fi + done +} + + +check_go_version() { + local expected_version=$1 + + if ! [ -x "$(command -v go)" ]; then + echo 'Error: Go is not installed.' + exit 1 + fi + + go_version=$(go version | { read _ _ v _; echo ${v#go}; }) + op=$(vercomp "$go_version" "$1") + + if [[ $op = "Less" ]] + then + echo "Go version is lower than $expected_version (it is $go_version), Please update your go installation!" + exit 1 + fi +} + +check_tool() { + if ! [ -x "$(command -v g++)" ]; then + echo 'Error: g++ is not installed.' + exit 1 + fi + if ! [ -x "$(command -v protoc)" ]; then + echo 'Error: protoc is not installed.' + exit 1 + fi + if ! [ -x "$(command -v jq)" ]; then + echo 'Error: jq is not installed.' + exit 1 + fi + if ! [ -x "$(command -v mage)" ]; then + echo 'Error: mage is not installed.' + exit 1 + fi + if ! [ -x "$(command -v pnpm)" ]; then + echo 'Error: pnpm is not installed.' + exit 1 + fi + if ! [ -x "$(command -v forge)" ]; then + echo 'Error: foundry is not installed.' + exit 1 + fi + if ! [ -x "$(command -v yarn)" ]; then + echo 'Error: yarn is not installed.' + exit 1 + fi + if [[ "$(uname)" == "Darwin" && -z "${IN_NIX_SHELL:-}" ]]; then + if ! [ -x "$(command -v gdate)" ]; then + echo 'Error: gdate (GNU Date) is not installed.' + exit 1 + fi + else + if ! [ -x "$(command -v date)" ]; then + echo 'Error: date is not installed.' + exit 1 + fi + fi + + check_node_version 22 + check_go_version 1.21.2 +} + +wait_contract_deployed() { + local ready="" + while [ -z "$ready" ]; do + if [ -f "$output_dir/contracts.json" ]; then + ready="true" + fi + sleep 2 + done +} diff --git a/test/scripts/bridge/setup-relayer.sh b/test/scripts/bridge/setup-relayer.sh new file mode 100755 index 000000000..25884c5ab --- /dev/null +++ b/test/scripts/bridge/setup-relayer.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Exit on any error +set -eu + +scripts_path="$(realpath ./scripts/bridge)" +source $scripts_path/set-env.sh + + +data_store_dir="$output_dir/relayer_data" +mkdir -p $data_store_dir + +config_relayer() { + # Configure beefy relay + jq \ + --arg k1 "$(address_for BeefyClient)" \ + --arg eth_endpoint_ws $eth_endpoint_ws \ + --arg eth_gas_limit $eth_gas_limit \ + --arg relay_chain_endpoint $RELAYCHAIN_ENDPOINT \ + ' + .sink.contracts.BeefyClient = $k1 + | .sink.ethereum.endpoint = $eth_endpoint_ws + | .sink.ethereum."gas-limit" = $eth_gas_limit + | .source.polkadot.endpoint = $relay_chain_endpoint + ' \ + $assets_dir/beefy-relay.json > $output_dir/beefy-relay.json + + # Configure beacon relay + local deneb_forked_epoch=132608 + deneb_forked_epoch=0 + jq \ + --arg beacon_endpoint_http $beacon_endpoint_http \ + --argjson deneb_forked_epoch $deneb_forked_epoch \ + --arg relay_chain_endpoint $RELAYCHAIN_ENDPOINT \ + --arg data_store_dir $data_store_dir \ + ' + .source.beacon.endpoint = $beacon_endpoint_http + | .source.beacon.spec.denebForkedEpoch = $deneb_forked_epoch + | .sink.parachain.endpoint = $relay_chain_endpoint + | .source.beacon.datastore.location = $data_store_dir + ' \ + $assets_dir/beacon-relay.json >$output_dir/beacon-relay.json +} + +write_beacon_checkpoint() { + pushd $output_dir > /dev/null + $relay_bin generate-beacon-checkpoint --config $output_dir/beacon-relay.json --export-json > /dev/null + cat $output_dir/dump-initial-checkpoint.json + popd > /dev/null +} + +wait_beacon_chain_ready() { + local initial_beacon_block="" + while [ -z "$initial_beacon_block" ] || [ "$initial_beacon_block" == "0x0000000000000000000000000000000000000000000000000000000000000000" ]; do + initial_beacon_block=$(curl -s "$beacon_endpoint_http/eth/v1/beacon/states/head/finality_checkpoints" | + jq -r '.data.finalized.root' || true) + sleep 3 + done +} + + +setup-relayer() { + config_relayer + wait_beacon_chain_ready + write_beacon_checkpoint +} + +setup-relayer diff --git a/test/scripts/bridge/start-ethereum-node.sh b/test/scripts/bridge/start-ethereum-node.sh new file mode 100755 index 000000000..79dbf52cd --- /dev/null +++ b/test/scripts/bridge/start-ethereum-node.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# Exit on any error +set -e + +# Start ethereum nodes, the nodes must be built first +scripts_path="$(realpath ./scripts/bridge)" +source $scripts_path/set-env.sh + + +mkdir -p $ethereum_data_dir +echo $ethereum_data_dir + +mkdir -p $output_dir +mkdir -p $logs_dir + +start_geth() { + echo "Starting geth local node" + local timestamp="0" #start Cancun from genesis + jq \ + --argjson timestamp "$timestamp" \ + ' + .config.CancunTime = $timestamp + ' \ + $assets_dir/genesis.json > $output_dir/genesis.json + geth init --datadir "$ethereum_data_dir" --state.scheme=hash "$output_dir/genesis.json" + geth --vmdebug --datadir "$ethereum_data_dir" --networkid 11155111 \ + --http --http.api debug,personal,eth,net,web3,txpool,engine,miner --ws --ws.api debug,eth,net,web3 \ + --rpc.allow-unprotected-txs --mine \ + --miner.etherbase=0xBe68fC2d8249eb60bfCf0e71D5A0d2F2e292c4eD \ + --authrpc.addr="127.0.0.1" \ + --http.addr="0.0.0.0" \ + --ws.addr="0.0.0.0" \ + --http.corsdomain '*' \ + --allow-insecure-unlock \ + --authrpc.jwtsecret $assets_dir/jwtsecret \ + --password /dev/null \ + --rpc.gascap 0 \ + --ws.origins "*" \ + --trace "$ethereum_data_dir/trace" \ + --gcmode archive \ + --syncmode=full \ + --state.scheme=hash \ + >"$logs_dir/geth.log" 2>&1 & + echo "geth=$!" >> $artifacts_dir/daemons.pid +} + +start_lodestar() { + echo "Starting lodestar local node" + local genesisHash=$(curl $eth_endpoint_http \ + -X POST \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc": "2.0", "id": "1", "method": "eth_getBlockByNumber","params": ["0x0", false]}' | jq -r '.result.hash') + echo "genesisHash is: $genesisHash" + # use gdate here for raw macos without nix + local timestamp="" + if [[ "$(uname)" == "Darwin" && -z "${IN_NIX_SHELL:-}" ]]; then + timestamp=$(gdate -d'+10second' +%s) + else + timestamp=$(date -d'+10second' +%s) + fi + + export LODESTAR_PRESET="mainnet" + + pushd $artifacts_dir/lodestar + ./lodestar dev \ + --genesisValidators 8 \ + --genesisTime $timestamp \ + --startValidators "0..7" \ + --enr.ip6 "127.0.0.1" \ + --rest.address "0.0.0.0" \ + --eth1.providerUrls "http://127.0.0.1:8545" \ + --execution.urls "http://127.0.0.1:8551" \ + --dataDir "$ethereum_data_dir" \ + --reset \ + --terminal-total-difficulty-override 0 \ + --genesisEth1Hash $genesisHash \ + --params.ALTAIR_FORK_EPOCH 0 \ + --params.BELLATRIX_FORK_EPOCH 0 \ + --params.CAPELLA_FORK_EPOCH 0 \ + --params.DENEB_FORK_EPOCH 0 \ + --eth1=true \ + --rest.namespace="*" \ + --jwt-secret $assets_dir/jwtsecret \ + --chain.archiveStateEpochFrequency 1 \ + >"$logs_dir/lodestar.log" 2>&1 & + echo "lodestar=$!" >> $artifacts_dir/daemons.pid + popd +} + +deploy_local() { + # 1. deploy execution client + echo "Starting execution node" + start_geth + + echo "Waiting for geth API to be ready" + sleep 3 + + # 2. deploy consensus client + echo "Starting beacon node" + start_lodestar +} + +echo "start ethereum only!" +trap kill_all SIGINT SIGTERM EXIT +deploy_local +echo "ethereum local nodes started!" +wait diff --git a/test/scripts/bridge/start-relayer.sh b/test/scripts/bridge/start-relayer.sh new file mode 100755 index 000000000..000a9442b --- /dev/null +++ b/test/scripts/bridge/start-relayer.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Exit on any error +set -e + +scripts_path="$(realpath ./scripts/bridge)" +source $scripts_path/set-env.sh + +data_store_dir="$output_dir/relayer_data" +mkdir -p $data_store_dir +mkdir -p $logs_dir + +echo "$logs_dir"/beefy-relay.log + +# Requires that relayers are configured +start_relayer() { + echo "Starting relay services" + # Launch beefy relay + ( + : >"$logs_dir"/beefy-relay.log + while :; do + echo "Starting beefy relay at $(date)" + "${relay_bin}" run beefy \ + --config "$output_dir/beefy-relay.json" \ + --ethereum.private-key $beefy_relay_eth_key \ + >>"$logs_dir"/beefy-relay.log 2>&1 || true + sleep 20 + done + ) & + echo "beefy_relay=$!" >> $artifacts_dir/daemons.pid + + # Launch beacon relay + ( + : >"$logs_dir"/beacon-relay.log + while :; do + echo "Starting beacon relay at $(date)" + "${relay_bin}" run beacon \ + --config $output_dir/beacon-relay.json \ + --substrate.private-key "//BeaconRelay" \ + >>"$logs_dir"/beacon-relay.log 2>&1 || true + sleep 20 + done + ) & + echo "beacon_relay=$!" >> $artifacts_dir/daemons.pid +} + +echo "start relayers only!" +trap kill_all SIGINT SIGTERM EXIT +start_relayer +wait diff --git a/test/suites/zombie-tanssi-relay-eth-bridge/test-consensus-bridge.ts b/test/suites/zombie-tanssi-relay-eth-bridge/test-consensus-bridge.ts new file mode 100644 index 000000000..b37a69b21 --- /dev/null +++ b/test/suites/zombie-tanssi-relay-eth-bridge/test-consensus-bridge.ts @@ -0,0 +1,156 @@ +import { beforeAll, describeSuite, expect, afterAll } from "@moonwall/cli"; +import { ApiPromise, Keyring } from "@polkadot/api"; +import { spawn, exec } from "node:child_process"; +import { signAndSendAndInclude, waitSessions } from "../../util/block.ts"; +import { ethers } from "ethers"; + +function execCommand(command: string, options?) { + return new Promise((resolve, reject) => { + exec(command, options, (error: child.ExecException, stdout: string, stderr: string) => { + if (error) { + reject(error); + } else { + resolve({ stdout, stderr }); + } + }); + }); +} + +describeSuite({ + id: "ZR-01", + title: "Zombie Tanssi Relay Test", + foundationMethods: "zombie", + testCases: function ({ it, context }) { + let relayApi: ApiPromise; + let ethereumNodeChildProcess; + let relayerChildProcess; + let alice; + let beefyClientDetails; + + beforeAll(async () => { + relayApi = context.polkadotJs("Tanssi-relay"); + const relayNetwork = relayApi.consts.system.version.specName.toString(); + expect(relayNetwork, "Relay API incorrect").to.contain("dancelight"); + + // //BeaconRelay + const keyring = new Keyring({ type: "sr25519" }); + alice = keyring.addFromUri("//Alice", { name: "Alice default" }); + const beaconRelay = keyring.addFromUri("//BeaconRelay", { name: "Beacon relay default" }); + + const txHash = await relayApi.tx.balances + .transferAllowDeath(beaconRelay.address, 1_000_000_000_000) + .signAndSend(alice); + console.log("Transferred money to beacon relay", txHash.toHex()); + + ethereumNodeChildProcess = spawn("./scripts/bridge/start-ethereum-node.sh", { + shell: true, + detached: true, + }); + ethereumNodeChildProcess.stderr.setEncoding("utf-8"); + ethereumNodeChildProcess.stderr.on("data", (chunk) => console.log(chunk)); + + await execCommand("./scripts/bridge/generate-beefy-checkpoint.sh", { + env: { + RELAYCHAIN_ENDPOINT: "ws://127.0.0.1:9947", + ...process.env, + }, + }); + + // Waiting till ethreum node produces one block + console.log("Waiting some time for ethereum node to produce block, before we deploy contract"); + await sleep(20000); + + await execCommand("./scripts/bridge/deploy-ethereum-contracts.sh"); + + console.log("Contracts deployed"); + + const contractInfoData = JSON.parse( + (await execCommand("./scripts/bridge/generate-contract-info.sh")).stdout + ); + console.log("BeefyClient contract address is:", contractInfoData.contracts.BeefyClient.address); + beefyClientDetails = contractInfoData.contracts.BeefyClient; + + const initialBeaconUpdate = JSON.parse( + ( + await execCommand("./scripts/bridge/setup-relayer.sh", { + env: { + RELAYCHAIN_ENDPOINT: "ws://127.0.0.1:9947", + ...process.env, + }, + }) + ).stdout + ); + + // We need to read initial checkpoint data and address of gateway contract to setup the ethereum client + // Once that is done, we can start the relayer + await signAndSendAndInclude( + relayApi.tx.sudo.sudo(relayApi.tx.ethereumBeaconClient.forceCheckpoint(initialBeaconUpdate)), + alice + ); + + relayerChildProcess = spawn("./scripts/bridge/start-relayer.sh", { + shell: true, + detached: true, + env: { + RELAYCHAIN_ENDPOINT: "ws://127.0.0.1:9947", + ...process.env, + }, + }); + relayerChildProcess.stderr.setEncoding("utf-8"); + relayerChildProcess.stderr.on("data", (chunk) => console.log(chunk)); + }, 12000000); + + it({ + id: "T01", + title: "Ethereum Blocks are being recognized on tanssi-relay", + test: async function () { + await waitSessions(context, relayApi, 1, null, "Tanssi-relay"); + const firstFinalizedBlockRoot = ( + await relayApi.query.ethereumBeaconClient.latestFinalizedBlockRoot() + ).toJSON(); + expect(firstFinalizedBlockRoot).to.not.equal( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); + await waitSessions(context, relayApi, 2, null, "Tanssi-relay"); + const secondFinalizedBlockRoot = ( + await relayApi.query.ethereumBeaconClient.latestFinalizedBlockRoot() + ).toJSON(); + expect(secondFinalizedBlockRoot).to.not.equal(firstFinalizedBlockRoot); + }, + }); + + it({ + id: "T02", + title: "Dancelight Blocks are being recognized on ethereum", + test: async function () { + const url = "ws://127.0.0.1:8546"; + const customHttpProvider = new ethers.providers.WebSocketProvider(url); + const beefyContract = new ethers.Contract( + beefyClientDetails.address, + beefyClientDetails.abi, + customHttpProvider + ); + const currentBeefyBlock = (await beefyContract.latestBeefyBlock()).toNumber(); + expect(currentBeefyBlock).to.greaterThan(0); + await waitSessions(context, relayApi, 1, null, "Tanssi-relay"); + const nextBeefyBlock = (await beefyContract.latestBeefyBlock()).toNumber(); + expect(nextBeefyBlock).to.greaterThan(currentBeefyBlock); + }, + }); + + afterAll(async () => { + console.log("Cleaning up"); + if (ethereumNodeChildProcess) { + ethereumNodeChildProcess.kill("SIGINT"); + } + if (relayerChildProcess) { + relayerChildProcess.kill("SIGINT"); + } + await execCommand("./scripts/bridge/cleanup.sh olep"); + }); + }, +}); + +const sleep = (ms: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); +};