diff --git a/.github/actions/performance-tests/art.yaml b/.github/actions/performance-tests/art.yaml index 35f79a788f..f854e93d13 100644 --- a/.github/actions/performance-tests/art.yaml +++ b/.github/actions/performance-tests/art.yaml @@ -1,5 +1,5 @@ config: - target: "-=http_request_protocol=-://-=endpoint_to_test=-" + target: "http://localhost:9545" phases: - duration: 300 arrivalRate: 400 @@ -14,6 +14,11 @@ config: ensure: - type: "failure" threshold: 1 + publish-metrics: + - type: prometheus + pushgateway: 'https://pushgateway.dev-zetachain-internal.com' + tags: + - 'type:loadtest' summary: true reports: - type: "html" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea1e2cad2d..c8b5869c15 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - develop + - release/* merge_group: pull_request: branches: @@ -12,7 +13,6 @@ on: - synchronize - opened - reopened - - ready_for_review concurrency: group: pr-testing-${{ github.head_ref || github.run_id }} diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index e0dc06924a..aab15adf8f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -4,15 +4,49 @@ on: push: branches: - develop + - release/* pull_request: branches: - "*" merge_group: - workflow_dispatch: schedule: # run at 6AM UTC Daily # 6AM UTC -> 11PM PT - cron: "0 6 * * *" + workflow_dispatch: + inputs: + default-test: + type: boolean + required: false + default: false + upgrade-light-test: + type: boolean + required: false + default: false + upgrade-test: + type: boolean + required: false + default: false + admin-test: + type: boolean + required: false + default: false + upgrade-import-mainnet-test: + type: boolean + required: false + default: false + performance-test: + type: boolean + required: false + default: false + stateful-data-test: + type: boolean + required: false + default: false + tss-migration-test: + type: boolean + required: false + default: false concurrency: group: e2e-${{ github.head_ref || github.sha }} @@ -27,34 +61,64 @@ jobs: DEFAULT_TESTS: ${{ steps.matrix-conditionals.outputs.DEFAULT_TESTS }} UPGRADE_TESTS: ${{ steps.matrix-conditionals.outputs.UPGRADE_TESTS }} UPGRADE_LIGHT_TESTS: ${{ steps.matrix-conditionals.outputs.UPGRADE_LIGHT_TESTS }} + UPGRADE_IMPORT_MAINNET_TESTS: ${{ steps.matrix-conditionals.outputs.UPGRADE_IMPORT_MAINNET_TESTS }} ADMIN_TESTS: ${{ steps.matrix-conditionals.outputs.ADMIN_TESTS }} + PERFORMANCE_TESTS: ${{ steps.matrix-conditionals.outputs.PERFORMANCE_TESTS }} + STATEFUL_DATA_TESTS: ${{ steps.matrix-conditionals.outputs.STATEFUL_DATA_TESTS }} + TSS_MIGRATION_TESTS: ${{ steps.matrix-conditionals.outputs.TSS_MIGRATION_TESTS }} + steps: - # use cli rather than event context to avoid race conditions (label added after push) + # use api rather than event context to avoid race conditions (label added after push) - id: matrix-conditionals - run: | - if [[ ${{ github.event_name }} == 'pull_request' ]]; then - echo "DEFAULT_TESTS=true" >> $GITHUB_OUTPUT - labels=$(gh pr view -R ${{github.repository}} ${{github.event.pull_request.number}} --json labels -q '.labels[].name') - if [[ "$labels" == *"UPGRADE_TESTS"* ]]; then - echo "UPGRADE_TESTS=true" >> $GITHUB_OUTPUT - fi - - if [[ "$labels" == *"UPGRADE_LIGHT_TESTS"* ]]; then - echo "UPGRADE_LIGHT_TESTS=true" >> $GITHUB_OUTPUT - fi - - if [[ "$labels" == *"ADMIN_TESTS"* ]]; then - echo "ADMIN_TESTS=true" >> $GITHUB_OUTPUT - fi - elif [[ ${{ github.event_name }} == 'merge_group' ]]; then - echo "DEFAULT_TESTS=true" >> $GITHUB_OUTPUT - elif [[ ${{ github.event_name }} == 'push' && ${{ github.ref }} == 'refs/heads/develop' ]]; then - echo "DEFAULT_TESTS=true" >> $GITHUB_OUTPUT - elif [[ ${{ github.event_name }} == 'schedule' ]]; then - echo "UPGRADE_TESTS=true" >> $GITHUB_OUTPUT - echo "UPGRADE_LIGHT_TESTS=true" >> $GITHUB_OUTPUT - echo "ADMIN_TESTS=true" >> $GITHUB_OUTPUT - fi + uses: actions/github-script@v7 + with: + script: | + if (context.eventName === 'pull_request') { + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + }); + const labels = pr.labels.map(label => label.name); + console.log("labels:", labels); + core.setOutput('DEFAULT_TESTS', true); + core.setOutput('UPGRADE_TESTS', labels.includes('UPGRADE_TESTS')); + core.setOutput('UPGRADE_LIGHT_TESTS', labels.includes('UPGRADE_LIGHT_TESTS')); + core.setOutput('UPGRADE_IMPORT_MAINNET_TESTS', labels.includes('UPGRADE_IMPORT_MAINNET_TESTS')); + core.setOutput('ADMIN_TESTS', labels.includes('ADMIN_TESTS')); + core.setOutput('PERFORMANCE_TESTS', labels.includes('PERFORMANCE_TESTS')); + core.setOutput('STATEFUL_DATA_TESTS', labels.includes('STATEFUL_DATA_TESTS')); + core.setOutput('TSS_MIGRATION_TESTS', labels.includes('TSS_MIGRATION_TESTS')); + } else if (context.eventName === 'merge_group') { + core.setOutput('DEFAULT_TESTS', true); + } else if (context.eventName === 'push' && context.ref === 'refs/heads/develop') { + core.setOutput('DEFAULT_TESTS', true); + } else if (context.eventName === 'push' && context.ref.startsWith('refs/heads/release/')) { + core.setOutput('DEFAULT_TESTS', true); + core.setOutput('UPGRADE_TESTS', true); + core.setOutput('UPGRADE_LIGHT_TESTS', true); + core.setOutput('UPGRADE_IMPORT_MAINNET_TESTS', true); + core.setOutput('ADMIN_TESTS', true); + core.setOutput('PERFORMANCE_TESTS', true); + core.setOutput('STATEFUL_DATA_TESTS', true); + } else if (context.eventName === 'schedule') { + core.setOutput('DEFAULT_TESTS', true); + core.setOutput('UPGRADE_TESTS', true); + core.setOutput('UPGRADE_LIGHT_TESTS', true); + core.setOutput('UPGRADE_IMPORT_MAINNET_TESTS', true); + core.setOutput('ADMIN_TESTS', true); + core.setOutput('PERFORMANCE_TESTS', true); + core.setOutput('STATEFUL_DATA_TESTS', true); + } else if (context.eventName === 'workflow_dispatch') { + core.setOutput('DEFAULT_TESTS', context.payload.inputs['default-test']); + core.setOutput('UPGRADE_TESTS', context.payload.inputs['upgrade-test']); + core.setOutput('UPGRADE_LIGHT_TESTS', context.payload.inputs['upgrade-light-test']); + core.setOutput('UPGRADE_IMPORT_MAINNET_TESTS', context.payload.inputs['upgrade-import-mainnet-test']); + core.setOutput('ADMIN_TESTS', context.payload.inputs['admin-test']); + core.setOutput('PERFORMANCE_TESTS', context.payload.inputs['performance-test']); + core.setOutput('STATEFUL_DATA_TESTS', context.payload.inputs['stateful-data-test']); + core.setOutput('TSS_MIGRATION_TESTS', context.payload.inputs['tss-migration-test']); + } e2e: needs: matrix-conditionals @@ -71,9 +135,21 @@ jobs: - make-target: "start-upgrade-test-light" runs-on: ubuntu-20.04 run: ${{ needs.matrix-conditionals.outputs.UPGRADE_LIGHT_TESTS == 'true' }} + - make-target: "start-upgrade-import-mainnet-test" + runs-on: buildjet-16vcpu-ubuntu-2204 + run: ${{ needs.matrix-conditionals.outputs.UPGRADE_IMPORT_MAINNET_TESTS == 'true' }} - make-target: "start-e2e-admin-test" runs-on: ubuntu-20.04 run: ${{ needs.matrix-conditionals.outputs.ADMIN_TESTS == 'true' }} + - make-target: "start-e2e-performance-test" + runs-on: buildjet-4vcpu-ubuntu-2204 + run: ${{ needs.matrix-conditionals.outputs.PERFORMANCE_TESTS == 'true' }} + - make-target: "start-e2e-import-mainnet-test" + runs-on: buildjet-16vcpu-ubuntu-2204 + run: ${{ needs.matrix-conditionals.outputs.STATEFUL_DATA_TESTS == 'true' }} + - make-target: "start-tss-migration-test" + runs-on: ubuntu-20.04 + run: ${{ needs.matrix-conditionals.outputs.TSS_MIGRATION_TESTS == 'true' }} name: ${{ matrix.make-target }} uses: ./.github/workflows/reusable-e2e.yml with: @@ -84,7 +160,9 @@ jobs: # this allows you to set a required status check e2e-ok: runs-on: ubuntu-22.04 - needs: e2e + needs: + - matrix-conditionals + - e2e if: always() steps: - run: | diff --git a/.github/workflows/execute_advanced_tests.yaml b/.github/workflows/execute_advanced_tests.yaml deleted file mode 100644 index e8a4683812..0000000000 --- a/.github/workflows/execute_advanced_tests.yaml +++ /dev/null @@ -1,119 +0,0 @@ -name: "TESTING:ADVANCED:E2E" - -on: - workflow_dispatch: - inputs: - e2e-stateful-upgrade-test: - type: boolean - required: false - default: false - e2e-performance-test: - type: boolean - required: false - default: false - e2e-stateful-data-test: - type: boolean - required: false - default: false - debug: - type: boolean - required: false - default: false - schedule: - # run at 6AM UTC Daily - # 6AM UTC -> 11PM PT - - cron: "0 6 * * *" - -jobs: - e2e-stateful-upgrade-test: - if: ${{ github.event.inputs.e2e-stateful-upgrade-test == 'true' || github.event_name == 'schedule' }} - runs-on: buildjet-16vcpu-ubuntu-2204 - timeout-minutes: 120 - steps: - - name: "Checkout Code" - uses: actions/checkout@v4 - - - name: Start Test - run: make start-upgrade-import-mainnet-test - - - name: Watch Test - run: | - container_id=$(docker ps --filter "ancestor=orchestrator:latest" --format "{{.ID}}") - docker logs -f "${container_id}" & - exit $(docker wait "${container_id}") - - - name: Full Log Dump On Failure - if: failure() - run: | - make stop-localnet - - - name: Notify Slack on Failure - if: failure() && github.event_name == 'schedule' - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - fields: repo,message,commit,author,action,eventName,ref,workflow,job,took - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_CI_ALERTS }} - - e2e-performance-test: - if: ${{ github.event.inputs.e2e-performance-test == 'true' }} - runs-on: buildjet-4vcpu-ubuntu-2204 - timeout-minutes: 120 - steps: - - name: "Checkout Code" - uses: actions/checkout@v4 - - - name: Start Test - run: make start-e2e-performance-test - - - name: Watch Test - run: | - container_id=$(docker ps --filter "ancestor=orchestrator:latest" --format "{{.ID}}") - docker logs -f "${container_id}" & - exit $(docker wait "${container_id}") - - - name: Full Log Dump On Failure - if: failure() - run: | - make stop-localnet - - - name: Notify Slack on Failure - if: failure() && github.event_name == 'schedule' - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - fields: repo,message,commit,author,action,eventName,ref,workflow,job,took - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_CI_ALERTS }} - - e2e-stateful-data-test: - if: ${{ github.event.inputs.e2e-stateful-data-test == 'true' || github.event_name == 'schedule' }} - runs-on: buildjet-16vcpu-ubuntu-2204 - timeout-minutes: 120 - steps: - - name: "Checkout Code" - uses: actions/checkout@v4 - - - name: Start Test - run: make start-e2e-import-mainnet-test - - - name: Watch Test - run: | - container_id=$(docker ps --filter "ancestor=orchestrator:latest" --format "{{.ID}}") - docker logs -f "${container_id}" & - exit $(docker wait "${container_id}") - - - name: Full Log Dump On Failure - if: failure() - run: | - make stop-localnet - - - name: Notify Slack on Failure - if: failure() && github.event_name == 'schedule' - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - fields: repo,message,commit,author,action,eventName,ref,workflow,job,took - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_CI_ALERTS }} \ No newline at end of file diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 44e3fa0538..ff454a4032 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -11,7 +11,7 @@ on: type: boolean required: false default: false - description: 'Use this to skip: gosec, gosec-cosmos, check-changelog, check-upgrade-uandler-updated, build-test, smoke-test and go straight to approval step.' + description: 'Use this to skip: check-changelog and check-upgrade-handler-updated go straight to approval step.' skip_release: type: boolean required: false @@ -31,99 +31,6 @@ jobs: run: | echo "${{ github.ref }}" - gosec: - needs: - - check_branch - runs-on: ubuntu-22.04 - env: - GO111MODULE: on - steps: - - name: Checkout Source - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - - name: Run Gosec Security Scanner - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: securego/gosec@v2.19.0 - with: - args: ./... - - - name: Mark Job Complete Skipped - if: ${{ github.event.inputs.skip_checks == 'true' }} - shell: bash - run: | - echo "continue" - - gosec-cosmos: - needs: - - check_branch - runs-on: ubuntu-22.04 - env: - GO111MODULE: on - steps: - - name: Checkout Source - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - - name: Run Cosmos Gosec Security Scanner - if: ${{ github.event.inputs.skip_checks != 'true' }} - run: make lint-cosmos-gosec - - - name: Mark Job Complete Skipped - if: ${{ github.event.inputs.skip_checks == 'true' }} - shell: bash - run: | - echo "continue" - - lint: - needs: - - check_branch - runs-on: ubuntu-22.04 - timeout-minutes: 15 - env: - GO111MODULE: on - steps: - - name: Checkout Source - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - - name: Run golangci-lint - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: golangci/golangci-lint-action@v6 - with: - version: v1.59 - skip-cache: true - - - name: Mark Job Complete Skipped - if: ${{ github.event.inputs.skip_checks == 'true' }} - shell: bash - run: | - echo "continue" - check-changelog: needs: - check_branch @@ -198,232 +105,6 @@ jobs: run: | echo "continue" - build-test: - needs: - - check_branch - runs-on: ubuntu-22.04 - timeout-minutes: 15 - concurrency: - group: "build-test" - steps: - - name: "Checkout Code" - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/checkout@v4 - - - name: Set CPU Architecture - if: ${{ github.event.inputs.skip_checks != 'true' }} - shell: bash - run: | - if [ "$(uname -m)" == "aarch64" ]; then - echo "CPU_ARCH=arm64" >> $GITHUB_ENV - elif [ "$(uname -m)" == "x86_64" ]; then - echo "CPU_ARCH=amd64" >> $GITHUB_ENV - else - echo "Unsupported architecture" >&2 - exit 1 - fi - - - name: Install Pipeline Dependencies - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: ./.github/actions/install-dependencies - timeout-minutes: 8 - with: - cpu_architecture: ${{ env.CPU_ARCH }} - skip_python: "true" - skip_aws_cli: "true" - skip_docker_compose: "false" - - - name: Test - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: nick-fields/retry@v2 - with: - timeout_minutes: 20 - max_attempts: 2 - retry_on: error - command: | - echo "Running Build Tests" - make clean - make test-coverage - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4.0.1 - with: - file: coverage.out - token: ${{ secrets.CODECOV_TOKEN }} - slug: zeta-chain/node - - - name: Build zetacored and zetaclientd - if: ${{ github.event.inputs.skip_checks != 'true' }} - env: - CGO_ENABLED: 1 - GOOS: linux - GOARCH: ${{ env.CPU_ARCH }} - run: | - make install - cp "$HOME"/go/bin/* ./ - chmod a+x ./zetacored - ./zetacored version - - - name: Clean Up Workspace - if: always() - shell: bash - run: rm -rf * - - - name: Mark Job Complete Skipped - if: ${{ github.event.inputs.skip_checks == 'true' }} - shell: bash - run: | - echo "continue" - - smoke-test: - needs: - - check_branch - runs-on: ubuntu-22.04 - timeout-minutes: 25 - steps: - - name: "Checkout Code" - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/checkout@v4 - - - name: Set CPU Architecture - if: ${{ github.event.inputs.skip_checks != 'true' }} - shell: bash - run: | - if [ "$(uname -m)" == "aarch64" ]; then - echo "CPU_ARCH=arm64" >> $GITHUB_ENV - elif [ "$(uname -m)" == "x86_64" ]; then - echo "CPU_ARCH=amd64" >> $GITHUB_ENV - else - echo "Unsupported architecture" >&2 - exit 1 - fi - - - name: Install Pipeline Dependencies - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: ./.github/actions/install-dependencies - timeout-minutes: 8 - with: - cpu_architecture: ${{ env.CPU_ARCH }} - skip_python: "false" - skip_aws_cli: "true" - skip_docker_compose: "false" - - - name: Login to Docker Hub - uses: docker/login-action@v2 - if: ${{ github.event.repository.full_name == 'zetachain-chain/node' && github.event.inputs.skip_checks != 'true' }} - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_READ_ONLY }} - - - name: Build zetanode - if: ${{ github.event.inputs.skip_checks != 'true' }} - run: | - make zetanode - - - name: Start Private Network - if: ${{ github.event.inputs.skip_checks != 'true' }} - run: | - cd contrib/localnet/ - docker compose up -d zetacore0 zetacore1 zetaclient0 zetaclient1 eth bitcoin - - - name: Run Smoke Test - if: ${{ github.event.inputs.skip_checks != 'true' }} - run: | - cd contrib/localnet - docker-compose up orchestrator --exit-code-from orchestrator - if [ $? -ne 0 ]; then - echo "Smoke Test Failed" - exit 1 - fi - - - name: Stop Private Network - if: ${{ always() && github.event.inputs.skip_checks != 'true' }} - run: | - make stop-localnet - - - name: Clean Up Workspace - if: always() - shell: bash - run: sudo rm -rf * - - - name: Mark Job Complete Skipped - if: ${{ github.event.inputs.skip_checks == 'true' }} - shell: bash - run: | - echo "continue" - - e2e-admin-tests: - needs: - - check_branch - runs-on: ubuntu-22.04 - timeout-minutes: 120 - steps: - - name: "Checkout Code" - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/checkout@v4 - - - name: Execute e2e-admin-tests - if: ${{ github.event.inputs.skip_checks != 'true' }} - shell: bash - run: | - make start-e2e-admin-test - container_id=$(docker ps --filter "ancestor=orchestrator:latest" --format "{{.ID}}") - docker logs -f "${container_id}" & exit $(docker wait "${container_id}") - - - name: Mark Job Complete Skipped - if: ${{ github.event.inputs.skip_checks == 'true' }} - shell: bash - run: | - echo "continue" - - e2e-upgrade-test: - needs: - - check_branch - runs-on: buildjet-16vcpu-ubuntu-2204 - timeout-minutes: 120 - steps: - - name: "Checkout Code" - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/checkout@v4 - - - name: Execute upgrade-test - if: ${{ github.event.inputs.skip_checks != 'true' }} - shell: bash - run: | - make start-upgrade-import-mainnet-test - container_id=$(docker ps --filter "ancestor=orchestrator:latest" --format "{{.ID}}") - docker logs -f "${container_id}" & exit $(docker wait "${container_id}") - - - name: Mark Job Complete Skipped - if: ${{ github.event.inputs.skip_checks == 'true' }} - shell: bash - run: | - echo "continue" - - e2e-stateful-data-test: - needs: - - check_branch - runs-on: buildjet-16vcpu-ubuntu-2204 - timeout-minutes: 120 - steps: - - name: "Checkout Code" - if: ${{ github.event.inputs.skip_checks != 'true' }} - uses: actions/checkout@v3 - - - name: Execute stateful-data-test - if: ${{ github.event.inputs.skip_checks != 'true' }} - shell: bash - run: | - make start-e2e-import-mainnet-test - container_id=$(docker ps --filter "ancestor=orchestrator:latest" --format "{{.ID}}") - docker logs -f "${container_id}" & exit $(docker wait "${container_id}") - - - name: Mark Job Complete Skipped - if: ${{ github.event.inputs.skip_checks == 'true' }} - shell: bash - run: | - echo "continue" - publish-release: permissions: id-token: write @@ -431,16 +112,8 @@ jobs: attestations: write if: ${{ github.event.inputs.skip_release == 'false' }} needs: - - gosec - - gosec-cosmos - - lint - check-changelog - check-upgrade-handler-updated - - smoke-test - - build-test - - e2e-admin-tests - - e2e-stateful-data-test - - e2e-upgrade-test - check_branch runs-on: ubuntu-22.04 timeout-minutes: 60 diff --git a/.github/workflows/reusable-e2e.yml b/.github/workflows/reusable-e2e.yml index 2bc75efcfb..d67f53db0e 100644 --- a/.github/workflows/reusable-e2e.yml +++ b/.github/workflows/reusable-e2e.yml @@ -101,7 +101,7 @@ jobs: path: /tmp/logs.txt - name: Notify Slack on Failure - if: failure() && ((github.event_name == 'push' && github.ref == 'refs/heads/develop') || github.event_name == 'schedule') + if: failure() && (github.event_name == 'push' || github.event_name == 'schedule') uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} diff --git a/.github/workflows/sast-linters.yml b/.github/workflows/sast-linters.yml index 30a8ab1f33..270e9cffc6 100644 --- a/.github/workflows/sast-linters.yml +++ b/.github/workflows/sast-linters.yml @@ -1,13 +1,15 @@ name: Linters and SAST on: push: + branches: + - develop + - release/* tags: - "*" merge_group: pull_request: types: - opened - - edited - synchronize concurrency: diff --git a/Makefile b/Makefile index 9f1482f882..75bd9e9f92 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ build-testnet-ubuntu: go.sum docker rm temp-container install: go.sum - @echo "--> Installing zetacored & zetaclientd" + @echo "--> Installing zetacored, zetaclientd, and zetaclientd-supervisor" @go install -mod=readonly $(BUILD_FLAGS) ./cmd/zetacored @go install -mod=readonly $(BUILD_FLAGS) ./cmd/zetaclientd @go install -mod=readonly $(BUILD_FLAGS) ./cmd/zetaclientd-supervisor @@ -254,6 +254,11 @@ start-stress-test: zetanode @echo "--> Starting stress test" cd contrib/localnet/ && $(DOCKER) compose --profile stress -f docker-compose.yml up -d +start-tss-migration-test: zetanode + @echo "--> Starting migration test" + export E2E_ARGS="--test-tss-migration" && \ + cd contrib/localnet/ && $(DOCKER) compose up -d + ############################################################################### ### Upgrade Tests ### ############################################################################### diff --git a/changelog.md b/changelog.md index a9a8cfc3a0..d3ccb52b5a 100644 --- a/changelog.md +++ b/changelog.md @@ -30,7 +30,7 @@ * [2339](https://github.com/zeta-chain/node/pull/2339) - add binaries related question to syncing issue form * [2366](https://github.com/zeta-chain/node/pull/2366) - add migration script for adding authorizations table * [2372](https://github.com/zeta-chain/node/pull/2372) - add queries for tss fund migration info -* [2416g](https://github.com/zeta-chain/node/pull/2416) - add Solana chain information +* [2416](https://github.com/zeta-chain/node/pull/2416) - add Solana chain information ### Refactor @@ -59,6 +59,7 @@ * [2380](https://github.com/zeta-chain/node/pull/2380) - use `ChainInfo` in `authority` to allow dynamically support new chains * [2395](https://github.com/zeta-chain/node/pull/2395) - converge AppContext with ZetaCoreContext in zetaclient * [2428](https://github.com/zeta-chain/node/pull/2428) - propagate context across codebase & refactor zetacore client +* [2464](https://github.com/zeta-chain/node/pull/2464) - move common voting logic to voting.go and add new function VoteOnBallot ### Tests @@ -79,6 +80,7 @@ * [2369](https://github.com/zeta-chain/node/pull/2369) - fix random cross-chain swap failure caused by using tiny UTXO * [2549](https://github.com/zeta-chain/node/pull/2459) - add separate accounts for each policy in e2e tests * [2415](https://github.com/zeta-chain/node/pull/2415) - add e2e test for upgrade and test admin functionalities +* [2440](https://github.com/zeta-chain/node/pull/2440) - Add e2e test for TSS migration * [2473](https://github.com/zeta-chain/node/pull/2473) - add e2e tests for most used admin transactions ### Fixes @@ -94,8 +96,10 @@ * [2382](https://github.com/zeta-chain/node/pull/2382) - add tx input and gas in rpc methods for synthetic eth txs * [2396](https://github.com/zeta-chain/node/issues/2386) - special handle bitcoin testnet gas price estimator * [2434](https://github.com/zeta-chain/node/pull/2434) - the default database when running `zetacored init` is now pebbledb +* [2481](https://github.com/zeta-chain/node/pull/2481) - increase gas limit inbound and outbound vote message to 500k ### CI + * [2388](https://github.com/zeta-chain/node/pull/2388) - added GitHub attestations of binaries produced in the release workflow. * [2285](https://github.com/zeta-chain/node/pull/2285) - added nightly EVM performance testing pipeline, modified localnet testing docker image to utilitze debian:bookworm, removed build-jet runners where applicable, removed deprecated/removed upgrade path testing pipeline * [2268](https://github.com/zeta-chain/node/pull/2268) - updated the publish-release pipeline to utilize the Github Actions Ubuntu 20.04 Runners @@ -113,6 +117,10 @@ * [2321](https://github.com/zeta-chain/node/pull/2321) - improve documentation for ZetaClient functions and packages +### Performance + +* [2482](https://github.com/zeta-chain/node/pull/2482) - increase the outbound tracker buffer length from 2 to 5 + ## v17.0.0 ### Fixes @@ -215,7 +223,7 @@ * [1861](https://github.com/zeta-chain/node/pull/1861) - fix `ObserverSlashAmount` invalid read * [1880](https://github.com/zeta-chain/node/issues/1880) - lower the gas price multiplier for EVM chains * [1883](https://github.com/zeta-chain/node/issues/1883) - zetaclient should check 'IsSupported' flag to pause/unpause a specific chain -* * [2076](https://github.com/zeta-chain/node/pull/2076) - automatically deposit native zeta to an address if it doesn't exist on ZEVM +* [2076](https://github.com/zeta-chain/node/pull/2076) - automatically deposit native zeta to an address if it doesn't exist on ZEVM * [1633](https://github.com/zeta-chain/node/issues/1633) - zetaclient should be able to pick up new connector and erc20Custody addresses * [1944](https://github.com/zeta-chain/node/pull/1944) - fix evm signer unit tests * [1888](https://github.com/zeta-chain/node/issues/1888) - zetaclient should stop inbound/outbound txs according to cross-chain flags @@ -238,11 +246,12 @@ ## Version: v15.0.0 ### Features + * [1912](https://github.com/zeta-chain/node/pull/1912) - add reset chain nonces msg ## Version: v14.0.1 -- [1817](https://github.com/zeta-chain/node/pull/1817) - Add migration script to fix pending and chain nonces on testnet +* [1817](https://github.com/zeta-chain/node/pull/1817) - Add migration script to fix pending and chain nonces on testnet ## Version: v13.0.0 @@ -551,4 +560,4 @@ Getting the correct TSS address for Bitcoin now requires proviidng the Bitcoin c ### CI * [1218](https://github.com/zeta-chain/node/pull/1218) - cross-compile release binaries and simplify PR testings -* [1302](https://github.com/zeta-chain/node/pull/1302) - add mainnet builds to goreleaser +* [1302](https://github.com/zeta-chain/node/pull/1302) - add mainnet builds to goreleaser \ No newline at end of file diff --git a/cmd/zetaclientd-supervisor/lib.go b/cmd/zetaclientd-supervisor/lib.go index e8f1b54470..71f492e88b 100644 --- a/cmd/zetaclientd-supervisor/lib.go +++ b/cmd/zetaclientd-supervisor/lib.go @@ -12,6 +12,7 @@ import ( "runtime" "strings" "sync" + "syscall" "time" "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" @@ -20,6 +21,7 @@ import ( "github.com/rs/zerolog" "google.golang.org/grpc" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" ) @@ -66,6 +68,7 @@ type zetaclientdSupervisor struct { upgradesDir string upgradePlanName string enableAutoDownload bool + restartChan chan os.Signal } func newZetaclientdSupervisor( @@ -81,19 +84,24 @@ func newZetaclientdSupervisor( if err != nil { return nil, fmt.Errorf("grpc dial: %w", err) } - + // these signals will result in the supervisor process only restarting zetaclientd + restartChan := make(chan os.Signal, 1) return &zetaclientdSupervisor{ zetacoredConn: conn, logger: logger, reloadSignals: make(chan bool, 1), upgradesDir: defaultUpgradesDir, enableAutoDownload: enableAutoDownload, + restartChan: restartChan, }, nil } func (s *zetaclientdSupervisor) Start(ctx context.Context) { go s.watchForVersionChanges(ctx) go s.handleCoreUpgradePlan(ctx) + go s.handleNewKeygen(ctx) + go s.handleNewTSSKeyGeneration(ctx) + go s.handleTSSUpdate(ctx) } func (s *zetaclientdSupervisor) WaitForReloadSignal(ctx context.Context) { @@ -169,6 +177,125 @@ func (s *zetaclientdSupervisor) watchForVersionChanges(ctx context.Context) { } } +func (s *zetaclientdSupervisor) handleTSSUpdate(ctx context.Context) { + maxRetries := 11 + retryInterval := 5 * time.Second + + // TODO : use retry library under pkg/retry + // https://github.com/zeta-chain/node/issues/2492 + for i := 0; i < maxRetries; i++ { + client := observertypes.NewQueryClient(s.zetacoredConn) + tss, err := client.TSS(ctx, &observertypes.QueryGetTSSRequest{}) + if err != nil { + s.logger.Warn().Err(err).Msg("unable to get original tss") + time.Sleep(retryInterval) + continue + } + i = 0 + for { + select { + case <-time.After(time.Second): + case <-ctx.Done(): + return + } + tssNew, err := client.TSS(ctx, &observertypes.QueryGetTSSRequest{}) + if err != nil { + s.logger.Warn().Err(err).Msg("unable to get tss") + continue + } + + if tssNew.TSS.TssPubkey == tss.TSS.TssPubkey { + continue + } + + tss = tssNew + s.logger.Info(). + Msgf("tss address is updated from %s to %s", tss.TSS.TssPubkey, tssNew.TSS.TssPubkey) + time.Sleep(6 * time.Second) + s.logger.Info().Msg("restarting zetaclientd to update tss address") + s.restartChan <- syscall.SIGHUP + } + } + s.logger.Warn().Msg("handleTSSUpdate exiting without success") +} + +func (s *zetaclientdSupervisor) handleNewTSSKeyGeneration(ctx context.Context) { + maxRetries := 11 + retryInterval := 5 * time.Second + + // TODO : use retry library under pkg/retry + for i := 0; i < maxRetries; i++ { + client := observertypes.NewQueryClient(s.zetacoredConn) + alltss, err := client.TssHistory(ctx, &observertypes.QueryTssHistoryRequest{}) + if err != nil { + s.logger.Warn().Err(err).Msg("unable to get tss original history") + time.Sleep(retryInterval) + continue + } + i = 0 + tssLenCurrent := len(alltss.TssList) + for { + select { + case <-time.After(time.Second): + case <-ctx.Done(): + return + } + tssListNew, err := client.TssHistory(ctx, &observertypes.QueryTssHistoryRequest{}) + if err != nil { + s.logger.Warn().Err(err).Msg("unable to get tss new history") + continue + } + tssLenUpdated := len(tssListNew.TssList) + + if tssLenUpdated == tssLenCurrent { + continue + } + if tssLenUpdated < tssLenCurrent { + tssLenCurrent = len(tssListNew.TssList) + continue + } + + tssLenCurrent = tssLenUpdated + s.logger.Info().Msgf("tss list updated from %d to %d", tssLenCurrent, tssLenUpdated) + time.Sleep(5 * time.Second) + s.logger.Info().Msg("restarting zetaclientd to update tss list") + s.restartChan <- syscall.SIGHUP + } + } + s.logger.Warn().Msg("handleNewTSSKeyGeneration exiting without success") +} + +func (s *zetaclientdSupervisor) handleNewKeygen(ctx context.Context) { + client := observertypes.NewQueryClient(s.zetacoredConn) + prevKeygenBlock := int64(0) + for { + select { + case <-time.After(time.Second): + case <-ctx.Done(): + return + } + resp, err := client.Keygen(ctx, &observertypes.QueryGetKeygenRequest{}) + if err != nil { + s.logger.Warn().Err(err).Msg("unable to get keygen") + continue + } + if resp.Keygen == nil { + s.logger.Warn().Err(err).Msg("keygen is nil") + continue + } + + if resp.Keygen.Status != observertypes.KeygenStatus_PendingKeygen { + continue + } + keygenBlock := resp.Keygen.BlockNumber + if prevKeygenBlock == keygenBlock { + continue + } + prevKeygenBlock = keygenBlock + s.logger.Info().Msgf("got new keygen at block %d", keygenBlock) + s.restartChan <- syscall.SIGHUP + } +} func (s *zetaclientdSupervisor) handleCoreUpgradePlan(ctx context.Context) { client := upgradetypes.NewQueryClient(s.zetacoredConn) diff --git a/cmd/zetaclientd-supervisor/main.go b/cmd/zetaclientd-supervisor/main.go index 6017050986..ee1e247be4 100644 --- a/cmd/zetaclientd-supervisor/main.go +++ b/cmd/zetaclientd-supervisor/main.go @@ -36,10 +36,6 @@ func main() { shutdownChan := make(chan os.Signal, 1) signal.Notify(shutdownChan, syscall.SIGINT, syscall.SIGTERM) - // these signals will result in the supervisor process only restarting zetaclientd - restartChan := make(chan os.Signal, 1) - signal.Notify(restartChan, syscall.SIGHUP) - hotkeyPassword, tssPassword, err := promptPasswords() if err != nil { logger.Error().Err(err).Msg("unable to get passwords") @@ -53,6 +49,8 @@ func main() { os.Exit(1) } supervisor.Start(ctx) + // listen for SIGHUP to trigger a restart of zetaclientd + signal.Notify(supervisor.restartChan, syscall.SIGHUP) shouldRestart := true for shouldRestart { @@ -82,7 +80,7 @@ func main() { select { case <-ctx.Done(): return nil - case sig := <-restartChan: + case sig := <-supervisor.restartChan: logger.Info().Msgf("got signal %d, sending SIGINT to zetaclientd", sig) case sig := <-shutdownChan: logger.Info().Msgf("got signal %d, shutting down", sig) diff --git a/cmd/zetaclientd/keygen_tss.go b/cmd/zetaclientd/keygen_tss.go index 05e6d7f01f..8d677a2680 100644 --- a/cmd/zetaclientd/keygen_tss.go +++ b/cmd/zetaclientd/keygen_tss.go @@ -8,65 +8,36 @@ import ( "fmt" "time" - "github.com/cometbft/cometbft/crypto/secp256k1" "github.com/rs/zerolog" tsscommon "github.com/zeta-chain/go-tss/common" "github.com/zeta-chain/go-tss/keygen" - "github.com/zeta-chain/go-tss/p2p" + "github.com/zeta-chain/go-tss/tss" "golang.org/x/crypto/sha3" "github.com/zeta-chain/zetacore/pkg/chains" observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/metrics" mc "github.com/zeta-chain/zetacore/zetaclient/tss" "github.com/zeta-chain/zetacore/zetaclient/zetacore" ) -func GenerateTss( +// GenerateTSS generates a new TSS if keygen is set. +// If a TSS was generated successfully in the past,and the keygen was successful, the function will return without doing anything. +// If a keygen has been set the functions will wait for the correct block to arrive and generate a new TSS. +// In case of a successful keygen a TSS success vote is broadcasted to zetacore and the newly generate TSS is tested. The generated keyshares are stored in the correct directory +// In case of a failed keygen a TSS failed vote is broadcasted to zetacore. +func GenerateTSS( ctx context.Context, logger zerolog.Logger, - client *zetacore.Client, - peers p2p.AddrList, - priKey secp256k1.PrivKey, - ts *metrics.TelemetryServer, - tssHistoricalList []observertypes.TSS, - tssPassword string, - hotkeyPassword string, -) (*mc.TSS, error) { - app, err := zctx.FromContext(ctx) - if err != nil { - return nil, err - } - + zetaCoreClient *zetacore.Client, + keygenTssServer *tss.TssServer) error { keygenLogger := logger.With().Str("module", "keygen").Logger() - - // Bitcoin chain ID is currently used for using the correct signature format - // TODO: remove this once we have a better way to determine the signature format - // https://github.com/zeta-chain/node/issues/1397 - bitcoinChainID := chains.BitcoinRegtest.ChainId - btcChain, _, btcEnabled := app.GetBTCChainAndConfig() - if btcEnabled { - bitcoinChainID = btcChain.ChainId - } - - tss, err := mc.NewTSS( - ctx, - app, - peers, - priKey, - preParams, - client, - tssHistoricalList, - bitcoinChainID, - tssPassword, - hotkeyPassword, - ) + app, err := zctx.FromContext(ctx) if err != nil { - keygenLogger.Error().Err(err).Msg("NewTSS error") - return nil, err + return err } - ts.SetP2PID(tss.Server.GetLocalPeerID()) // If Keygen block is set it will try to generate new TSS at the block // This is a blocking thread and will wait until the ceremony is complete successfully // If the TSS generation is unsuccessful , it will loop indefinitely until a new TSS is generated @@ -81,10 +52,9 @@ func GenerateTss( // Break out of loop only when TSS is generated successfully, either at the keygenBlock or if it has been generated already , Block set as zero in genesis file // This loop will try keygen at the keygen block and then wait for keygen to be successfully reported by all nodes before breaking out of the loop. // If keygen is unsuccessful, it will reset the triedKeygenAtBlock flag and try again at a new keygen block. - keyGen := app.GetKeygen() if keyGen.Status == observertypes.KeygenStatus_KeyGenSuccess { - return tss, nil + return nil } // Arrive at this stage only if keygen is unsuccessfully reported by every node . This will reset the flag and to try again at a new keygen block if keyGen.Status == observertypes.KeygenStatus_KeyGenFailed { @@ -94,7 +64,7 @@ func GenerateTss( // Try generating TSS at keygen block , only when status is pending keygen and generation has not been tried at the block if keyGen.Status == observertypes.KeygenStatus_PendingKeygen { // Return error if RPC is not working - currentBlock, err := client.GetBlockHeight(ctx) + currentBlock, err := zetaCoreClient.GetBlockHeight(ctx) if err != nil { keygenLogger.Error().Err(err).Msg("GetBlockHeight RPC error") continue @@ -115,50 +85,33 @@ func GenerateTss( } // Try keygen only once at a particular block, irrespective of whether it is successful or failure triedKeygenAtBlock = true - err = keygenTss(ctx, keyGen, tss, keygenLogger) + newPubkey, err := keygenTSS(ctx, keyGen, *keygenTssServer, zetaCoreClient, keygenLogger) if err != nil { - keygenLogger.Error().Err(err).Msg("keygenTss error") - tssFailedVoteHash, err := client.PostVoteTSS( - ctx, - "", - keyGen.BlockNumber, - chains.ReceiveStatus_failed, - ) + keygenLogger.Error().Err(err).Msg("keygenTSS error") + tssFailedVoteHash, err := zetaCoreClient.PostVoteTSS(ctx, + "", keyGen.BlockNumber, chains.ReceiveStatus_failed) if err != nil { keygenLogger.Error().Err(err).Msg("Failed to broadcast Failed TSS Vote to zetacore") - return nil, err + return err } keygenLogger.Info().Msgf("TSS Failed Vote: %s", tssFailedVoteHash) continue } - - newTss := mc.TSS{ - Server: tss.Server, - Keys: tss.Keys, - CurrentPubkey: tss.CurrentPubkey, - Signers: tss.Signers, - ZetacoreClient: nil, - } - - // If TSS is successful , broadcast the vote to zetacore and set Pubkey - tssSuccessVoteHash, err := client.PostVoteTSS( - ctx, - newTss.CurrentPubkey, + // If TSS is successful , broadcast the vote to zetacore and also set the Pubkey + tssSuccessVoteHash, err := zetaCoreClient.PostVoteTSS(ctx, + newPubkey, keyGen.BlockNumber, chains.ReceiveStatus_success, ) if err != nil { keygenLogger.Error().Err(err).Msg("TSS successful but unable to broadcast vote to zeta-core") - return nil, err + return err } keygenLogger.Info().Msgf("TSS successful Vote: %s", tssSuccessVoteHash) - err = SetTSSPubKey(tss, keygenLogger) - if err != nil { - keygenLogger.Error().Err(err).Msg("SetTSSPubKey error") - } - err = TestTSS(&newTss, keygenLogger) + + err = TestTSS(newPubkey, *keygenTssServer, keygenLogger) if err != nil { - keygenLogger.Error().Err(err).Msgf("TestTSS error: %s", newTss.CurrentPubkey) + keygenLogger.Error().Err(err).Msgf("TestTSS error: %s", newPubkey) } continue } @@ -166,31 +119,40 @@ func GenerateTss( keygenLogger.Debug(). Msgf("Waiting for TSS to be generated or Current Keygen to be be finalized. Keygen Block : %d ", keyGen.BlockNumber) } - return nil, errors.New("unexpected state for TSS generation") + return errors.New("unexpected state for TSS generation") } -func keygenTss(ctx context.Context, keyGen observertypes.Keygen, tss *mc.TSS, keygenLogger zerolog.Logger) error { +// keygenTSS generates a new TSS using the keygen request and the TSS server. +// If the keygen is successful, the function returns the new TSS pubkey. +// If the keygen is unsuccessful, the function posts blame and returns an error. +func keygenTSS( + ctx context.Context, + keyGen observertypes.Keygen, + tssServer tss.TssServer, + zetacoreClient interfaces.ZetacoreClient, + keygenLogger zerolog.Logger, +) (string, error) { keygenLogger.Info().Msgf("Keygen at blocknum %d , TSS signers %s ", keyGen.BlockNumber, keyGen.GranteePubkeys) var req keygen.Request req = keygen.NewRequest(keyGen.GranteePubkeys, keyGen.BlockNumber, "0.14.0") - res, err := tss.Server.Keygen(req) + res, err := tssServer.Keygen(req) if res.Status != tsscommon.Success || res.PubKey == "" { keygenLogger.Error().Msgf("keygen fail: reason %s blame nodes %s", res.Blame.FailReason, res.Blame.BlameNodes) // Need to broadcast keygen blame result here digest, err := digestReq(req) if err != nil { - return err + return "", err } index := fmt.Sprintf("keygen-%s-%d", digest, keyGen.BlockNumber) - zetaHash, err := tss.ZetacoreClient.PostVoteBlameData( + zetaHash, err := zetacoreClient.PostVoteBlameData( ctx, &res.Blame, - tss.ZetacoreClient.Chain().ChainId, + zetacoreClient.Chain().ChainId, index, ) if err != nil { keygenLogger.Error().Err(err).Msg("error sending blame data to core") - return err + return "", err } // Increment Blame counter @@ -199,35 +161,23 @@ func keygenTss(ctx context.Context, keyGen observertypes.Keygen, tss *mc.TSS, ke } keygenLogger.Info().Msgf("keygen posted blame data tx hash: %s", zetaHash) - return fmt.Errorf("keygen fail: reason %s blame nodes %s", res.Blame.FailReason, res.Blame.BlameNodes) + return "", fmt.Errorf("keygen fail: reason %s blame nodes %s", res.Blame.FailReason, res.Blame.BlameNodes) } if err != nil { keygenLogger.Error().Msgf("keygen fail: reason %s ", err.Error()) - return err + return "", err } - // Keeping this line here for now, but this is redundant as CurrentPubkey is updated from zeta-core - tss.CurrentPubkey = res.PubKey - tss.Signers = keyGen.GranteePubkeys - - // Keygen succeed! Report TSS address - keygenLogger.Debug().Msgf("Keygen success! keygen response: %v", res) - return nil + // Keygen succeed + keygenLogger.Info().Msgf("Keygen success! keygen response: %v", res) + return res.PubKey, nil } -func SetTSSPubKey(tss *mc.TSS, logger zerolog.Logger) error { - err := tss.InsertPubKey(tss.CurrentPubkey) - if err != nil { - logger.Error().Msgf("SetPubKey fail") - return err - } - logger.Info().Msgf("TSS address in hex: %s", tss.EVMAddress().Hex()) - return nil -} -func TestTSS(tss *mc.TSS, logger zerolog.Logger) error { +// TestTSS tests the TSS keygen by signing a sample message with the TSS key. +func TestTSS(pubkey string, tssServer tss.TssServer, logger zerolog.Logger) error { keygenLogger := logger.With().Str("module", "test-keygen").Logger() keygenLogger.Info().Msgf("KeyGen success ! Doing a Key-sign test") // KeySign can fail even if TSS keygen is successful, just logging the error here to break out of outer loop and report TSS - err := mc.TestKeysign(tss.CurrentPubkey, tss.Server) + err := mc.TestKeysign(pubkey, tssServer) if err != nil { return err } diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 4dfbeadf3e..fef035aee5 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -23,6 +23,7 @@ import ( "github.com/zeta-chain/go-tss/p2p" "github.com/zeta-chain/zetacore/pkg/authz" + "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/constant" observerTypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" @@ -30,6 +31,7 @@ import ( zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/orchestrator" + mc "github.com/zeta-chain/zetacore/zetaclient/tss" ) type Multiaddr = core.Multiaddr @@ -200,22 +202,40 @@ func start(_ *cobra.Command, _ []string) error { } telemetryServer.SetIPAddress(cfg.PublicIP) - tss, err := GenerateTss( + // Create TSS server + server, err := mc.SetupTSSServer(peers, priKey, preParams, appContext.Config(), tssKeyPass, true) + if err != nil { + return fmt.Errorf("SetupTSSServer error: %w", err) + } + // Set P2P ID for telemetry + telemetryServer.SetP2PID(server.GetLocalPeerID()) + + // Generate a new TSS if keygen is set and add it into the tss server + // If TSS has already been generated, and keygen was successful ; we use the existing TSS + err = GenerateTSS(ctx, masterLogger, zetacoreClient, server) + if err != nil { + return err + } + + bitcoinChainID := chains.BitcoinRegtest.ChainId + btcChain, _, btcEnabled := appContext.GetBTCChainAndConfig() + if btcEnabled { + bitcoinChainID = btcChain.ChainId + } + tss, err := mc.NewTSS( ctx, - masterLogger, zetacoreClient, - peers, - priKey, - telemetryServer, tssHistoricalList, - tssKeyPass, + bitcoinChainID, hotkeyPass, + server, ) if err != nil { + startLogger.Error().Err(err).Msg("NewTSS error") return err } if cfg.TestTssKeysign { - err = TestTSS(tss, masterLogger) + err = TestTSS(tss.CurrentPubkey, *tss.Server, masterLogger) if err != nil { startLogger.Error().Err(err).Msgf("TestTSS error : %s", tss.CurrentPubkey) } diff --git a/cmd/zetae2e/config/local.yml b/cmd/zetae2e/config/local.yml index ee5e171ccc..f4aa45599c 100644 --- a/cmd/zetae2e/config/local.yml +++ b/cmd/zetae2e/config/local.yml @@ -25,8 +25,10 @@ contracts: uniswap_router: "0x2ca7d64A7EFE2D62A725E2B35Cf7230D6677FfEe" connector_zevm: "0x239e96c8f17C85c30100AC26F635Ea15f23E9c67" wzeta: "0x5F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf" + test_dapp: "0xA8D5060feb6B456e886F023709A2795373691E63" evm: zeta_eth: "0x733aB8b06DDDEf27Eaa72294B0d7c9cEF7f12db9" connector_eth: "0xD28D6A0b8189305551a0A8bd247a6ECa9CE781Ca" custody: "0xff3135df4F2775f4091b81f4c7B6359CfA07862a" - erc20: "0xbD1e64A22B9F92D9Ce81aA9B4b0fFacd80215564" \ No newline at end of file + erc20: "0xbD1e64A22B9F92D9Ce81aA9B4b0fFacd80215564" + test_dapp: "0xBFF76e77D56B3C1202107f059425D56f0AEF87Ed" \ No newline at end of file diff --git a/cmd/zetae2e/config/localnet.yml b/cmd/zetae2e/config/localnet.yml index 114f1c4f32..cbd703ba6a 100644 --- a/cmd/zetae2e/config/localnet.yml +++ b/cmd/zetae2e/config/localnet.yml @@ -32,6 +32,10 @@ additional_accounts: bech32_address: "zeta17w0adeg64ky0daxwd2ugyuneellmjgnx4e483s" evm_address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" private_key: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + user_migration: + bech32_address: "zeta1pvtxa708yvdmszn687nne6nl8qn704daf420xz" + evm_address: "0x0B166ef9e7231Bb80A7A3FA73CEA7F3827E7D5BD" + private_key: "0bcc2fa28b526f90e1d54648d612db901e860bf68248555593f91ea801c6b482" policy_accounts: emergency_policy_account: bech32_address: "zeta16m2cnrdwtgweq4njc6t470vl325gw4kp6s7tap" diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 6e850447af..a5c87940aa 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -8,6 +8,7 @@ import ( "github.com/fatih/color" "github.com/spf13/cobra" + "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" zetae2econfig "github.com/zeta-chain/zetacore/cmd/zetae2e/config" @@ -19,6 +20,7 @@ import ( "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/testutil" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) const ( @@ -34,6 +36,7 @@ const ( flagLight = "light" flagSetupOnly = "setup-only" flagSkipSetup = "skip-setup" + flagTestTSSMigration = "test-tss-migration" flagSkipBitcoinSetup = "skip-bitcoin-setup" flagSkipHeaderProof = "skip-header-proof" ) @@ -66,6 +69,7 @@ func NewLocalCmd() *cobra.Command { cmd.Flags().Bool(flagSkipSetup, false, "set to true to skip setup") cmd.Flags().Bool(flagSkipBitcoinSetup, false, "set to true to skip bitcoin wallet setup") cmd.Flags().Bool(flagSkipHeaderProof, false, "set to true to skip header proof tests") + cmd.Flags().Bool(flagTestTSSMigration, false, "set to true to include a migration test at the end") return cmd } @@ -86,6 +90,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { skipSetup = must(cmd.Flags().GetBool(flagSkipSetup)) skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) + testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) ) logger := runner.NewLogger(verbose, color.FgWhite, "setup") @@ -151,7 +156,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { // wait for keygen to be completed // if setup is skipped, we assume that the keygen is already completed if !skipSetup { - waitKeygenHeight(ctx, deployerRunner.CctxClient, logger) + waitKeygenHeight(ctx, deployerRunner.CctxClient, deployerRunner.ObserverClient, logger, 10) } // query and set the TSS @@ -201,6 +206,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { // run tests var eg errgroup.Group + if !skipRegular { // defines all tests, if light is enabled, only the most basic tests are run and advanced are skipped erc20Tests := []string{ @@ -275,6 +281,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, testHeader, bitcoinTests...)) eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, testHeader, ethereumTests...)) } + if testAdmin { eg.Go(adminTestRoutine(conf, deployerRunner, verbose, e2etests.TestRateLimiterName, @@ -313,7 +320,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } // if all tests pass, cancel txs priority monitoring and check if tx priority is not correct in some blocks - logger.Print("⏳ e2e tests passed, checking tx priority") + logger.Print("⏳ e2e tests passed,checking tx priority") monitorPriorityCancel() if err := <-txPriorityErrCh; err != nil { logger.Print("❌ %v", err) @@ -323,6 +330,10 @@ func localE2ETest(cmd *cobra.Command, _ []string) { logger.Print("✅ e2e tests completed in %s", time.Since(testStartTime).String()) + if testTSSMigration { + runTSSMigrationTest(deployerRunner, logger, verbose, conf) + } + // print and validate report networkReport, err := deployerRunner.GenerateNetworkReport() if err != nil { @@ -341,10 +352,24 @@ func localE2ETest(cmd *cobra.Command, _ []string) { func waitKeygenHeight( ctx context.Context, cctxClient crosschaintypes.QueryClient, + observerClient observertypes.QueryClient, logger *runner.Logger, + bufferBlocks int64, ) { // wait for keygen to be completed - keygenHeight := int64(35) + resp, err := observerClient.Keygen(ctx, &observertypes.QueryGetKeygenRequest{}) + if err != nil { + logger.Error("observerClient.Keygen error: %s", err) + return + } + if resp.Keygen == nil { + logger.Error("observerClient.Keygen keygen is nil") + return + } + if resp.Keygen.Status != observertypes.KeygenStatus_PendingKeygen { + return + } + keygenHeight := resp.Keygen.BlockNumber logger.Print("⏳ wait height %v for keygen to be completed", keygenHeight) for { time.Sleep(2 * time.Second) @@ -353,13 +378,55 @@ func waitKeygenHeight( logger.Error("cctxClient.LastZetaHeight error: %s", err) continue } - if response.Height >= keygenHeight { + if response.Height >= keygenHeight+bufferBlocks { break } logger.Info("Last ZetaHeight: %d", response.Height) } } +func runTSSMigrationTest(deployerRunner *runner.E2ERunner, logger *runner.Logger, verbose bool, conf config.Config) { + migrationStartTime := time.Now() + logger.Print("🏁 starting tss migration") + + response, err := deployerRunner.CctxClient.LastZetaHeight( + deployerRunner.Ctx, + &crosschaintypes.QueryLastZetaHeightRequest{}, + ) + require.NoError(deployerRunner, err) + err = deployerRunner.ZetaTxServer.UpdateKeygen(response.Height) + require.NoError(deployerRunner, err) + + // Generate new TSS + waitKeygenHeight(deployerRunner.Ctx, deployerRunner.CctxClient, deployerRunner.ObserverClient, logger, 0) + + // migration test is a blocking thread, we cannot run other tests in parallel + // The migration test migrates funds to a new TSS and then updates the TSS address on zetacore. + // The necessary restarts are done by the zetaclient supervisor + fn := migrationTestRoutine(conf, deployerRunner, verbose, e2etests.TestMigrateTSSName) + + if err := fn(); err != nil { + logger.Print("❌ %v", err) + logger.Print("❌ tss migration failed") + os.Exit(1) + } + + logger.Print("✅ migration completed in %s ", time.Since(migrationStartTime).String()) + logger.Print("🏁 starting post migration tests") + + tests := []string{ + e2etests.TestBitcoinWithdrawSegWitName, + e2etests.TestEtherWithdrawName, + } + fn = postMigrationTestRoutine(conf, deployerRunner, verbose, tests...) + + if err := fn(); err != nil { + logger.Print("❌ %v", err) + logger.Print("❌ post migration tests failed") + os.Exit(1) + } +} + func must[T any](v T, err error) T { return testutil.Must(v, err) } diff --git a/cmd/zetae2e/local/migration.go b/cmd/zetae2e/local/migration.go new file mode 100644 index 0000000000..d6fab1b709 --- /dev/null +++ b/cmd/zetae2e/local/migration.go @@ -0,0 +1,63 @@ +package local + +import ( + "fmt" + "time" + + "github.com/fatih/color" + + "github.com/zeta-chain/zetacore/e2e/config" + "github.com/zeta-chain/zetacore/e2e/e2etests" + "github.com/zeta-chain/zetacore/e2e/runner" +) + +// migrationTestRoutine runs migration related e2e tests +func migrationTestRoutine( + conf config.Config, + deployerRunner *runner.E2ERunner, + verbose bool, + testNames ...string, +) func() error { + return func() (err error) { + account := conf.AdditionalAccounts.UserMigration + // initialize runner for migration test + migrationTestRunner, err := initTestRunner( + "migration", + conf, + deployerRunner, + account, + runner.NewLogger(verbose, color.FgHiGreen, "migration"), + runner.WithZetaTxServer(deployerRunner.ZetaTxServer), + ) + if err != nil { + return err + } + + migrationTestRunner.Logger.Print("🏃 starting migration tests") + startTime := time.Now() + + if len(testNames) == 0 { + migrationTestRunner.Logger.Print("🍾 migration tests completed in %s", time.Since(startTime).String()) + return nil + } + // run migration test + testsToRun, err := migrationTestRunner.GetE2ETestsToRunByName( + e2etests.AllE2ETests, + testNames..., + ) + if err != nil { + return fmt.Errorf("migration tests failed: %v", err) + } + + if err := migrationTestRunner.RunE2ETests(testsToRun); err != nil { + return fmt.Errorf("migration tests failed: %v", err) + } + if err := migrationTestRunner.CheckBtcTSSBalance(); err != nil { + return err + } + + migrationTestRunner.Logger.Print("🍾 migration tests completed in %s", time.Since(startTime).String()) + + return err + } +} diff --git a/cmd/zetae2e/local/post_migration.go b/cmd/zetae2e/local/post_migration.go new file mode 100644 index 0000000000..f339ce0bc1 --- /dev/null +++ b/cmd/zetae2e/local/post_migration.go @@ -0,0 +1,58 @@ +package local + +import ( + "time" + + "github.com/fatih/color" + "github.com/pkg/errors" + + "github.com/zeta-chain/zetacore/e2e/config" + "github.com/zeta-chain/zetacore/e2e/e2etests" + "github.com/zeta-chain/zetacore/e2e/runner" +) + +// postMigrationTestRoutine runs post migration tests +func postMigrationTestRoutine( + conf config.Config, + deployerRunner *runner.E2ERunner, + verbose bool, + testNames ...string, +) func() error { + return func() (err error) { + account := conf.AdditionalAccounts.UserBitcoin + // initialize runner for post migration test + postMigrationRunner, err := initTestRunner( + "postMigration", + conf, + deployerRunner, + account, + runner.NewLogger(verbose, color.FgMagenta, "postMigrationRunner"), + ) + if err != nil { + return err + } + + postMigrationRunner.Logger.Print("🏃 starting postMigration tests") + startTime := time.Now() + + testsToRun, err := postMigrationRunner.GetE2ETestsToRunByName( + e2etests.AllE2ETests, + testNames..., + ) + if err != nil { + return errors.Wrap(err, "postMigrationRunner tests failed") + } + + if err := postMigrationRunner.RunE2ETests(testsToRun); err != nil { + return errors.Wrap(err, "postMigrationRunner tests failed") + } + + if err := postMigrationRunner.CheckBtcTSSBalance(); err != nil { + return err + } + + postMigrationRunner.Logger.Print("🍾 PostMigration tests completed in %s", time.Since(startTime).String()) + + return err + } +} diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index 676d55e26e..b51c1e6344 100644 --- a/contrib/localnet/orchestrator/start-zetae2e.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -77,6 +77,11 @@ address=$(yq -r '.additional_accounts.user_admin.evm_address' config.yml) echo "funding admin tester address ${address} with 10000 Ether" geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +# unlock migration tests accounts +address=$(yq -r '.additional_accounts.user_migration.evm_address' config.yml) +echo "funding migration tester address ${address} with 10000 Ether" +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 + ### Run zetae2e command depending on the option passed if [ "$LOCALNET_MODE" == "upgrade" ]; then @@ -176,7 +181,7 @@ else exit 0 fi - echo "running e2e tests..." + echo "running e2e tests with arguments: $E2E_ARGS" zetae2e local $E2E_ARGS --skip-setup --config deployed.yml ZETAE2E_EXIT_CODE=$? diff --git a/contrib/localnet/scripts/start-zetacored.sh b/contrib/localnet/scripts/start-zetacored.sh index c3e53c6c93..fca8e25152 100755 --- a/contrib/localnet/scripts/start-zetacored.sh +++ b/contrib/localnet/scripts/start-zetacored.sh @@ -259,6 +259,9 @@ then # operational policy account address=$(yq -r '.policy_accounts.operational_policy_account.bech32_address' /root/config.yml) zetacored add-genesis-account "$address" 100000000000000000000000000azeta +# migration tester + address=$(yq -r '.additional_accounts.user_migration.bech32_address' /root/config.yml) + zetacored add-genesis-account "$address" 100000000000000000000000000azeta # 3. Copy the genesis.json to all the nodes .And use it to create a gentx for every node zetacored gentx operator 1000000000000000000000azeta --chain-id=$CHAINID --keyring-backend=$KEYRING --gas-prices 20000000000azeta diff --git a/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml index 4bbe9b3942..8d16822b27 100644 --- a/docs/openapi/openapi.swagger.yaml +++ b/docs/openapi/openapi.swagger.yaml @@ -57297,7 +57297,7 @@ definitions: type: array items: type: object - $ref: '#/definitions/crosschainTxHashList' + $ref: '#/definitions/crosschainTxHash' crosschainQueryAllCctxResponse: type: object properties: @@ -57531,7 +57531,7 @@ definitions: - NotFinalized: the corresponding tx is not finalized - Finalized: the corresponding tx is finalized but not executed yet - Executed: the corresponding tx is executed - crosschainTxHashList: + crosschainTxHash: type: object properties: tx_hash: diff --git a/docs/spec/fungible/messages.md b/docs/spec/fungible/messages.md index 93c7864aef..ce845608e6 100644 --- a/docs/spec/fungible/messages.md +++ b/docs/spec/fungible/messages.md @@ -56,7 +56,7 @@ Authorized: admin policy group 2. ```proto message MsgRemoveForeignCoin { string creator = 1; - string name = 2; + string zrc20_address = 2; } ``` diff --git a/docs/zetaclient/migration_v12.2->v12.3.md b/docs/zetaclient/migration_v12.2-v12.3.md similarity index 100% rename from docs/zetaclient/migration_v12.2->v12.3.md rename to docs/zetaclient/migration_v12.2-v12.3.md diff --git a/e2e/config/config.go b/e2e/config/config.go index 6cde9bac26..6132dccbbd 100644 --- a/e2e/config/config.go +++ b/e2e/config/config.go @@ -65,6 +65,7 @@ type AdditionalAccounts struct { UserEther Account `yaml:"user_ether"` UserMisc Account `yaml:"user_misc"` UserAdmin Account `yaml:"user_admin"` + UserMigration Account `yaml:"user_migration"` } type PolicyAccounts struct { @@ -198,6 +199,7 @@ func (a AdditionalAccounts) AsSlice() []Account { a.UserEther, a.UserMisc, a.UserAdmin, + a.UserMigration, } } @@ -282,6 +284,11 @@ func (c *Config) GenerateKeys() error { if err != nil { return err } + c.AdditionalAccounts.UserMigration, err = generateAccount() + if err != nil { + return err + } + c.PolicyAccounts.EmergencyPolicyAccount, err = generateAccount() if err != nil { return err diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index d7954b3d29..3859d09e5b 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -102,6 +102,8 @@ const ( TestRateLimiterName = "rate_limiter" TestAdminTransactionsName = "admin_transactions" + TestMigrateTSSName = "migrate_TSS" + /* Special tests Not used to test functionalities but do various interactions with the netwoks @@ -549,4 +551,10 @@ var AllE2ETests = []runner.E2ETest{ }, TestDeployContract, ), + runner.NewE2ETest( + TestMigrateTSSName, + "migrate TSS funds", + []runner.ArgDefinition{}, + TestMigrateTSS, + ), } diff --git a/e2e/e2etests/test_message_passing_evm_to_zevm_revert.go b/e2e/e2etests/test_message_passing_evm_to_zevm_revert.go index 99a49fba56..85aca0caae 100644 --- a/e2e/e2etests/test_message_passing_evm_to_zevm_revert.go +++ b/e2e/e2etests/test_message_passing_evm_to_zevm_revert.go @@ -13,9 +13,15 @@ import ( cctxtypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) +// fungibleModuleAddress is a constant representing the EVM address of the Fungible module account +const fungibleModuleAddress = "0x735b14BB79463307AAcBED86DAf3322B1e6226aB" + func TestMessagePassingEVMtoZEVMRevert(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) + fungibleEthAddress := ethcommon.HexToAddress(fungibleModuleAddress) + require.True(r, fungibleEthAddress != ethcommon.Address{}, "invalid fungible module address") + amount, ok := big.NewInt(0).SetString(args[0], 10) require.True(r, ok) @@ -46,6 +52,9 @@ func TestMessagePassingEVMtoZEVMRevert(r *runner.E2ERunner, args []string) { previousBalanceEVM, err := r.ZetaEth.BalanceOf(&bind.CallOpts{}, r.EvmTestDAppAddr) require.NoError(r, err) + previousFungibleBalance, err := r.WZeta.BalanceOf(&bind.CallOpts{}, fungibleEthAddress) + require.NoError(r, err) + // Call the SendHelloWorld function on the EVM dapp Contract which would in turn create a new send, to be picked up by the zeta-clients // set Do revert to true which adds a message to signal the ZEVM zetaReceiver to revert the transaction tx, err = testDAppEVM.SendHelloWorld(r.EVMAuth, destinationAddress, zEVMChainID, amount, true) @@ -105,4 +114,17 @@ func TestMessagePassingEVMtoZEVMRevert(r *runner.E2ERunner, args []string) { previousBalanceAndAmountEVM.String(), newBalanceEVM.String(), ) + + // Check ZETA balance on Fungible Module and check new balance is previous balance + newFungibleBalance, err := r.WZeta.BalanceOf(&bind.CallOpts{}, fungibleEthAddress) + require.NoError(r, err) + + require.Equal( + r, + 0, + newFungibleBalance.Cmp(previousFungibleBalance), + "expected new balance to be %s, got %s", + previousFungibleBalance.String(), + newFungibleBalance.String(), + ) } diff --git a/e2e/e2etests/test_migrate_tss.go b/e2e/e2etests/test_migrate_tss.go new file mode 100644 index 0000000000..c72b876f0f --- /dev/null +++ b/e2e/e2etests/test_migrate_tss.go @@ -0,0 +1,182 @@ +package e2etests + +import ( + "context" + "fmt" + "sort" + "strconv" + "time" + + sdkmath "cosmossdk.io/math" + "github.com/btcsuite/btcutil" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/pkg/chains" + zetacrypto "github.com/zeta-chain/zetacore/pkg/crypto" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestMigrateTSS(r *runner.E2ERunner, _ []string) { + r.SetBtcAddress(r.Name, false) + stop := r.MineBlocksIfLocalBitcoin() + defer stop() + + // Pause inbound procoessing for tss migration + r.Logger.Info("Pause inbound processing") + msg := observertypes.NewMsgDisableCCTX( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.EmergencyPolicyName), + false, + true) + _, err := r.ZetaTxServer.BroadcastTx(utils.EmergencyPolicyName, msg) + require.NoError(r, err) + + // Migrate btc + // Fetch balance of BTC TSS address + utxos, err := r.GetTop20UTXOsForTssAddress() + require.NoError(r, err) + + var btcBalance float64 + for _, utxo := range utxos { + btcBalance += utxo.Amount + } + + btcTSSBalanceOld := btcBalance + // Use fixed fee of 0.01 for migration + btcBalance = btcBalance - 0.01 + btcChain := chains.BitcoinRegtest.ChainId + + //migrate btc funds + // #nosec G701 e2eTest - always in range + migrationAmountBTC := sdkmath.NewUint(uint64(btcBalance * 1e8)) + msgMigrateFunds := crosschaintypes.NewMsgMigrateTssFunds( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + btcChain, + migrationAmountBTC, + ) + _, err = r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msgMigrateFunds) + require.NoError(r, err) + + // Fetch migrator cctx for btc migration + migrator, err := r.ObserverClient.TssFundsMigratorInfo(r.Ctx, &observertypes.QueryTssFundsMigratorInfoRequest{ + ChainId: btcChain}) + require.NoError(r, err) + cctxBTCMigration := migrator.TssFundsMigrator.MigrationCctxIndex + + // ETH migration + // Fetch balance of ETH TSS address + tssBalance, err := r.EVMClient.BalanceAt(context.Background(), r.TSSAddress, nil) + require.NoError(r, err) + ethTSSBalanceOld := tssBalance + + tssBalanceUint := sdkmath.NewUintFromString(tssBalance.String()) + evmChainID, err := r.EVMClient.ChainID(r.Ctx) + require.NoError(r, err) + + // Migrate TSS funds for the eth chain + msgMigrateFunds = crosschaintypes.NewMsgMigrateTssFunds( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + evmChainID.Int64(), + tssBalanceUint, + ) + _, err = r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msgMigrateFunds) + require.NoError(r, err) + + // Fetch migrator cctx for eth migration + migrator, err = r.ObserverClient.TssFundsMigratorInfo( + r.Ctx, + &observertypes.QueryTssFundsMigratorInfoRequest{ChainId: evmChainID.Int64()}, + ) + require.NoError(r, err) + cctxETHMigration := migrator.TssFundsMigrator.MigrationCctxIndex + + cctxBTC := utils.WaitCCTXMinedByIndex(r.Ctx, cctxBTCMigration, r.CctxClient, r.Logger, r.CctxTimeout) + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctxBTC.CctxStatus.Status) + + cctxETH := utils.WaitCCTXMinedByIndex(r.Ctx, cctxETHMigration, r.CctxClient, r.Logger, r.CctxTimeout) + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctxETH.CctxStatus.Status) + + // Check if new TSS is added to list + allTss, err := r.ObserverClient.TssHistory(r.Ctx, &observertypes.QueryTssHistoryRequest{}) + require.NoError(r, err) + + require.Len(r, allTss.TssList, 2) + + // Update TSS to new address + sort.Slice(allTss.TssList, func(i, j int) bool { + return allTss.TssList[i].FinalizedZetaHeight < allTss.TssList[j].FinalizedZetaHeight + }) + msgUpdateTss := crosschaintypes.NewMsgUpdateTssAddress( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + allTss.TssList[1].TssPubkey, + ) + _, err = r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msgUpdateTss) + require.NoError(r, err) + + // Wait for atleast one block for the TSS to be updated + time.Sleep(8 * time.Second) + + currentTss, err := r.ObserverClient.TSS(r.Ctx, &observertypes.QueryGetTSSRequest{}) + require.NoError(r, err) + require.Equal(r, allTss.TssList[1].TssPubkey, currentTss.TSS.TssPubkey) + + newTss, err := r.ObserverClient.GetTssAddress(r.Ctx, &observertypes.QueryGetTssAddressRequest{}) + require.NoError(r, err) + + // Check balance of new TSS address to make sure all funds have been transferred + // BTC + btcTssAddress, err := zetacrypto.GetTssAddrBTC(currentTss.TSS.TssPubkey, r.BitcoinParams) + require.NoError(r, err) + + btcTssAddressNew, err := btcutil.DecodeAddress(btcTssAddress, r.BitcoinParams) + require.NoError(r, err) + + r.BTCTSSAddress = btcTssAddressNew + r.AddTSSToNode() + + utxos, err = r.GetTop20UTXOsForTssAddress() + require.NoError(r, err) + + var btcTSSBalanceNew float64 + // #nosec G701 e2eTest - always in range + for _, utxo := range utxos { + btcTSSBalanceNew += utxo.Amount + } + + r.Logger.Info(fmt.Sprintf("BTC Balance Old: %f", btcTSSBalanceOld*1e8)) + r.Logger.Info(fmt.Sprintf("BTC Balance New: %f", btcTSSBalanceNew*1e8)) + r.Logger.Info(fmt.Sprintf("Migrator amount : %s", cctxBTC.GetCurrentOutboundParam().Amount)) + + // btcTSSBalanceNew should be less than btcTSSBalanceOld as there is some loss of funds during migration + // #nosec G701 e2eTest - always in range + require.Equal( + r, + strconv.FormatInt(int64(btcTSSBalanceNew*1e8), 10), + cctxBTC.GetCurrentOutboundParam().Amount.String(), + ) + require.LessOrEqual(r, btcTSSBalanceNew*1e8, btcTSSBalanceOld*1e8) + + // ETH + + r.TSSAddress = common.HexToAddress(newTss.Eth) + ethTSSBalanceNew, err := r.EVMClient.BalanceAt(context.Background(), r.TSSAddress, nil) + require.NoError(r, err) + + r.Logger.Info(fmt.Sprintf("TSS Balance Old: %s", ethTSSBalanceOld.String())) + r.Logger.Info(fmt.Sprintf("TSS Balance New: %s", ethTSSBalanceNew.String())) + r.Logger.Info(fmt.Sprintf("Migrator amount : %s", cctxETH.GetCurrentOutboundParam().Amount.String())) + + // ethTSSBalanceNew should be less than ethTSSBalanceOld as there is some loss of funds during migration + require.Equal(r, ethTSSBalanceNew.String(), cctxETH.GetCurrentOutboundParam().Amount.String()) + require.True(r, ethTSSBalanceNew.Cmp(ethTSSBalanceOld) < 0) + + msgEnable := observertypes.NewMsgEnableCCTX( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.OperationalPolicyName), + true, + true) + _, err = r.ZetaTxServer.BroadcastTx(utils.OperationalPolicyName, msgEnable) + require.NoError(r, err) +} diff --git a/e2e/runner/accounting.go b/e2e/runner/accounting.go index d5a7e3a9e2..862b925974 100644 --- a/e2e/runner/accounting.go +++ b/e2e/runner/accounting.go @@ -8,6 +8,10 @@ import ( "net/http" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + zetacrypto "github.com/zeta-chain/zetacore/pkg/crypto" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) type Amount struct { @@ -31,30 +35,63 @@ func (r *E2ERunner) CheckZRC20ReserveAndSupply() error { } func (r *E2ERunner) checkEthTSSBalance() error { - tssBal, err := r.EVMClient.BalanceAt(r.Ctx, r.TSSAddress, nil) + allTssAddress, err := r.ObserverClient.TssHistory(r.Ctx, &observertypes.QueryTssHistoryRequest{}) if err != nil { return err } + + tssTotalBalance := big.NewInt(0) + + for _, tssAddress := range allTssAddress.TssList { + evmAddress, err := r.ObserverClient.GetTssAddressByFinalizedHeight( + r.Ctx, + &observertypes.QueryGetTssAddressByFinalizedHeightRequest{ + FinalizedZetaHeight: tssAddress.FinalizedZetaHeight, + }, + ) + if err != nil { + continue + } + + tssBal, err := r.EVMClient.BalanceAt(r.Ctx, common.HexToAddress(evmAddress.Eth), nil) + if err != nil { + continue + } + tssTotalBalance.Add(tssTotalBalance, tssBal) + } + zrc20Supply, err := r.ETHZRC20.TotalSupply(&bind.CallOpts{}) if err != nil { return err } - if tssBal.Cmp(zrc20Supply) < 0 { - return fmt.Errorf("ETH: TSS balance (%d) < ZRC20 TotalSupply (%d) ", tssBal, zrc20Supply) + if tssTotalBalance.Cmp(zrc20Supply) < 0 { + return fmt.Errorf("ETH: TSS balance (%d) < ZRC20 TotalSupply (%d) ", tssTotalBalance, zrc20Supply) } - r.Logger.Info("ETH: TSS balance (%d) >= ZRC20 TotalSupply (%d)", tssBal, zrc20Supply) + r.Logger.Info("ETH: TSS balance (%d) >= ZRC20 TotalSupply (%d)", tssTotalBalance, zrc20Supply) return nil } func (r *E2ERunner) CheckBtcTSSBalance() error { - utxos, err := r.BtcRPCClient.ListUnspent() + allTssAddress, err := r.ObserverClient.TssHistory(r.Ctx, &observertypes.QueryTssHistoryRequest{}) if err != nil { return err } - var btcBalance float64 - for _, utxo := range utxos { - if utxo.Address == r.BTCTSSAddress.EncodeAddress() { - btcBalance += utxo.Amount + + tssTotalBalance := float64(0) + + for _, tssAddress := range allTssAddress.TssList { + btcTssAddress, err := zetacrypto.GetTssAddrBTC(tssAddress.TssPubkey, r.BitcoinParams) + if err != nil { + continue + } + utxos, err := r.BtcRPCClient.ListUnspent() + if err != nil { + continue + } + for _, utxo := range utxos { + if utxo.Address == btcTssAddress { + tssTotalBalance += utxo.Amount + } } } @@ -65,19 +102,19 @@ func (r *E2ERunner) CheckBtcTSSBalance() error { // check the balance in TSS is greater than the total supply on ZetaChain // the amount minted to initialize the pool is subtracted from the total supply - // #nosec G115 test - always in range - if int64(btcBalance*1e8) < (zrc20Supply.Int64() - 10000000) { - // #nosec G115 test - always in range + // #nosec G701 test - always in range + if int64(tssTotalBalance*1e8) < (zrc20Supply.Int64() - 10000000) { + // #nosec G701 test - always in range return fmt.Errorf( "BTC: TSS Balance (%d) < ZRC20 TotalSupply (%d)", - int64(btcBalance*1e8), + int64(tssTotalBalance*1e8), zrc20Supply.Int64()-10000000, ) } // #nosec G115 test - always in range r.Logger.Info( "BTC: Balance (%d) >= ZRC20 TotalSupply (%d)", - int64(btcBalance*1e8), + int64(tssTotalBalance*1e8), zrc20Supply.Int64()-10000000, ) diff --git a/e2e/runner/bitcoin.go b/e2e/runner/bitcoin.go index 3165d745ca..868c344766 100644 --- a/e2e/runner/bitcoin.go +++ b/e2e/runner/bitcoin.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "fmt" + "sort" "time" "github.com/btcsuite/btcd/btcjson" @@ -24,6 +25,7 @@ import ( lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" zetabitcoin "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" btcobserver "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" + "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/signer" ) var blockHeaderBTCTimeout = 5 * time.Minute @@ -54,6 +56,29 @@ func (r *E2ERunner) ListDeployerUTXOs() ([]btcjson.ListUnspentResult, error) { return utxos, nil } +// GetTop20UTXOsForTssAddress returns the top 20 UTXOs for the TSS address. +// Top 20 utxos should be used for TSS migration, as we can only migrate at max 20 utxos at a time. +func (r *E2ERunner) GetTop20UTXOsForTssAddress() ([]btcjson.ListUnspentResult, error) { + // query UTXOs from node + utxos, err := r.BtcRPCClient.ListUnspentMinMaxAddresses( + 0, + 9999999, + []btcutil.Address{r.BTCTSSAddress}, + ) + if err != nil { + return nil, err + } + + sort.SliceStable(utxos, func(i, j int) bool { + return utxos[i].Amount < utxos[j].Amount + }) + + if len(utxos) > signer.MaxNoOfInputsPerTx { + utxos = utxos[:signer.MaxNoOfInputsPerTx] + } + return utxos, nil +} + // DepositBTCWithAmount deposits BTC on ZetaChain with a specific amount func (r *E2ERunner) DepositBTCWithAmount(amount float64) *chainhash.Hash { r.Logger.Print("⏳ depositing BTC into ZEVM") @@ -185,7 +210,7 @@ func (r *E2ERunner) SendToTSSFromDeployerWithMemo( scriptPubkeys[i] = utxo.ScriptPubKey } - feeSats := btcutil.Amount(0.0001 * btcutil.SatoshiPerBitcoin) + feeSats := btcutil.Amount(0.0005 * btcutil.SatoshiPerBitcoin) amountSats := btcutil.Amount(amount * btcutil.SatoshiPerBitcoin) change := inputSats - feeSats - amountSats diff --git a/e2e/runner/setup_bitcoin.go b/e2e/runner/setup_bitcoin.go index 15b6d4a11d..0a0e2c0e09 100644 --- a/e2e/runner/setup_bitcoin.go +++ b/e2e/runner/setup_bitcoin.go @@ -10,6 +10,22 @@ import ( "github.com/stretchr/testify/require" ) +func (r *E2ERunner) AddTSSToNode() { + r.Logger.Print("⚙️ add new tss to Bitcoin node") + startTime := time.Now() + defer func() { + r.Logger.Print("✅ Bitcoin account setup in %s\n", time.Since(startTime)) + }() + + // import the TSS address + err := r.BtcRPCClient.ImportAddress(r.BTCTSSAddress.EncodeAddress()) + require.NoError(r, err) + + // mine some blocks to get some BTC into the deployer address + _, err = r.GenerateToAddressIfLocalBitcoin(101, r.BTCDeployerAddress) + require.NoError(r, err) +} + func (r *E2ERunner) SetupBitcoinAccount(initNetwork bool) { r.Logger.Print("⚙️ setting up Bitcoin account") startTime := time.Now() diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index 9b93aee293..0af460607f 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -438,6 +438,16 @@ func (zts ZetaTxServer) FundEmissionsPool(account string, amount *big.Int) error return err } +// UpdateKeygen sets a new keygen height . The new height is the current height + 30 +func (zts ZetaTxServer) UpdateKeygen(height int64) error { + keygenHeight := height + 30 + _, err := zts.BroadcastTx(zts.GetAccountName(0), observertypes.NewMsgUpdateKeygen( + zts.GetAccountAddress(0), + keygenHeight, + )) + return err +} + // newCodec returns the codec for msg server func newCodec() (*codec.ProtoCodec, codectypes.InterfaceRegistry) { encodingConfig := app.MakeEncodingConfig() diff --git a/e2e/utils/require.go b/e2e/utils/require.go index ffedb62e59..7471bf9b4e 100644 --- a/e2e/utils/require.go +++ b/e2e/utils/require.go @@ -16,7 +16,7 @@ func RequireCCTXStatus( expected crosschaintypes.CctxStatus, msgAndArgs ...any, ) { - msg := fmt.Sprintf("cctx status is not %q", expected.String()) + msg := fmt.Sprintf("cctx status is not %q cctx index %s", expected.String(), cctx.Index) require.NotNil(t, cctx.CctxStatus) require.Equal(t, expected, cctx.CctxStatus.Status, msg+errSuffix(msgAndArgs...)) diff --git a/pkg/gas/gas_limits.go b/pkg/gas/gas_limits.go index d4a3938adb..b585918b10 100644 --- a/pkg/gas/gas_limits.go +++ b/pkg/gas/gas_limits.go @@ -7,7 +7,8 @@ import ( const ( // EVMSend is the gas limit required to transfer tokens on an EVM based chain - EVMSend = 21000 + EVMSend = 21_000 + // TODO: Move gas limits from zeta-client to this file // https://github.com/zeta-chain/node/issues/1606 ) diff --git a/proto/zetachain/zetacore/crosschain/outbound_tracker.proto b/proto/zetachain/zetacore/crosschain/outbound_tracker.proto index 4547703444..2599a0c7da 100644 --- a/proto/zetachain/zetacore/crosschain/outbound_tracker.proto +++ b/proto/zetachain/zetacore/crosschain/outbound_tracker.proto @@ -3,7 +3,7 @@ package zetachain.zetacore.crosschain; option go_package = "github.com/zeta-chain/zetacore/x/crosschain/types"; -message TxHashList { +message TxHash { string tx_hash = 1; string tx_signer = 2; bool proved = 3; @@ -12,5 +12,5 @@ message OutboundTracker { string index = 1; // format: "chain-nonce" int64 chain_id = 2; uint64 nonce = 3; - repeated TxHashList hash_list = 4; + repeated TxHash hash_list = 4; } diff --git a/proto/zetachain/zetacore/fungible/tx.proto b/proto/zetachain/zetacore/fungible/tx.proto index bdb9ee8939..a73dcc2350 100644 --- a/proto/zetachain/zetacore/fungible/tx.proto +++ b/proto/zetachain/zetacore/fungible/tx.proto @@ -73,7 +73,7 @@ message MsgDeployFungibleCoinZRC20Response { string address = 1; } message MsgRemoveForeignCoin { string creator = 1; - string name = 2; + string zrc20_address = 2; } message MsgRemoveForeignCoinResponse {} diff --git a/readme.md b/readme.md index 3bc4c33d1a..3f00b4b7f5 100644 --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ smart contracts and messaging between any blockchain. ## Prerequisites -- [Go](https://golang.org/doc/install) 1.20 +- [Go](https://golang.org/doc/install) 1.22 - [Docker](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) (optional, for running tests locally) diff --git a/rpc/backend/blocks_test.go b/rpc/backend/blocks_test.go index bec50ff5a5..ef8023e448 100644 --- a/rpc/backend/blocks_test.go +++ b/rpc/backend/blocks_test.go @@ -1716,7 +1716,7 @@ func (suite *BackendTestSuite) TestEthAndSyntheticGetBlockByNumber() { suite.Require().Equal("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7", resSyntheticTx.To.Hex()) suite.Require().Equal("0x58", resSyntheticTx.Type.String()) suite.Require().Equal("0x1", resSyntheticTx.Nonce.String()) - suite.Require().Nil(resSyntheticTx.V) - suite.Require().Nil(resSyntheticTx.R) - suite.Require().Nil(resSyntheticTx.S) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), resSyntheticTx.V) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), resSyntheticTx.R) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), resSyntheticTx.S) } diff --git a/rpc/backend/tx_info_test.go b/rpc/backend/tx_info_test.go index 0ceb0160a6..f564494f19 100644 --- a/rpc/backend/tx_info_test.go +++ b/rpc/backend/tx_info_test.go @@ -59,9 +59,9 @@ func (suite *BackendTestSuite) TestGetSyntheticTransactionByHash() { gas, _ := hexutil.DecodeUint64(res.Gas.String()) suite.Require().Equal(uint64(21000), gas) suite.Require().Equal("0x1234", res.Input.String()) - suite.Require().Nil(res.V) - suite.Require().Nil(res.R) - suite.Require().Nil(res.S) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), res.V) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), res.R) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), res.S) } func (suite *BackendTestSuite) TestGetSyntheticTransactionReceiptByHash() { @@ -127,9 +127,9 @@ func (suite *BackendTestSuite) TestGetSyntheticTransactionByBlockNumberAndIndex( suite.Require().Equal(uint64(88), txType) suite.Require().Equal(int64(7001), res.ChainID.ToInt().Int64()) suite.Require().Equal(int64(1000), res.Value.ToInt().Int64()) - suite.Require().Nil(res.V) - suite.Require().Nil(res.R) - suite.Require().Nil(res.S) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), res.V) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), res.R) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), res.S) } func (suite *BackendTestSuite) TestGetSyntheticTransactionByBlockNumberAndIndexWithRealTransaction() { @@ -160,9 +160,9 @@ func (suite *BackendTestSuite) TestGetSyntheticTransactionByBlockNumberAndIndexW suite.Require().Equal(uint64(88), txType) suite.Require().Equal(int64(7001), res.ChainID.ToInt().Int64()) suite.Require().Equal(int64(1000), res.Value.ToInt().Int64()) - suite.Require().Nil(res.V) - suite.Require().Nil(res.R) - suite.Require().Nil(res.S) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), res.V) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), res.R) + suite.Require().Equal((*hexutil.Big)(big.NewInt(0)), res.S) } func (suite *BackendTestSuite) TestGetTransactionByHash() { diff --git a/rpc/types/utils.go b/rpc/types/utils.go index cd00d55746..5e93d97ae8 100644 --- a/rpc/types/utils.go +++ b/rpc/types/utils.go @@ -257,9 +257,9 @@ func NewRPCTransactionFromIncompleteMsg( Nonce: hexutil.Uint64(txAdditional.Nonce), // TODO: get nonce for "from" from ethermint To: to, Value: (*hexutil.Big)(txAdditional.Value), - V: nil, - R: nil, - S: nil, + V: (*hexutil.Big)(big.NewInt(0)), + R: (*hexutil.Big)(big.NewInt(0)), + S: (*hexutil.Big)(big.NewInt(0)), ChainID: (*hexutil.Big)(chainID), } if blockHash != (common.Hash{}) { diff --git a/typescript/zetachain/zetacore/crosschain/outbound_tracker_pb.d.ts b/typescript/zetachain/zetacore/crosschain/outbound_tracker_pb.d.ts index 58b98223c9..ddf1798fc8 100644 --- a/typescript/zetachain/zetacore/crosschain/outbound_tracker_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/outbound_tracker_pb.d.ts @@ -7,9 +7,9 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialM import { Message, proto3 } from "@bufbuild/protobuf"; /** - * @generated from message zetachain.zetacore.crosschain.TxHashList + * @generated from message zetachain.zetacore.crosschain.TxHash */ -export declare class TxHashList extends Message { +export declare class TxHash extends Message { /** * @generated from field: string tx_hash = 1; */ @@ -25,19 +25,19 @@ export declare class TxHashList extends Message { */ proved: boolean; - constructor(data?: PartialMessage); + constructor(data?: PartialMessage); static readonly runtime: typeof proto3; - static readonly typeName = "zetachain.zetacore.crosschain.TxHashList"; + static readonly typeName = "zetachain.zetacore.crosschain.TxHash"; static readonly fields: FieldList; - static fromBinary(bytes: Uint8Array, options?: Partial): TxHashList; + static fromBinary(bytes: Uint8Array, options?: Partial): TxHash; - static fromJson(jsonValue: JsonValue, options?: Partial): TxHashList; + static fromJson(jsonValue: JsonValue, options?: Partial): TxHash; - static fromJsonString(jsonString: string, options?: Partial): TxHashList; + static fromJsonString(jsonString: string, options?: Partial): TxHash; - static equals(a: TxHashList | PlainMessage | undefined, b: TxHashList | PlainMessage | undefined): boolean; + static equals(a: TxHash | PlainMessage | undefined, b: TxHash | PlainMessage | undefined): boolean; } /** @@ -62,9 +62,9 @@ export declare class OutboundTracker extends Message { nonce: bigint; /** - * @generated from field: repeated zetachain.zetacore.crosschain.TxHashList hash_list = 4; + * @generated from field: repeated zetachain.zetacore.crosschain.TxHash hash_list = 4; */ - hashList: TxHashList[]; + hashList: TxHash[]; constructor(data?: PartialMessage); diff --git a/typescript/zetachain/zetacore/fungible/tx_pb.d.ts b/typescript/zetachain/zetacore/fungible/tx_pb.d.ts index a1038d4bfd..131f438020 100644 --- a/typescript/zetachain/zetacore/fungible/tx_pb.d.ts +++ b/typescript/zetachain/zetacore/fungible/tx_pb.d.ts @@ -276,9 +276,9 @@ export declare class MsgRemoveForeignCoin extends Message creator: string; /** - * @generated from field: string name = 2; + * @generated from field: string zrc20_address = 2; */ - name: string; + zrc20Address: string; constructor(data?: PartialMessage); diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index 3a6f9a8135..d9061be54a 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -33,7 +33,7 @@ func (c CCTXGatewayZEVM) InitiateOutbound( } newCCTXStatus = c.crosschainKeeper.ValidateOutboundZEVM(ctx, config.CCTX, err, isContractReverted) - if newCCTXStatus == types.CctxStatus_OutboundMined { + if newCCTXStatus == types.CctxStatus_OutboundMined || newCCTXStatus == types.CctxStatus_PendingRevert { commit() } diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go index 4bb90ec915..4cda69a5c3 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound.go @@ -3,6 +3,8 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/crypto" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -19,6 +21,11 @@ func (k Keeper) ValidateInbound( return nil, types.ErrCannotFindTSSKeys } + err := k.CheckIfTSSMigrationTransfer(ctx, msg) + if err != nil { + return nil, err + } + // Do not process if inbound is disabled if !k.zetaObserverKeeper.IsInboundEnabled(ctx) { return nil, observertypes.ErrInboundDisabled @@ -48,3 +55,49 @@ func (k Keeper) ValidateInbound( return &cctx, nil } + +// CheckIfTSSMigrationTransfer checks if the sender is a TSS address and returns an error if it is. +// If the sender is an older TSS address, this means that it is a migration transfer, and we do not need to treat this as a deposit and process the CCTX +func (k Keeper) CheckIfTSSMigrationTransfer(ctx sdk.Context, msg *types.MsgVoteInbound) error { + additionalChains := k.GetAuthorityKeeper().GetAdditionalChainList(ctx) + + historicalTSSList := k.zetaObserverKeeper.GetAllTSS(ctx) + chain, found := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.SenderChainId) + if !found { + return observertypes.ErrSupportedChains.Wrapf("chain not found for chainID %d", msg.SenderChainId) + } + + // the check is only necessary if the inbound is validated from observers from a connected chain + if chain.CctxGateway != chains.CCTXGateway_observers { + return nil + } + + switch { + case chains.IsEVMChain(chain.ChainId, additionalChains): + for _, tss := range historicalTSSList { + ethTssAddress, err := crypto.GetTssAddrEVM(tss.TssPubkey) + if err != nil { + continue + } + if ethTssAddress.Hex() == msg.Sender { + return types.ErrMigrationFromOldTss + } + } + case chains.IsBitcoinChain(chain.ChainId, additionalChains): + bitcoinParams, err := chains.BitcoinNetParamsFromChainID(chain.ChainId) + if err != nil { + return err + } + for _, tss := range historicalTSSList { + btcTssAddress, err := crypto.GetTssAddrBTC(tss.TssPubkey, bitcoinParams) + if err != nil { + continue + } + if btcTssAddress == msg.Sender { + return types.ErrMigrationFromOldTss + } + } + } + + return nil +} diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_inbound_test.go b/x/crosschain/keeper/cctx_orchestrator_validate_inbound_test.go new file mode 100644 index 0000000000..0f09c61025 --- /dev/null +++ b/x/crosschain/keeper/cctx_orchestrator_validate_inbound_test.go @@ -0,0 +1,668 @@ +package keeper_test + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/crypto" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" + observerTypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestKeeper_ValidateInbound(t *testing.T) { + t.Run("successfully validate inbound", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseObserverMock: true, + UseFungibleMock: true, + UseAuthorityMock: true, + }) + + // Setup mock data + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + receiver := sample.EthAddress() + creator := sample.AccAddress() + amount := sdkmath.NewUint(42) + message := "test" + inboundBlockHeight := uint64(420) + inboundHash := sample.Hash() + gasLimit := uint64(100) + asset := "test-asset" + eventIndex := uint64(1) + cointType := coin.CoinType_ERC20 + tss := sample.Tss() + receiverChain := chains.Goerli + senderChain := chains.Goerli + sender := sample.EthAddress() + tssList := sample.TssList(3) + + // Set up mocks for CheckIfTSSMigrationTransfer + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId).Return(senderChain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + // setup Mocks for GetTSS + observerMock.On("GetTSS", mock.Anything).Return(tss, true) + // setup Mocks for IsInboundEnabled + observerMock.On("IsInboundEnabled", ctx).Return(true) + // setup mocks for Initiate Outbound + observerMock.On("GetChainNonces", mock.Anything, mock.Anything). + Return(observerTypes.ChainNonces{Nonce: 1}, true) + observerMock.On("GetPendingNonces", mock.Anything, mock.Anything, mock.Anything). + Return(observerTypes.PendingNonces{NonceHigh: 1}, true) + observerMock.On("SetChainNonces", mock.Anything, mock.Anything).Return(nil) + observerMock.On("SetPendingNonces", mock.Anything, mock.Anything).Return(nil) + // setup Mocks for SetCctxAndNonceToCctxAndInboundHashToCctx + observerMock.On("SetNonceToCctx", mock.Anything, mock.Anything).Return(nil) + + k.SetGasPrice(ctx, types.GasPrice{ + ChainId: senderChain.ChainId, + MedianIndex: 0, + Prices: []uint64{100}, + }) + + // call InitiateOutbound + msg := types.MsgVoteInbound{ + Creator: creator, + Sender: sender.String(), + SenderChainId: senderChain.ChainId, + Receiver: receiver.String(), + ReceiverChain: receiverChain.ChainId, + Amount: amount, + Message: message, + InboundHash: inboundHash.String(), + InboundBlockHeight: inboundBlockHeight, + GasLimit: gasLimit, + CoinType: cointType, + TxOrigin: sender.String(), + Asset: asset, + EventIndex: eventIndex, + } + + _, err := k.ValidateInbound(ctx, &msg, false) + require.NoError(t, err) + require.Len(t, k.GetAllCrossChainTx(ctx), 1) + }) + + t.Run("fail if tss not found", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseObserverMock: true, + UseFungibleMock: true, + UseAuthorityMock: true, + }) + + // Setup mock data + observerMock := keepertest.GetCrosschainObserverMock(t, k) + receiver := sample.EthAddress() + creator := sample.AccAddress() + amount := sdkmath.NewUint(42) + message := "test" + inboundBlockHeight := uint64(420) + inboundHash := sample.Hash() + gasLimit := uint64(100) + asset := "test-asset" + eventIndex := uint64(1) + cointType := coin.CoinType_ERC20 + tss := sample.Tss() + receiverChain := chains.Goerli + senderChain := chains.Goerli + sender := sample.EthAddress() + + // setup Mocks for GetTSS + observerMock.On("GetTSS", mock.Anything).Return(tss, false) + // setup Mocks for IsInboundEnabled + + k.SetGasPrice(ctx, types.GasPrice{ + ChainId: senderChain.ChainId, + MedianIndex: 0, + Prices: []uint64{100}, + }) + + // call InitiateOutbound + msg := types.MsgVoteInbound{ + Creator: creator, + Sender: sender.String(), + SenderChainId: senderChain.ChainId, + Receiver: receiver.String(), + ReceiverChain: receiverChain.ChainId, + Amount: amount, + Message: message, + InboundHash: inboundHash.String(), + InboundBlockHeight: inboundBlockHeight, + GasLimit: gasLimit, + CoinType: cointType, + TxOrigin: sender.String(), + Asset: asset, + EventIndex: eventIndex, + } + + _, err := k.ValidateInbound(ctx, &msg, false) + require.ErrorIs(t, err, types.ErrCannotFindTSSKeys) + }) + + t.Run("fail if InitiateOutbound fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseObserverMock: true, + UseFungibleMock: true, + UseAuthorityMock: true, + }) + + // Setup mock data + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + receiver := sample.EthAddress() + creator := sample.AccAddress() + amount := sdkmath.NewUint(42) + message := "test" + inboundBlockHeight := uint64(420) + inboundHash := sample.Hash() + gasLimit := uint64(100) + asset := "test-asset" + eventIndex := uint64(1) + cointType := coin.CoinType_ERC20 + tss := sample.Tss() + receiverChain := chains.Goerli + senderChain := chains.Goerli + sender := sample.EthAddress() + tssList := sample.TssList(3) + + // Set up mocks for CheckIfTSSMigrationTransfer + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId).Return(senderChain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + // setup Mocks for GetTSS + observerMock.On("GetTSS", mock.Anything).Return(tss, true) + // setup Mocks for IsInboundEnabled + observerMock.On("IsInboundEnabled", ctx).Return(true) + // setup mocks for Initiate Outbound + observerMock.On("GetChainNonces", mock.Anything, mock.Anything). + Return(observerTypes.ChainNonces{Nonce: 1}, false) + + k.SetGasPrice(ctx, types.GasPrice{ + ChainId: senderChain.ChainId, + MedianIndex: 0, + Prices: []uint64{100}, + }) + + // call InitiateOutbound + msg := types.MsgVoteInbound{ + Creator: creator, + Sender: sender.String(), + SenderChainId: senderChain.ChainId, + Receiver: receiver.String(), + ReceiverChain: receiverChain.ChainId, + Amount: amount, + Message: message, + InboundHash: inboundHash.String(), + InboundBlockHeight: inboundBlockHeight, + GasLimit: gasLimit, + CoinType: cointType, + TxOrigin: sender.String(), + Asset: asset, + EventIndex: eventIndex, + } + + _, err := k.ValidateInbound(ctx, &msg, false) + require.ErrorIs(t, err, types.ErrCannotFindReceiverNonce) + }) + + t.Run("does not set cctx if SetCctxAndNonceToCctxAndInboundHashToCctx fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseObserverMock: true, + UseFungibleMock: true, + UseAuthorityMock: true, + }) + + // Setup mock data + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + receiver := sample.EthAddress() + creator := sample.AccAddress() + amount := sdkmath.NewUint(42) + message := "test" + inboundBlockHeight := uint64(420) + inboundHash := sample.Hash() + gasLimit := uint64(100) + asset := "test-asset" + eventIndex := uint64(1) + cointType := coin.CoinType_ERC20 + tss := sample.Tss() + receiverChain := chains.Goerli + senderChain := chains.Goerli + sender := sample.EthAddress() + tssList := sample.TssList(3) + + // Set up mocks for CheckIfTSSMigrationTransfer + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId).Return(senderChain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + // setup Mocks for GetTSS + observerMock.On("GetTSS", mock.Anything).Return(tss, true).Twice() + // setup Mocks for IsInboundEnabled + observerMock.On("IsInboundEnabled", ctx).Return(true) + // setup mocks for Initiate Outbound + observerMock.On("GetChainNonces", mock.Anything, mock.Anything). + Return(observerTypes.ChainNonces{Nonce: 1}, true) + observerMock.On("GetPendingNonces", mock.Anything, mock.Anything, mock.Anything). + Return(observerTypes.PendingNonces{NonceHigh: 1}, true) + observerMock.On("SetChainNonces", mock.Anything, mock.Anything).Return(nil) + observerMock.On("SetPendingNonces", mock.Anything, mock.Anything).Return(nil) + // setup Mocks for SetCctxAndNonceToCctxAndInboundHashToCctx + observerMock.On("GetTSS", mock.Anything).Return(tss, false).Once() + + k.SetGasPrice(ctx, types.GasPrice{ + ChainId: senderChain.ChainId, + MedianIndex: 0, + Prices: []uint64{100}, + }) + + // call InitiateOutbound + msg := types.MsgVoteInbound{ + Creator: creator, + Sender: sender.String(), + SenderChainId: senderChain.ChainId, + Receiver: receiver.String(), + ReceiverChain: receiverChain.ChainId, + Amount: amount, + Message: message, + InboundHash: inboundHash.String(), + InboundBlockHeight: inboundBlockHeight, + GasLimit: gasLimit, + CoinType: cointType, + TxOrigin: sender.String(), + Asset: asset, + EventIndex: eventIndex, + } + + _, err := k.ValidateInbound(ctx, &msg, false) + require.NoError(t, err) + require.Len(t, k.GetAllCrossChainTx(ctx), 0) + }) + + t.Run("fail if inbound is disabled", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseObserverMock: true, + UseFungibleMock: true, + UseAuthorityMock: true, + }) + + // Setup mock data + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + receiver := sample.EthAddress() + creator := sample.AccAddress() + amount := sdkmath.NewUint(42) + message := "test" + inboundBlockHeight := uint64(420) + inboundHash := sample.Hash() + gasLimit := uint64(100) + asset := "test-asset" + eventIndex := uint64(1) + cointType := coin.CoinType_ERC20 + tss := sample.Tss() + receiverChain := chains.Goerli + senderChain := chains.Goerli + sender := sample.EthAddress() + tssList := sample.TssList(3) + + // Set up mocks for CheckIfTSSMigrationTransfer + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId).Return(senderChain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + // setup Mocks for GetTSS + observerMock.On("GetTSS", mock.Anything).Return(tss, true) + // setup Mocks for IsInboundEnabled + observerMock.On("IsInboundEnabled", ctx).Return(false) + + k.SetGasPrice(ctx, types.GasPrice{ + ChainId: senderChain.ChainId, + MedianIndex: 0, + Prices: []uint64{100}, + }) + + // call InitiateOutbound + msg := types.MsgVoteInbound{ + Creator: creator, + Sender: sender.String(), + SenderChainId: senderChain.ChainId, + Receiver: receiver.String(), + ReceiverChain: receiverChain.ChainId, + Amount: amount, + Message: message, + InboundHash: inboundHash.String(), + InboundBlockHeight: inboundBlockHeight, + GasLimit: gasLimit, + CoinType: cointType, + TxOrigin: sender.String(), + Asset: asset, + EventIndex: eventIndex, + } + + _, err := k.ValidateInbound(ctx, &msg, false) + require.ErrorIs(t, err, observerTypes.ErrInboundDisabled) + }) + + t.Run("fails when CheckIfTSSMigrationTransfer fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseObserverMock: true, + UseFungibleMock: true, + UseAuthorityMock: true, + }) + + // Setup mock data + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + receiver := sample.EthAddress() + creator := sample.AccAddress() + amount := sdkmath.NewUint(42) + message := "test" + inboundBlockHeight := uint64(420) + inboundHash := sample.Hash() + gasLimit := uint64(100) + asset := "test-asset" + eventIndex := uint64(1) + cointType := coin.CoinType_ERC20 + receiverChain := chains.Goerli + senderChain := chains.Goerli + sender := sample.EthAddress() + tssList := sample.TssList(3) + + // setup Mocks for GetTSS + observerMock.On("GetTSS", mock.Anything).Return(tssList[0], true) + + // Set up mocks for CheckIfTSSMigrationTransfer + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", mock.Anything, senderChain.ChainId).Return(senderChain, false) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + k.SetGasPrice(ctx, types.GasPrice{ + ChainId: senderChain.ChainId, + MedianIndex: 0, + Prices: []uint64{100}, + }) + + // call InitiateOutbound + msg := types.MsgVoteInbound{ + Creator: creator, + Sender: sender.String(), + SenderChainId: senderChain.ChainId, + Receiver: receiver.String(), + ReceiverChain: receiverChain.ChainId, + Amount: amount, + Message: message, + InboundHash: inboundHash.String(), + InboundBlockHeight: inboundBlockHeight, + GasLimit: gasLimit, + CoinType: cointType, + TxOrigin: sender.String(), + Asset: asset, + EventIndex: eventIndex, + } + + _, err := k.ValidateInbound(ctx, &msg, false) + require.ErrorIs(t, err, observerTypes.ErrSupportedChains) + }) +} +func TestKeeper_CheckMigration(t *testing.T) { + t.Run("Do not return error if sender is not a TSS address for evm chain", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + UseObserverMock: true, + }) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chain := chains.Goerli + tssList := sample.TssList(3) + sender := sample.AccAddress() + + // Set up mocks + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", ctx, chain.ChainId).Return(chain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + msg := types.MsgVoteInbound{ + SenderChainId: chain.ChainId, + Sender: sender, + } + + err := k.CheckIfTSSMigrationTransfer(ctx, &msg) + require.NoError(t, err) + }) + + t.Run("Do not return error if sender is not a TSS address for btc chain", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + UseObserverMock: true, + }) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chain := chains.BitcoinTestnet + tssList := sample.TssList(3) + sender := sample.AccAddress() + + // Set up mocks + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", ctx, chain.ChainId).Return(chain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + msg := types.MsgVoteInbound{ + SenderChainId: chain.ChainId, + Sender: sender, + } + + err := k.CheckIfTSSMigrationTransfer(ctx, &msg) + require.NoError(t, err) + }) + + t.Run("fails when chain is not supported", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + UseObserverMock: true, + }) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chain := chains.Chain{ + ChainId: 999, + } + tssList := sample.TssList(3) + sender := sample.AccAddress() + + // Set up mocks + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", ctx, chain.ChainId).Return(chain, false) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + msg := types.MsgVoteInbound{ + SenderChainId: chain.ChainId, + Sender: sender, + } + + err := k.CheckIfTSSMigrationTransfer(ctx, &msg) + require.ErrorIs(t, err, observerTypes.ErrSupportedChains) + }) + + t.Run("skips check when an older tss address is invalid for bitcoin chain", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + UseObserverMock: true, + }) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chain := chains.BitcoinTestnet + tssList := sample.TssList(3) + tssList[0].TssPubkey = "invalid" + sender := sample.AccAddress() + + // Set up mocks + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", ctx, chain.ChainId).Return(chain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + msg := types.MsgVoteInbound{ + SenderChainId: chain.ChainId, + Sender: sender, + } + + err := k.CheckIfTSSMigrationTransfer(ctx, &msg) + require.NoError(t, err) + }) + + t.Run("skips check when an older tss address is invalid for evm chain", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + UseObserverMock: true, + }) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chain := chains.Goerli + tssList := sample.TssList(3) + tssList[0].TssPubkey = "invalid" + sender := sample.AccAddress() + + // Set up mocks + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", ctx, chain.ChainId).Return(chain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + msg := types.MsgVoteInbound{ + SenderChainId: chain.ChainId, + Sender: sender, + } + + err := k.CheckIfTSSMigrationTransfer(ctx, &msg) + require.NoError(t, err) + }) + + t.Run("fails when sender is a TSS address for evm chain for evm chain", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + UseObserverMock: true, + }) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chain := chains.Goerli + tssList := sample.TssList(3) + sender, err := crypto.GetTssAddrEVM(tssList[0].TssPubkey) + require.NoError(t, err) + + // Set up mocks + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", ctx, chain.ChainId).Return(chain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + msg := types.MsgVoteInbound{ + SenderChainId: chain.ChainId, + Sender: sender.String(), + } + + err = k.CheckIfTSSMigrationTransfer(ctx, &msg) + require.ErrorIs(t, err, types.ErrMigrationFromOldTss) + }) + + t.Run("fails when sender is a TSS address for btc chain for btc chain", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + UseObserverMock: true, + }) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chain := chains.BitcoinTestnet + tssList := sample.TssList(3) + bitcoinParams, err := chains.BitcoinNetParamsFromChainID(chain.ChainId) + require.NoError(t, err) + sender, err := crypto.GetTssAddrBTC(tssList[0].TssPubkey, bitcoinParams) + require.NoError(t, err) + + // Set up mocks + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", ctx, chain.ChainId).Return(chain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + msg := types.MsgVoteInbound{ + SenderChainId: chain.ChainId, + Sender: sender, + } + + err = k.CheckIfTSSMigrationTransfer(ctx, &msg) + require.ErrorIs(t, err, types.ErrMigrationFromOldTss) + }) + + t.Run("fails if bitcoin network params not found for BTC chain", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + UseObserverMock: true, + }) + + observerMock := keepertest.GetCrosschainObserverMock(t, k) + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chain := chains.Chain{ + ChainId: 999, + Consensus: chains.Consensus_bitcoin, + CctxGateway: chains.CCTXGateway_observers, + } + tssList := sample.TssList(3) + sender := sample.AccAddress() + + // Set up mocks + observerMock.On("GetAllTSS", ctx).Return(tssList) + observerMock.On("GetSupportedChainFromChainID", ctx, chain.ChainId).Return(chain, true) + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{chain}) + + msg := types.MsgVoteInbound{ + SenderChainId: chain.ChainId, + Sender: sender, + } + + err := k.CheckIfTSSMigrationTransfer(ctx, &msg) + require.ErrorContains(t, err, "no Bitcoin net params for chain ID: 999") + }) + + t.Run("fails if gateway is not observer ", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, + keepertest.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + authorityMock := keepertest.GetCrosschainAuthorityMock(t, k) + chain := chains.GoerliLocalnet + chain.CctxGateway = chains.CCTXGateway_zevm + sender := sample.AccAddress() + + // Set up mocks + authorityMock.On("GetAdditionalChainList", ctx).Return([]chains.Chain{}) + + msg := types.MsgVoteInbound{ + SenderChainId: chain.ChainId, + Sender: sender, + } + + err := k.CheckIfTSSMigrationTransfer(ctx, &msg) + require.NoError(t, err) + }) +} diff --git a/x/crosschain/keeper/evm_hooks_test.go b/x/crosschain/keeper/evm_hooks_test.go index 36b8938a8a..12e01fba14 100644 --- a/x/crosschain/keeper/evm_hooks_test.go +++ b/x/crosschain/keeper/evm_hooks_test.go @@ -214,7 +214,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { chain := chains.BitcoinMainnet chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) event, err := crosschainkeeper.ParseZRC20WithdrawalEvent(*sample.GetValidZRC20WithdrawToBTC(t).Logs[3]) @@ -239,7 +240,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { chain := chains.Ethereum chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) event, err := crosschainkeeper.ParseZRC20WithdrawalEvent(*sample.GetValidZrc20WithdrawToETH(t).Logs[11]) @@ -378,7 +380,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { chain := chains.Ethereum chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) k.RemoveGasPrice(ctx, strconv.FormatInt(chainID, 10)) @@ -400,7 +403,8 @@ func TestKeeper_ProcessZRC20WithdrawalEvent(t *testing.T) { chain := chains.Ethereum chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ Index: chain.ChainName.String(), @@ -472,7 +476,8 @@ func TestKeeper_ProcessZetaSentEvent(t *testing.T) { chain := chains.Ethereum chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) admin := keepertest.SetAdminPolicies(ctx, zk.AuthorityKeeper) @@ -604,7 +609,8 @@ func TestKeeper_ProcessZetaSentEvent(t *testing.T) { chain := chains.Ethereum chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) amount, ok := sdkmath.NewIntFromString("20000000000000000000000") @@ -633,7 +639,8 @@ func TestKeeper_ProcessZetaSentEvent(t *testing.T) { chain := chains.Ethereum chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) admin := keepertest.SetAdminPolicies(ctx, zk.AuthorityKeeper) @@ -673,7 +680,8 @@ func TestKeeper_ProcessLogs(t *testing.T) { chain := chains.BitcoinMainnet chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) block := sample.GetValidZRC20WithdrawToBTC(t) @@ -699,7 +707,8 @@ func TestKeeper_ProcessLogs(t *testing.T) { chain := chains.Ethereum chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) admin := keepertest.SetAdminPolicies(ctx, zk.AuthorityKeeper) SetupStateForProcessLogsZetaSent(t, ctx, k, zk, sdkk, chain, admin) @@ -799,7 +808,8 @@ func TestKeeper_ProcessLogs(t *testing.T) { chain := chains.BitcoinMainnet chainID := chain.ChainId - setSupportedChain(ctx, zk, chainID) + senderChain := chains.ZetaChainMainnet + setSupportedChain(ctx, zk, []int64{chainID, senderChain.ChainId}...) SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) block := sample.GetValidZRC20WithdrawToBTC(t) diff --git a/x/crosschain/keeper/msg_server_add_outbound_tracker.go b/x/crosschain/keeper/msg_server_add_outbound_tracker.go index 5a3c98e9e9..b82fba7f79 100644 --- a/x/crosschain/keeper/msg_server_add_outbound_tracker.go +++ b/x/crosschain/keeper/msg_server_add_outbound_tracker.go @@ -15,7 +15,7 @@ import ( ) // MaxOutboundTrackerHashes is the maximum number of hashes that can be stored in the outbound transaction tracker -const MaxOutboundTrackerHashes = 2 +const MaxOutboundTrackerHashes = 5 // AddOutboundTracker adds a new record to the outbound transaction tracker. // only the admin policy account and the observer validators are authorized to broadcast this message without proof. @@ -83,7 +83,7 @@ func (k msgServer) AddOutboundTracker( // fetch the tracker // if the tracker does not exist, initialize a new one tracker, found := k.GetOutboundTracker(ctx, msg.ChainId, msg.Nonce) - hash := types.TxHashList{ + hash := types.TxHash{ TxHash: msg.TxHash, TxSigner: msg.Creator, Proved: isProven, @@ -93,7 +93,7 @@ func (k msgServer) AddOutboundTracker( Index: "", ChainId: msg.ChainId, Nonce: msg.Nonce, - HashList: []*types.TxHashList{&hash}, + HashList: []*types.TxHash{&hash}, }) return &types.MsgAddOutboundTrackerResponse{}, nil } diff --git a/x/crosschain/keeper/msg_server_add_outbound_tracker_test.go b/x/crosschain/keeper/msg_server_add_outbound_tracker_test.go index 7abe63fe15..b6cb5d4bff 100644 --- a/x/crosschain/keeper/msg_server_add_outbound_tracker_test.go +++ b/x/crosschain/keeper/msg_server_add_outbound_tracker_test.go @@ -117,7 +117,7 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) { k.SetOutboundTracker(ctx, types.OutboundTracker{ ChainId: chainID, Nonce: 42, - HashList: []*types.TxHashList{ + HashList: []*types.TxHash{ { TxHash: existinghHash, }, @@ -249,9 +249,9 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) { observerMock.On("IsNonTombstonedObserver", mock.Anything, mock.Anything).Return(false) keepertest.MockCctxByNonce(t, ctx, *k, observerMock, types.CctxStatus_PendingOutbound, false) - hashes := make([]*types.TxHashList, keeper.MaxOutboundTrackerHashes) + hashes := make([]*types.TxHash, keeper.MaxOutboundTrackerHashes) for i := 0; i < keeper.MaxOutboundTrackerHashes; i++ { - hashes[i] = &types.TxHashList{ + hashes[i] = &types.TxHash{ TxHash: sample.Hash().Hex(), } } @@ -298,7 +298,7 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) { k.SetOutboundTracker(ctx, types.OutboundTracker{ ChainId: chainID, Nonce: 42, - HashList: []*types.TxHashList{ + HashList: []*types.TxHash{ { TxHash: existinghHash, }, @@ -397,7 +397,7 @@ func TestMsgServer_AddToOutboundTracker(t *testing.T) { k.SetOutboundTracker(ctx, types.OutboundTracker{ ChainId: chainID, Nonce: 42, - HashList: []*types.TxHashList{ + HashList: []*types.TxHash{ { TxHash: sample.Hash().Hex(), Proved: false, diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds.go b/x/crosschain/keeper/msg_server_migrate_tss_funds.go index f5558fe689..8656cf2e80 100644 --- a/x/crosschain/keeper/msg_server_migrate_tss_funds.go +++ b/x/crosschain/keeper/msg_server_migrate_tss_funds.go @@ -181,7 +181,10 @@ func (k Keeper) MigrateTSSFundsForChain( ), ) } - cctx.GetCurrentOutboundParam().Amount = amount.Sub(evmFee) + + cctx.GetCurrentOutboundParam().Amount = amount.Sub( + evmFee.Add(sdkmath.NewUintFromString(types.TSSMigrationBufferAmountEVM)), + ) } // Set the sender and receiver addresses for Bitcoin chain if chains.IsBitcoinChain(chainID, additionalChains) { diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go b/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go index 9da596f876..7e86307fc0 100644 --- a/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go +++ b/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/gas" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" @@ -386,7 +385,8 @@ func TestMsgServer_MigrateTssFunds(t *testing.T) { cctx, found := k.GetCrossChainTx(ctx, index) require.True(t, found) feeCalculated := sdk.NewUint(cctx.GetCurrentOutboundParam().GasLimit). - Mul(sdkmath.NewUintFromString(cctx.GetCurrentOutboundParam().GasPrice)) + Mul(sdkmath.NewUintFromString(cctx.GetCurrentOutboundParam().GasPrice)). + Add(sdkmath.NewUintFromString(crosschaintypes.TSSMigrationBufferAmountEVM)) require.Equal(t, cctx.GetCurrentOutboundParam().Amount.String(), amount.Sub(feeCalculated).String()) }) diff --git a/x/crosschain/keeper/msg_server_update_tss.go b/x/crosschain/keeper/msg_server_update_tss.go index 56052b6671..78c3b833d7 100644 --- a/x/crosschain/keeper/msg_server_update_tss.go +++ b/x/crosschain/keeper/msg_server_update_tss.go @@ -38,10 +38,10 @@ func (k msgServer) UpdateTssAddress( tssMigrators := k.zetaObserverKeeper.GetAllTssFundMigrators(ctx) // Each connected chain should have its own tss migrator - if len(k.zetaObserverKeeper.GetSupportedChains(ctx)) != len(tssMigrators) { + if len(k.zetaObserverKeeper.GetSupportedForeignChains(ctx)) != len(tssMigrators) { return nil, errorsmod.Wrap( types.ErrUnableToUpdateTss, - "cannot update tss address not enough migrations have been created and completed", + "cannot update tss address incorrect number of migrations have been created and completed", ) } diff --git a/x/crosschain/keeper/msg_server_update_tss_test.go b/x/crosschain/keeper/msg_server_update_tss_test.go index 4e894e1586..761ff617c8 100644 --- a/x/crosschain/keeper/msg_server_update_tss_test.go +++ b/x/crosschain/keeper/msg_server_update_tss_test.go @@ -65,7 +65,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) { k.GetObserverKeeper().SetTSSHistory(ctx, tssOld) k.GetObserverKeeper().SetTSSHistory(ctx, tssNew) k.GetObserverKeeper().SetTSS(ctx, tssOld) - for _, chain := range k.GetObserverKeeper().GetSupportedChains(ctx) { + for _, chain := range k.GetObserverKeeper().GetSupportedForeignChains(ctx) { index := chain.ChainName.String() + "_migration_tx_index" k.GetObserverKeeper().SetFundMigrator(ctx, types.TssFundMigratorInfo{ ChainId: chain.ChainId, @@ -78,7 +78,7 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) { require.Equal( t, len(k.GetObserverKeeper().GetAllTssFundMigrators(ctx)), - len(k.GetObserverKeeper().GetSupportedChains(ctx)), + len(k.GetObserverKeeper().GetSupportedForeignChains(ctx)), ) msg := crosschaintypes.MsgUpdateTssAddress{ @@ -224,7 +224,11 @@ func TestMsgServer_UpdateTssAddress(t *testing.T) { } keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) _, err := msgServer.UpdateTssAddress(ctx, &msg) - require.ErrorContains(t, err, "cannot update tss address not enough migrations have been created and completed") + require.ErrorContains( + t, + err, + "cannot update tss address incorrect number of migrations have been created and completed: unable to update TSS address", + ) require.ErrorIs(t, err, crosschaintypes.ErrUnableToUpdateTss) tss, found := k.GetObserverKeeper().GetTSS(ctx) require.True(t, found) diff --git a/x/crosschain/keeper/msg_server_vote_gas_price.go b/x/crosschain/keeper/msg_server_vote_gas_price.go index 040c85fd75..aa56a73e26 100644 --- a/x/crosschain/keeper/msg_server_vote_gas_price.go +++ b/x/crosschain/keeper/msg_server_vote_gas_price.go @@ -2,7 +2,6 @@ package keeper import ( "context" - "fmt" "math/big" "sort" "strconv" @@ -28,7 +27,7 @@ func (k msgServer) VoteGasPrice( chain, found := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.ChainId) if !found { - return nil, cosmoserrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID : %d ", msg.ChainId)) + return nil, cosmoserrors.Wrapf(types.ErrUnsupportedChain, "ChainID: %d ", msg.ChainId) } if ok := k.zetaObserverKeeper.IsNonTombstonedObserver(ctx, msg.Creator); !ok { return nil, observertypes.ErrNotObserver diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index e597509ab1..5aa925b363 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -2,14 +2,15 @@ package keeper import ( "context" - "fmt" - cosmoserrors "cosmossdk.io/errors" + sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/x/crosschain/types" ) +const voteInboundID = "Vote Inbound" + // FIXME: use more specific error types & codes // VoteInbound casts a vote on an inbound transaction observed on a connected chain. If this @@ -73,7 +74,7 @@ func (k msgServer) VoteInbound( msg.InboundHash, ) if err != nil { - return nil, err + return nil, sdkerrors.Wrap(err, voteInboundID) } // If it is a new ballot, check if an inbound with the same hash, sender chain and event index has already been finalized @@ -81,14 +82,13 @@ func (k msgServer) VoteInbound( // This check prevents double spending if isNew { if k.IsFinalizedInbound(tmpCtx, msg.InboundHash, msg.SenderChainId, msg.EventIndex) { - return nil, cosmoserrors.Wrap( + return nil, sdkerrors.Wrapf( types.ErrObservedTxAlreadyFinalized, - fmt.Sprintf( - "inboundHash:%s, SenderChainID:%d, EventIndex:%d", - msg.InboundHash, - msg.SenderChainId, - msg.EventIndex, - ), + "%s, InboundHash:%s, SenderChainID:%d, EventIndex:%d", + voteInboundID, + msg.InboundHash, + msg.SenderChainId, + msg.EventIndex, ) } } @@ -100,7 +100,7 @@ func (k msgServer) VoteInbound( cctx, err := k.ValidateInbound(ctx, msg, true) if err != nil { - return nil, err + return nil, sdkerrors.Wrap(err, voteInboundID) } // Save the inbound CCTX to the store. This is called irrespective of the status of the CCTX or the outcome of the process function. diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx.go b/x/crosschain/keeper/msg_server_vote_outbound_tx.go index 8cb4a2ce6d..61e1d9389e 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx.go @@ -14,6 +14,8 @@ import ( observerkeeper "github.com/zeta-chain/zetacore/x/observer/keeper" ) +const voteOutboundID = "Vote Outbound" + // VoteOutbound casts a vote on an outbound transaction observed on a connected chain (after // it has been broadcasted to and finalized on a connected chain). If this is // the first vote, a new ballot is created. When a threshold of votes is @@ -61,14 +63,13 @@ func (k msgServer) VoteOutbound( ) (*types.MsgVoteOutboundResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - // Validate the message params to verify it against an existing cctx + // Validate the message params to verify it against an existing CCTX. cctx, err := k.ValidateOutboundMessage(ctx, *msg) if err != nil { - return nil, err + return nil, cosmoserrors.Wrap(err, voteOutboundID) } - // get ballot index + ballotIndex := msg.Digest() - // vote on outbound ballot isFinalizingVote, isNew, ballot, observationChain, err := k.zetaObserverKeeper.VoteOnOutboundBallot( ctx, ballotIndex, @@ -76,23 +77,26 @@ func (k msgServer) VoteOutbound( msg.Status, msg.Creator) if err != nil { - return nil, err + return nil, cosmoserrors.Wrap(err, voteOutboundID) } - // if the ballot is new, set the index to the CCTX + + // If the ballot is new, set the index to the CCTX. if isNew { observerkeeper.EmitEventBallotCreated(ctx, ballot, msg.ObservedOutboundHash, observationChain) } - // if not finalized commit state here + + // If not finalized commit state here. if !isFinalizingVote { return &types.MsgVoteOutboundResponse{}, nil } - // if ballot successful, the value received should be the out tx amount + // If ballot is successful, the value received should be the out tx amount. err = cctx.AddOutbound(ctx, *msg, ballot.BallotStatus) if err != nil { - return nil, err + return nil, cosmoserrors.Wrap(err, voteOutboundID) } - // Fund the gas stability pool with the remaining funds + + // Fund the gas stability pool with the remaining funds. k.FundStabilityPool(ctx, &cctx) err = k.ValidateOutboundObservers(ctx, &cctx, ballot.BallotStatus, msg.ValueReceived.String()) @@ -100,6 +104,7 @@ func (k msgServer) VoteOutbound( k.SaveFailedOutbound(ctx, &cctx, err.Error(), ballotIndex) return &types.MsgVoteOutboundResponse{}, nil } + k.SaveSuccessfulOutbound(ctx, &cctx, ballotIndex) return &types.MsgVoteOutboundResponse{}, nil } @@ -112,7 +117,7 @@ func (k Keeper) FundStabilityPool(ctx sdk.Context, cctx *types.CrossChainTx) { // Fund the gas stability pool with the remaining funds if err := k.FundGasStabilityPoolFromRemainingFees(ctx, *cctx.GetCurrentOutboundParam(), cctx.GetCurrentOutboundParam().ReceiverChainId); err != nil { ctx.Logger(). - Error(fmt.Sprintf("VoteOutbound: CCTX: %s Can't fund the gas stability pool with remaining fees %s", cctx.Index, err.Error())) + Error("%s: CCTX: %s Can't fund the gas stability pool with remaining fees %s", voteOutboundID, cctx.Index, err.Error()) } } @@ -136,16 +141,18 @@ func (k Keeper) FundGasStabilityPoolFromRemainingFees( remainingGas := gasLimit - gasUsed remainingFees := math.NewUint(remainingGas).Mul(gasPrice).BigInt() - // We fund the stability pool with a portion of the remaining fees + // We fund the stability pool with a portion of the remaining fees. remainingFees = percentOf(remainingFees, RemainingFeesToStabilityPoolPercent) - // Fund the gas stability pool + + // Fund the gas stability pool. if err := k.fungibleKeeper.FundGasStabilityPool(ctx, chainID, remainingFees); err != nil { return err } } else { - return fmt.Errorf("VoteOutbound: The gas limit %d is less than the gas used %d", gasLimit, gasUsed) + return fmt.Errorf("%s: The gas limit %d is less than the gas used %d", voteOutboundID, gasLimit, gasUsed) } } + return nil } @@ -167,7 +174,6 @@ SaveFailedOutbound saves a failed outbound transaction.It does the following thi func (k Keeper) SaveFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, errMessage string, ballotIndex string) { cctx.SetAbort(errMessage) ctx.Logger().Error(errMessage) - k.SaveOutbound(ctx, cctx, ballotIndex) } @@ -195,48 +201,51 @@ func (k Keeper) SaveOutbound(ctx sdk.Context, cctx *types.CrossChainTx, ballotIn outTxTssNonce := cctx.GetCurrentOutboundParam().TssNonce cctx.GetCurrentOutboundParam().BallotIndex = ballotIndex + // #nosec G115 always in range k.GetObserverKeeper().RemoveFromPendingNonces(ctx, tssPubkey, receiverChain, int64(outTxTssNonce)) k.RemoveOutboundTrackerFromStore(ctx, receiverChain, outTxTssNonce) ctx.Logger(). - Info(fmt.Sprintf("Remove tracker %s: , Block Height : %d ", getOutboundTrackerIndex(receiverChain, outTxTssNonce), ctx.BlockHeight())) + Info("%s: Remove tracker %s: , Block Height : %d ", voteOutboundID, getOutboundTrackerIndex(receiverChain, outTxTssNonce), ctx.BlockHeight()) + // This should set nonce to cctx only if a new revert is created. k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, *cctx) } func (k Keeper) ValidateOutboundMessage(ctx sdk.Context, msg types.MsgVoteOutbound) (types.CrossChainTx, error) { - // check if CCTX exists and if the nonce matches + // Check if CCTX exists and if the nonce matches. cctx, found := k.GetCrossChainTx(ctx, msg.CctxHash) if !found { - return types.CrossChainTx{}, cosmoserrors.Wrap( + return types.CrossChainTx{}, cosmoserrors.Wrapf( sdkerrors.ErrInvalidRequest, - fmt.Sprintf("CCTX %s does not exist", msg.CctxHash), - ) + "%s, CCTX %s does not exist", voteOutboundID, msg.CctxHash) } + if cctx.GetCurrentOutboundParam().TssNonce != msg.OutboundTssNonce { - return types.CrossChainTx{}, cosmoserrors.Wrap( + return types.CrossChainTx{}, cosmoserrors.Wrapf( sdkerrors.ErrInvalidRequest, - fmt.Sprintf( - "OutboundTssNonce %d does not match CCTX OutboundTssNonce %d", - msg.OutboundTssNonce, - cctx.GetCurrentOutboundParam().TssNonce, - ), + "%s, OutboundTssNonce %d does not match CCTX OutboundTssNonce %d", + voteOutboundID, + msg.OutboundTssNonce, + cctx.GetCurrentOutboundParam().TssNonce, ) } - // do not process an outbound vote if TSS is not found + + // Do not process an outbound vote if TSS is not found. _, found = k.zetaObserverKeeper.GetTSS(ctx) if !found { - return types.CrossChainTx{}, types.ErrCannotFindTSSKeys + return types.CrossChainTx{}, cosmoserrors.Wrap(types.ErrCannotFindTSSKeys, voteOutboundID) } + if cctx.GetCurrentOutboundParam().ReceiverChainId != msg.OutboundChain { - return types.CrossChainTx{}, cosmoserrors.Wrap( + return types.CrossChainTx{}, cosmoserrors.Wrapf( sdkerrors.ErrInvalidRequest, - fmt.Sprintf( - "OutboundChain %d does not match CCTX OutboundChain %d", - msg.OutboundChain, - cctx.GetCurrentOutboundParam().ReceiverChainId, - ), + "%s, OutboundChain %d does not match CCTX OutboundChain %d", + voteOutboundID, + msg.OutboundChain, + cctx.GetCurrentOutboundParam().ReceiverChainId, ) } + return cctx, nil } diff --git a/x/crosschain/types/errors.go b/x/crosschain/types/errors.go index 6f430e5e1a..b468d2c962 100644 --- a/x/crosschain/types/errors.go +++ b/x/crosschain/types/errors.go @@ -50,4 +50,10 @@ var ( ErrMaxTxOutTrackerHashesReached = errorsmod.Register(ModuleName, 1153, "max tx out tracker hashes reached") ErrInitiatitingOutbound = errorsmod.Register(ModuleName, 1154, "cannot initiate outbound") ErrInvalidWithdrawalAmount = errorsmod.Register(ModuleName, 1155, "invalid withdrawal amount") + ErrMigrationFromOldTss = errorsmod.Register( + ModuleName, + 1156, + "migration tx from an old tss address detected", + ) + ErrValidatingInbound = errorsmod.Register(ModuleName, 1157, "unable to validate inbound") ) diff --git a/x/crosschain/types/keys.go b/x/crosschain/types/keys.go index da56a5e797..5bb89a61ea 100644 --- a/x/crosschain/types/keys.go +++ b/x/crosschain/types/keys.go @@ -27,6 +27,8 @@ const ( //TssMigrationGasMultiplierEVM is multiplied to the median gas price to get the gas price for the tss migration . This is done to avoid the tss migration tx getting stuck in the mempool TssMigrationGasMultiplierEVM = "2.5" + // TSSMigrationBufferAmountEVM is the buffer amount added to the gas price for the tss migration transaction + TSSMigrationBufferAmountEVM = "2100000000" // CCTXIndexLength is the length of a crosschain transaction index CCTXIndexLength = 66 diff --git a/x/crosschain/types/outbound_tracker.pb.go b/x/crosschain/types/outbound_tracker.pb.go index d457726b91..03e17709f6 100644 --- a/x/crosschain/types/outbound_tracker.pb.go +++ b/x/crosschain/types/outbound_tracker.pb.go @@ -22,24 +22,24 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -type TxHashList struct { +type TxHash struct { TxHash string `protobuf:"bytes,1,opt,name=tx_hash,json=txHash,proto3" json:"tx_hash,omitempty"` TxSigner string `protobuf:"bytes,2,opt,name=tx_signer,json=txSigner,proto3" json:"tx_signer,omitempty"` Proved bool `protobuf:"varint,3,opt,name=proved,proto3" json:"proved,omitempty"` } -func (m *TxHashList) Reset() { *m = TxHashList{} } -func (m *TxHashList) String() string { return proto.CompactTextString(m) } -func (*TxHashList) ProtoMessage() {} -func (*TxHashList) Descriptor() ([]byte, []int) { +func (m *TxHash) Reset() { *m = TxHash{} } +func (m *TxHash) String() string { return proto.CompactTextString(m) } +func (*TxHash) ProtoMessage() {} +func (*TxHash) Descriptor() ([]byte, []int) { return fileDescriptor_77cb2cfe04eb42d9, []int{0} } -func (m *TxHashList) XXX_Unmarshal(b []byte) error { +func (m *TxHash) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *TxHashList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *TxHash) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_TxHashList.Marshal(b, m, deterministic) + return xxx_messageInfo_TxHash.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -49,33 +49,33 @@ func (m *TxHashList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return b[:n], nil } } -func (m *TxHashList) XXX_Merge(src proto.Message) { - xxx_messageInfo_TxHashList.Merge(m, src) +func (m *TxHash) XXX_Merge(src proto.Message) { + xxx_messageInfo_TxHash.Merge(m, src) } -func (m *TxHashList) XXX_Size() int { +func (m *TxHash) XXX_Size() int { return m.Size() } -func (m *TxHashList) XXX_DiscardUnknown() { - xxx_messageInfo_TxHashList.DiscardUnknown(m) +func (m *TxHash) XXX_DiscardUnknown() { + xxx_messageInfo_TxHash.DiscardUnknown(m) } -var xxx_messageInfo_TxHashList proto.InternalMessageInfo +var xxx_messageInfo_TxHash proto.InternalMessageInfo -func (m *TxHashList) GetTxHash() string { +func (m *TxHash) GetTxHash() string { if m != nil { return m.TxHash } return "" } -func (m *TxHashList) GetTxSigner() string { +func (m *TxHash) GetTxSigner() string { if m != nil { return m.TxSigner } return "" } -func (m *TxHashList) GetProved() bool { +func (m *TxHash) GetProved() bool { if m != nil { return m.Proved } @@ -83,10 +83,10 @@ func (m *TxHashList) GetProved() bool { } type OutboundTracker struct { - Index string `protobuf:"bytes,1,opt,name=index,proto3" json:"index,omitempty"` - ChainId int64 `protobuf:"varint,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` - Nonce uint64 `protobuf:"varint,3,opt,name=nonce,proto3" json:"nonce,omitempty"` - HashList []*TxHashList `protobuf:"bytes,4,rep,name=hash_list,json=hashList,proto3" json:"hash_list,omitempty"` + Index string `protobuf:"bytes,1,opt,name=index,proto3" json:"index,omitempty"` + ChainId int64 `protobuf:"varint,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + Nonce uint64 `protobuf:"varint,3,opt,name=nonce,proto3" json:"nonce,omitempty"` + HashList []*TxHash `protobuf:"bytes,4,rep,name=hash_list,json=hashList,proto3" json:"hash_list,omitempty"` } func (m *OutboundTracker) Reset() { *m = OutboundTracker{} } @@ -143,7 +143,7 @@ func (m *OutboundTracker) GetNonce() uint64 { return 0 } -func (m *OutboundTracker) GetHashList() []*TxHashList { +func (m *OutboundTracker) GetHashList() []*TxHash { if m != nil { return m.HashList } @@ -151,7 +151,7 @@ func (m *OutboundTracker) GetHashList() []*TxHashList { } func init() { - proto.RegisterType((*TxHashList)(nil), "zetachain.zetacore.crosschain.TxHashList") + proto.RegisterType((*TxHash)(nil), "zetachain.zetacore.crosschain.TxHash") proto.RegisterType((*OutboundTracker)(nil), "zetachain.zetacore.crosschain.OutboundTracker") } @@ -160,29 +160,29 @@ func init() { } var fileDescriptor_77cb2cfe04eb42d9 = []byte{ - // 303 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xcd, 0x4a, 0x3b, 0x31, - 0x14, 0xc5, 0x9b, 0x7f, 0xfb, 0x6f, 0xa7, 0x71, 0x21, 0x04, 0xd1, 0x11, 0x31, 0x94, 0xae, 0xea, - 0xc2, 0x14, 0x3f, 0x9e, 0xc0, 0x85, 0x28, 0x0a, 0xc2, 0xd8, 0x55, 0x37, 0xc3, 0x7c, 0x84, 0x26, - 0xa8, 0xc9, 0x90, 0xdc, 0x91, 0xe8, 0x53, 0xf8, 0x08, 0x3e, 0x8e, 0xcb, 0x2e, 0x5d, 0xca, 0xcc, - 0x8b, 0x48, 0x33, 0xa3, 0x15, 0x17, 0xee, 0xee, 0xef, 0x5e, 0xce, 0xe1, 0xdc, 0x83, 0x4f, 0x9f, - 0x39, 0x24, 0x99, 0x48, 0xa4, 0x9a, 0xfa, 0x49, 0x1b, 0x3e, 0xcd, 0x8c, 0xb6, 0xb6, 0xd9, 0xe9, - 0x12, 0x52, 0x5d, 0xaa, 0x3c, 0x06, 0x93, 0x64, 0x77, 0xdc, 0xb0, 0xc2, 0x68, 0xd0, 0x64, 0xff, - 0x5b, 0xc5, 0xbe, 0x54, 0x6c, 0xad, 0x1a, 0xcf, 0x31, 0x9e, 0xb9, 0x8b, 0xc4, 0x8a, 0x6b, 0x69, - 0x81, 0xec, 0xe0, 0x01, 0xb8, 0x58, 0x24, 0x56, 0x84, 0x68, 0x84, 0x26, 0xc3, 0xa8, 0x0f, 0xfe, - 0x48, 0xf6, 0xf0, 0x10, 0x5c, 0x6c, 0xe5, 0x42, 0x71, 0x13, 0xfe, 0xf3, 0xa7, 0x00, 0xdc, 0xad, - 0x67, 0xb2, 0x8d, 0xfb, 0x85, 0xd1, 0x8f, 0x3c, 0x0f, 0xbb, 0x23, 0x34, 0x09, 0xa2, 0x96, 0xc6, - 0xaf, 0x08, 0x6f, 0xde, 0xb4, 0xa9, 0x66, 0x4d, 0x28, 0xb2, 0x85, 0xff, 0x4b, 0x95, 0x73, 0xd7, - 0xfa, 0x37, 0x40, 0x76, 0x71, 0xe0, 0xe3, 0xc4, 0x32, 0xf7, 0xee, 0xdd, 0x68, 0xe0, 0xf9, 0x32, - 0x5f, 0x09, 0x94, 0x56, 0x19, 0xf7, 0xde, 0xbd, 0xa8, 0x01, 0x72, 0x8e, 0x87, 0xab, 0x94, 0xf1, - 0xbd, 0xb4, 0x10, 0xf6, 0x46, 0xdd, 0xc9, 0xc6, 0xf1, 0x01, 0xfb, 0xf3, 0x53, 0xb6, 0x7e, 0x33, - 0x0a, 0x44, 0x3b, 0x9d, 0x5d, 0xbd, 0x55, 0x14, 0x2d, 0x2b, 0x8a, 0x3e, 0x2a, 0x8a, 0x5e, 0x6a, - 0xda, 0x59, 0xd6, 0xb4, 0xf3, 0x5e, 0xd3, 0xce, 0xfc, 0x68, 0x21, 0x41, 0x94, 0x29, 0xcb, 0xf4, - 0x83, 0xaf, 0xfb, 0xf0, 0x57, 0xf3, 0xee, 0x67, 0xf7, 0xf0, 0x54, 0x70, 0x9b, 0xf6, 0x7d, 0xe3, - 0x27, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x81, 0x92, 0xa6, 0x84, 0xa9, 0x01, 0x00, 0x00, -} - -func (m *TxHashList) Marshal() (dAtA []byte, err error) { + // 301 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xcb, 0x4a, 0x03, 0x31, + 0x14, 0x86, 0x1b, 0x5b, 0xa7, 0xd3, 0xb8, 0x10, 0x82, 0xe8, 0x88, 0x18, 0x4a, 0x41, 0xe8, 0xc6, + 0x14, 0x2f, 0x4f, 0xd0, 0x95, 0xa2, 0x20, 0x8c, 0xc5, 0x85, 0x9b, 0x61, 0x3a, 0x13, 0x3a, 0x41, + 0x4d, 0x4a, 0x72, 0x46, 0xa2, 0x4f, 0xe1, 0x03, 0xf8, 0x40, 0x2e, 0xbb, 0x74, 0x29, 0x33, 0x2f, + 0x22, 0x93, 0x8c, 0x17, 0x5c, 0xb8, 0xcb, 0x97, 0xc3, 0x77, 0xf8, 0xcf, 0x8f, 0x4f, 0x9f, 0x39, + 0xa4, 0x59, 0x91, 0x0a, 0x39, 0x71, 0x2f, 0xa5, 0xf9, 0x24, 0xd3, 0xca, 0x18, 0xff, 0xa7, 0x4a, + 0x98, 0xab, 0x52, 0xe6, 0x09, 0xe8, 0x34, 0xbb, 0xe3, 0x9a, 0x2d, 0xb5, 0x02, 0x45, 0xf6, 0xbf, + 0x2d, 0xf6, 0x65, 0xb1, 0x1f, 0x6b, 0x74, 0x83, 0x83, 0x99, 0x3d, 0x4b, 0x4d, 0x41, 0x76, 0x70, + 0x1f, 0x6c, 0x52, 0xa4, 0xa6, 0x88, 0xd0, 0x10, 0x8d, 0x07, 0x71, 0x00, 0x7e, 0xb0, 0x87, 0x07, + 0x60, 0x13, 0x23, 0x16, 0x92, 0xeb, 0x68, 0xcd, 0x8d, 0x42, 0xb0, 0xd7, 0x8e, 0xc9, 0x36, 0x0e, + 0x96, 0x5a, 0x3d, 0xf2, 0x3c, 0xea, 0x0e, 0xd1, 0x38, 0x8c, 0x5b, 0x1a, 0xbd, 0x22, 0xbc, 0x79, + 0xd5, 0x26, 0x9a, 0xf9, 0x40, 0x64, 0x0b, 0xaf, 0x0b, 0x99, 0x73, 0xdb, 0xee, 0xf7, 0x40, 0x76, + 0x71, 0xe8, 0xa2, 0x24, 0x22, 0x77, 0xdb, 0xbb, 0x71, 0xdf, 0xf1, 0x79, 0xde, 0x08, 0x52, 0xc9, + 0x8c, 0xbb, 0xdd, 0xbd, 0xd8, 0x03, 0x99, 0xe2, 0x41, 0x93, 0x32, 0xb9, 0x17, 0x06, 0xa2, 0xde, + 0xb0, 0x3b, 0xde, 0x38, 0x3e, 0x60, 0xff, 0x5e, 0xc9, 0xfc, 0x89, 0x71, 0xd8, 0x78, 0x97, 0xc2, + 0xc0, 0xf4, 0xe2, 0xad, 0xa2, 0x68, 0x55, 0x51, 0xf4, 0x51, 0x51, 0xf4, 0x52, 0xd3, 0xce, 0xaa, + 0xa6, 0x9d, 0xf7, 0x9a, 0x76, 0x6e, 0x8f, 0x16, 0x02, 0x8a, 0x72, 0xce, 0x32, 0xf5, 0xe0, 0x6a, + 0x3e, 0xfc, 0xd3, 0xb8, 0xfd, 0xdd, 0x39, 0x3c, 0x2d, 0xb9, 0x99, 0x07, 0xae, 0xe9, 0x93, 0xcf, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xec, 0x29, 0xc8, 0x7e, 0xa1, 0x01, 0x00, 0x00, +} + +func (m *TxHash) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -192,12 +192,12 @@ func (m *TxHashList) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *TxHashList) MarshalTo(dAtA []byte) (int, error) { +func (m *TxHash) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *TxHashList) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *TxHash) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -294,7 +294,7 @@ func encodeVarintOutboundTracker(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *TxHashList) Size() (n int) { +func (m *TxHash) Size() (n int) { if m == nil { return 0 } @@ -345,7 +345,7 @@ func sovOutboundTracker(x uint64) (n int) { func sozOutboundTracker(x uint64) (n int) { return sovOutboundTracker(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *TxHashList) Unmarshal(dAtA []byte) error { +func (m *TxHash) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -368,10 +368,10 @@ func (m *TxHashList) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: TxHashList: wiretype end group for non-group") + return fmt.Errorf("proto: TxHash: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: TxHashList: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: TxHash: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -607,7 +607,7 @@ func (m *OutboundTracker) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.HashList = append(m.HashList, &TxHashList{}) + m.HashList = append(m.HashList, &TxHash{}) if err := m.HashList[len(m.HashList)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index 8deaaf0e1b..dbbae8294f 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -746,10 +746,6 @@ func (k Keeper) CallEVMWithData( return nil, err } - if res.Failed() { - return res, cosmoserrors.Wrap(evmtypes.ErrVMExecution, fmt.Sprintf("%s: ret 0x%x", res.VmError, res.Ret)) - } - // Emit events and log for the transaction if it is committed if commit { msgBytes, err := json.Marshal(msg) @@ -826,5 +822,9 @@ func (k Keeper) CallEVMWithData( } } + if res.Failed() { + return res, cosmoserrors.Wrapf(evmtypes.ErrVMExecution, "%s: ret 0x%x", res.VmError, res.Ret) + } + return res, nil } diff --git a/x/fungible/keeper/msg_server_remove_foreign_coin.go b/x/fungible/keeper/msg_server_remove_foreign_coin.go index 6903531cd3..dbbcf36fe6 100644 --- a/x/fungible/keeper/msg_server_remove_foreign_coin.go +++ b/x/fungible/keeper/msg_server_remove_foreign_coin.go @@ -24,7 +24,7 @@ func (k msgServer) RemoveForeignCoin( if err != nil { return nil, cosmoserrors.Wrap(authoritytypes.ErrUnauthorized, err.Error()) } - index := msg.Name + index := msg.Zrc20Address _, found := k.GetForeignCoins(ctx, index) if !found { return nil, cosmoserrors.Wrapf(sdkerrors.ErrInvalidRequest, "foreign coin not found") diff --git a/x/fungible/types/message_remove_foreign_coin.go b/x/fungible/types/message_remove_foreign_coin.go index 0c7282dc4a..7fc1781b76 100644 --- a/x/fungible/types/message_remove_foreign_coin.go +++ b/x/fungible/types/message_remove_foreign_coin.go @@ -10,10 +10,10 @@ const TypeMsgRemoveForeignCoin = "remove_foreign_coin" var _ sdk.Msg = &MsgRemoveForeignCoin{} -func NewMsgRemoveForeignCoin(creator string, name string) *MsgRemoveForeignCoin { +func NewMsgRemoveForeignCoin(creator string, zrc20Address string) *MsgRemoveForeignCoin { return &MsgRemoveForeignCoin{ - Creator: creator, - Name: name, + Creator: creator, + Zrc20Address: zrc20Address, } } diff --git a/x/fungible/types/tx.pb.go b/x/fungible/types/tx.pb.go index f72a00bc24..8409ef6f36 100644 --- a/x/fungible/types/tx.pb.go +++ b/x/fungible/types/tx.pb.go @@ -473,8 +473,8 @@ func (m *MsgDeployFungibleCoinZRC20Response) GetAddress() string { } type MsgRemoveForeignCoin struct { - Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` + Zrc20Address string `protobuf:"bytes,2,opt,name=zrc20_address,json=zrc20Address,proto3" json:"zrc20_address,omitempty"` } func (m *MsgRemoveForeignCoin) Reset() { *m = MsgRemoveForeignCoin{} } @@ -517,9 +517,9 @@ func (m *MsgRemoveForeignCoin) GetCreator() string { return "" } -func (m *MsgRemoveForeignCoin) GetName() string { +func (m *MsgRemoveForeignCoin) GetZrc20Address() string { if m != nil { - return m.Name + return m.Zrc20Address } return "" } @@ -947,70 +947,69 @@ func init() { } var fileDescriptor_7bea9688d1d01113 = []byte{ - // 998 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xdd, 0x6e, 0xdb, 0x46, - 0x13, 0x35, 0xed, 0xf8, 0x6f, 0x3e, 0xcb, 0xf6, 0xb7, 0x50, 0x62, 0x86, 0x2e, 0xe4, 0x84, 0x71, - 0x13, 0x37, 0x68, 0xc4, 0x54, 0x75, 0x1b, 0x14, 0x68, 0x52, 0xd4, 0x4a, 0xdc, 0x06, 0x88, 0x80, - 0x82, 0x71, 0x52, 0xd4, 0x37, 0xc4, 0x9a, 0x5c, 0x53, 0x84, 0xa5, 0x5d, 0x96, 0x4b, 0x55, 0x51, - 0xee, 0x0a, 0xf4, 0x2a, 0x40, 0x81, 0x00, 0x7d, 0x80, 0x3e, 0x43, 0xdf, 0x22, 0x97, 0xb9, 0x2c, - 0x82, 0x22, 0x28, 0xec, 0x17, 0x29, 0x76, 0xf9, 0x63, 0x52, 0x12, 0x25, 0x59, 0xed, 0x8d, 0xcd, - 0x1d, 0x9d, 0x39, 0x7b, 0x66, 0x76, 0x66, 0x96, 0x84, 0xed, 0x97, 0x24, 0xc4, 0x76, 0x13, 0x7b, - 0xd4, 0x90, 0x4f, 0x2c, 0x20, 0xc6, 0x71, 0x87, 0xba, 0xde, 0x51, 0x8b, 0x18, 0xe1, 0x8b, 0xaa, - 0x1f, 0xb0, 0x90, 0xa1, 0xcd, 0x14, 0x55, 0x4d, 0x50, 0xd5, 0x04, 0xa5, 0x95, 0x5d, 0xe6, 0x32, - 0x89, 0x33, 0xc4, 0x53, 0xe4, 0xa2, 0xdd, 0x1c, 0x42, 0xec, 0x9f, 0xb8, 0x86, 0xcd, 0x3c, 0x2a, - 0xff, 0x44, 0x38, 0x7d, 0x17, 0xd4, 0x06, 0x77, 0x1f, 0x12, 0xbf, 0xc5, 0x7a, 0x4f, 0x7b, 0x3c, - 0x24, 0xed, 0x3a, 0xa3, 0x61, 0x80, 0xed, 0x90, 0x23, 0x15, 0x16, 0xed, 0x80, 0xe0, 0x90, 0x05, - 0xaa, 0x72, 0x4d, 0xd9, 0x59, 0x36, 0x93, 0xa5, 0xfe, 0x97, 0x02, 0xd7, 0x8a, 0xdc, 0x4c, 0xc2, - 0x7d, 0x46, 0x39, 0x41, 0xb7, 0x61, 0xbd, 0x43, 0x3d, 0xde, 0xc5, 0xfe, 0xf3, 0xda, 0x3e, 0xb6, - 0x43, 0x16, 0xf4, 0x62, 0x9e, 0x01, 0x3b, 0x2a, 0xc3, 0x7c, 0x57, 0xe8, 0x54, 0x67, 0x25, 0x20, - 0x5a, 0xa0, 0x1d, 0x58, 0x4b, 0x91, 0x26, 0xeb, 0x84, 0x24, 0x50, 0xe7, 0xe4, 0xef, 0xfd, 0x66, - 0xb4, 0x0d, 0x25, 0x9b, 0x51, 0x4a, 0x04, 0xdb, 0xe1, 0xa3, 0xe7, 0x0d, 0xf5, 0x92, 0xc4, 0xe5, - 0x8d, 0xe8, 0x26, 0xac, 0xf2, 0x9c, 0x58, 0x75, 0x5e, 0xc2, 0xfa, 0xac, 0xfa, 0xab, 0x59, 0xb8, - 0xda, 0xe0, 0xee, 0x33, 0xdf, 0xc1, 0x21, 0x39, 0x34, 0xeb, 0xb5, 0xbb, 0xdf, 0x7b, 0x61, 0xd3, - 0x09, 0x70, 0x77, 0x9f, 0x90, 0xe2, 0xb4, 0xa0, 0x1b, 0x50, 0x7a, 0x19, 0xd8, 0xb5, 0xbb, 0x16, - 0x76, 0x9c, 0x80, 0x70, 0x1e, 0x47, 0xb3, 0x22, 0x8d, 0x5f, 0x47, 0x36, 0xf4, 0x03, 0xac, 0x53, - 0xd2, 0xb5, 0xba, 0x31, 0xa3, 0x75, 0x4c, 0x88, 0xba, 0x20, 0x70, 0x7b, 0xc6, 0x9b, 0xf7, 0x5b, - 0x33, 0xef, 0xde, 0x6f, 0xdd, 0x72, 0xbd, 0xb0, 0xd9, 0x39, 0xaa, 0xda, 0xac, 0x6d, 0xd8, 0x8c, - 0xb7, 0x19, 0x8f, 0xff, 0xdd, 0xe1, 0xce, 0x89, 0x11, 0xf6, 0x7c, 0xc2, 0xab, 0xcf, 0x3c, 0x1a, - 0x9a, 0xab, 0x94, 0x74, 0xb3, 0xca, 0x9e, 0x42, 0x49, 0x50, 0xbb, 0x98, 0x5b, 0x2d, 0xaf, 0xed, - 0x85, 0xea, 0xe2, 0x74, 0xbc, 0xff, 0xa3, 0xa4, 0xfb, 0x0d, 0xe6, 0x4f, 0x04, 0x87, 0x7e, 0x03, - 0xae, 0x17, 0xe6, 0x22, 0x39, 0x6b, 0x3d, 0x80, 0x8d, 0x14, 0x94, 0xaf, 0x87, 0x11, 0xe9, 0xba, - 0x0f, 0x9b, 0x42, 0x6e, 0x94, 0x7c, 0xcb, 0x8e, 0x1d, 0xfa, 0x92, 0xa7, 0x52, 0xd2, 0xcd, 0x33, - 0xc6, 0x89, 0xd4, 0xaf, 0xc3, 0x56, 0xc1, 0x9e, 0xa9, 0xac, 0xdf, 0x67, 0x41, 0x4b, 0xeb, 0x74, - 0x3f, 0xee, 0x98, 0x3a, 0xf3, 0xa8, 0x0c, 0x64, 0x84, 0xb4, 0x32, 0xcc, 0x3f, 0x12, 0x90, 0xa4, - 0x1e, 0xe5, 0x02, 0xed, 0xc0, 0xfa, 0x31, 0x0b, 0x88, 0xe7, 0x52, 0x4b, 0xb6, 0x96, 0xe5, 0x39, - 0xb2, 0x20, 0xe7, 0xcc, 0xd5, 0xd8, 0x5e, 0x17, 0xe6, 0xc7, 0x0e, 0xd2, 0x60, 0xc9, 0x21, 0xb6, - 0xd7, 0xc6, 0x2d, 0x2e, 0x4b, 0xb1, 0x64, 0xa6, 0x6b, 0x84, 0xe0, 0x12, 0xc5, 0x6d, 0x12, 0xd7, - 0x9e, 0x7c, 0x46, 0x57, 0x60, 0x81, 0xf7, 0xda, 0x47, 0xac, 0x15, 0x95, 0x82, 0x19, 0xaf, 0xd0, - 0x1e, 0x2c, 0x8b, 0x66, 0xb5, 0xc4, 0xe1, 0xc8, 0xd3, 0x5c, 0xad, 0x7d, 0x58, 0x1d, 0x32, 0x0d, - 0xfc, 0x13, 0xb7, 0x2a, 0xbb, 0x5a, 0x04, 0x77, 0xd0, 0xf3, 0x89, 0xb9, 0x64, 0xc7, 0x4f, 0x68, - 0x13, 0x96, 0xcf, 0x2b, 0x62, 0x49, 0xca, 0x5d, 0x72, 0x93, 0xd3, 0x7d, 0x00, 0x7a, 0x71, 0x82, - 0xd2, 0x56, 0x56, 0x61, 0x31, 0x39, 0x95, 0x38, 0x51, 0xf1, 0x52, 0x7f, 0x08, 0xe5, 0x06, 0x77, - 0x4d, 0xd2, 0x66, 0x3f, 0x91, 0xfd, 0x38, 0x07, 0xcc, 0xa3, 0x23, 0x52, 0x9b, 0x84, 0x3f, 0x7b, - 0x1e, 0xbe, 0x5e, 0x81, 0x0f, 0x86, 0xb1, 0xa4, 0xe7, 0xf8, 0x8b, 0x92, 0x69, 0xc8, 0xe4, 0x94, - 0xf7, 0x7a, 0x21, 0xb1, 0x99, 0x33, 0xaa, 0x21, 0x3f, 0x82, 0xf5, 0x82, 0xb2, 0x5a, 0xb3, 0xf3, - 0xd5, 0x84, 0xf4, 0xa8, 0x77, 0x04, 0xa1, 0xd5, 0xc4, 0xbc, 0x19, 0x4f, 0x1a, 0xd1, 0x0a, 0x75, - 0xe6, 0x90, 0x6f, 0x31, 0x6f, 0xe6, 0x5a, 0xa1, 0x5f, 0x45, 0xaa, 0xf5, 0x0f, 0x45, 0xd6, 0x5c, - 0xa6, 0x61, 0x9e, 0x78, 0x3f, 0x76, 0x3c, 0xc7, 0x0b, 0x7b, 0x75, 0xec, 0xff, 0xdb, 0xe9, 0x71, - 0x00, 0xa5, 0x56, 0x42, 0x67, 0xd9, 0xd8, 0x8f, 0x64, 0x5e, 0xbc, 0xc5, 0x57, 0x5a, 0x19, 0x51, - 0xfa, 0xb6, 0xac, 0x82, 0x02, 0xc9, 0x69, 0x64, 0x26, 0x94, 0x1a, 0xdc, 0xfd, 0x0e, 0x77, 0x38, - 0x19, 0xd7, 0x3f, 0xb7, 0x60, 0x2d, 0x17, 0x0b, 0x11, 0xd1, 0xcc, 0x89, 0x51, 0x9b, 0x8d, 0x86, - 0x70, 0x7d, 0x03, 0x2e, 0xe7, 0x38, 0xd3, 0xcd, 0x0e, 0x60, 0x4d, 0x48, 0xa2, 0xfe, 0x7f, 0xba, - 0xdd, 0xd5, 0x68, 0x4e, 0x65, 0x58, 0x93, 0x0d, 0x6b, 0xef, 0x96, 0x61, 0xae, 0xc1, 0x5d, 0xf4, - 0xab, 0x02, 0x97, 0x87, 0xdf, 0x87, 0x9f, 0x55, 0x47, 0xdc, 0xc3, 0xd5, 0xa2, 0xfb, 0x50, 0xbb, - 0x3f, 0x95, 0x5b, 0xda, 0x7b, 0xbf, 0x29, 0xb0, 0x51, 0x34, 0xc0, 0xee, 0x4d, 0x46, 0x3d, 0xe0, - 0xa8, 0x7d, 0x35, 0xa5, 0x63, 0xaa, 0xea, 0x67, 0x05, 0xfe, 0x3f, 0xd8, 0xf5, 0x9f, 0x8c, 0xa3, - 0x1d, 0x70, 0xd1, 0xbe, 0xb8, 0xb0, 0x4b, 0xaa, 0xe1, 0x95, 0x02, 0xe5, 0xa1, 0x57, 0xce, 0xee, - 0x38, 0xce, 0x61, 0x5e, 0xda, 0x97, 0xd3, 0x78, 0xa5, 0x62, 0x5e, 0x2b, 0x70, 0xa5, 0x60, 0x3e, - 0x7d, 0x3e, 0x19, 0x71, 0xbf, 0x9f, 0xf6, 0x60, 0x3a, 0xbf, 0x21, 0x92, 0x06, 0xde, 0x61, 0x26, - 0x94, 0xd4, 0xef, 0x37, 0xa9, 0xa4, 0xa2, 0xf7, 0x04, 0x59, 0xcc, 0x45, 0x93, 0xf1, 0xde, 0x05, - 0xb8, 0xb3, 0x8e, 0xe3, 0x8b, 0x79, 0xcc, 0x60, 0x43, 0x2d, 0x80, 0xcc, 0x54, 0xbb, 0x3d, 0x8e, - 0xee, 0x1c, 0xab, 0xd5, 0x26, 0xc7, 0xa6, 0xbb, 0x05, 0xb0, 0x92, 0x1b, 0x6b, 0x1f, 0x8f, 0x95, - 0x9f, 0x41, 0x6b, 0xbb, 0x17, 0x41, 0x27, 0x7b, 0xee, 0x3d, 0x7e, 0x73, 0x5a, 0x51, 0xde, 0x9e, - 0x56, 0x94, 0xbf, 0x4f, 0x2b, 0xca, 0xeb, 0xb3, 0xca, 0xcc, 0xdb, 0xb3, 0xca, 0xcc, 0x9f, 0x67, - 0x95, 0x99, 0x43, 0x23, 0x73, 0x63, 0x08, 0xbe, 0x3b, 0x7d, 0x1f, 0x0d, 0x2f, 0x32, 0xdf, 0x23, - 0xe2, 0xfa, 0x38, 0x5a, 0x90, 0x1f, 0x0e, 0x9f, 0xfe, 0x13, 0x00, 0x00, 0xff, 0xff, 0x6a, 0x35, - 0xf0, 0xc9, 0xbb, 0x0c, 0x00, 0x00, + // 992 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xdd, 0x6e, 0xdb, 0x36, + 0x14, 0x8e, 0x92, 0xe6, 0xef, 0x2c, 0x4e, 0x32, 0xc2, 0x6d, 0x54, 0x65, 0x70, 0x5a, 0x35, 0x6b, + 0xb3, 0x62, 0xb5, 0x3a, 0x2f, 0x5b, 0x31, 0x60, 0xed, 0xb0, 0x78, 0xcd, 0x56, 0xa0, 0x06, 0x06, + 0x35, 0xe9, 0xb0, 0xdc, 0x08, 0x8c, 0xc4, 0xc8, 0x42, 0x6c, 0x52, 0x13, 0xe5, 0xb9, 0xee, 0xdd, + 0x80, 0x5d, 0x15, 0x18, 0x50, 0x60, 0x0f, 0xb0, 0x67, 0xd8, 0x5b, 0xf4, 0xb2, 0x97, 0x43, 0x31, + 0x14, 0x43, 0xf2, 0x22, 0x03, 0xa9, 0x9f, 0x4a, 0xb6, 0x15, 0x3b, 0x4e, 0x6f, 0x12, 0x91, 0xfe, + 0xce, 0xc7, 0x8f, 0x87, 0xe7, 0x3b, 0x94, 0x60, 0xf3, 0x39, 0x09, 0xb1, 0xdd, 0xc4, 0x1e, 0x35, + 0xe4, 0x13, 0x0b, 0x88, 0x71, 0xd4, 0xa1, 0xae, 0x77, 0xd8, 0x22, 0x46, 0xf8, 0xac, 0xea, 0x07, + 0x2c, 0x64, 0x68, 0x3d, 0x45, 0x55, 0x13, 0x54, 0x35, 0x41, 0x69, 0x65, 0x97, 0xb9, 0x4c, 0xe2, + 0x0c, 0xf1, 0x14, 0x85, 0x68, 0x37, 0x87, 0x10, 0xfb, 0xc7, 0xae, 0x61, 0x33, 0x8f, 0xca, 0x3f, + 0x11, 0x4e, 0xdf, 0x06, 0xb5, 0xc1, 0xdd, 0xef, 0x88, 0xdf, 0x62, 0xbd, 0x27, 0x3d, 0x1e, 0x92, + 0x76, 0x9d, 0xd1, 0x30, 0xc0, 0x76, 0xc8, 0x91, 0x0a, 0xf3, 0x76, 0x40, 0x70, 0xc8, 0x02, 0x55, + 0xb9, 0xa6, 0x6c, 0x2d, 0x9a, 0xc9, 0x50, 0xff, 0x57, 0x81, 0x6b, 0x45, 0x61, 0x26, 0xe1, 0x3e, + 0xa3, 0x9c, 0xa0, 0xdb, 0xb0, 0xda, 0xa1, 0x1e, 0xef, 0x62, 0xff, 0x69, 0x6d, 0x17, 0xdb, 0x21, + 0x0b, 0x7a, 0x31, 0xcf, 0xc0, 0x3c, 0x2a, 0xc3, 0x6c, 0x57, 0xe8, 0x54, 0xa7, 0x25, 0x20, 0x1a, + 0xa0, 0x2d, 0x58, 0x49, 0x91, 0x26, 0xeb, 0x84, 0x24, 0x50, 0x67, 0xe4, 0xef, 0xfd, 0xd3, 0x68, + 0x13, 0x4a, 0x36, 0xa3, 0x94, 0x08, 0xb6, 0x83, 0x87, 0x4f, 0x1b, 0xea, 0x25, 0x89, 0xcb, 0x4f, + 0xa2, 0x9b, 0xb0, 0xcc, 0x73, 0x62, 0xd5, 0x59, 0x09, 0xeb, 0x9b, 0xd5, 0x5f, 0x4c, 0xc3, 0xd5, + 0x06, 0x77, 0xf7, 0x7d, 0x07, 0x87, 0xe4, 0xc0, 0xac, 0xd7, 0xee, 0xfe, 0xe4, 0x85, 0x4d, 0x27, + 0xc0, 0xdd, 0x5d, 0x42, 0x8a, 0xd3, 0x82, 0x6e, 0x40, 0xe9, 0x79, 0x60, 0xd7, 0xee, 0x5a, 0xd8, + 0x71, 0x02, 0xc2, 0x79, 0xbc, 0x9b, 0x25, 0x39, 0xf9, 0x6d, 0x34, 0x87, 0x7e, 0x86, 0x55, 0x4a, + 0xba, 0x56, 0x37, 0x66, 0xb4, 0x8e, 0x08, 0x51, 0xe7, 0x04, 0x6e, 0xc7, 0x78, 0xf5, 0x76, 0x63, + 0xea, 0xcd, 0xdb, 0x8d, 0x5b, 0xae, 0x17, 0x36, 0x3b, 0x87, 0x55, 0x9b, 0xb5, 0x0d, 0x9b, 0xf1, + 0x36, 0xe3, 0xf1, 0xbf, 0x3b, 0xdc, 0x39, 0x36, 0xc2, 0x9e, 0x4f, 0x78, 0x75, 0xdf, 0xa3, 0xa1, + 0xb9, 0x4c, 0x49, 0x37, 0xab, 0xec, 0x09, 0x94, 0x04, 0xb5, 0x8b, 0xb9, 0xd5, 0xf2, 0xda, 0x5e, + 0xa8, 0xce, 0x4f, 0xc6, 0xfb, 0x01, 0x25, 0xdd, 0xef, 0x31, 0x7f, 0x2c, 0x38, 0xf4, 0x1b, 0x70, + 0xbd, 0x30, 0x17, 0xc9, 0x59, 0xeb, 0x01, 0xac, 0xa5, 0xa0, 0x7c, 0x3d, 0x9c, 0x91, 0xae, 0xfb, + 0xb0, 0x2e, 0xe4, 0x46, 0xc9, 0xb7, 0xec, 0x38, 0xa0, 0x2f, 0x79, 0x2a, 0x25, 0xdd, 0x3c, 0x63, + 0x9c, 0x48, 0xfd, 0x3a, 0x6c, 0x14, 0xac, 0x99, 0xca, 0xfa, 0x6b, 0x1a, 0xb4, 0xb4, 0x4e, 0x77, + 0x63, 0xc7, 0xd4, 0x99, 0x47, 0xe5, 0x46, 0xce, 0x90, 0x56, 0x86, 0xd9, 0x87, 0x02, 0x92, 0xd4, + 0xa3, 0x1c, 0xa0, 0x2d, 0x58, 0x3d, 0x62, 0x01, 0xf1, 0x5c, 0x6a, 0x49, 0x6b, 0x59, 0x9e, 0x23, + 0x0b, 0x72, 0xc6, 0x5c, 0x8e, 0xe7, 0xeb, 0x62, 0xfa, 0x91, 0x83, 0x34, 0x58, 0x70, 0x88, 0xed, + 0xb5, 0x71, 0x8b, 0xcb, 0x52, 0x2c, 0x99, 0xe9, 0x18, 0x21, 0xb8, 0x44, 0x71, 0x9b, 0xc4, 0xb5, + 0x27, 0x9f, 0xd1, 0x15, 0x98, 0xe3, 0xbd, 0xf6, 0x21, 0x6b, 0x45, 0xa5, 0x60, 0xc6, 0x23, 0xb4, + 0x03, 0x8b, 0xc2, 0xac, 0x96, 0x38, 0x1c, 0x79, 0x9a, 0xcb, 0xb5, 0x8f, 0xab, 0x43, 0xba, 0x81, + 0x7f, 0xec, 0x56, 0xa5, 0xab, 0xc5, 0xe6, 0xf6, 0x7a, 0x3e, 0x31, 0x17, 0xec, 0xf8, 0x09, 0xad, + 0xc3, 0xe2, 0xbb, 0x8a, 0x58, 0x90, 0x72, 0x17, 0xdc, 0xe4, 0x74, 0x1f, 0x80, 0x5e, 0x9c, 0xa0, + 0xd4, 0xca, 0x2a, 0xcc, 0x27, 0xa7, 0x12, 0x27, 0x2a, 0x1e, 0xea, 0xfb, 0x50, 0x6e, 0x70, 0xd7, + 0x24, 0x6d, 0xf6, 0x2b, 0xd9, 0x8d, 0x73, 0xc0, 0x3c, 0x7a, 0x41, 0x93, 0xe8, 0x15, 0xf8, 0x68, + 0x18, 0x6d, 0x7a, 0xb0, 0xbf, 0x2b, 0x19, 0x87, 0x26, 0xc7, 0xbe, 0xd3, 0x0b, 0x89, 0xcd, 0x9c, + 0xb3, 0x1c, 0xfa, 0x09, 0xac, 0x16, 0xd4, 0xd9, 0x8a, 0x9d, 0x2f, 0x2f, 0xa4, 0x47, 0x66, 0x12, + 0x84, 0x56, 0x13, 0xf3, 0x66, 0xdc, 0x7a, 0x84, 0x37, 0xea, 0xcc, 0x21, 0x3f, 0x60, 0xde, 0xcc, + 0x79, 0xa3, 0x5f, 0x45, 0xaa, 0xf5, 0x6f, 0x45, 0x16, 0x61, 0xc6, 0x41, 0x8f, 0xbd, 0x5f, 0x3a, + 0x9e, 0xe3, 0x85, 0xbd, 0x3a, 0xf6, 0x2f, 0xda, 0x4e, 0xf6, 0xa0, 0xd4, 0x4a, 0xe8, 0x2c, 0x1b, + 0xfb, 0x91, 0xcc, 0xf3, 0x7b, 0x7e, 0xa9, 0x95, 0x11, 0xa5, 0x6f, 0xca, 0xb2, 0x28, 0x90, 0x9c, + 0xee, 0xcc, 0x84, 0x52, 0x83, 0xbb, 0x3f, 0xe2, 0x0e, 0x27, 0xa3, 0x0c, 0x75, 0x0b, 0x56, 0x72, + 0x7b, 0x21, 0x62, 0x37, 0x33, 0xa2, 0xf7, 0x66, 0x77, 0x43, 0xb8, 0xbe, 0x06, 0x97, 0x73, 0x9c, + 0xe9, 0x62, 0x7b, 0xb0, 0x22, 0x24, 0x51, 0xff, 0xbd, 0x2e, 0x77, 0x35, 0x6a, 0x5c, 0x19, 0xd6, + 0x64, 0xc1, 0xda, 0x9b, 0x45, 0x98, 0x69, 0x70, 0x17, 0xfd, 0xa1, 0xc0, 0xe5, 0xe1, 0x17, 0xe4, + 0x17, 0xd5, 0x33, 0x2e, 0xe6, 0x6a, 0xd1, 0x05, 0xa9, 0xdd, 0x9f, 0x28, 0x2c, 0x35, 0xe3, 0x9f, + 0x0a, 0xac, 0x15, 0x75, 0xb4, 0x7b, 0xe3, 0x51, 0x0f, 0x04, 0x6a, 0xdf, 0x4c, 0x18, 0x98, 0xaa, + 0xfa, 0x4d, 0x81, 0x0f, 0x07, 0xdb, 0xc0, 0x67, 0xa3, 0x68, 0x07, 0x42, 0xb4, 0xaf, 0xce, 0x1d, + 0x92, 0x6a, 0x78, 0xa1, 0x40, 0x79, 0xe8, 0x1d, 0xb4, 0x3d, 0x8a, 0x73, 0x58, 0x94, 0xf6, 0xf5, + 0x24, 0x51, 0xa9, 0x98, 0x97, 0x0a, 0x5c, 0x29, 0xe8, 0x4f, 0x5f, 0x8e, 0x47, 0xdc, 0x1f, 0xa7, + 0x3d, 0x98, 0x2c, 0x6e, 0x88, 0xa4, 0x81, 0x97, 0x9a, 0x31, 0x25, 0xf5, 0xc7, 0x8d, 0x2b, 0xa9, + 0xe8, 0xc5, 0x41, 0x16, 0x73, 0x51, 0x67, 0xbc, 0x77, 0x0e, 0xee, 0x6c, 0xe0, 0xe8, 0x62, 0x1e, + 0xd1, 0xd8, 0x50, 0x0b, 0x20, 0xd3, 0xd5, 0x6e, 0x8f, 0xa2, 0x7b, 0x87, 0xd5, 0x6a, 0xe3, 0x63, + 0xd3, 0xd5, 0x02, 0x58, 0xca, 0xb5, 0xb5, 0x4f, 0x47, 0xca, 0xcf, 0xa0, 0xb5, 0xed, 0xf3, 0xa0, + 0x93, 0x35, 0x77, 0x1e, 0xbd, 0x3a, 0xa9, 0x28, 0xaf, 0x4f, 0x2a, 0xca, 0x7f, 0x27, 0x15, 0xe5, + 0xe5, 0x69, 0x65, 0xea, 0xf5, 0x69, 0x65, 0xea, 0x9f, 0xd3, 0xca, 0xd4, 0x81, 0x91, 0xb9, 0x31, + 0x04, 0xdf, 0x9d, 0xbe, 0xaf, 0x88, 0x67, 0x99, 0x0f, 0x14, 0x71, 0x7d, 0x1c, 0xce, 0xc9, 0x2f, + 0x89, 0xcf, 0xff, 0x0f, 0x00, 0x00, 0xff, 0xff, 0xe6, 0xe8, 0x3c, 0x66, 0xcc, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1730,10 +1729,10 @@ func (m *MsgRemoveForeignCoin) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Name) > 0 { - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = encodeVarintTx(dAtA, i, uint64(len(m.Name))) + if len(m.Zrc20Address) > 0 { + i -= len(m.Zrc20Address) + copy(dAtA[i:], m.Zrc20Address) + i = encodeVarintTx(dAtA, i, uint64(len(m.Zrc20Address))) i-- dAtA[i] = 0x12 } @@ -2200,7 +2199,7 @@ func (m *MsgRemoveForeignCoin) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - l = len(m.Name) + l = len(m.Zrc20Address) if l > 0 { n += 1 + l + sovTx(uint64(l)) } @@ -3423,7 +3422,7 @@ func (m *MsgRemoveForeignCoin) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Zrc20Address", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3451,7 +3450,7 @@ func (m *MsgRemoveForeignCoin) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + m.Zrc20Address = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex diff --git a/x/observer/keeper/msg_server_update_observer.go b/x/observer/keeper/msg_server_update_observer.go index a1e4c134d1..f7686c4782 100644 --- a/x/observer/keeper/msg_server_update_observer.go +++ b/x/observer/keeper/msg_server_update_observer.go @@ -2,7 +2,6 @@ package keeper import ( "context" - "fmt" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -25,18 +24,16 @@ func (k msgServer) UpdateObserver( return nil, errorsmod.Wrap(types.ErrUpdateObserver, err.Error()) } if !ok { - return nil, errorsmod.Wrap( + return nil, errorsmod.Wrapf( types.ErrUpdateObserver, - fmt.Sprintf("Unable to update observer with update reason : %s", msg.UpdateReason), - ) + "Unable to update observer with update reason : %s", msg.UpdateReason) } // We do not use IsNonTombstonedObserver here because we want to allow tombstoned observers to be updated if !k.IsAddressPartOfObserverSet(ctx, msg.OldObserverAddress) { - return nil, errorsmod.Wrap( + return nil, errorsmod.Wrapf( types.ErrNotObserver, - fmt.Sprintf("Observer address is not authorized : %s", msg.OldObserverAddress), - ) + "Observer address is not authorized : %s", msg.OldObserverAddress) } err = k.IsValidator(ctx, msg.NewObserverAddress) @@ -53,10 +50,9 @@ func (k msgServer) UpdateObserver( // Update the node account with the new operator address nodeAccount, found := k.GetNodeAccount(ctx, msg.OldObserverAddress) if !found { - return nil, errorsmod.Wrap( + return nil, errorsmod.Wrapf( types.ErrNodeAccountNotFound, - fmt.Sprintf("Observer node account not found : %s", msg.OldObserverAddress), - ) + "Observer node account not found : %s", msg.OldObserverAddress) } newNodeAccount := nodeAccount newNodeAccount.Operator = msg.NewObserverAddress @@ -68,15 +64,15 @@ func (k msgServer) UpdateObserver( // Check LastBlockObserver count just to be safe observerSet, found := k.GetObserverSet(ctx) if !found { - return nil, errorsmod.Wrap(types.ErrObserverSetNotFound, fmt.Sprintf("Observer set not found")) + return nil, errorsmod.Wrap(types.ErrObserverSetNotFound, "Observer set not found") } totalObserverCountCurrentBlock := observerSet.LenUint() lastBlockCount, found := k.GetLastObserverCount(ctx) if !found { - return nil, errorsmod.Wrap(types.ErrLastObserverCountNotFound, fmt.Sprintf("Observer count not found")) + return nil, errorsmod.Wrap(types.ErrLastObserverCountNotFound, "Observer count not found") } if lastBlockCount.Count != totalObserverCountCurrentBlock { - return nil, errorsmod.Wrap(types.ErrUpdateObserver, fmt.Sprintf("Observer count mismatch")) + return nil, errorsmod.Wrap(types.ErrUpdateObserver, "Observer count mismatch") } return &types.MsgUpdateObserverResponse{}, nil } @@ -88,9 +84,7 @@ func (k Keeper) CheckUpdateReason(ctx sdk.Context, msg *types.MsgUpdateObserver) if msg.Creator != msg.OldObserverAddress { return false, errorsmod.Wrap( types.ErrUpdateObserver, - fmt.Sprintf( - "Creator address and old observer address need to be same for updating tombstoned observer", - ), + "Creator address and old observer address need to be same for updating tombstoned observer", ) } return k.IsOperatorTombstoned(ctx, msg.Creator) diff --git a/x/observer/keeper/msg_server_vote_blame.go b/x/observer/keeper/msg_server_vote_blame.go index 217d5e5912..61cbce01db 100644 --- a/x/observer/keeper/msg_server_vote_blame.go +++ b/x/observer/keeper/msg_server_vote_blame.go @@ -2,63 +2,59 @@ package keeper import ( "context" - "fmt" - cosmoserrors "cosmossdk.io/errors" + sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" crosschainTypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/x/observer/types" ) +const voteBlameID = "Vote Blame" + func (k msgServer) VoteBlame( goCtx context.Context, - vote *types.MsgVoteBlame, + msg *types.MsgVoteBlame, ) (*types.MsgVoteBlameResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - observationType := types.ObservationType_TSSKeySign // GetChainFromChainID makes sure we are getting only supported chains , if a chain support has been turned on using gov proposal, this function returns nil - observationChain, found := k.GetSupportedChainFromChainID(ctx, vote.ChainId) + observationChain, found := k.GetSupportedChainFromChainID(ctx, msg.ChainId) if !found { - return nil, cosmoserrors.Wrap( + return nil, sdkerrors.Wrapf( crosschainTypes.ErrUnsupportedChain, - fmt.Sprintf("ChainID %d, Blame vote", vote.ChainId), - ) + "%s, ChainID %d", voteBlameID, msg.ChainId) } - if ok := k.IsNonTombstonedObserver(ctx, vote.Creator); !ok { - return nil, types.ErrNotObserver + if ok := k.IsNonTombstonedObserver(ctx, msg.Creator); !ok { + return nil, sdkerrors.Wrap( + types.ErrNotObserver, voteBlameID) } - index := vote.Digest() - // Add votes and Set Ballot - // GetBallot checks against the supported chains list before querying for Ballot - ballot, isNew, err := k.FindBallot(ctx, index, observationChain, observationType) + ballot, isFinalized, isNew, err := k.VoteOnBallot( + ctx, + observationChain, + msg.Digest(), + types.ObservationType_TSSKeySign, + msg.Creator, + types.VoteType_SuccessObservation, + ) if err != nil { - return nil, err + return nil, sdkerrors.Wrapf( + err, + "%s, BallotIdentifier %v", voteBlameID, ballot.BallotIdentifier) } if isNew { - EmitEventBallotCreated(ctx, ballot, vote.BlameInfo.Index, observationChain.String()) + EmitEventBallotCreated(ctx, ballot, msg.BlameInfo.Index, observationChain.String()) } - // AddVoteToBallot adds a vote and sets the ballot - ballot, err = k.AddVoteToBallot(ctx, ballot, vote.Creator, types.VoteType_SuccessObservation) - if err != nil { - return nil, err - } - - _, isFinalized := k.CheckIfFinalizingVote(ctx, ballot) if !isFinalized { - // Return nil here to add vote to ballot and commit state + // Return nil here to add vote to ballot and commit state. return &types.MsgVoteBlameResponse{}, nil } - // ****************************************************************************** - // below only happens when ballot is finalized: exactly when threshold vote is in - // ****************************************************************************** - - k.SetBlame(ctx, vote.BlameInfo) + // Ballot is finalized: exactly when threshold vote is in. + k.SetBlame(ctx, msg.BlameInfo) return &types.MsgVoteBlameResponse{}, nil } diff --git a/x/observer/keeper/msg_server_vote_block_header.go b/x/observer/keeper/msg_server_vote_block_header.go index 9ae7a35a56..b4e0d57d0d 100644 --- a/x/observer/keeper/msg_server_vote_block_header.go +++ b/x/observer/keeper/msg_server_vote_block_header.go @@ -3,13 +3,15 @@ package keeper import ( "context" - cosmoserrors "cosmossdk.io/errors" + sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" "github.com/zeta-chain/zetacore/x/observer/types" ) +const voteBlockHeaderID = "Vote BlockHeader" + // VoteBlockHeader vote for a new block header to the storers func (k msgServer) VoteBlockHeader( goCtx context.Context, @@ -20,30 +22,36 @@ func (k msgServer) VoteBlockHeader( // check if the chain is enabled chain, found := k.GetSupportedChainFromChainID(ctx, msg.ChainId) if !found { - return nil, cosmoserrors.Wrapf(types.ErrSupportedChains, "chain id: %d", msg.ChainId) + return nil, sdkerrors.Wrapf( + types.ErrSupportedChains, + "%s, ChainID %d", voteBlockHeaderID, msg.ChainId) } // check if observer if ok := k.IsNonTombstonedObserver(ctx, msg.Creator); !ok { - return nil, types.ErrNotObserver + return nil, sdkerrors.Wrap(types.ErrNotObserver, voteBlockHeaderID) } // check the new block header is valid parentHash, err := k.lightclientKeeper.CheckNewBlockHeader(ctx, msg.ChainId, msg.BlockHash, msg.Height, msg.Header) if err != nil { - return nil, cosmoserrors.Wrap(lightclienttypes.ErrInvalidBlockHeader, err.Error()) + return nil, sdkerrors.Wrapf( + lightclienttypes.ErrInvalidBlockHeader, + "%s, parent hash %s", voteBlockHeaderID, parentHash) } - // add vote to ballot - ballot, isNew, err := k.FindBallot(ctx, msg.Digest(), chain, types.ObservationType_InboundTx) - if err != nil { - return nil, cosmoserrors.Wrap(err, "failed to find ballot") - } - ballot, err = k.AddVoteToBallot(ctx, ballot, msg.Creator, types.VoteType_SuccessObservation) + _, isFinalized, isNew, err := k.VoteOnBallot( + ctx, + chain, + msg.Digest(), + types.ObservationType_InboundTx, + msg.Creator, + types.VoteType_SuccessObservation, + ) if err != nil { - return nil, cosmoserrors.Wrap(err, "failed to add vote to ballot") + return nil, sdkerrors.Wrap(err, voteBlockHeaderID) } - _, isFinalized := k.CheckIfFinalizingVote(ctx, ballot) + if !isFinalized { return &types.MsgVoteBlockHeaderResponse{ BallotCreated: isNew, @@ -51,9 +59,8 @@ func (k msgServer) VoteBlockHeader( }, nil } - // add the new block header to the store + // Add the new block header to the store. k.lightclientKeeper.AddBlockHeader(ctx, msg.ChainId, msg.Height, msg.BlockHash, msg.Header, parentHash) - return &types.MsgVoteBlockHeaderResponse{ BallotCreated: isNew, VoteFinalized: true, diff --git a/x/observer/keeper/msg_server_vote_tss.go b/x/observer/keeper/msg_server_vote_tss.go index ae40c41444..617411d575 100644 --- a/x/observer/keeper/msg_server_vote_tss.go +++ b/x/observer/keeper/msg_server_vote_tss.go @@ -2,7 +2,6 @@ package keeper import ( "context" - "fmt" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -13,6 +12,8 @@ import ( "github.com/zeta-chain/zetacore/x/observer/types" ) +const voteTSSid = "Vote TSS" + // VoteTSS votes on creating a TSS key and recording the information about it (public // key, participant and operator addresses, finalized and keygen heights). // @@ -26,37 +27,38 @@ import ( func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types.MsgVoteTSSResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - // checks whether a signer is authorized to sign , by checking their address against the observer mapper which contains the observer list for the chain and type + // Checks whether a signer is authorized to sign, by checking their address against the observer mapper + // which contains the observer list for the chain and type. _, found := k.GetNodeAccount(ctx, msg.Creator) if !found { - return nil, errorsmod.Wrap( + return nil, errorsmod.Wrapf( sdkerrors.ErrorInvalidSigner, - fmt.Sprintf("signer %s does not have a node account set", msg.Creator), - ) + "%s, signer %s does not have a node account set", voteTSSid, msg.Creator) } - // no need to create a ballot if keygen does not exist + + // No need to create a ballot if keygen does not exist. keygen, found := k.GetKeygen(ctx) if !found { - return &types.MsgVoteTSSResponse{}, types.ErrKeygenNotFound + return &types.MsgVoteTSSResponse{}, errorsmod.Wrap(types.ErrKeygenNotFound, voteTSSid) } - // use a separate transaction to update KEYGEN status to pending when trying to change the TSS address + + // Use a separate transaction to update keygen status to pending when trying to change the TSS address. if keygen.Status == types.KeygenStatus_KeyGenSuccess { - return &types.MsgVoteTSSResponse{}, types.ErrKeygenCompleted + return &types.MsgVoteTSSResponse{}, errorsmod.Wrap(types.ErrKeygenCompleted, voteTSSid) } - // add votes and set Ballot - // GetBallot checks against the supported chains list before querying for Ballot - // TODO : https://github.com/zeta-chain/node/issues/896 + // GetBallot checks against the supported chains list before querying for Ballot. ballotCreated := false index := msg.Digest() ballot, found := k.GetBallot(ctx, index) if !found { - // if ballot does not exist, create a new ballot + // If ballot does not exist, create a new ballot. var voterList []string for _, nodeAccount := range k.GetAllNodeAccount(ctx) { voterList = append(voterList, nodeAccount.Operator) } + ballot = types.Ballot{ Index: "", BallotIdentifier: index, @@ -73,29 +75,27 @@ func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types ballotCreated = true } - // vote the ballot - var err error vote := types.VoteType_SuccessObservation if msg.Status == chains.ReceiveStatus_failed { vote = types.VoteType_FailureObservation } - ballot, err = k.AddVoteToBallot(ctx, ballot, msg.Creator, vote) + + ballot, err := k.AddVoteToBallot(ctx, ballot, msg.Creator, vote) if err != nil { - return &types.MsgVoteTSSResponse{}, err + return &types.MsgVoteTSSResponse{}, errorsmod.Wrap(err, voteTSSid) } - // returns here if the ballot is not finalized ballot, isFinalized := k.CheckIfFinalizingVote(ctx, ballot) if !isFinalized { return &types.MsgVoteTSSResponse{ - VoteFinalized: false, + VoteFinalized: isFinalized, BallotCreated: ballotCreated, KeygenSuccess: false, }, nil } - // set TSS only on success, set Keygen either way. - // keygen block can be updated using a policy transaction if keygen fails + // Set TSS only on success, set keygen either way. + // Keygen block can be updated using a policy transaction if keygen fails. keygenSuccess := false if ballot.BallotStatus == types.BallotStatus_BallotFinalized_FailureObservation { keygen.Status = types.KeygenStatus_KeyGenFailed @@ -108,8 +108,9 @@ func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types FinalizedZetaHeight: ctx.BlockHeight(), KeyGenZetaHeight: msg.KeygenZetaHeight, } - // set TSS history only, current TSS is updated via admin transaction - // in Case this is the first TSS address update both current and history + + // Set TSS history only, current TSS is updated via admin transaction. + // In the case this is the first TSS address update both current and history. tssList := k.GetAllTSS(ctx) if len(tssList) == 0 { k.SetTssAndUpdateNonce(ctx, tss) @@ -123,7 +124,7 @@ func (k msgServer) VoteTSS(goCtx context.Context, msg *types.MsgVoteTSS) (*types k.SetKeygen(ctx, keygen) return &types.MsgVoteTSSResponse{ - VoteFinalized: true, + VoteFinalized: isFinalized, BallotCreated: ballotCreated, KeygenSuccess: keygenSuccess, }, nil diff --git a/x/observer/keeper/vote_inbound.go b/x/observer/keeper/vote_inbound.go index 676e93aeaa..2a2bc8a66b 100644 --- a/x/observer/keeper/vote_inbound.go +++ b/x/observer/keeper/vote_inbound.go @@ -1,10 +1,8 @@ package keeper import ( - "fmt" - + sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/observer/types" @@ -21,7 +19,7 @@ func (k Keeper) VoteOnInboundBallot( voter string, ballotIndex string, inboundHash string, -) (bool, bool, error) { +) (isFinalized bool, isNew bool, err error) { if !k.IsInboundEnabled(ctx) { return false, false, types.ErrInboundDisabled } @@ -31,11 +29,10 @@ func (k Keeper) VoteOnInboundBallot( // this function returns nil senderChain, found := k.GetSupportedChainFromChainID(ctx, senderChainID) if !found { - return false, false, sdkerrors.Wrap(types.ErrSupportedChains, fmt.Sprintf( + return false, false, sdkerrors.Wrapf(types.ErrSupportedChains, "ChainID %d, Observation %s", senderChainID, - types.ObservationType_InboundTx.String()), - ) + types.ObservationType_InboundTx.String()) } // checks the voter is authorized to vote on the observation chain @@ -46,11 +43,10 @@ func (k Keeper) VoteOnInboundBallot( // makes sure we are getting only supported chains receiverChain, found := k.GetSupportedChainFromChainID(ctx, receiverChainID) if !found { - return false, false, sdkerrors.Wrap(types.ErrSupportedChains, fmt.Sprintf( + return false, false, sdkerrors.Wrapf(types.ErrSupportedChains, "ChainID %d, Observation %s", receiverChainID, - types.ObservationType_InboundTx.String()), - ) + types.ObservationType_InboundTx.String()) } // check if we want to send ZETA to external chain, but there is no ZETA token. @@ -64,22 +60,21 @@ func (k Keeper) VoteOnInboundBallot( } } - // checks against the supported chains list before querying for Ballot - ballot, isNew, err := k.FindBallot(ctx, ballotIndex, senderChain, types.ObservationType_InboundTx) + ballot, isFinalized, isNew, err := k.VoteOnBallot( + ctx, + senderChain, + ballotIndex, + types.ObservationType_InboundTx, + voter, + types.VoteType_SuccessObservation, + ) if err != nil { - return false, false, err + return false, false, sdkerrors.Wrap(err, msgVoteOnBallot) } + if isNew { EmitEventBallotCreated(ctx, ballot, inboundHash, senderChain.String()) } - // adds a vote and sets the ballot - ballot, err = k.AddVoteToBallot(ctx, ballot, voter, types.VoteType_SuccessObservation) - if err != nil { - return false, isNew, err - } - - // checks if the ballot is finalized - _, isFinalized := k.CheckIfFinalizingVote(ctx, ballot) return isFinalized, isNew, nil } diff --git a/x/observer/keeper/vote_outbound.go b/x/observer/keeper/vote_outbound.go index a8212980ed..5713ee2bf1 100644 --- a/x/observer/keeper/vote_outbound.go +++ b/x/observer/keeper/vote_outbound.go @@ -1,6 +1,7 @@ package keeper import ( + sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/pkg/chains" @@ -36,18 +37,17 @@ func (k Keeper) VoteOnOutboundBallot( return false, false, ballot, "", observertypes.ErrNotObserver } - // fetch or create ballot - ballot, isNew, err = k.FindBallot(ctx, ballotIndex, observationChain, observertypes.ObservationType_OutboundTx) + ballot, isFinalized, isNew, err = k.VoteOnBallot( + ctx, + observationChain, + ballotIndex, + observertypes.ObservationType_OutboundTx, + voter, + observertypes.ConvertReceiveStatusToVoteType(receiveStatus), + ) if err != nil { - return false, false, ballot, "", err + return false, false, ballot, "", sdkerrors.Wrap(err, msgVoteOnBallot) } - // add vote to ballot - ballot, err = k.AddVoteToBallot(ctx, ballot, voter, observertypes.ConvertReceiveStatusToVoteType(receiveStatus)) - if err != nil { - return false, false, ballot, "", err - } - - ballot, isFinalizedInThisBlock := k.CheckIfFinalizingVote(ctx, ballot) - return isFinalizedInThisBlock, isNew, ballot, observationChain.String(), nil + return isFinalized, isNew, ballot, observationChain.String(), nil } diff --git a/x/observer/keeper/utils.go b/x/observer/keeper/voting.go similarity index 81% rename from x/observer/keeper/utils.go rename to x/observer/keeper/voting.go index 3215a86307..d340f0bf32 100644 --- a/x/observer/keeper/utils.go +++ b/x/observer/keeper/voting.go @@ -3,12 +3,17 @@ package keeper import ( "fmt" + sdkerrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/x/observer/types" ) +const ( + msgVoteOnBallot = "error while voting on ballot" +) + func (k Keeper) AddVoteToBallot( ctx sdk.Context, ballot types.Ballot, @@ -147,3 +152,33 @@ func (k Keeper) CheckObserverSelfDelegation(ctx sdk.Context, accAddress string) } return nil } + +// VoteOnBallot finds a ballot or creates a new one if not found, +// and casts a vote on it. Then proceed to check if the vote has been finalized. +// This function holds generic logic for all types of votes. +func (k Keeper) VoteOnBallot( + ctx sdk.Context, + chain chains.Chain, + ballotIndex string, + observationType types.ObservationType, + voter string, + voteType types.VoteType, +) ( + ballot types.Ballot, + isFinalized bool, + isNew bool, + err error) { + ballot, isNew, err = k.FindBallot(ctx, ballotIndex, chain, observationType) + if err != nil { + return ballot, false, false, sdkerrors.Wrap(err, msgVoteOnBallot) + } + + ballot, err = k.AddVoteToBallot(ctx, ballot, voter, voteType) + if err != nil { + return ballot, false, isNew, sdkerrors.Wrap(err, msgVoteOnBallot) + } + + ballot, isFinalized = k.CheckIfFinalizingVote(ctx, ballot) + + return ballot, isFinalized, isNew, nil +} diff --git a/x/observer/keeper/utils_test.go b/x/observer/keeper/voting_test.go similarity index 63% rename from x/observer/keeper/utils_test.go rename to x/observer/keeper/voting_test.go index effd815ad5..d50e6e5f80 100644 --- a/x/observer/keeper/utils_test.go +++ b/x/observer/keeper/voting_test.go @@ -320,3 +320,242 @@ func TestKeeper_FindBallot(t *testing.T) { require.Error(t, err) }) } + +func TestKeeper_VoteOnBallot(t *testing.T) { + t.Run("fails if chain is not supported", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: false, + }, + }, + }) + + chain, _ := k.GetSupportedChainFromChainID(ctx, 0) + index := sample.ZetaIndex(t) + _, _, _, err := k.VoteOnBallot( + ctx, + chain, + index, + types.ObservationType_InboundTx, + sample.AccAddress(), + types.VoteType_SuccessObservation) + + require.ErrorIs(t, err, types.ErrSupportedChains) + }) + + t.Run("fails if the user can not add a vote", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + + chain, _ := k.GetSupportedChainFromChainID(ctx, getValidEthChainIDWithIndex(t, 0)) + index := sample.ZetaIndex(t) + _, _, _, err := k.VoteOnBallot( + ctx, + chain, + index, + types.ObservationType_InboundTx, + sample.AccAddress(), + types.VoteType_SuccessObservation) + + require.ErrorIs(t, err, types.ErrUnableToAddVote) + }) + + t.Run("user can create a ballot and add a vote", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + + voter := sample.AccAddress() + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{voter}, + }) + + chain, _ := k.GetSupportedChainFromChainID(ctx, getValidEthChainIDWithIndex(t, 0)) + index := sample.ZetaIndex(t) + ballot, isFinalized, isNew, err := k.VoteOnBallot( + ctx, + chain, + index, + types.ObservationType_InboundTx, + voter, + types.VoteType_SuccessObservation) + + require.NoError(t, err) + require.True(t, isFinalized) + require.True(t, isNew) + expectedBallot, found := k.GetBallot(ctx, index) + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) + + t.Run("user can create a ballot and add a vote, without finalizing ballot", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeper(t) + + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + BallotThreshold: threshold, + }, + }, + }) + + voter := sample.AccAddress() + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{ + voter, + sample.AccAddress(), + }, + }) + + chain, _ := k.GetSupportedChainFromChainID(ctx, getValidEthChainIDWithIndex(t, 0)) + index := sample.ZetaIndex(t) + ballot, isFinalized, isNew, err := k.VoteOnBallot( + ctx, + chain, + index, + types.ObservationType_InboundTx, + voter, + types.VoteType_SuccessObservation) + + require.NoError(t, err) + require.False(t, isFinalized) + require.True(t, isNew) + expectedBallot, found := k.GetBallot(ctx, index) + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) + + t.Run("user can add a vote to an existing ballot", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + + voter := sample.AccAddress() + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{voter}, + }) + + chain, _ := k.GetSupportedChainFromChainID(ctx, getValidEthChainIDWithIndex(t, 0)) + index := sample.ZetaIndex(t) + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + + ballot := types.Ballot{ + Index: index, + BallotIdentifier: index, + VoterList: []string{ + sample.AccAddress(), + sample.AccAddress(), + voter, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(5), + ObservationType: types.ObservationType_OutboundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + ballot, isFinalized, isNew, err := k.VoteOnBallot( + ctx, + chain, + index, + types.ObservationType_InboundTx, + voter, + types.VoteType_SuccessObservation) + + require.NoError(t, err) + require.False(t, isFinalized) + require.False(t, isNew) + expectedBallot, found := k.GetBallot(ctx, index) + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) + + t.Run("user can add a vote to an existing ballot, and finalize it", func(t *testing.T) { + k, ctx, _, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + + voter := sample.AccAddress() + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{voter}, + }) + + index := sample.ZetaIndex(t) + threshold, err := sdk.NewDecFromStr("0.1") + require.NoError(t, err) + + ballot := types.Ballot{ + Index: index, + BallotIdentifier: index, + VoterList: []string{ + sample.AccAddress(), + sample.AccAddress(), + voter, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(5), + ObservationType: types.ObservationType_OutboundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + chain, _ := k.GetSupportedChainFromChainID(ctx, getValidEthChainIDWithIndex(t, 0)) + ballot, isFinalized, isNew, err := k.VoteOnBallot( + ctx, + chain, + index, + types.ObservationType_InboundTx, + voter, + types.VoteType_SuccessObservation) + + require.NoError(t, err) + require.True(t, isFinalized) + require.False(t, isNew) + expectedBallot, found := k.GetBallot(ctx, index) + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) +} diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index ce0d74062a..47a8177cff 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -86,6 +86,7 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error { } // #nosec G115 checked positive lastBlock := uint64(cnt) + if lastBlock < ob.LastBlock() { return fmt.Errorf( "observeInboundBTC: block number should not decrease: current %d last %d", @@ -130,7 +131,6 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error { ob.logger.Inbound.Warn().Err(err).Msgf("observeInboundBTC: error posting block header %d", blockNumber) } } - if len(res.Block.Tx) > 1 { // get depositor fee depositorFee := bitcoin.CalcDepositorFee(res.Block, ob.Chain().ChainId, ob.netParams, ob.logger.Inbound) diff --git a/zetaclient/chains/bitcoin/observer/outbound.go b/zetaclient/chains/bitcoin/observer/outbound.go index 7bae9de963..c7c1c649d7 100644 --- a/zetaclient/chains/bitcoin/observer/outbound.go +++ b/zetaclient/chains/bitcoin/observer/outbound.go @@ -184,6 +184,11 @@ func (ob *Observer) IsOutboundProcessed( // It's safe to use cctx's amount to post confirmation because it has already been verified in observeOutbound() amountInSat := params.Amount.BigInt() if res.Confirmations < ob.ConfirmationsThreshold(amountInSat) { + ob.logger.Outbound.Debug(). + Int64("currentConfirmations", res.Confirmations). + Int64("requiredConfirmations", ob.ConfirmationsThreshold(amountInSat)). + Msg("IsOutboundProcessed: outbound not confirmed yet") + return true, false, nil } @@ -467,7 +472,6 @@ func (ob *Observer) setIncludedTx(nonce uint64, getTxResult *btcjson.GetTransact ob.Mu().Lock() defer ob.Mu().Unlock() res, found := ob.includedTxResults[outboundID] - if !found { // not found. ob.includedTxHashes[txHash] = true ob.includedTxResults[outboundID] = getTxResult // include new outbound and enforce rigid 1-to-1 mapping: nonce <===> txHash @@ -476,7 +480,7 @@ func (ob *Observer) setIncludedTx(nonce uint64, getTxResult *btcjson.GetTransact } ob.logger.Outbound.Info(). Msgf("setIncludedTx: included new bitcoin outbound %s outboundID %s pending nonce %d", txHash, outboundID, ob.pendingNonce) - } else if txHash == res.TxID { // found same hash. + } else if txHash == res.TxID { // found same hash ob.includedTxResults[outboundID] = getTxResult // update tx result as confirmations may increase if getTxResult.Confirmations > res.Confirmations { ob.logger.Outbound.Info().Msgf("setIncludedTx: bitcoin outbound %s got confirmations %d", txHash, getTxResult.Confirmations) diff --git a/zetaclient/chains/bitcoin/signer/signer.go b/zetaclient/chains/bitcoin/signer/signer.go index 93b2fac800..a6a5a3e51d 100644 --- a/zetaclient/chains/bitcoin/signer/signer.go +++ b/zetaclient/chains/bitcoin/signer/signer.go @@ -34,7 +34,7 @@ import ( const ( // the maximum number of inputs per outbound - maxNoOfInputsPerTx = 20 + MaxNoOfInputsPerTx = 20 // the rank below (or equal to) which we consolidate UTXOs consolidationRank = 10 @@ -198,7 +198,7 @@ func (signer *Signer) SignWithdrawTx( prevOuts, total, consolidatedUtxo, consolidatedValue, err := observer.SelectUTXOs( ctx, amount+estimateFee+float64(nonceMark)*1e-8, - maxNoOfInputsPerTx, + MaxNoOfInputsPerTx, nonce, consolidationRank, false, diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index 4f51e3354c..b0034f048e 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -806,7 +806,6 @@ func (ob *Observer) ObserveTSSReceiveInBlock(ctx context.Context, blockNumber ui if err != nil { return errors.Wrapf(err, "error getting block %d for chain %d", blockNumber, ob.Chain().ChainId) } - for i := range block.Transactions { tx := block.Transactions[i] if ethcommon.HexToAddress(tx.To) == ob.TSS().EVMAddress() { diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go index 54ac2aab1d..7b4f200d52 100644 --- a/zetaclient/chains/evm/observer/outbound.go +++ b/zetaclient/chains/evm/observer/outbound.go @@ -9,7 +9,6 @@ import ( "time" "cosmossdk.io/math" - "github.com/ethereum/go-ethereum" ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" @@ -390,11 +389,18 @@ func (ob *Observer) checkConfirmedTx( if err != nil { log.Error(). Err(err). - Msgf("confirmTxByHash: error getting transaction for outbound %s chain %d", txHash, ob.Chain().ChainId) + Str("function", "confirmTxByHash"). + Str("outboundTxHash", txHash). + Int64("chainID", ob.Chain().ChainId). + Msg("error getting transaction for outbound") return nil, nil, false } if transaction == nil { // should not happen - log.Error().Msgf("confirmTxByHash: transaction is nil for txHash %s nonce %d", txHash, nonce) + log.Error(). + Str("function", "confirmTxByHash"). + Str("outboundTxHash", txHash). + Uint64("nonce", nonce). + Msg("transaction is nil for txHash") return nil, nil, false } @@ -404,17 +410,51 @@ func (ob *Observer) checkConfirmedTx( if err != nil { log.Error(). Err(err). - Msgf("confirmTxByHash: local recovery of sender address failed for outbound %s chain %d", transaction.Hash().Hex(), ob.Chain().ChainId) + Str("function", "confirmTxByHash"). + Str("outboundTxHash", transaction.Hash().Hex()). + Int64("chainID", ob.Chain().ChainId). + Msg("local recovery of sender address failed for outbound") return nil, nil, false } if from != ob.TSS().EVMAddress() { // must be TSS address - log.Error().Msgf("confirmTxByHash: sender %s for outbound %s chain %d is not TSS address %s", - from.Hex(), transaction.Hash().Hex(), ob.Chain().ChainId, ob.TSS().EVMAddress().Hex()) - return nil, nil, false + // If from is not TSS address, check if it is one of the previous TSS addresses We can still try to confirm a tx which was broadcast by an old TSS + // This is to handle situations where the outbound has already been broad-casted by an older TSS address and the zetacore is waiting for the all the required block confirmations + // to go through before marking the cctx into a finalized state + + // TODO : improve this logic to verify that the correct TSS address is the from address. + // https://github.com/zeta-chain/node/issues/2487 + log.Info(). + Str("function", "confirmTxByHash"). + Str("sender", from.Hex()). + Str("outboundTxHash", transaction.Hash().Hex()). + Int64("chainID", ob.Chain().ChainId). + Str("currentTSSAddress", ob.TSS().EVMAddress().Hex()). + Msg("sender is not current TSS address") + addressList := ob.TSS().EVMAddressList() + isOldTssAddress := false + for _, addr := range addressList { + if from == addr { + isOldTssAddress = true + } + } + if !isOldTssAddress { + log.Error(). + Str("function", "confirmTxByHash"). + Str("sender", from.Hex()). + Str("outboundTxHash", transaction.Hash().Hex()). + Int64("chainID", ob.Chain().ChainId). + Str("currentTSSAddress", ob.TSS().EVMAddress().Hex()). + Msg("sender is not current or old TSS address") + return nil, nil, false + } } if transaction.Nonce() != nonce { // must match cctx nonce log.Error(). - Msgf("confirmTxByHash: outbound %s nonce mismatch: wanted %d, got tx nonce %d", txHash, nonce, transaction.Nonce()) + Str("function", "confirmTxByHash"). + Str("outboundTxHash", txHash). + Uint64("wantedNonce", nonce). + Uint64("gotTxNonce", transaction.Nonce()). + Msg("outbound nonce mismatch") return nil, nil, false } @@ -427,21 +467,41 @@ func (ob *Observer) checkConfirmedTx( // query receipt receipt, err := ob.evmClient.TransactionReceipt(ctx, ethcommon.HexToHash(txHash)) if err != nil { - if err != ethereum.NotFound { - log.Warn().Err(err).Msgf("confirmTxByHash: TransactionReceipt error, txHash %s nonce %d", txHash, nonce) - } + log.Error(). + Err(err). + Str("function", "confirmTxByHash"). + Str("outboundTxHash", txHash). + Uint64("nonce", nonce). + Msg("transactionReceipt error") return nil, nil, false } if receipt == nil { // should not happen - log.Error().Msgf("confirmTxByHash: receipt is nil for txHash %s nonce %d", txHash, nonce) + log.Error(). + Str("function", "confirmTxByHash"). + Str("outboundTxHash", txHash). + Uint64("nonce", nonce). + Msg("receipt is nil") return nil, nil, false } - + ob.LastBlock() // check confirmations - if !ob.HasEnoughConfirmations(receipt, ob.LastBlock()) { + lastHeight, err := ob.evmClient.BlockNumber(ctx) + if err != nil { + log.Error(). + Str("function", "confirmTxByHash"). + Err(err). + Int64("chainID", ob.GetChainParams().ChainId). + Msg("error getting block number for chain") + return nil, nil, false + } + if !ob.HasEnoughConfirmations(receipt, lastHeight) { log.Debug(). - Msgf("confirmTxByHash: txHash %s nonce %d included but not confirmed: receipt block %d, current block %d", - txHash, nonce, receipt.BlockNumber, ob.LastBlock()) + Str("function", "confirmTxByHash"). + Str("txHash", txHash). + Uint64("nonce", nonce). + Uint64("receiptBlock", receipt.BlockNumber.Uint64()). + Uint64("currentBlock", lastHeight). + Msg("txHash included but not confirmed") return nil, nil, false } @@ -449,7 +509,13 @@ func (ob *Observer) checkConfirmedTx( // Note: a guard for false BlockNumber in receipt. The blob-carrying tx won't come here err = ob.CheckTxInclusion(transaction, receipt) if err != nil { - log.Error().Err(err).Msgf("confirmTxByHash: checkTxInclusion error for txHash %s nonce %d", txHash, nonce) + log.Error(). + Err(err). + Str("function", "confirmTxByHash"). + Str("errorContext", "checkTxInclusion"). + Str("txHash", txHash). + Uint64("nonce", nonce). + Msg("checkTxInclusion error") return nil, nil, false } diff --git a/zetaclient/chains/interfaces/interfaces.go b/zetaclient/chains/interfaces/interfaces.go index 7ed2d7bb4d..5d6fa4f56f 100644 --- a/zetaclient/chains/interfaces/interfaces.go +++ b/zetaclient/chains/interfaces/interfaces.go @@ -214,6 +214,8 @@ type TSSSigner interface { SignBatch(ctx context.Context, digests [][]byte, height uint64, nonce uint64, chainID int64) ([][65]byte, error) EVMAddress() ethcommon.Address + + EVMAddressList() []ethcommon.Address BTCAddress() string BTCAddressWitnessPubkeyHash() *btcutil.AddressWitnessPubKeyHash PubKeyCompressedBytes() []byte diff --git a/zetaclient/testutils/mocks/tss_signer.go b/zetaclient/testutils/mocks/tss_signer.go index a7a9690293..b4f989aaf4 100644 --- a/zetaclient/testutils/mocks/tss_signer.go +++ b/zetaclient/testutils/mocks/tss_signer.go @@ -108,6 +108,10 @@ func (s *TSS) EVMAddress() ethcommon.Address { return crypto.PubkeyToAddress(s.PrivKey.PublicKey) } +func (s *TSS) EVMAddressList() []ethcommon.Address { + return []ethcommon.Address{s.EVMAddress()} +} + func (s *TSS) BTCAddress() string { // force use btcAddress if set if s.btcAddress != "" { diff --git a/zetaclient/tss/tss_signer.go b/zetaclient/tss/tss_signer.go index dc813eb784..7db6f27a8e 100644 --- a/zetaclient/tss/tss_signer.go +++ b/zetaclient/tss/tss_signer.go @@ -33,7 +33,7 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/config" - appcontext "github.com/zeta-chain/zetacore/zetaclient/context" + zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/keys" "github.com/zeta-chain/zetacore/zetaclient/metrics" ) @@ -89,41 +89,37 @@ type TSS struct { BitcoinChainID int64 } -// NewTSS creates a new TSS instance +// NewTSS creates a new TSS instance which can be used to sign transactions func NewTSS( ctx context.Context, - appContext *appcontext.AppContext, - peer p2p.AddrList, - privkey tmcrypto.PrivKey, - preParams *keygen.LocalPreParams, client interfaces.ZetacoreClient, tssHistoricalList []observertypes.TSS, bitcoinChainID int64, - tssPassword string, hotkeyPassword string, + tssServer *tss.TssServer, ) (*TSS, error) { logger := log.With().Str("module", "tss_signer").Logger() - server, err := SetupTSSServer(peer, privkey, preParams, appContext.Config(), tssPassword) + app, err := zctx.FromContext(ctx) if err != nil { - return nil, fmt.Errorf("SetupTSSServer error: %w", err) + return nil, err } newTss := TSS{ - Server: server, + Server: tssServer, Keys: make(map[string]*Key), - CurrentPubkey: appContext.GetCurrentTssPubKey(), + CurrentPubkey: app.GetCurrentTssPubKey(), logger: logger, ZetacoreClient: client, KeysignsTracker: NewKeysignsTracker(logger), BitcoinChainID: bitcoinChainID, } - err = newTss.LoadTssFilesFromDirectory(appContext.Config().TssPath) + err = newTss.LoadTssFilesFromDirectory(app.Config().TssPath) if err != nil { return nil, err } - _, pubkeyInBech32, err := keys.GetKeyringKeybase(appContext.Config(), hotkeyPassword) + _, pubkeyInBech32, err := keys.GetKeyringKeybase(app.Config(), hotkeyPassword) if err != nil { return nil, err } @@ -144,6 +140,8 @@ func NewTSS( } metrics.NumActiveMsgSigns.Set(0) + newTss.Signers = app.GetKeygen().GranteePubkeys + return &newTss, nil } @@ -155,6 +153,7 @@ func SetupTSSServer( preParams *keygen.LocalPreParams, cfg config.Config, tssPassword string, + enableMonitor bool, ) (*tss.TssServer, error) { bootstrapPeers := peer log.Info().Msgf("Peers AddrList %v", bootstrapPeers) @@ -183,7 +182,7 @@ func SetupTSSServer( "MetaMetaOpenTheDoor", tsspath, thorcommon.TssConfig{ - EnableMonitor: true, + EnableMonitor: enableMonitor, KeyGenTimeout: 300 * time.Second, // must be shorter than constants.JailTimeKeygen KeySignTimeout: 30 * time.Second, // must be shorter than constants.JailTimeKeysign PartyTimeout: 30 * time.Second, @@ -446,6 +445,19 @@ func (tss *TSS) EVMAddress() ethcommon.Address { return addr } +func (tss *TSS) EVMAddressList() []ethcommon.Address { + addresses := make([]ethcommon.Address, 0) + for _, key := range tss.Keys { + addr, err := GetTssAddrEVM(key.PubkeyInBech32) + if err != nil { + log.Error().Err(err).Msg("getKeyAddr error") + return nil + } + addresses = append(addresses, addr) + } + return addresses +} + // BTCAddress generates a bech32 p2wpkh address from pubkey func (tss *TSS) BTCAddress() string { addr, err := GetTssAddrBTC(tss.CurrentPubkey, tss.BitcoinChainID) @@ -585,7 +597,8 @@ func GetTssAddrEVM(tssPubkey string) (ethcommon.Address, error) { // TestKeysign tests the keysign // it is called when a new TSS is generated to ensure the network works as expected // TODO(revamp): move to a test package -func TestKeysign(tssPubkey string, tssServer *tss.TssServer) error { + +func TestKeysign(tssPubkey string, tssServer tss.TssServer) error { log.Info().Msg("trying keysign...") data := []byte("hello meta") H := crypto.Keccak256Hash(data) diff --git a/zetaclient/zetacore/client_monitor.go b/zetaclient/zetacore/client_monitor.go index 6c124ced48..b505af90a1 100644 --- a/zetaclient/zetacore/client_monitor.go +++ b/zetaclient/zetacore/client_monitor.go @@ -168,15 +168,11 @@ func retryWithBackoff(call func() error, attempts int, minInternal, maxInterval if attempts < 1 { return errors.New("attempts must be positive") } + bo := backoff.NewExponentialBackOff() + bo.InitialInterval = minInternal + bo.MaxInterval = maxInterval - bo := backoff.WithMaxRetries( - backoff.NewExponentialBackOff( - backoff.WithInitialInterval(minInternal), - backoff.WithMaxInterval(maxInterval), - ), - // #nosec G115 always positive - uint64(attempts), - ) + backoffWithRetry := backoff.WithMaxRetries(bo, uint64(attempts)) - return retry.DoWithBackoff(call, bo) + return retry.DoWithBackoff(call, backoffWithRetry) } diff --git a/zetaclient/zetacore/constant.go b/zetaclient/zetacore/constant.go index e3ddafad78..a5a4e16829 100644 --- a/zetaclient/zetacore/constant.go +++ b/zetaclient/zetacore/constant.go @@ -13,7 +13,7 @@ const ( PostGasPriceGasLimit = 1_500_000 // PostVoteInboundGasLimit is the gas limit for voting on observed inbound tx (for zetachain itself) - PostVoteInboundGasLimit = 400_000 + PostVoteInboundGasLimit = 500_000 // PostVoteInboundExecutionGasLimit is the gas limit for voting on observed inbound tx and executing it PostVoteInboundExecutionGasLimit = 4_000_000 @@ -37,7 +37,7 @@ const ( DefaultRetryInterval = 5 // PostVoteOutboundGasLimit is the gas limit for voting on observed outbound tx (for zetachain itself) - PostVoteOutboundGasLimit = 400_000 + PostVoteOutboundGasLimit = 500_000 // PostVoteOutboundRevertGasLimit is the gas limit for voting on observed outbound tx for revert (when outbound fails) // The value needs to be higher because reverting implies interacting with the EVM to perform swaps for the gas token