diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5df26b4a9d9a..c9737ad46f63 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,15 +9,14 @@ /message/ @gyuho /network/ @danlaine @joshua-kim @StephenButtolph /network/throttling/ @danlaine @dboehm-avalabs @StephenButtolph -/proto/ @gyuho @hexfusion +/proto/ @gyuho /snow/ @danlaine @StephenButtolph /snow/consensus/ @gyuho @StephenButtolph /snow/engine/snowman/syncer/ @abi87 /snow/uptime/ @ceyonur /utils/logging/ @ceyonur -/vms/platformvm/ @abi87 @danlaine @StephenButtolph +/vms/platformvm/ @abi87 @danlaine @dhrubabasu @StephenButtolph /vms/proposervm/ @abi87 @StephenButtolph -/vms/rpcchainvm/ @hexfusion @StephenButtolph /vms/registry/ @joshua-kim /tests/ @abi87 @gyuho @marun /x/ @danlaine @darioush @dboehm-avalabs diff --git a/.github/workflows/auto-generated-checker.yml b/.github/workflows/auto-generated-checker.yml deleted file mode 100644 index eb53ff4c5b83..000000000000 --- a/.github/workflows/auto-generated-checker.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Auto-Generated Code Checker -on: - push: - tags: - - "*" - branches: - - master - - dev - pull_request: - merge_group: - types: [checks_requested] - -jobs: - protobuf_codegen: - name: Protobuf - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version: '~1.20.10' - check-latest: true - - uses: bufbuild/buf-setup-action@v1.26.1 - - shell: bash - run: scripts/protobuf_codegen.sh - - shell: bash - run: .github/workflows/check-clean-branch.sh - mock_gen: - name: Mocks - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version: '~1.20.10' - check-latest: true - - shell: bash - run: scripts/mock.gen.sh - - shell: bash - run: .github/workflows/check-clean-branch.sh - go_mod_tidy: - name: go mod tidy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version: '~1.20.10' - check-latest: true - - shell: bash - run: go mod tidy - - shell: bash - run: .github/workflows/check-clean-branch.sh diff --git a/.github/workflows/buf-lint.yml b/.github/workflows/buf-lint.yml deleted file mode 100644 index 5665842e2fff..000000000000 --- a/.github/workflows/buf-lint.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Lint proto files - -on: - push: - tags: - - "*" - branches: - - master - - dev - pull_request: - merge_group: - types: [checks_requested] - -permissions: - contents: read - -jobs: - buf-lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: bufbuild/buf-setup-action@v1.26.1 - with: - github_token: ${{ github.token }} - - uses: bufbuild/buf-lint-action@v1 - with: - input: "proto" diff --git a/.github/workflows/build-linux-binaries.yml b/.github/workflows/build-linux-binaries.yml index dc1a358da550..e9c0bfde74be 100644 --- a/.github/workflows/build-linux-binaries.yml +++ b/.github/workflows/build-linux-binaries.yml @@ -10,6 +10,9 @@ on: tags: - "*" +env: + go_version: '~1.20.12' + jobs: build-x86_64-binaries-tarball: runs-on: ubuntu-20.04 @@ -19,7 +22,7 @@ jobs: - uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: ${{ env.go_version }} check-latest: true - run: go version @@ -81,7 +84,7 @@ jobs: - uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: ${{ env.go_version }} check-latest: true - run: go version diff --git a/.github/workflows/build-macos-release.yml b/.github/workflows/build-macos-release.yml index 3699fc0b5e37..c1a884aebf4f 100644 --- a/.github/workflows/build-macos-release.yml +++ b/.github/workflows/build-macos-release.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: '~1.20.12' check-latest: true - run: go version diff --git a/.github/workflows/build-public-ami.yml b/.github/workflows/build-public-ami.yml index 1e483ccd7f6a..32c816efdd3d 100644 --- a/.github/workflows/build-public-ami.yml +++ b/.github/workflows/build-public-ami.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: '~1.20.12' check-latest: true - run: go version diff --git a/.github/workflows/build-ubuntu-amd64-release.yml b/.github/workflows/build-ubuntu-amd64-release.yml index bbb0cbd3f4a2..627d696e412b 100644 --- a/.github/workflows/build-ubuntu-amd64-release.yml +++ b/.github/workflows/build-ubuntu-amd64-release.yml @@ -10,6 +10,9 @@ on: tags: - "*" +env: + go_version: '~1.20.12' + jobs: build-jammy-amd64-package: runs-on: ubuntu-22.04 @@ -18,7 +21,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: ${{ env.go_version }} check-latest: true - run: go version @@ -78,7 +81,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: ${{ env.go_version }} check-latest: true - run: go version diff --git a/.github/workflows/build-ubuntu-arm64-release.yml b/.github/workflows/build-ubuntu-arm64-release.yml index 8d1ba4a2ea2e..054cd4f2c827 100644 --- a/.github/workflows/build-ubuntu-arm64-release.yml +++ b/.github/workflows/build-ubuntu-arm64-release.yml @@ -10,6 +10,9 @@ on: tags: - "*" +env: + go_version: '~1.20.12' + jobs: build-jammy-arm64-package: runs-on: [self-hosted, linux, ARM64, jammy] @@ -18,7 +21,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: ${{ env.go_version }} check-latest: true - run: go version @@ -78,7 +81,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: ${{ env.go_version }} check-latest: true - run: go version diff --git a/.github/workflows/build-win-release.yml b/.github/workflows/build-win-release.yml index c84767d4c87d..d71725d124c2 100644 --- a/.github/workflows/build-win-release.yml +++ b/.github/workflows/build-win-release.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: '~1.20.12' check-latest: true - run: go version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000000..b08cb01068d5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,184 @@ +name: Tests + +on: + push: + tags: + - "*" + branches: + - master + - dev + pull_request: + merge_group: + types: [checks_requested] + +permissions: + contents: read + +# Cancel ongoing workflow runs if a new one is started +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + go_version: '~1.20.12' + tmpnet_data_path: ~/.tmpnet/networks/1000 + +jobs: + Unit: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-12, ubuntu-20.04, ubuntu-22.04, windows-2022, [self-hosted, linux, ARM64, focal], [self-hosted, linux, ARM64, jammy]] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.go_version }} + check-latest: true + - name: Set timeout on Windows # Windows UT run slower and need a longer timeout + shell: bash + if: matrix.os == 'windows-2022' + run: echo "TIMEOUT=240s" >> $GITHUB_ENV + - name: build_test + shell: bash + run: ./scripts/build_test.sh + env: + TIMEOUT: ${{ env.TIMEOUT }} + Fuzz: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.go_version }} + check-latest: true + - name: fuzz_test + shell: bash + run: ./scripts/build_fuzz.sh 10 # Run each fuzz test 10 seconds + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.go_version }} + check-latest: true + - name: Build AvalancheGo Binary + shell: bash + run: ./scripts/build.sh -r + - name: Run e2e tests + shell: bash + run: E2E_SERIAL=1 ./scripts/tests.e2e.sh + - name: Upload tmpnet network dir + uses: actions/upload-artifact@v3 + if: always() + with: + name: e2e-tmpnet-data + path: ${{ env.tmpnet_data_path }} + if-no-files-found: error + e2e_existing_network: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.go_version }} + check-latest: true + - name: Build AvalancheGo Binary + shell: bash + run: ./scripts/build.sh -r + - name: Run e2e tests with existing network + shell: bash + run: E2E_SERIAL=1 ./scripts/tests.e2e.existing.sh + - name: Upload tmpnet network dir + uses: actions/upload-artifact@v3 + if: always() + with: + name: e2e-existing-network-tmpnet-data + path: ${{ env.tmpnet_data_path }} + if-no-files-found: error + Upgrade: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.go_version }} + check-latest: true + - name: Build AvalancheGo Binary + shell: bash + run: ./scripts/build.sh + # TODO: re-activate this test after there is a compatible tag to use + # - name: Run e2e tests + # shell: bash + # run: ./scripts/tests.upgrade.sh + # - name: Upload tmpnet network dir + # uses: actions/upload-artifact@v3 + # if: always() + # with: + # name: upgrade-tmpnet-data + # path: ${{ env.tmpnet_data_path }} + # if-no-files-found: error + Lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.go_version }} + check-latest: true + - name: Run static analysis tests + shell: bash + run: scripts/lint.sh + buf-lint: + name: Protobuf Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: bufbuild/buf-setup-action@v1.26.1 + with: + github_token: ${{ github.token }} + - uses: bufbuild/buf-lint-action@v1 + with: + input: "proto" + check_generated_protobuf: + name: Up-to-date protobuf + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.go_version }} + check-latest: true + - uses: bufbuild/buf-setup-action@v1.26.1 + - shell: bash + run: scripts/protobuf_codegen.sh + - shell: bash + run: .github/workflows/check-clean-branch.sh + check_mockgen: + name: Up-to-date mocks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.go_version }} + check-latest: true + - shell: bash + run: scripts/mock.gen.sh + - shell: bash + run: .github/workflows/check-clean-branch.sh + go_mod_tidy: + name: Up-to-date go.mod and go.sum + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ env.go_version }} + check-latest: true + - shell: bash + run: go mod tidy + - shell: bash + run: .github/workflows/check-clean-branch.sh diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 7fa95a88e7f3..fdca7ab41b8b 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -18,8 +18,8 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '~1.20.10' + go-version: '~1.20.12' check-latest: true - name: Run fuzz tests shell: bash - run: ./scripts/build_fuzz.sh 30 # Run each fuzz test 30 seconds + run: ./scripts/build_fuzz.sh 180 # Run each fuzz test 180 seconds diff --git a/.github/workflows/fuzz_merkledb.yml b/.github/workflows/fuzz_merkledb.yml new file mode 100644 index 000000000000..c232bd933d17 --- /dev/null +++ b/.github/workflows/fuzz_merkledb.yml @@ -0,0 +1,27 @@ +name: Scheduled Fuzz Testing + +on: + workflow_dispatch: + schedule: + # Run every 6 hours + - cron: "0 0,6,12,18 * * *" + +permissions: + contents: read + +jobs: + MerkleDB: + runs-on: ubuntu-latest + steps: + - name: Git checkout + uses: actions/checkout@v3 + with: + ref: 'dev' + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: '~1.20.12' + check-latest: true + - name: Run merkledb fuzz tests + shell: bash + run: ./scripts/build_fuzz.sh 900 ./x/merkledb # Run each merkledb fuzz tests 15 minutes diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml deleted file mode 100644 index 8510a693632c..000000000000 --- a/.github/workflows/static-analysis.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Static analysis -on: - push: - tags: - - "*" - branches: - - master - - dev - pull_request: - merge_group: - types: [checks_requested] - -jobs: - run_static_analysis: - name: Static analysis - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '~1.20.10' - check-latest: true - - name: Run static analysis tests - shell: bash - run: scripts/lint.sh diff --git a/.github/workflows/test.e2e.existing.yml b/.github/workflows/test.e2e.existing.yml deleted file mode 100644 index a50943888f48..000000000000 --- a/.github/workflows/test.e2e.existing.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Test e2e with existing network - -on: - push: - tags: - - "*" - branches: - - master - - dev - pull_request: - merge_group: - types: [checks_requested] - -permissions: - contents: read - -jobs: - test_e2e_existing: - runs-on: ubuntu-latest - steps: - - name: Git checkout - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '~1.20.10' - check-latest: true - - name: Build the avalanchego binary - shell: bash - run: ./scripts/build.sh -r - - name: Run e2e tests with existing network - shell: bash - run: E2E_SERIAL=1 ./scripts/tests.e2e.existing.sh - - name: Upload tmpnet network dir - uses: actions/upload-artifact@v3 - if: always() - with: - name: tmpnet-data - path: ~/.tmpnet/networks/1000 diff --git a/.github/workflows/test.e2e.yml b/.github/workflows/test.e2e.yml deleted file mode 100644 index 0edc46afebb0..000000000000 --- a/.github/workflows/test.e2e.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Test e2e - -on: - push: - tags: - - "*" - branches: - - master - - dev - pull_request: - merge_group: - types: [checks_requested] - -permissions: - contents: read - -jobs: - test_e2e: - runs-on: ubuntu-latest - steps: - - name: Git checkout - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '~1.20.10' - check-latest: true - - name: Build the avalanchego binary - shell: bash - run: ./scripts/build.sh -r - - name: Run e2e tests - shell: bash - run: E2E_SERIAL=1 ./scripts/tests.e2e.sh - - name: Upload tmpnet network dir - uses: actions/upload-artifact@v3 - if: always() - with: - name: tmpnet-data - path: ~/.tmpnet/networks/1000 diff --git a/.github/workflows/test.unit.yml b/.github/workflows/test.unit.yml deleted file mode 100644 index ae4cb85983c3..000000000000 --- a/.github/workflows/test.unit.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Unit Tests - -on: - push: - tags: - - "*" - branches: - - master - - dev - pull_request: - merge_group: - types: [checks_requested] - -# Cancel ongoing workflow runs if a new one is started -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - unit_tests: - name: unit_tests - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-12, ubuntu-20.04, ubuntu-22.04, windows-2022, [self-hosted, linux, ARM64, focal], [self-hosted, linux, ARM64, jammy]] - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version: '~1.20.10' - check-latest: true - - name: Set timeout on Windows # Windows UT run slower and need a longer timeout - shell: bash - if: matrix.os == 'windows-2022' - run: echo "TIMEOUT=240s" >> $GITHUB_ENV - - name: build_test - shell: bash - run: ./scripts/build_test.sh - env: - TIMEOUT: ${{ env.TIMEOUT }} - - name: fuzz_test - shell: bash - if: matrix.os == 'ubuntu-22.04' # Only run on Ubuntu 22.04 - run: ./scripts/build_fuzz.sh 15 # Run each fuzz test 15 seconds diff --git a/.github/workflows/test.upgrade.yml b/.github/workflows/test.upgrade.yml deleted file mode 100644 index 61be20787900..000000000000 --- a/.github/workflows/test.upgrade.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Test upgrade - -on: - push: - tags: - - "*" - branches: - - master - - dev - pull_request: - merge_group: - types: [checks_requested] - -permissions: - contents: read - -jobs: - test_upgrade: - runs-on: ubuntu-latest - steps: - - name: Git checkout - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '~1.20.10' - check-latest: true - - name: Build the avalanchego binary - shell: bash - run: ./scripts/build.sh - - name: Run upgrade tests - shell: bash - # 1.10.7 is the first version compatible with the ephnet fixture by - # virtue of writing a process context file on node start. - run: ./scripts/tests.upgrade.sh 1.10.7 - - name: Upload ephnet network dir - uses: actions/upload-artifact@v3 - if: always() - with: - name: ephnet-data - path: ~/.ephnet/networks/1000 diff --git a/.golangci.yml b/.golangci.yml index b5c99facb2b1..fad97cb63712 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -67,6 +67,7 @@ linters: - nolintlint - perfsprint - prealloc + - predeclared - revive - staticcheck - stylecheck diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 968f72ef24fe..2e850274d96f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ To start developing on AvalancheGo, you'll need a few things installed. -- Golang version >= 1.20.8 +- Golang version >= 1.20.12 - gcc - g++ @@ -25,7 +25,7 @@ To start developing on AvalancheGo, you'll need a few things installed. ## Features -- If you want to start a discussion about the development of a new feature or the modfiication of an existing one, start a thread under GitHub [discussions](https://github.com/ava-labs/avalanchego/discussions/categories/ideas). +- If you want to start a discussion about the development of a new feature or the modification of an existing one, start a thread under GitHub [discussions](https://github.com/ava-labs/avalanchego/discussions/categories/ideas). - Post a thread about your idea and why it should be added to AvalancheGo. - Don't start working on a pull request until you've received positive feedback from the maintainers. @@ -33,7 +33,7 @@ To start developing on AvalancheGo, you'll need a few things installed. - Open a new GitHub pull request containing your changes. - Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. -- The PR should be opened against the `dev` branch. +- The PR should be opened against the `master` branch. - If your PR isn't ready to be reviewed just yet, you can open it as a draft to collect early feedback on your changes. - Once the PR is ready for review, mark it as ready-for-review and request review from one of the maintainers. diff --git a/Dockerfile b/Dockerfile index f4e6c21441cf..21ab344ef4b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ # README.md # go.mod # ============= Compilation Stage ================ -FROM golang:1.20.10-bullseye AS builder +FROM golang:1.20.12-bullseye AS builder WORKDIR /build # Copy and download avalanche dependencies using go mod diff --git a/LICENSE b/LICENSE index c9be72c59aa5..6178f77a85af 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (C) 2019-2023, Ava Labs, Inc. +Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 7842615f35be..ac3cd62841a9 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The minimum recommended hardware specification for nodes connected to Mainnet is If you plan to build AvalancheGo from source, you will also need the following software: -- [Go](https://golang.org/doc/install) version >= 1.20.10 +- [Go](https://golang.org/doc/install) version >= 1.20.12 - [gcc](https://gcc.gnu.org/) - g++ diff --git a/RELEASES.md b/RELEASES.md index a7c232d7674b..24d42b4d6027 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,259 @@ # Release Notes +## [v1.10.18](https://github.com/ava-labs/avalanchego/releases/tag/v1.10.18) + +This version is backwards compatible to [v1.10.0](https://github.com/ava-labs/avalanchego/releases/tag/v1.10.0). It is optional, but encouraged. + +The plugin version is updated to `31` all plugins must update to be compatible. + +### APIs + +- Added `info.acps` API +- Added `supportedACPs` and `objectedACPs` for each peer returned by `info.peers` +- Added `txs` field to `BanffProposalBlock`'s json format +- Added metrics: + - `avalanche_network_validator_ips` + - `avalanche_network_gossipable_ips` + - `avalanche_network_ip_bloom_count` + - `avalanche_network_ip_bloom_entries` + - `avalanche_network_ip_bloom_hashes` + - `avalanche_network_ip_bloom_max_count` + - `avalanche_network_ip_bloom_reset_count` +- Added metrics related to `get_peer_list` message handling +- Added p2p SDK metrics to the P-chain and X-chain +- Renamed metrics related to message handling: + - `version` -> `handshake` + - `appRequestFailed` -> `appError` + - `crossChainAppRequestFailed` -> `crossChainAppError` +- Removed `gzip` compression time metrics +- Converted p2p SDK metrics to use vectors rather than independent metrics +- Converted client name reported over the p2p network from `avalanche` to `avalanchego` + + +### Configs + +- Added: + - `--acp-support` + - `--acp-object` + - `snow-commit-threshold` + - `network-peer-list-pull-gossip-frequency` + - `network-peer-list-bloom-reset-frequency` + - `network` to the X-chain and P-chain configs including: + - `max-validator-set-staleness` + - `target-gossip-size` + - `pull-gossip-poll-size` + - `pull-gossip-frequency` + - `pull-gossip-throttling-period` + - `pull-gossip-throttling-limit` + - `expected-bloom-filter-elements` + - `expected-bloom-filter-false-positive-probability` + - `max-bloom-filter-false-positive-probability` + - `legacy-push-gossip-cache-size` +- Deprecated: + - `snow-virtuous-commit-threshold` + - `snow-rogue-commit-threshold` + - `network-peer-list-validator-gossip-size` + - `network-peer-list-non-validator-gossip-size` + - `network-peer-list-peers-gossip-size` + - `network-peer-list-gossip-frequency` +- Removed: + - `gzip` as an option for `network-compression-type` + +### Fixes + +- Fixed `platformvm.SetPreference` to correctly reset the block building timer +- Fixed early bootstrapping termination +- Fixed duplicated transaction initialization in the X-chain and P-chain +- Fixed IP gossip when using dynamically allocated staking ports +- Updated `golang.org/x/exp` dependency to fix downstream compilation errors +- Updated `golang.org/x/crypto` dependency to address `CVE-2023-48795` +- Updated minimum golang version to address `CVE-2023-39326` +- Restricted `GOPROXY` during compilation to avoid `direct` version control fallbacks +- Fixed `merkledb` deletion of the empty key +- Fixed `merkledb` race condition when interacting with invalidated or closed trie views +- Fixed `json.Marshal` for `wallet` transactions +- Fixed duplicate outbound dialer for manually tracked nodes in the p2p network + +### What's Changed + +- testing: Update to latest version of ginkgo by @marun in https://github.com/ava-labs/avalanchego/pull/2390 +- `vms/platformvm`: Cleanup block builder tests by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2406 +- Drop Pending Stakers 0 - De-duplicate staking tx verification by @abi87 in https://github.com/ava-labs/avalanchego/pull/2335 +- `vms/platformvm`: Initialize txs in `Transactions` field for `BanffProposalBlock` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2419 +- `vms/platformvm`: Move `VerifyUniqueInputs` from `verifier` to `backend` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2410 +- Fix duplicated bootstrapper engine termination by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2334 +- allow user of `build_fuzz.sh` to specify a directory to fuzz in by @danlaine in https://github.com/ava-labs/avalanchego/pull/2414 +- Update slices dependency to use Compare by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2424 +- `vms/platformvm`: Cleanup some block tests by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2422 +- ProposerVM Extend windows 0 - Cleanup by @abi87 in https://github.com/ava-labs/avalanchego/pull/2404 +- `vms/platformvm`: Add `decisionTxs` parameter to `NewBanffProposalBlock` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2411 +- Update minimum golang version to v1.20.12 by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2427 +- Fix platformvm.SetPreference by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2429 +- Restrict GOPROXY by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2434 +- Drop Pending Stakers 1 - introduced ScheduledStaker txs by @abi87 in https://github.com/ava-labs/avalanchego/pull/2323 +- Run merkledb fuzz tests every 6 hours by @danlaine in https://github.com/ava-labs/avalanchego/pull/2415 +- Remove unused error by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2426 +- Make `messageQueue.msgAndCtxs` a circular buffer by @danlaine in https://github.com/ava-labs/avalanchego/pull/2433 +- ProposerVM Extend windows 1 - UTs Cleanup by @abi87 in https://github.com/ava-labs/avalanchego/pull/2412 +- Change seed from int64 to uint64 by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2438 +- Remove usage of timer.Timer in node by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2441 +- Remove staged timer again by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2440 +- `merkledb` / `sync` -- Disambiguate no end root from no start root by @danlaine in https://github.com/ava-labs/avalanchego/pull/2437 +- Drop Pending Stakers 2 - Replace txs.ScheduledStaker with txs.Staker by @abi87 in https://github.com/ava-labs/avalanchego/pull/2305 +- `vms/platformvm`: Remove double block building logic by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2380 +- Remove usage of timer.Timer in benchlist by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2446 +- `vms/avm`: Simplify `Peek` function in mempool by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2449 +- `vms/platformvm`: Remove `standardBlockState` struct by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2450 +- Refactor sampler seeding by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2456 +- Update tmpnet fixture to include Proof-of-Possession for initial stakers by @marun in https://github.com/ava-labs/avalanchego/pull/2391 +- `vms/platformvm`: Remove `EnableAdding` and `DisableAdding` from `Mempool` interface by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2463 +- `vms/avm`: Add `exists` bool to mempool `Peek` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2465 +- `vms/platformvm`: Remove `PeekTxs` from `Mempool` interface by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2378 +- `vms/platformvm`: Add `processStandardTxs` helper by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2461 +- `vms/platformvm`: Process `atomicRequests` and `onAcceptFunc` in option blocks by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2459 +- `e2e`: Rename 'funded key' to 'pre-funded key' for consistency by @marun in https://github.com/ava-labs/avalanchego/pull/2455 +- `vms/platformvm`: Surface `VerifyUniqueInputs` in the `Manager` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2467 +- `vms/platformvm`: Add `TestBuildBlockShouldReward` test by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2466 +- Switch client version to a proto type from a string by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2188 +- Remove stale TODO by @danlaine in https://github.com/ava-labs/avalanchego/pull/2468 +- `vms/platformvm`: Add `TestBuildBlockDoesNotBuildWithEmptyMempool` test by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2469 +- `vms/platformvm`: Add `TestBuildBlockShouldAdvanceTime` test by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2471 +- `vms/platformvm`: Permit usage of the `Transactions` field in `BanffProposalBlock` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2451 +- `vms/platformvm`: Add `TestBuildBlockForceAdvanceTime` test by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2472 +- P2P AppError handling by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2248 +- `vms/platformvm`: Verify txs before building a block by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2359 +- Refactor p2p unit tests by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2475 +- Add ACP signaling by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2476 +- Refactor SDK by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2452 +- Cleanup CI by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2480 +- Ensure upgrade test uses the correct binary on restart by @marun in https://github.com/ava-labs/avalanchego/pull/2478 +- Prefetch Improvement by @dboehm-avalabs in https://github.com/ava-labs/avalanchego/pull/2435 +- ci: run each fuzz test for 10 seconds by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2483 +- Remove nullable options by @nytzuga in https://github.com/ava-labs/avalanchego/pull/2481 +- `merkledb` -- dynamic root by @danlaine in https://github.com/ava-labs/avalanchego/pull/2177 +- fix onEvictCache by @danlaine in https://github.com/ava-labs/avalanchego/pull/2484 +- Remove cached node bytes from merkle nodes by @dboehm-avalabs in https://github.com/ava-labs/avalanchego/pull/2393 +- Fix race in view iteration by @dboehm-avalabs in https://github.com/ava-labs/avalanchego/pull/2486 +- MerkleDB -- update readme by @danlaine in https://github.com/ava-labs/avalanchego/pull/2423 +- Drop Pending Stakers 3 - persist stakers' StartTime by @abi87 in https://github.com/ava-labs/avalanchego/pull/2306 +- SDK Push Gossiper implementation by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2428 +- `tmpnet`: Move tmpnet/local to tmpnet package by @marun in https://github.com/ava-labs/avalanchego/pull/2457 +- `merkledb` -- make tests use time as randomness seed by @danlaine in https://github.com/ava-labs/avalanchego/pull/2470 +- `tmpnet`: Break config.go up into coherent parts by @marun in https://github.com/ava-labs/avalanchego/pull/2462 +- Drop Pending Stakers 4 - minimal UT infra cleanup by @abi87 in https://github.com/ava-labs/avalanchego/pull/2332 +- ProposerVM Extend windows 2- extend windowing by @abi87 in https://github.com/ava-labs/avalanchego/pull/2401 +- Support json marshalling txs returned from the wallet by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2494 +- Avoid escaping to improve readability by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2496 +- Allow OutputOwners to be json marshalled without InitCtx by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2495 +- Drop Pending Stakers 5 - validated PostDurango StakerTxs by @abi87 in https://github.com/ava-labs/avalanchego/pull/2314 +- Bump golang.org/x/crypto from 0.14.0 to 0.17.0 by @dependabot in https://github.com/ava-labs/avalanchego/pull/2502 +- Remove unused `BuildGenesisTest` function by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2503 +- Remove unused `AcceptorTracker` struct by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2508 +- Dedupe secp256k1 key usage in tests by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2511 +- Merkledb readme updates by @danlaine in https://github.com/ava-labs/avalanchego/pull/2510 +- Gossip Test structs by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2514 +- `tmpnet`: Separate node into orchestration, config and process by @marun in https://github.com/ava-labs/avalanchego/pull/2460 +- Move `snow.DefaultConsensusContextTest` to `snowtest.ConsensusContext` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2507 +- Add gossip Marshaller interface by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2509 +- Include chain creation error in health check by @marun in https://github.com/ava-labs/avalanchego/pull/2519 +- Make X-chain mempool safe for concurrent use by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2520 +- Initialize transactions once by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2521 +- `vms/avm`: Remove usage of `require.Contains` from service tests by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2517 +- Move context lock into issueTx by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2524 +- Rework X-chain locking in tests by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2526 +- `vms/avm`: Simplify `mempool.Remove` signature by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2527 +- Remove unused mocks by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2528 +- Move `avm.newContext` to `snowtest.Context` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2513 +- Do not fail-fast Tests / Unit by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2530 +- Make P-Chain Mempool thread-safe by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2523 +- `vms/platformvm`: Use `snowtest.Context` helper by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2515 +- Export mempool errors by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2531 +- Move locking into issueTx by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2532 +- Fix merge in wallet service by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2534 +- Introduce TxVerifier interface to network by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2533 +- Export P-Chain Mempool Errors by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2535 +- Rename `Version` message to `Handshake` by @danlaine in https://github.com/ava-labs/avalanchego/pull/2479 +- Rename myVersionTime to ipSigningTime by @danlaine in https://github.com/ava-labs/avalanchego/pull/2537 +- Remove resolved TODO by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2540 +- Only initialize Txs once by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2538 +- JSON marshal the `Transactions` field in `BanffProposalBlocks` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2541 +- Enable `predeclared` linter by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2539 +- Move context lock into `network.issueTx` by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2525 +- Remove comment on treating failed sends as FATAL by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2544 +- Add TxVerifier interface to network by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2542 +- X-chain SDK gossip by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2490 +- Remove network context by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2543 +- Remove `snow.DefaultContextTest` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2518 +- Fix windowing when no validator is available by @abi87 in https://github.com/ava-labs/avalanchego/pull/2529 +- Unexport fields from gossip.BloomFilter by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2547 +- P-Chain SDK Gossip by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2487 +- Documentation Fixes: Grammatical Corrections and Typo Fixes Across Multiple Files by @joaolago1113 in https://github.com/ava-labs/avalanchego/pull/2550 +- Notify block builder of txs after reject by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2549 +- Set dependabot target branch to `dev` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2553 +- Remove `MockLogger` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2554 +- Clean up merkleDB interface and duplicate code by @dboehm-avalabs in https://github.com/ava-labs/avalanchego/pull/2445 +- Do not mark txs as dropped when mempool is full by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2557 +- Update bug bounty program to immunefi by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2558 +- Fix p2p sdk metric labels by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2561 +- Suppress gossip warnings due to no sampled peers by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2562 +- Remove dead code and unnecessary lock from reflect codec by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2560 +- Remove unused index interface by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2564 +- Implement SetMap and use it in XP-chain mempools by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2555 +- `vms/platformvm`: Add `TestIterate` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2565 +- Cleanup codec usage by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2563 +- Remove `len` tag parsing from the reflect codec by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2559 +- Use more specific type by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2567 +- Standardize `onShutdownCtx` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2568 +- Verify avm mempool txs against the last accepted state by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2569 +- Update `CODEOWNERS` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2570 +- Remove license from mocks by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2574 +- Add missing import by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2573 +- `vms/platformvm`: Prune mempool periodically by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2566 +- Update license header to 2024 by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2572 +- [MerkleDB] Make intermediate node cache two layered by @dboehm-avalabs in https://github.com/ava-labs/avalanchego/pull/2576 +- Fix merkledb rebuild iterator by @dboehm-avalabs in https://github.com/ava-labs/avalanchego/pull/2581 +- Fix intermediate node caching by @dboehm-avalabs in https://github.com/ava-labs/avalanchego/pull/2585 +- Remove codec length check after Durango by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2586 +- `tmpnet`: Use AvalancheLocalChainConfig for cchain genesis by @marun in https://github.com/ava-labs/avalanchego/pull/2583 +- `testing`: Ensure CheckBootstrapIsPossible is safe for teardown by @marun in https://github.com/ava-labs/avalanchego/pull/2582 +- `tmpnet`: Separate network into orchestration and configuration by @marun in https://github.com/ava-labs/avalanchego/pull/2464 +- Update uintsize implementation by @danlaine in https://github.com/ava-labs/avalanchego/pull/2590 +- Optimize bloom filter by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2588 +- Remove TLS key gen from networking tests by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2596 +- [utils/bloom] Optionally Update Bloom Filter Size on Reset by @patrick-ogrady in https://github.com/ava-labs/avalanchego/pull/2591 +- [ci] Increase Fuzz Time in Periodic Runs by @patrick-ogrady in https://github.com/ava-labs/avalanchego/pull/2599 +- `tmpnet`: Save metrics snapshot to disk before node shutdown by @marun in https://github.com/ava-labs/avalanchego/pull/2601 +- chore: Fix typo s/useage/usage by @hugo-syn in https://github.com/ava-labs/avalanchego/pull/2602 +- Deprecate `SnowRogueCommitThresholdKey` and `SnowVirtuousCommitThresholdKey` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2600 +- Fix networking invalid field log by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2604 +- chore: Fix typo s/seperate/separate/ by @hugo-syn in https://github.com/ava-labs/avalanchego/pull/2605 +- Support dynamic port peerlist gossip by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2603 +- Replace `PeerListAck` with `GetPeerList` by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2580 +- Log critical consensus values during health checks by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2609 +- Update contributions branch to master by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2610 +- Add ip bloom metrics by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2614 +- `x/sync`: Auto-generate `MockNetworkClient` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2617 +- Remove CreateStaticHandlers from VM interface by @joshua-kim in https://github.com/ava-labs/avalanchego/pull/2589 +- `tmpnet`: Add support for subnets by @marun in https://github.com/ava-labs/avalanchego/pull/2492 +- Update `go.uber.org/mock/gomock` to `v0.4.0` by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2618 +- Add `mockgen` source mode for generics + bls imports by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2615 +- Verify all MockGen generated files are re-generated in CI by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2616 +- Move division by 0 check out of the bloom loops by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2622 +- P-chain Add UTs around stakers persistence in platformvm state by @abi87 in https://github.com/ava-labs/avalanchego/pull/2505 +- Revert "Set dependabot target branch to `dev` (#2553)" by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2623 +- Remove remaining 2023 remnants by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2624 +- Deprecate push-based peerlist gossip flags by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2625 +- Remove support for compressing gzip messages by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2627 +- Always attempt to install mockgen `v0.4.0` before execution by @dhrubabasu in https://github.com/ava-labs/avalanchego/pull/2628 +- Modify TLS parsing rules for Durango by @StephenButtolph in https://github.com/ava-labs/avalanchego/pull/2458 + +### New Contributors + +- @joaolago1113 made their first contribution in https://github.com/ava-labs/avalanchego/pull/2550 +- @hugo-syn made their first contribution in https://github.com/ava-labs/avalanchego/pull/2602 + +**Full Changelog**: https://github.com/ava-labs/avalanchego/compare/v1.10.17...v1.10.18 + ## [v1.10.17](https://github.com/ava-labs/avalanchego/releases/tag/v1.10.17) This version is backwards compatible to [v1.10.0](https://github.com/ava-labs/avalanchego/releases/tag/v1.10.0). It is optional, but encouraged. @@ -36,7 +290,7 @@ The plugin version is unchanged at `30` and is compatible with versions `v1.10.1 ### Fixes - Fixed `duplicated operation on provided value` error when executing atomic operations after state syncing the C-chain -- Removed useage of atomic trie after commitment +- Removed usage of atomic trie after commitment - Fixed atomic trie root overwrite during state sync - Prevented closure of `stdout` and `stderr` when shutting down the logger diff --git a/SECURITY.md b/SECURITY.md index 26e938c8df29..9a04ba2528f5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,13 +4,13 @@ Avalanche takes the security of the platform and of its users very seriously. We ## Reporting a Vulnerability -**Please do not file a public ticket** mentioning the vulnerability. To disclose a vulnerability submit it through our [Bug Bounty Program](https://hackenproof.com/avalanche). +**Please do not file a public ticket** mentioning the vulnerability. To disclose a vulnerability submit it through our [Bug Bounty Program](https://immunefi.com/bounty/avalanche/). Vulnerabilities must be disclosed to us privately with reasonable time to respond, and avoid compromise of other users and accounts, or loss of funds that are not your own. We do not reward spam or social engineering vulnerabilities. Do not test for or validate any security issues in the live Avalanche networks (Mainnet and Fuji testnet), confirm all exploits in a local private testnet. -Please refer to the [Bug Bounty Page](https://hackenproof.com/avalanche) for the most up-to-date program rules and scope. +Please refer to the [Bug Bounty Page](https://immunefi.com/bounty/avalanche/) for the most up-to-date program rules and scope. ## Supported Versions diff --git a/api/admin/client.go b/api/admin/client.go index 77f3835f060c..11ee2e5cef2c 100644 --- a/api/admin/client.go +++ b/api/admin/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package admin diff --git a/api/admin/client_test.go b/api/admin/client_test.go index d005c49b448a..486a0a716564 100644 --- a/api/admin/client_test.go +++ b/api/admin/client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package admin diff --git a/api/admin/service.go b/api/admin/service.go index f94a426b366c..cf57a28264e7 100644 --- a/api/admin/service.go +++ b/api/admin/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package admin @@ -334,7 +334,7 @@ func (a *Admin) LoadVMs(r *http.Request, _ *struct{}, reply *LoadVMsReply) error defer a.lock.Unlock() ctx := r.Context() - loadedVMs, failedVMs, err := a.VMRegistry.ReloadWithReadLock(ctx) + loadedVMs, failedVMs, err := a.VMRegistry.Reload(ctx) if err != nil { return err } diff --git a/api/admin/service_test.go b/api/admin/service_test.go index 09665a52c9d9..ea159c655c63 100644 --- a/api/admin/service_test.go +++ b/api/admin/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package admin @@ -20,7 +20,6 @@ import ( type loadVMsTest struct { admin *Admin ctrl *gomock.Controller - mockLog *logging.MockLogger mockVMManager *vms.MockManager mockVMRegistry *registry.MockVMRegistry } @@ -28,18 +27,16 @@ type loadVMsTest struct { func initLoadVMsTest(t *testing.T) *loadVMsTest { ctrl := gomock.NewController(t) - mockLog := logging.NewMockLogger(ctrl) mockVMRegistry := registry.NewMockVMRegistry(ctrl) mockVMManager := vms.NewMockManager(ctrl) return &loadVMsTest{ admin: &Admin{Config: Config{ - Log: mockLog, + Log: logging.NoLog{}, VMRegistry: mockVMRegistry, VMManager: mockVMManager, }}, ctrl: ctrl, - mockLog: mockLog, mockVMManager: mockVMManager, mockVMRegistry: mockVMRegistry, } @@ -67,8 +64,7 @@ func TestLoadVMsSuccess(t *testing.T) { id2: alias2[1:], } - resources.mockLog.EXPECT().Debug(gomock.Any(), gomock.Any()).Times(1) - resources.mockVMRegistry.EXPECT().ReloadWithReadLock(gomock.Any()).Times(1).Return(newVMs, failedVMs, nil) + resources.mockVMRegistry.EXPECT().Reload(gomock.Any()).Times(1).Return(newVMs, failedVMs, nil) resources.mockVMManager.EXPECT().Aliases(id1).Times(1).Return(alias1, nil) resources.mockVMManager.EXPECT().Aliases(id2).Times(1).Return(alias2, nil) @@ -84,9 +80,8 @@ func TestLoadVMsReloadFails(t *testing.T) { resources := initLoadVMsTest(t) - resources.mockLog.EXPECT().Debug(gomock.Any(), gomock.Any()).Times(1) // Reload fails - resources.mockVMRegistry.EXPECT().ReloadWithReadLock(gomock.Any()).Times(1).Return(nil, nil, errTest) + resources.mockVMRegistry.EXPECT().Reload(gomock.Any()).Times(1).Return(nil, nil, errTest) reply := LoadVMsReply{} err := resources.admin.LoadVMs(&http.Request{}, nil, &reply) @@ -108,8 +103,7 @@ func TestLoadVMsGetAliasesFails(t *testing.T) { // every vm is at least aliased to itself. alias1 := []string{id1.String(), "vm1-alias-1", "vm1-alias-2"} - resources.mockLog.EXPECT().Debug(gomock.Any(), gomock.Any()).Times(1) - resources.mockVMRegistry.EXPECT().ReloadWithReadLock(gomock.Any()).Times(1).Return(newVMs, failedVMs, nil) + resources.mockVMRegistry.EXPECT().Reload(gomock.Any()).Times(1).Return(newVMs, failedVMs, nil) resources.mockVMManager.EXPECT().Aliases(id1).Times(1).Return(alias1, nil) resources.mockVMManager.EXPECT().Aliases(id2).Times(1).Return(nil, errTest) diff --git a/api/auth/auth.go b/api/auth/auth.go index 0f78192cb111..f01b1d2fcd73 100644 --- a/api/auth/auth.go +++ b/api/auth/auth.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package auth @@ -41,7 +41,7 @@ const ( var ( errNoToken = errors.New("auth token not provided") errAuthHeaderNotParsable = fmt.Errorf( - "couldn't parse auth token. Header \"%s\" should be \"%sTOKEN.GOES.HERE\"", + `couldn't parse auth token. Header "%s" should be "%sTOKEN.GOES.HERE"`, headerKey, headerValStart, ) diff --git a/api/auth/auth_test.go b/api/auth/auth_test.go index d8b7a4cca59b..caf921ca2a26 100644 --- a/api/auth/auth_test.go +++ b/api/auth/auth_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package auth diff --git a/api/auth/claims.go b/api/auth/claims.go index e2bf55d3078b..1cdda3d4a224 100644 --- a/api/auth/claims.go +++ b/api/auth/claims.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package auth diff --git a/api/auth/response.go b/api/auth/response.go index e87065c71501..eca4b39da9b8 100644 --- a/api/auth/response.go +++ b/api/auth/response.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package auth diff --git a/api/auth/service.go b/api/auth/service.go index 77517c174a5c..badb544c5ccb 100644 --- a/api/auth/service.go +++ b/api/auth/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package auth diff --git a/api/common_args_responses.go b/api/common_args_responses.go index 73624d529d9b..bcb33dcf4136 100644 --- a/api/common_args_responses.go +++ b/api/common_args_responses.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package api diff --git a/api/health/checker.go b/api/health/checker.go index efc895177ed3..b30e450660b8 100644 --- a/api/health/checker.go +++ b/api/health/checker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/client.go b/api/health/client.go index 7c615757f0ce..59daa555cdda 100644 --- a/api/health/client.go +++ b/api/health/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/client_test.go b/api/health/client_test.go index e42be5dbe852..e019829e68e4 100644 --- a/api/health/client_test.go +++ b/api/health/client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/handler.go b/api/health/handler.go index a8bd8269a158..a95c66a322c0 100644 --- a/api/health/handler.go +++ b/api/health/handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/health.go b/api/health/health.go index 14f1bb7b4b4a..80012cf8c02e 100644 --- a/api/health/health.go +++ b/api/health/health.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/health_test.go b/api/health/health_test.go index 432cefdf6194..64661c710929 100644 --- a/api/health/health_test.go +++ b/api/health/health_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/metrics.go b/api/health/metrics.go index d567bc483d71..fdb7b2ed813b 100644 --- a/api/health/metrics.go +++ b/api/health/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/result.go b/api/health/result.go index df9edb3419cc..e243cba1466d 100644 --- a/api/health/result.go +++ b/api/health/result.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/service.go b/api/health/service.go index 368d986c52bb..7b48507075b2 100644 --- a/api/health/service.go +++ b/api/health/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/service_test.go b/api/health/service_test.go index 0e60d467000a..b25e6dccc017 100644 --- a/api/health/service_test.go +++ b/api/health/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/health/worker.go b/api/health/worker.go index f0e7a71ed13d..e42e77a4d52c 100644 --- a/api/health/worker.go +++ b/api/health/worker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package health diff --git a/api/info/client.go b/api/info/client.go index f952f62a3c49..6caafd422233 100644 --- a/api/info/client.go +++ b/api/info/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package info diff --git a/api/info/client_test.go b/api/info/client_test.go index 292a1841bd3c..7923ff94aff8 100644 --- a/api/info/client_test.go +++ b/api/info/client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package info diff --git a/api/info/service.go b/api/info/service.go index 47112e55b630..e315afd259ef 100644 --- a/api/info/service.go +++ b/api/info/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package info @@ -17,10 +17,12 @@ import ( "github.com/ava-labs/avalanchego/network" "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/snow/networking/benchlist" + "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/json" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms" "github.com/ava-labs/avalanchego/vms/platformvm/signer" @@ -32,6 +34,7 @@ var errNoChainProvided = errors.New("argument 'chain' not given") type Info struct { Parameters log logging.Logger + validators validators.Manager myIP ips.DynamicIPPort networking network.Network chainManager chains.Manager @@ -59,6 +62,7 @@ type Parameters struct { func NewService( parameters Parameters, log logging.Logger, + validators validators.Manager, chainManager chains.Manager, vmManager vms.Manager, myIP ips.DynamicIPPort, @@ -73,6 +77,7 @@ func NewService( &Info{ Parameters: parameters, log: log, + validators: validators, chainManager: chainManager, vmManager: vmManager, myIP: myIP, @@ -319,6 +324,64 @@ func (i *Info) Uptime(_ *http.Request, args *UptimeRequest, reply *UptimeRespons return nil } +type ACP struct { + SupportWeight json.Uint64 `json:"supportWeight"` + Supporters set.Set[ids.NodeID] `json:"supporters"` + ObjectWeight json.Uint64 `json:"objectWeight"` + Objectors set.Set[ids.NodeID] `json:"objectors"` + AbstainWeight json.Uint64 `json:"abstainWeight"` +} + +type ACPsReply struct { + ACPs map[uint32]*ACP `json:"acps"` +} + +func (a *ACPsReply) getACP(acpNum uint32) *ACP { + acp, ok := a.ACPs[acpNum] + if !ok { + acp = &ACP{} + a.ACPs[acpNum] = acp + } + return acp +} + +func (i *Info) Acps(_ *http.Request, _ *struct{}, reply *ACPsReply) error { + i.log.Debug("API called", + zap.String("service", "info"), + zap.String("method", "acps"), + ) + + reply.ACPs = make(map[uint32]*ACP, constants.CurrentACPs.Len()) + peers := i.networking.PeerInfo(nil) + for _, peer := range peers { + weight := json.Uint64(i.validators.GetWeight(constants.PrimaryNetworkID, peer.ID)) + if weight == 0 { + continue + } + + for acpNum := range peer.SupportedACPs { + acp := reply.getACP(acpNum) + acp.Supporters.Add(peer.ID) + acp.SupportWeight += weight + } + for acpNum := range peer.ObjectedACPs { + acp := reply.getACP(acpNum) + acp.Objectors.Add(peer.ID) + acp.ObjectWeight += weight + } + } + + totalWeight, err := i.validators.TotalWeight(constants.PrimaryNetworkID) + if err != nil { + return err + } + for acpNum := range constants.CurrentACPs { + acp := reply.getACP(acpNum) + acp.AbstainWeight = json.Uint64(totalWeight) - acp.SupportWeight - acp.ObjectWeight + } + return nil +} + type GetTxFeeResponse struct { TxFee json.Uint64 `json:"txFee"` CreateAssetTxFee json.Uint64 `json:"createAssetTxFee"` diff --git a/api/info/service_test.go b/api/info/service_test.go index 312d8182ea83..b91f87354d1d 100644 --- a/api/info/service_test.go +++ b/api/info/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package info @@ -21,24 +21,20 @@ var errTest = errors.New("non-nil error") type getVMsTest struct { info *Info ctrl *gomock.Controller - mockLog *logging.MockLogger mockVMManager *vms.MockManager } func initGetVMsTest(t *testing.T) *getVMsTest { ctrl := gomock.NewController(t) - - service := Info{} - mockLog := logging.NewMockLogger(ctrl) mockVMManager := vms.NewMockManager(ctrl) - - service.log = mockLog - service.VMManager = mockVMManager - return &getVMsTest{ - info: &service, + info: &Info{ + Parameters: Parameters{ + VMManager: mockVMManager, + }, + log: logging.NoLog{}, + }, ctrl: ctrl, - mockLog: mockLog, mockVMManager: mockVMManager, } } @@ -62,7 +58,6 @@ func TestGetVMsSuccess(t *testing.T) { id2: alias2[1:], } - resources.mockLog.EXPECT().Debug(gomock.Any(), gomock.Any()).Times(1) resources.mockVMManager.EXPECT().ListFactories().Times(1).Return(vmIDs, nil) resources.mockVMManager.EXPECT().Aliases(id1).Times(1).Return(alias1, nil) resources.mockVMManager.EXPECT().Aliases(id2).Times(1).Return(alias2, nil) @@ -76,7 +71,6 @@ func TestGetVMsSuccess(t *testing.T) { func TestGetVMsVMsListFactoriesFails(t *testing.T) { resources := initGetVMsTest(t) - resources.mockLog.EXPECT().Debug(gomock.Any(), gomock.Any()).Times(1) resources.mockVMManager.EXPECT().ListFactories().Times(1).Return(nil, errTest) reply := GetVMsReply{} @@ -93,7 +87,6 @@ func TestGetVMsGetAliasesFails(t *testing.T) { vmIDs := []ids.ID{id1, id2} alias1 := []string{id1.String(), "vm1-alias-1", "vm1-alias-2"} - resources.mockLog.EXPECT().Debug(gomock.Any(), gomock.Any()).Times(1) resources.mockVMManager.EXPECT().ListFactories().Times(1).Return(vmIDs, nil) resources.mockVMManager.EXPECT().Aliases(id1).Times(1).Return(alias1, nil) resources.mockVMManager.EXPECT().Aliases(id2).Times(1).Return(nil, errTest) diff --git a/api/ipcs/client.go b/api/ipcs/client.go index 95391f0f4469..121c1855bc8f 100644 --- a/api/ipcs/client.go +++ b/api/ipcs/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ipcs diff --git a/api/ipcs/service.go b/api/ipcs/service.go index b9bb90479ce9..efe6f2e7280b 100644 --- a/api/ipcs/service.go +++ b/api/ipcs/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ipcs diff --git a/api/keystore/blockchain_keystore.go b/api/keystore/blockchain_keystore.go index 4c163b9627b7..31a3bdc59109 100644 --- a/api/keystore/blockchain_keystore.go +++ b/api/keystore/blockchain_keystore.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keystore diff --git a/api/keystore/client.go b/api/keystore/client.go index 43442ace79fe..9d12ea0d1df9 100644 --- a/api/keystore/client.go +++ b/api/keystore/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keystore diff --git a/api/keystore/codec.go b/api/keystore/codec.go index ebb196ccbfff..b925747c44ec 100644 --- a/api/keystore/codec.go +++ b/api/keystore/codec.go @@ -1,27 +1,28 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keystore import ( + "time" + "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/utils/units" ) const ( - maxPackerSize = 1 * units.GiB // max size, in bytes, of something being marshalled by Marshal() - maxSliceLength = linearcodec.DefaultMaxSliceLength + CodecVersion = 0 - codecVersion = 0 + maxPackerSize = 1 * units.GiB // max size, in bytes, of something being marshalled by Marshal() ) -var c codec.Manager +var Codec codec.Manager func init() { - lc := linearcodec.NewCustomMaxLength(maxSliceLength) - c = codec.NewManager(maxPackerSize) - if err := c.RegisterCodec(codecVersion, lc); err != nil { + lc := linearcodec.NewDefault(time.Time{}) + Codec = codec.NewManager(maxPackerSize) + if err := Codec.RegisterCodec(CodecVersion, lc); err != nil { panic(err) } } diff --git a/api/keystore/gkeystore/keystore_client.go b/api/keystore/gkeystore/keystore_client.go index 6bbfc6f92c1e..87527a640412 100644 --- a/api/keystore/gkeystore/keystore_client.go +++ b/api/keystore/gkeystore/keystore_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gkeystore diff --git a/api/keystore/gkeystore/keystore_server.go b/api/keystore/gkeystore/keystore_server.go index 9244939de3b9..65e6e90e99d9 100644 --- a/api/keystore/gkeystore/keystore_server.go +++ b/api/keystore/gkeystore/keystore_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gkeystore diff --git a/api/keystore/keystore.go b/api/keystore/keystore.go index cd7f0b8a8f21..ed3c9d21e57e 100644 --- a/api/keystore/keystore.go +++ b/api/keystore/keystore.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keystore @@ -188,7 +188,7 @@ func (ks *keystore) CreateUser(username, pw string) error { return err } - passwordBytes, err := c.Marshal(codecVersion, passwordHash) + passwordBytes, err := Codec.Marshal(CodecVersion, passwordHash) if err != nil { return err } @@ -288,14 +288,14 @@ func (ks *keystore) ImportUser(username, pw string, userBytes []byte) error { } userData := user{} - if _, err := c.Unmarshal(userBytes, &userData); err != nil { + if _, err := Codec.Unmarshal(userBytes, &userData); err != nil { return err } if !userData.Hash.Check(pw) { return fmt.Errorf("%w: user %q", errIncorrectPassword, username) } - usrBytes, err := c.Marshal(codecVersion, &userData.Hash) + usrBytes, err := Codec.Marshal(CodecVersion, &userData.Hash) if err != nil { return err } @@ -355,7 +355,7 @@ func (ks *keystore) ExportUser(username, pw string) ([]byte, error) { } // Return the byte representation of the user - return c.Marshal(codecVersion, &userData) + return Codec.Marshal(CodecVersion, &userData) } func (ks *keystore) getPassword(username string) (*password.Hash, error) { @@ -377,6 +377,6 @@ func (ks *keystore) getPassword(username string) (*password.Hash, error) { } passwordHash = &password.Hash{} - _, err = c.Unmarshal(userBytes, passwordHash) + _, err = Codec.Unmarshal(userBytes, passwordHash) return passwordHash, err } diff --git a/api/keystore/service.go b/api/keystore/service.go index d4c845743bbb..aa56433ee6e7 100644 --- a/api/keystore/service.go +++ b/api/keystore/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keystore diff --git a/api/keystore/service_test.go b/api/keystore/service_test.go index 842ab7d76cc7..c011c92e78e1 100644 --- a/api/keystore/service_test.go +++ b/api/keystore/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keystore diff --git a/api/metrics/gatherer_test.go b/api/metrics/gatherer_test.go index 2059c1ab584f..334c361ebcc0 100644 --- a/api/metrics/gatherer_test.go +++ b/api/metrics/gatherer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/api/metrics/multi_gatherer.go b/api/metrics/multi_gatherer.go index ce9af54936be..79affd4b7b2e 100644 --- a/api/metrics/multi_gatherer.go +++ b/api/metrics/multi_gatherer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics @@ -13,6 +13,8 @@ import ( dto "github.com/prometheus/client_model/go" "golang.org/x/exp/slices" + + "github.com/ava-labs/avalanchego/utils" ) var ( @@ -91,7 +93,7 @@ func (g *multiGatherer) Register(namespace string, gatherer prometheus.Gatherer) } func sortMetrics(m []*dto.MetricFamily) { - slices.SortFunc(m, func(i, j *dto.MetricFamily) bool { - return *i.Name < *j.Name + slices.SortFunc(m, func(i, j *dto.MetricFamily) int { + return utils.Compare(*i.Name, *j.Name) }) } diff --git a/api/metrics/multi_gatherer_test.go b/api/metrics/multi_gatherer_test.go index a2e59a90d51e..033e3e88b1e6 100644 --- a/api/metrics/multi_gatherer_test.go +++ b/api/metrics/multi_gatherer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/api/metrics/optional_gatherer.go b/api/metrics/optional_gatherer.go index f31603281cee..686856efcc86 100644 --- a/api/metrics/optional_gatherer.go +++ b/api/metrics/optional_gatherer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/api/metrics/optional_gatherer_test.go b/api/metrics/optional_gatherer_test.go index 887029a3572b..201750701313 100644 --- a/api/metrics/optional_gatherer_test.go +++ b/api/metrics/optional_gatherer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/api/server/allowed_hosts.go b/api/server/allowed_hosts.go index 6745f0e17565..7d2812b2782a 100644 --- a/api/server/allowed_hosts.go +++ b/api/server/allowed_hosts.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package server diff --git a/api/server/allowed_hosts_test.go b/api/server/allowed_hosts_test.go index ae7a824834a9..47b1a53df0ba 100644 --- a/api/server/allowed_hosts_test.go +++ b/api/server/allowed_hosts_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package server diff --git a/api/server/metrics.go b/api/server/metrics.go index 9859494f3ae4..e3b2d76c83ea 100644 --- a/api/server/metrics.go +++ b/api/server/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package server diff --git a/api/server/mock_server.go b/api/server/mock_server.go index 1d29c7054db3..769df9baa26f 100644 --- a/api/server/mock_server.go +++ b/api/server/mock_server.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/api/server (interfaces: Server) +// +// Generated by this command: +// +// mockgen -package=server -destination=api/server/mock_server.go github.com/ava-labs/avalanchego/api/server Server +// // Package server is a generated GoMock package. package server @@ -42,7 +44,7 @@ func (m *MockServer) EXPECT() *MockServerMockRecorder { // AddAliases mocks base method. func (m *MockServer) AddAliases(arg0 string, arg1 ...string) error { m.ctrl.T.Helper() - varargs := []interface{}{arg0} + varargs := []any{arg0} for _, a := range arg1 { varargs = append(varargs, a) } @@ -52,16 +54,16 @@ func (m *MockServer) AddAliases(arg0 string, arg1 ...string) error { } // AddAliases indicates an expected call of AddAliases. -func (mr *MockServerMockRecorder) AddAliases(arg0 interface{}, arg1 ...interface{}) *gomock.Call { +func (mr *MockServerMockRecorder) AddAliases(arg0 any, arg1 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) + varargs := append([]any{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAliases", reflect.TypeOf((*MockServer)(nil).AddAliases), varargs...) } // AddAliasesWithReadLock mocks base method. func (m *MockServer) AddAliasesWithReadLock(arg0 string, arg1 ...string) error { m.ctrl.T.Helper() - varargs := []interface{}{arg0} + varargs := []any{arg0} for _, a := range arg1 { varargs = append(varargs, a) } @@ -71,9 +73,9 @@ func (m *MockServer) AddAliasesWithReadLock(arg0 string, arg1 ...string) error { } // AddAliasesWithReadLock indicates an expected call of AddAliasesWithReadLock. -func (mr *MockServerMockRecorder) AddAliasesWithReadLock(arg0 interface{}, arg1 ...interface{}) *gomock.Call { +func (mr *MockServerMockRecorder) AddAliasesWithReadLock(arg0 any, arg1 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) + varargs := append([]any{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAliasesWithReadLock", reflect.TypeOf((*MockServer)(nil).AddAliasesWithReadLock), varargs...) } @@ -86,7 +88,7 @@ func (m *MockServer) AddRoute(arg0 http.Handler, arg1, arg2 string) error { } // AddRoute indicates an expected call of AddRoute. -func (mr *MockServerMockRecorder) AddRoute(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockServerMockRecorder) AddRoute(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRoute", reflect.TypeOf((*MockServer)(nil).AddRoute), arg0, arg1, arg2) } @@ -100,7 +102,7 @@ func (m *MockServer) AddRouteWithReadLock(arg0 http.Handler, arg1, arg2 string) } // AddRouteWithReadLock indicates an expected call of AddRouteWithReadLock. -func (mr *MockServerMockRecorder) AddRouteWithReadLock(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockServerMockRecorder) AddRouteWithReadLock(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRouteWithReadLock", reflect.TypeOf((*MockServer)(nil).AddRouteWithReadLock), arg0, arg1, arg2) } @@ -126,7 +128,7 @@ func (m *MockServer) RegisterChain(arg0 string, arg1 *snow.ConsensusContext, arg } // RegisterChain indicates an expected call of RegisterChain. -func (mr *MockServerMockRecorder) RegisterChain(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockServerMockRecorder) RegisterChain(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterChain", reflect.TypeOf((*MockServer)(nil).RegisterChain), arg0, arg1, arg2) } diff --git a/api/server/router.go b/api/server/router.go index b37c6282c90d..6adadf608be4 100644 --- a/api/server/router.go +++ b/api/server/router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package server diff --git a/api/server/router_test.go b/api/server/router_test.go index cae75d2c97bd..f6676a3727a3 100644 --- a/api/server/router_test.go +++ b/api/server/router_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package server diff --git a/api/server/server.go b/api/server/server.go index 0f676e0cef7f..a2364b6a17cd 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package server diff --git a/api/server/server_test.go b/api/server/server_test.go index 9f6f3732e591..584ad24a7862 100644 --- a/api/server/server_test.go +++ b/api/server/server_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package server @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" ) func TestRejectMiddleware(t *testing.T) { @@ -58,7 +59,8 @@ func TestRejectMiddleware(t *testing.T) { t.Run(tt.name, func(t *testing.T) { require := require.New(t) - ctx := &snow.ConsensusContext{} + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) ctx.State.Set(snow.EngineState{ State: tt.state, }) diff --git a/api/server/wrapper.go b/api/server/wrapper.go index e467dc968065..b6cca85c731e 100644 --- a/api/server/wrapper.go +++ b/api/server/wrapper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package server diff --git a/api/traced_handler.go b/api/traced_handler.go index 149be8208edc..9543c2ebbd15 100644 --- a/api/traced_handler.go +++ b/api/traced_handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package api diff --git a/app/app.go b/app/app.go index af651235ba77..26043ff449da 100644 --- a/app/app.go +++ b/app/app.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package app @@ -14,30 +14,20 @@ import ( "golang.org/x/sync/errgroup" - "github.com/ava-labs/avalanchego/nat" "github.com/ava-labs/avalanchego/node" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/utils/ulimit" ) -const ( - Header = ` _____ .__ .__ +const Header = ` _____ .__ .__ / _ \___ _______ | | _____ ____ ____ | |__ ____ ,_ o / /_\ \ \/ /\__ \ | | \__ \ / \_/ ___\| | \_/ __ \ / //\, / | \ / / __ \| |__/ __ \| | \ \___| Y \ ___/ \>> | \____|__ /\_/ (____ /____(____ /___| /\___ >___| /\___ > \\ \/ \/ \/ \/ \/ \/ \/` -) - -var ( - stakingPortName = fmt.Sprintf("%s-staking", constants.AppName) - httpPortName = fmt.Sprintf("%s-http", constants.AppName) - _ App = (*app)(nil) -) +var _ App = (*app)(nil) type App interface { // Start kicks off the application and returns immediately. @@ -88,7 +78,6 @@ func New(config node.Config) (App, error) { } return &app{ - config: config, node: n, log: log, logFactory: logFactory, @@ -133,7 +122,6 @@ func Run(app App) int { // app is a wrapper around a node that runs in this process type app struct { - config node.Config node *node.Node log logging.Logger logFactory logging.Factory @@ -144,88 +132,6 @@ type app struct { // Does not block until the node is done. Errors returned from this method // are not logged. func (a *app) Start() error { - // Track if sybil control is enforced - if !a.config.SybilProtectionEnabled { - a.log.Warn("sybil control is not enforced") - } - - // TODO move this to config - // SupportsNAT() for NoRouter is false. - // Which means we tried to perform a NAT activity but we were not successful. - if a.config.AttemptedNATTraversal && !a.config.Nat.SupportsNAT() { - a.log.Warn("UPnP and NAT-PMP router attach failed, " + - "you may not be listening publicly. " + - "Please confirm the settings in your router") - } - - if ip := a.config.IPPort.IPPort().IP; ip.IsLoopback() || ip.IsPrivate() { - a.log.Warn("P2P IP is private, you will not be publicly discoverable", - zap.Stringer("ip", ip), - ) - } - - // An empty host is treated as a wildcard to match all addresses, so it is - // considered public. - hostIsPublic := a.config.HTTPHost == "" - if !hostIsPublic { - ip, err := ips.Lookup(a.config.HTTPHost) - if err != nil { - a.log.Fatal("failed to lookup HTTP host", - zap.String("host", a.config.HTTPHost), - zap.Error(err), - ) - a.logFactory.Close() - return err - } - hostIsPublic = !ip.IsLoopback() && !ip.IsPrivate() - - a.log.Debug("finished HTTP host lookup", - zap.String("host", a.config.HTTPHost), - zap.Stringer("ip", ip), - zap.Bool("isPublic", hostIsPublic), - ) - } - - mapper := nat.NewPortMapper(a.log, a.config.Nat) - - // Open staking port we want for NAT traversal to have the external port - // (config.IP.Port) to connect to our internal listening port - // (config.InternalStakingPort) which should be the same in most cases. - if port := a.config.IPPort.IPPort().Port; port != 0 { - mapper.Map( - port, - port, - stakingPortName, - a.config.IPPort, - a.config.IPResolutionFreq, - ) - } - - // Don't open the HTTP port if the HTTP server is private - if hostIsPublic { - a.log.Warn("HTTP server is binding to a potentially public host. "+ - "You may be vulnerable to a DoS attack if your HTTP port is publicly accessible", - zap.String("host", a.config.HTTPHost), - ) - - // For NAT traversal we want to route from the external port - // (config.ExternalHTTPPort) to our internal port (config.HTTPPort). - if a.config.HTTPPort != 0 { - mapper.Map( - a.config.HTTPPort, - a.config.HTTPPort, - httpPortName, - nil, - a.config.IPResolutionFreq, - ) - } - } - - // Regularly update our public IP. - // Note that if the node config said to not dynamically resolve and - // update our public IP, [p.config.IPUdater] is a no-op implementation. - go a.config.IPUpdater.Dispatch(a.log) - // [p.ExitCode] will block until [p.exitWG.Done] is called a.exitWG.Add(1) go func() { @@ -238,9 +144,6 @@ func (a *app) Start() error { a.exitWG.Done() }() defer func() { - mapper.UnmapAllPorts() - a.config.IPUpdater.Stop() - // If [p.node.Dispatch()] panics, then we should log the panic and // then re-raise the panic. This is why the above defer is broken // into two parts. diff --git a/cache/cache.go b/cache/cache.go index 3d7206e79050..10ecad2c502f 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/cache/empty_cache.go b/cache/empty_cache.go index 767cf5b74266..3a70ea91fe5d 100644 --- a/cache/empty_cache.go +++ b/cache/empty_cache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/cache/lru_cache.go b/cache/lru_cache.go index 84d342db9f01..2a8a7ebe6d80 100644 --- a/cache/lru_cache.go +++ b/cache/lru_cache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/cache/lru_cache_benchmark_test.go b/cache/lru_cache_benchmark_test.go index d8e4f4185933..3ddf03cb06f7 100644 --- a/cache/lru_cache_benchmark_test.go +++ b/cache/lru_cache_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/cache/lru_cache_test.go b/cache/lru_cache_test.go index 9ae277299d94..e8f0b2883c1c 100644 --- a/cache/lru_cache_test.go +++ b/cache/lru_cache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/cache/lru_sized_cache.go b/cache/lru_sized_cache.go index 6d093e033195..5dc9b5fdec01 100644 --- a/cache/lru_sized_cache.go +++ b/cache/lru_sized_cache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/cache/lru_sized_cache_test.go b/cache/lru_sized_cache_test.go index 65dbcf8c8ab7..ad1c8b403362 100644 --- a/cache/lru_sized_cache_test.go +++ b/cache/lru_sized_cache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/cache/metercacher/cache.go b/cache/metercacher/cache.go index 6b6fcd909c81..c2ff666f25e7 100644 --- a/cache/metercacher/cache.go +++ b/cache/metercacher/cache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metercacher diff --git a/cache/metercacher/cache_test.go b/cache/metercacher/cache_test.go index 7ef1676c0874..3f575acdc1d4 100644 --- a/cache/metercacher/cache_test.go +++ b/cache/metercacher/cache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metercacher diff --git a/cache/metercacher/metrics.go b/cache/metercacher/metrics.go index a65e31805934..f08082e1be71 100644 --- a/cache/metercacher/metrics.go +++ b/cache/metercacher/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metercacher diff --git a/cache/test_cacher.go b/cache/test_cacher.go index 1b029bcb4b21..2e85502e4a55 100644 --- a/cache/test_cacher.go +++ b/cache/test_cacher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/cache/unique_cache.go b/cache/unique_cache.go index 24052d79355e..b958b1f3a870 100644 --- a/cache/unique_cache.go +++ b/cache/unique_cache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/cache/unique_cache_test.go b/cache/unique_cache_test.go index 3f0d40f8dc0d..199bdc87c081 100644 --- a/cache/unique_cache_test.go +++ b/cache/unique_cache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cache diff --git a/chains/atomic/codec.go b/chains/atomic/codec.go index bc2e93c27213..290713b3c258 100644 --- a/chains/atomic/codec.go +++ b/chains/atomic/codec.go @@ -1,22 +1,25 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic import ( + "math" + "time" + "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" ) -const codecVersion = 0 +const CodecVersion = 0 -// codecManager is used to marshal and unmarshal dbElements and chain IDs. -var codecManager codec.Manager +// Codec is used to marshal and unmarshal dbElements and chain IDs. +var Codec codec.Manager func init() { - linearCodec := linearcodec.NewDefault() - codecManager = codec.NewDefaultManager() - if err := codecManager.RegisterCodec(codecVersion, linearCodec); err != nil { + lc := linearcodec.NewDefault(time.Time{}) + Codec = codec.NewManager(math.MaxInt) + if err := Codec.RegisterCodec(CodecVersion, lc); err != nil { panic(err) } } diff --git a/chains/atomic/gsharedmemory/filtered_batch.go b/chains/atomic/gsharedmemory/filtered_batch.go index df63e8df2d16..a6ba81251f57 100644 --- a/chains/atomic/gsharedmemory/filtered_batch.go +++ b/chains/atomic/gsharedmemory/filtered_batch.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gsharedmemory diff --git a/chains/atomic/gsharedmemory/shared_memory_client.go b/chains/atomic/gsharedmemory/shared_memory_client.go index 649503a0313c..096a8117e7a1 100644 --- a/chains/atomic/gsharedmemory/shared_memory_client.go +++ b/chains/atomic/gsharedmemory/shared_memory_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gsharedmemory diff --git a/chains/atomic/gsharedmemory/shared_memory_server.go b/chains/atomic/gsharedmemory/shared_memory_server.go index 3e2d0d38940e..0aaa71c01f8e 100644 --- a/chains/atomic/gsharedmemory/shared_memory_server.go +++ b/chains/atomic/gsharedmemory/shared_memory_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gsharedmemory diff --git a/chains/atomic/gsharedmemory/shared_memory_test.go b/chains/atomic/gsharedmemory/shared_memory_test.go index 0ce546c94f77..02dfb7324a78 100644 --- a/chains/atomic/gsharedmemory/shared_memory_test.go +++ b/chains/atomic/gsharedmemory/shared_memory_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gsharedmemory diff --git a/chains/atomic/memory.go b/chains/atomic/memory.go index a8aa703fe217..76f5b6451d97 100644 --- a/chains/atomic/memory.go +++ b/chains/atomic/memory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic @@ -107,7 +107,7 @@ func sharedID(id1, id2 ids.ID) ids.ID { id1, id2 = id2, id1 } - combinedBytes, err := codecManager.Marshal(codecVersion, [2]ids.ID{id1, id2}) + combinedBytes, err := Codec.Marshal(CodecVersion, [2]ids.ID{id1, id2}) if err != nil { panic(err) } diff --git a/chains/atomic/memory_test.go b/chains/atomic/memory_test.go index 5acdb5233af4..7ca02e6d7275 100644 --- a/chains/atomic/memory_test.go +++ b/chains/atomic/memory_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic diff --git a/chains/atomic/mock_shared_memory.go b/chains/atomic/mock_shared_memory.go index d22bd0f995ff..0e63179314da 100644 --- a/chains/atomic/mock_shared_memory.go +++ b/chains/atomic/mock_shared_memory.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/chains/atomic (interfaces: SharedMemory) +// +// Generated by this command: +// +// mockgen -package=atomic -destination=chains/atomic/mock_shared_memory.go github.com/ava-labs/avalanchego/chains/atomic SharedMemory +// // Package atomic is a generated GoMock package. package atomic @@ -41,7 +43,7 @@ func (m *MockSharedMemory) EXPECT() *MockSharedMemoryMockRecorder { // Apply mocks base method. func (m *MockSharedMemory) Apply(arg0 map[ids.ID]*Requests, arg1 ...database.Batch) error { m.ctrl.T.Helper() - varargs := []interface{}{arg0} + varargs := []any{arg0} for _, a := range arg1 { varargs = append(varargs, a) } @@ -51,9 +53,9 @@ func (m *MockSharedMemory) Apply(arg0 map[ids.ID]*Requests, arg1 ...database.Bat } // Apply indicates an expected call of Apply. -func (mr *MockSharedMemoryMockRecorder) Apply(arg0 interface{}, arg1 ...interface{}) *gomock.Call { +func (mr *MockSharedMemoryMockRecorder) Apply(arg0 any, arg1 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) + varargs := append([]any{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockSharedMemory)(nil).Apply), varargs...) } @@ -67,7 +69,7 @@ func (m *MockSharedMemory) Get(arg0 ids.ID, arg1 [][]byte) ([][]byte, error) { } // Get indicates an expected call of Get. -func (mr *MockSharedMemoryMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockSharedMemoryMockRecorder) Get(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSharedMemory)(nil).Get), arg0, arg1) } @@ -84,7 +86,7 @@ func (m *MockSharedMemory) Indexed(arg0 ids.ID, arg1 [][]byte, arg2, arg3 []byte } // Indexed indicates an expected call of Indexed. -func (mr *MockSharedMemoryMockRecorder) Indexed(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockSharedMemoryMockRecorder) Indexed(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Indexed", reflect.TypeOf((*MockSharedMemory)(nil).Indexed), arg0, arg1, arg2, arg3, arg4) } diff --git a/chains/atomic/prefixes.go b/chains/atomic/prefixes.go index 08927384317e..adc21c36e2b0 100644 --- a/chains/atomic/prefixes.go +++ b/chains/atomic/prefixes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic diff --git a/chains/atomic/shared_memory.go b/chains/atomic/shared_memory.go index 7b2f8a562c82..d90c5685fa35 100644 --- a/chains/atomic/shared_memory.go +++ b/chains/atomic/shared_memory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic diff --git a/chains/atomic/shared_memory_test.go b/chains/atomic/shared_memory_test.go index bb3266d80602..1597d662131a 100644 --- a/chains/atomic/shared_memory_test.go +++ b/chains/atomic/shared_memory_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic diff --git a/chains/atomic/state.go b/chains/atomic/state.go index aee269915ea9..a9e9bbd05cd8 100644 --- a/chains/atomic/state.go +++ b/chains/atomic/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic @@ -8,11 +8,12 @@ import ( "errors" "fmt" + "golang.org/x/exp/slices" + "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/linkeddb" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/set" ) @@ -111,7 +112,7 @@ func (s *state) SetValue(e *Element) error { Traits: e.Traits, } - valueBytes, err := codecManager.Marshal(codecVersion, &dbElem) + valueBytes, err := Codec.Marshal(CodecVersion, &dbElem) if err != nil { return err } @@ -155,7 +156,7 @@ func (s *state) RemoveValue(key []byte) error { // The value doesn't exist, so we should optimistically delete it dbElem := dbElement{Present: false} - valueBytes, err := codecManager.Marshal(codecVersion, &dbElem) + valueBytes, err := Codec.Marshal(CodecVersion, &dbElem) if err != nil { return err } @@ -188,7 +189,7 @@ func (s *state) loadValue(key []byte) (*dbElement, error) { // The key was in the database value := &dbElement{} - _, err = codecManager.Unmarshal(valueBytes, value) + _, err = Codec.Unmarshal(valueBytes, value) return value, err } @@ -207,7 +208,7 @@ func (s *state) getKeys(traits [][]byte, startTrait, startKey []byte, limit int) lastKey := startKey // Iterate over the traits in order appending all of the keys that possess // the given [traits]. - utils.SortBytes(traits) + slices.SortFunc(traits, bytes.Compare) for _, trait := range traits { switch bytes.Compare(trait, startTrait) { case -1: diff --git a/chains/atomic/test_shared_memory.go b/chains/atomic/test_shared_memory.go index d89940c31c2f..82b1cbeff3a5 100644 --- a/chains/atomic/test_shared_memory.go +++ b/chains/atomic/test_shared_memory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic diff --git a/chains/atomic/writer.go b/chains/atomic/writer.go index d117218fe87d..6bcdd86b00b4 100644 --- a/chains/atomic/writer.go +++ b/chains/atomic/writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package atomic diff --git a/chains/linearizable_vm.go b/chains/linearizable_vm.go index f4fc93f7a696..97fe9eb4d1f4 100644 --- a/chains/linearizable_vm.go +++ b/chains/linearizable_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chains diff --git a/chains/manager.go b/chains/manager.go index e764a5dfb3ce..97f80e1a79c5 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chains @@ -368,7 +368,7 @@ func (m *manager) createChain(chainParams ChainParameters) { // created or not. This attempts to notify the node operator that their // node may not be properly validating the subnet they expect to be // validating. - healthCheckErr := fmt.Errorf("failed to create chain on subnet: %s", chainParams.SubnetID) + healthCheckErr := fmt.Errorf("failed to create chain on subnet %s: %w", chainParams.SubnetID, err) err := m.Health.RegisterHealthCheck( chainAlias, health.CheckerFunc(func(context.Context) (interface{}, error) { @@ -769,12 +769,15 @@ func (m *manager) createAvalancheChain( // using. var vmWrappingProposerVM block.ChainVM = proposervm.New( vmWrappedInsideProposerVM, - m.ApricotPhase4Time, - m.ApricotPhase4MinPChainHeight, - minBlockDelay, - numHistoricalBlocks, - m.stakingSigner, - m.stakingCert, + proposervm.Config{ + ActivationTime: m.ApricotPhase4Time, + DurangoTime: version.GetDurangoTime(m.NetworkID), + MinimumPChainHeight: m.ApricotPhase4MinPChainHeight, + MinBlkDelay: minBlockDelay, + NumHistoricalBlocks: numHistoricalBlocks, + StakingLeafSigner: m.stakingSigner, + StakingCertLeaf: m.stakingCert, + }, ) if m.MeterVMEnabled { @@ -1112,12 +1115,15 @@ func (m *manager) createSnowmanChain( vm = proposervm.New( vm, - m.ApricotPhase4Time, - m.ApricotPhase4MinPChainHeight, - minBlockDelay, - numHistoricalBlocks, - m.stakingSigner, - m.stakingCert, + proposervm.Config{ + ActivationTime: m.ApricotPhase4Time, + DurangoTime: version.GetDurangoTime(m.NetworkID), + MinimumPChainHeight: m.ApricotPhase4MinPChainHeight, + MinBlkDelay: minBlockDelay, + NumHistoricalBlocks: numHistoricalBlocks, + StakingLeafSigner: m.stakingSigner, + StakingCertLeaf: m.stakingCert, + }, ) if m.MeterVMEnabled { diff --git a/chains/registrant.go b/chains/registrant.go index 3a2137048c1b..cd3aa6e9c0bf 100644 --- a/chains/registrant.go +++ b/chains/registrant.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chains diff --git a/chains/test_manager.go b/chains/test_manager.go index e4dabea426f9..d142035b422c 100644 --- a/chains/test_manager.go +++ b/chains/test_manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chains diff --git a/codec/codec.go b/codec/codec.go index 413b6d174be2..7aacb9085848 100644 --- a/codec/codec.go +++ b/codec/codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package codec diff --git a/codec/general_codec.go b/codec/general_codec.go index ac32b84e6a87..3688065a021f 100644 --- a/codec/general_codec.go +++ b/codec/general_codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package codec diff --git a/codec/hierarchycodec/codec.go b/codec/hierarchycodec/codec.go index 1b82380bc576..db2ffed0425d 100644 --- a/codec/hierarchycodec/codec.go +++ b/codec/hierarchycodec/codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package hierarchycodec @@ -7,6 +7,7 @@ import ( "fmt" "reflect" "sync" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/reflectcodec" @@ -50,19 +51,19 @@ type hierarchyCodec struct { } // New returns a new, concurrency-safe codec -func New(tagNames []string, maxSliceLen uint32) Codec { +func New(durangoTime time.Time, tagNames []string, maxSliceLen uint32) Codec { hCodec := &hierarchyCodec{ currentGroupID: 0, nextTypeID: 0, registeredTypes: bimap.New[typeID, reflect.Type](), } - hCodec.Codec = reflectcodec.New(hCodec, tagNames, maxSliceLen) + hCodec.Codec = reflectcodec.New(hCodec, tagNames, durangoTime, maxSliceLen) return hCodec } // NewDefault returns a new codec with reasonable default values -func NewDefault() Codec { - return New([]string{reflectcodec.DefaultTagName}, defaultMaxSliceLength) +func NewDefault(durangoTime time.Time) Codec { + return New(durangoTime, []string{reflectcodec.DefaultTagName}, defaultMaxSliceLength) } // SkipRegistrations some number of type IDs diff --git a/codec/hierarchycodec/codec_test.go b/codec/hierarchycodec/codec_test.go index c4c71d76571c..8149cdcc65e2 100644 --- a/codec/hierarchycodec/codec_test.go +++ b/codec/hierarchycodec/codec_test.go @@ -1,29 +1,45 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package hierarchycodec import ( "testing" + "time" "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/utils/timer/mockable" ) func TestVectors(t *testing.T) { for _, test := range codec.Tests { - c := NewDefault() + c := NewDefault(mockable.MaxTime) test(c, t) } } func TestMultipleTags(t *testing.T) { for _, test := range codec.MultipleTagsTests { - c := New([]string{"tag1", "tag2"}, defaultMaxSliceLength) + c := New(mockable.MaxTime, []string{"tag1", "tag2"}, defaultMaxSliceLength) + test(c, t) + } +} + +func TestEnforceSliceLen(t *testing.T) { + for _, test := range codec.EnforceSliceLenTests { + c := NewDefault(mockable.MaxTime) + test(c, t) + } +} + +func TestIgnoreSliceLen(t *testing.T) { + for _, test := range codec.IgnoreSliceLenTests { + c := NewDefault(time.Time{}) test(c, t) } } func FuzzStructUnmarshalHierarchyCodec(f *testing.F) { - c := NewDefault() + c := NewDefault(mockable.MaxTime) codec.FuzzStructUnmarshal(c, f) } diff --git a/codec/linearcodec/codec.go b/codec/linearcodec/codec.go index 07097aee79eb..6ad36b8a197d 100644 --- a/codec/linearcodec/codec.go +++ b/codec/linearcodec/codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package linearcodec @@ -7,6 +7,7 @@ import ( "fmt" "reflect" "sync" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/reflectcodec" @@ -44,23 +45,23 @@ type linearCodec struct { // New returns a new, concurrency-safe codec; it allow to specify // both tagNames and maxSlicelenght -func New(tagNames []string, maxSliceLen uint32) Codec { +func New(durangoTime time.Time, tagNames []string, maxSliceLen uint32) Codec { hCodec := &linearCodec{ nextTypeID: 0, registeredTypes: bimap.New[uint32, reflect.Type](), } - hCodec.Codec = reflectcodec.New(hCodec, tagNames, maxSliceLen) + hCodec.Codec = reflectcodec.New(hCodec, tagNames, durangoTime, maxSliceLen) return hCodec } // NewDefault is a convenience constructor; it returns a new codec with reasonable default values -func NewDefault() Codec { - return New([]string{reflectcodec.DefaultTagName}, DefaultMaxSliceLength) +func NewDefault(durangoTime time.Time) Codec { + return New(durangoTime, []string{reflectcodec.DefaultTagName}, DefaultMaxSliceLength) } // NewCustomMaxLength is a convenience constructor; it returns a new codec with custom max length and default tags -func NewCustomMaxLength(maxSliceLen uint32) Codec { - return New([]string{reflectcodec.DefaultTagName}, maxSliceLen) +func NewCustomMaxLength(durangoTime time.Time, maxSliceLen uint32) Codec { + return New(durangoTime, []string{reflectcodec.DefaultTagName}, maxSliceLen) } // Skip some number of type IDs diff --git a/codec/linearcodec/codec_test.go b/codec/linearcodec/codec_test.go index db8a4e720dd6..3d2f3efff68a 100644 --- a/codec/linearcodec/codec_test.go +++ b/codec/linearcodec/codec_test.go @@ -1,29 +1,45 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package linearcodec import ( "testing" + "time" "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/utils/timer/mockable" ) func TestVectors(t *testing.T) { for _, test := range codec.Tests { - c := NewDefault() + c := NewDefault(mockable.MaxTime) test(c, t) } } func TestMultipleTags(t *testing.T) { for _, test := range codec.MultipleTagsTests { - c := New([]string{"tag1", "tag2"}, DefaultMaxSliceLength) + c := New(mockable.MaxTime, []string{"tag1", "tag2"}, DefaultMaxSliceLength) + test(c, t) + } +} + +func TestEnforceSliceLen(t *testing.T) { + for _, test := range codec.EnforceSliceLenTests { + c := NewDefault(mockable.MaxTime) + test(c, t) + } +} + +func TestIgnoreSliceLen(t *testing.T) { + for _, test := range codec.IgnoreSliceLenTests { + c := NewDefault(time.Time{}) test(c, t) } } func FuzzStructUnmarshalLinearCodec(f *testing.F) { - c := NewDefault() + c := NewDefault(mockable.MaxTime) codec.FuzzStructUnmarshal(c, f) } diff --git a/codec/manager.go b/codec/manager.go index 3a5e9eb174fc..6fb48aaad9f8 100644 --- a/codec/manager.go +++ b/codec/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package codec diff --git a/codec/mock_manager.go b/codec/mock_manager.go index 91961806bf4f..36bbae57e96f 100644 --- a/codec/mock_manager.go +++ b/codec/mock_manager.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/codec (interfaces: Manager) +// +// Generated by this command: +// +// mockgen -package=codec -destination=codec/mock_manager.go github.com/ava-labs/avalanchego/codec Manager +// // Package codec is a generated GoMock package. package codec @@ -37,7 +39,7 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder { } // Marshal mocks base method. -func (m *MockManager) Marshal(arg0 uint16, arg1 interface{}) ([]byte, error) { +func (m *MockManager) Marshal(arg0 uint16, arg1 any) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Marshal", arg0, arg1) ret0, _ := ret[0].([]byte) @@ -46,7 +48,7 @@ func (m *MockManager) Marshal(arg0 uint16, arg1 interface{}) ([]byte, error) { } // Marshal indicates an expected call of Marshal. -func (mr *MockManagerMockRecorder) Marshal(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) Marshal(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marshal", reflect.TypeOf((*MockManager)(nil).Marshal), arg0, arg1) } @@ -60,13 +62,13 @@ func (m *MockManager) RegisterCodec(arg0 uint16, arg1 Codec) error { } // RegisterCodec indicates an expected call of RegisterCodec. -func (mr *MockManagerMockRecorder) RegisterCodec(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) RegisterCodec(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterCodec", reflect.TypeOf((*MockManager)(nil).RegisterCodec), arg0, arg1) } // Size mocks base method. -func (m *MockManager) Size(arg0 uint16, arg1 interface{}) (int, error) { +func (m *MockManager) Size(arg0 uint16, arg1 any) (int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Size", arg0, arg1) ret0, _ := ret[0].(int) @@ -75,13 +77,13 @@ func (m *MockManager) Size(arg0 uint16, arg1 interface{}) (int, error) { } // Size indicates an expected call of Size. -func (mr *MockManagerMockRecorder) Size(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) Size(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Size", reflect.TypeOf((*MockManager)(nil).Size), arg0, arg1) } // Unmarshal mocks base method. -func (m *MockManager) Unmarshal(arg0 []byte, arg1 interface{}) (uint16, error) { +func (m *MockManager) Unmarshal(arg0 []byte, arg1 any) (uint16, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Unmarshal", arg0, arg1) ret0, _ := ret[0].(uint16) @@ -90,7 +92,7 @@ func (m *MockManager) Unmarshal(arg0 []byte, arg1 interface{}) (uint16, error) { } // Unmarshal indicates an expected call of Unmarshal. -func (mr *MockManagerMockRecorder) Unmarshal(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) Unmarshal(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unmarshal", reflect.TypeOf((*MockManager)(nil).Unmarshal), arg0, arg1) } diff --git a/codec/reflectcodec/struct_fielder.go b/codec/reflectcodec/struct_fielder.go index d266b60a3ebf..efac391e8362 100644 --- a/codec/reflectcodec/struct_fielder.go +++ b/codec/reflectcodec/struct_fielder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package reflectcodec @@ -6,100 +6,69 @@ package reflectcodec import ( "fmt" "reflect" - "strconv" "sync" "github.com/ava-labs/avalanchego/codec" ) -const ( - // SliceLenTagName that specifies the length of a slice. - SliceLenTagName = "len" - - // TagValue is the value the tag must have to be serialized. - TagValue = "true" - - // TagValue is the value the tag must have to be serialized, this variant - // includes the nullable option - TagWithNullableValue = "true,nullable" -) +// TagValue is the value the tag must have to be serialized. +const TagValue = "true" var _ StructFielder = (*structFielder)(nil) -type FieldDesc struct { - Index int - MaxSliceLen uint32 - Nullable bool -} - // StructFielder handles discovery of serializable fields in a struct. type StructFielder interface { // Returns the fields that have been marked as serializable in [t], which is - // a struct type. Additionally, returns the custom maximum length slice that - // may be serialized into the field, if any. + // a struct type. // Returns an error if a field has tag "[tagName]: [TagValue]" but the field // is un-exported. // GetSerializedField(Foo) --> [1,5,8] means Foo.Field(1), Foo.Field(5), // Foo.Field(8) are to be serialized/deserialized. - GetSerializedFields(t reflect.Type) ([]FieldDesc, error) + GetSerializedFields(t reflect.Type) ([]int, error) } -func NewStructFielder(tagNames []string, maxSliceLen uint32) StructFielder { +func NewStructFielder(tagNames []string) StructFielder { return &structFielder{ tags: tagNames, - maxSliceLen: maxSliceLen, - serializedFieldIndices: make(map[reflect.Type][]FieldDesc), + serializedFieldIndices: make(map[reflect.Type][]int), } } type structFielder struct { - lock sync.Mutex + lock sync.RWMutex // multiple tags per field can be specified. A field is serialized/deserialized // if it has at least one of the specified tags. tags []string - maxSliceLen uint32 - // Key: a struct type // Value: Slice where each element is index in the struct type of a field // that is serialized/deserialized e.g. Foo --> [1,5,8] means Foo.Field(1), // etc. are to be serialized/deserialized. We assume this cache is pretty // small (a few hundred keys at most) and doesn't take up much memory. - serializedFieldIndices map[reflect.Type][]FieldDesc + serializedFieldIndices map[reflect.Type][]int } -func (s *structFielder) GetSerializedFields(t reflect.Type) ([]FieldDesc, error) { +func (s *structFielder) GetSerializedFields(t reflect.Type) ([]int, error) { + if serializedFields, ok := s.getCachedSerializedFields(t); ok { // use pre-computed result + return serializedFields, nil + } + s.lock.Lock() defer s.lock.Unlock() - if s.serializedFieldIndices == nil { - s.serializedFieldIndices = make(map[reflect.Type][]FieldDesc) - } - if serializedFields, ok := s.serializedFieldIndices[t]; ok { // use pre-computed result - return serializedFields, nil - } numFields := t.NumField() - serializedFields := make([]FieldDesc, 0, numFields) + serializedFields := make([]int, 0, numFields) for i := 0; i < numFields; i++ { // Go through all fields of this struct field := t.Field(i) // Multiple tags per fields can be specified. // Serialize/Deserialize field if it has // any tag with the right value - var ( - captureField bool - nullable bool - ) + var captureField bool for _, tag := range s.tags { - switch field.Tag.Get(tag) { - case TagValue: - captureField = true - case TagWithNullableValue: + if field.Tag.Get(tag) == TagValue { captureField = true - nullable = true - } - if captureField { break } } @@ -112,18 +81,16 @@ func (s *structFielder) GetSerializedFields(t reflect.Type) ([]FieldDesc, error) field.Name, ) } - sliceLenField := field.Tag.Get(SliceLenTagName) - maxSliceLen := s.maxSliceLen - - if newLen, err := strconv.ParseUint(sliceLenField, 10, 31); err == nil { - maxSliceLen = uint32(newLen) - } - serializedFields = append(serializedFields, FieldDesc{ - Index: i, - MaxSliceLen: maxSliceLen, - Nullable: nullable, - }) + serializedFields = append(serializedFields, i) } s.serializedFieldIndices[t] = serializedFields // cache result return serializedFields, nil } + +func (s *structFielder) getCachedSerializedFields(t reflect.Type) ([]int, bool) { + s.lock.RLock() + defer s.lock.RUnlock() + + cachedFields, ok := s.serializedFieldIndices[t] + return cachedFields, ok +} diff --git a/codec/reflectcodec/type_codec.go b/codec/reflectcodec/type_codec.go index 9f9037f43d4e..312927559280 100644 --- a/codec/reflectcodec/type_codec.go +++ b/codec/reflectcodec/type_codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package reflectcodec @@ -9,6 +9,7 @@ import ( "fmt" "math" "reflect" + "time" "golang.org/x/exp/slices" @@ -72,16 +73,18 @@ type TypeCodec interface { // 7. nil slices are marshaled as empty slices type genericCodec struct { typer TypeCodec + durangoTime time.Time // Time after which [maxSliceLen] will be ignored maxSliceLen uint32 fielder StructFielder } // New returns a new, concurrency-safe codec -func New(typer TypeCodec, tagNames []string, maxSliceLen uint32) codec.Codec { +func New(typer TypeCodec, tagNames []string, durangoTime time.Time, maxSliceLen uint32) codec.Codec { return &genericCodec{ typer: typer, + durangoTime: durangoTime, maxSliceLen: maxSliceLen, - fielder: NewStructFielder(tagNames, maxSliceLen), + fielder: NewStructFielder(tagNames), } } @@ -90,16 +93,14 @@ func (c *genericCodec) Size(value interface{}) (int, error) { return 0, errMarshalNil // can't marshal nil } - size, _, err := c.size(reflect.ValueOf(value), false /*=nullable*/, nil /*=typeStack*/) + size, _, err := c.size(reflect.ValueOf(value), nil /*=typeStack*/) return size, err } // size returns the size of the value along with whether the value is constant -// sized. This function takes into account a `nullable` property which allows -// pointers and interfaces to serialize nil values +// sized. func (c *genericCodec) size( value reflect.Value, - nullable bool, typeStack set.Set[reflect.Type], ) (int, bool, error) { switch valueKind := value.Kind(); valueKind { @@ -125,24 +126,14 @@ func (c *genericCodec) size( return wrappers.StringLen(value.String()), false, nil case reflect.Ptr: if value.IsNil() { - if !nullable { - return 0, false, errMarshalNil - } - return wrappers.BoolLen, false, nil + return 0, false, errMarshalNil } - size, constSize, err := c.size(value.Elem(), false /*=nullable*/, typeStack) - if nullable { - return wrappers.BoolLen + size, false, err - } - return size, constSize, err + return c.size(value.Elem(), typeStack) case reflect.Interface: if value.IsNil() { - if !nullable { - return 0, false, errMarshalNil - } - return wrappers.BoolLen, false, nil + return 0, false, errMarshalNil } underlyingValue := value.Interface() @@ -153,12 +144,9 @@ func (c *genericCodec) size( typeStack.Add(underlyingType) prefixSize := c.typer.PrefixSize(underlyingType) - valueSize, _, err := c.size(value.Elem(), false /*=nullable*/, typeStack) + valueSize, _, err := c.size(value.Elem(), typeStack) typeStack.Remove(underlyingType) - if nullable { - return wrappers.BoolLen + prefixSize + valueSize, false, err - } return prefixSize + valueSize, false, err case reflect.Slice: @@ -167,7 +155,7 @@ func (c *genericCodec) size( return wrappers.IntLen, false, nil } - size, constSize, err := c.size(value.Index(0), nullable, typeStack) + size, constSize, err := c.size(value.Index(0), typeStack) if err != nil { return 0, false, err } @@ -179,7 +167,7 @@ func (c *genericCodec) size( } for i := 1; i < numElts; i++ { - innerSize, _, err := c.size(value.Index(i), nullable, typeStack) + innerSize, _, err := c.size(value.Index(i), typeStack) if err != nil { return 0, false, err } @@ -193,7 +181,7 @@ func (c *genericCodec) size( return 0, true, nil } - size, constSize, err := c.size(value.Index(0), nullable, typeStack) + size, constSize, err := c.size(value.Index(0), typeStack) if err != nil { return 0, false, err } @@ -205,7 +193,7 @@ func (c *genericCodec) size( } for i := 1; i < numElts; i++ { - innerSize, _, err := c.size(value.Index(i), nullable, typeStack) + innerSize, _, err := c.size(value.Index(i), typeStack) if err != nil { return 0, false, err } @@ -223,8 +211,8 @@ func (c *genericCodec) size( size int constSize = true ) - for _, fieldDesc := range serializedFields { - innerSize, innerConstSize, err := c.size(value.Field(fieldDesc.Index), fieldDesc.Nullable, typeStack) + for _, fieldIndex := range serializedFields { + innerSize, innerConstSize, err := c.size(value.Field(fieldIndex), typeStack) if err != nil { return 0, false, err } @@ -239,11 +227,11 @@ func (c *genericCodec) size( return wrappers.IntLen, false, nil } - keySize, keyConstSize, err := c.size(iter.Key(), false /*=nullable*/, typeStack) + keySize, keyConstSize, err := c.size(iter.Key(), typeStack) if err != nil { return 0, false, err } - valueSize, valueConstSize, err := c.size(iter.Value(), nullable, typeStack) + valueSize, valueConstSize, err := c.size(iter.Value(), typeStack) if err != nil { return 0, false, err } @@ -258,7 +246,7 @@ func (c *genericCodec) size( totalValueSize = valueSize ) for iter.Next() { - valueSize, _, err := c.size(iter.Value(), nullable, typeStack) + valueSize, _, err := c.size(iter.Value(), typeStack) if err != nil { return 0, false, err } @@ -272,7 +260,7 @@ func (c *genericCodec) size( totalKeySize = keySize ) for iter.Next() { - keySize, _, err := c.size(iter.Key(), false /*=nullable*/, typeStack) + keySize, _, err := c.size(iter.Key(), typeStack) if err != nil { return 0, false, err } @@ -283,11 +271,11 @@ func (c *genericCodec) size( default: totalSize := wrappers.IntLen + keySize + valueSize for iter.Next() { - keySize, _, err := c.size(iter.Key(), false /*=nullable*/, typeStack) + keySize, _, err := c.size(iter.Key(), typeStack) if err != nil { return 0, false, err } - valueSize, _, err := c.size(iter.Value(), nullable, typeStack) + valueSize, _, err := c.size(iter.Value(), typeStack) if err != nil { return 0, false, err } @@ -307,7 +295,7 @@ func (c *genericCodec) MarshalInto(value interface{}, p *wrappers.Packer) error return errMarshalNil // can't marshal nil } - return c.marshal(reflect.ValueOf(value), p, c.maxSliceLen, false /*=nullable*/, nil /*=typeStack*/) + return c.marshal(reflect.ValueOf(value), p, nil /*=typeStack*/) } // marshal writes the byte representation of [value] to [p] @@ -316,8 +304,6 @@ func (c *genericCodec) MarshalInto(value interface{}, p *wrappers.Packer) error func (c *genericCodec) marshal( value reflect.Value, p *wrappers.Packer, - maxSliceLen uint32, - nullable bool, typeStack set.Set[reflect.Type], ) error { switch valueKind := value.Kind(); valueKind { @@ -352,25 +338,13 @@ func (c *genericCodec) marshal( p.PackBool(value.Bool()) return p.Err case reflect.Ptr: - isNil := value.IsNil() - if nullable { - p.PackBool(isNil) - if isNil || p.Err != nil { - return p.Err - } - } else if isNil { + if value.IsNil() { return errMarshalNil } - return c.marshal(value.Elem(), p, c.maxSliceLen, false /*=nullable*/, typeStack) + return c.marshal(value.Elem(), p, typeStack) case reflect.Interface: - isNil := value.IsNil() - if nullable { - p.PackBool(isNil) - if isNil || p.Err != nil { - return p.Err - } - } else if isNil { + if value.IsNil() { return errMarshalNil } @@ -383,18 +357,25 @@ func (c *genericCodec) marshal( if err := c.typer.PackPrefix(p, underlyingType); err != nil { return err } - if err := c.marshal(value.Elem(), p, c.maxSliceLen, false /*=nullable*/, typeStack); err != nil { + if err := c.marshal(value.Elem(), p, typeStack); err != nil { return err } typeStack.Remove(underlyingType) return p.Err case reflect.Slice: numElts := value.Len() // # elements in the slice/array. 0 if this slice is nil. - if uint32(numElts) > maxSliceLen { + if numElts > math.MaxInt32 { return fmt.Errorf("%w; slice length, %d, exceeds maximum length, %d", codec.ErrMaxSliceLenExceeded, numElts, - maxSliceLen, + math.MaxInt32, + ) + } + if time.Now().Before(c.durangoTime) && uint32(numElts) > c.maxSliceLen { + return fmt.Errorf("%w; slice length, %d, exceeds maximum length, %d", + codec.ErrMaxSliceLenExceeded, + numElts, + c.maxSliceLen, ) } p.PackInt(uint32(numElts)) // pack # elements @@ -414,27 +395,20 @@ func (c *genericCodec) marshal( return p.Err } for i := 0; i < numElts; i++ { // Process each element in the slice - if err := c.marshal(value.Index(i), p, c.maxSliceLen, nullable, typeStack); err != nil { + if err := c.marshal(value.Index(i), p, typeStack); err != nil { return err } } return nil case reflect.Array: - numElts := value.Len() if elemKind := value.Type().Kind(); elemKind == reflect.Uint8 { sliceVal := value.Convert(reflect.TypeOf([]byte{})) p.PackFixedBytes(sliceVal.Bytes()) return p.Err } - if uint32(numElts) > c.maxSliceLen { - return fmt.Errorf("%w; array length, %d, exceeds maximum length, %d", - codec.ErrMaxSliceLenExceeded, - numElts, - c.maxSliceLen, - ) - } + numElts := value.Len() for i := 0; i < numElts; i++ { // Process each element in the array - if err := c.marshal(value.Index(i), p, c.maxSliceLen, nullable, typeStack); err != nil { + if err := c.marshal(value.Index(i), p, typeStack); err != nil { return err } } @@ -444,8 +418,8 @@ func (c *genericCodec) marshal( if err != nil { return err } - for _, fieldDesc := range serializedFields { // Go through all fields of this struct that are serialized - if err := c.marshal(value.Field(fieldDesc.Index), p, fieldDesc.MaxSliceLen, fieldDesc.Nullable, typeStack); err != nil { // Serialize the field and write to byte array + for _, fieldIndex := range serializedFields { // Go through all fields of this struct that are serialized + if err := c.marshal(value.Field(fieldIndex), p, typeStack); err != nil { // Serialize the field and write to byte array return err } } @@ -453,11 +427,18 @@ func (c *genericCodec) marshal( case reflect.Map: keys := value.MapKeys() numElts := len(keys) - if uint32(numElts) > maxSliceLen { + if numElts > math.MaxInt32 { + return fmt.Errorf("%w; slice length, %d, exceeds maximum length, %d", + codec.ErrMaxSliceLenExceeded, + numElts, + math.MaxInt32, + ) + } + if time.Now().Before(c.durangoTime) && uint32(numElts) > c.maxSliceLen { return fmt.Errorf("%w; map length, %d, exceeds maximum length, %d", codec.ErrMaxSliceLenExceeded, numElts, - maxSliceLen, + c.maxSliceLen, ) } p.PackInt(uint32(numElts)) // pack # elements @@ -476,7 +457,7 @@ func (c *genericCodec) marshal( startOffset := p.Offset endOffset := p.Offset for i, key := range keys { - if err := c.marshal(key, p, c.maxSliceLen, false /*=nullable*/, typeStack); err != nil { + if err := c.marshal(key, p, typeStack); err != nil { return err } if p.Err != nil { @@ -490,10 +471,10 @@ func (c *genericCodec) marshal( endOffset = p.Offset } - slices.SortFunc(sortedKeys, func(a, b keyTuple) bool { + slices.SortFunc(sortedKeys, func(a, b keyTuple) int { aBytes := p.Bytes[a.startIndex:a.endIndex] bBytes := p.Bytes[b.startIndex:b.endIndex] - return bytes.Compare(aBytes, bBytes) < 0 + return bytes.Compare(aBytes, bBytes) }) allKeyBytes := slices.Clone(p.Bytes[startOffset:p.Offset]) @@ -509,7 +490,7 @@ func (c *genericCodec) marshal( } // serialize and pack value - if err := c.marshal(value.MapIndex(key.key), p, c.maxSliceLen, nullable, typeStack); err != nil { + if err := c.marshal(value.MapIndex(key.key), p, typeStack); err != nil { return err } } @@ -534,7 +515,7 @@ func (c *genericCodec) Unmarshal(bytes []byte, dest interface{}) error { if destPtr.Kind() != reflect.Ptr { return errNeedPointer } - if err := c.unmarshal(&p, destPtr.Elem(), c.maxSliceLen, false /*=nullable*/, nil /*=typeStack*/); err != nil { + if err := c.unmarshal(&p, destPtr.Elem(), nil /*=typeStack*/); err != nil { return err } if p.Offset != len(bytes) { @@ -549,16 +530,10 @@ func (c *genericCodec) Unmarshal(bytes []byte, dest interface{}) error { // Unmarshal from p.Bytes into [value]. [value] must be addressable. // -// The [nullable] property affects how pointers and interfaces are unmarshalled, -// as an extra byte would be used to unmarshal nil values for pointers and -// interaces -// // c.lock should be held for the duration of this function func (c *genericCodec) unmarshal( p *wrappers.Packer, value reflect.Value, - maxSliceLen uint32, - nullable bool, typeStack set.Set[reflect.Type], ) error { switch value.Kind() { @@ -621,18 +596,18 @@ func (c *genericCodec) unmarshal( if p.Err != nil { return fmt.Errorf("couldn't unmarshal slice: %w", p.Err) } - if numElts32 > maxSliceLen { + if numElts32 > math.MaxInt32 { return fmt.Errorf("%w; array length, %d, exceeds maximum length, %d", codec.ErrMaxSliceLenExceeded, numElts32, - maxSliceLen, + math.MaxInt32, ) } - if numElts32 > math.MaxInt32 { + if time.Now().Before(c.durangoTime) && numElts32 > c.maxSliceLen { return fmt.Errorf("%w; array length, %d, exceeds maximum length, %d", codec.ErrMaxSliceLenExceeded, numElts32, - math.MaxInt32, + c.maxSliceLen, ) } numElts := int(numElts32) @@ -651,7 +626,7 @@ func (c *genericCodec) unmarshal( zeroValue := reflect.Zero(innerType) for i := 0; i < numElts; i++ { value.Set(reflect.Append(value, zeroValue)) - if err := c.unmarshal(p, value.Index(i), c.maxSliceLen, nullable, typeStack); err != nil { + if err := c.unmarshal(p, value.Index(i), typeStack); err != nil { return err } } @@ -669,7 +644,7 @@ func (c *genericCodec) unmarshal( return nil } for i := 0; i < numElts; i++ { - if err := c.unmarshal(p, value.Index(i), c.maxSliceLen, nullable, typeStack); err != nil { + if err := c.unmarshal(p, value.Index(i), typeStack); err != nil { return err } } @@ -681,13 +656,6 @@ func (c *genericCodec) unmarshal( } return nil case reflect.Interface: - if nullable { - isNil := p.UnpackBool() - if isNil || p.Err != nil { - return p.Err - } - } - intfImplementor, err := c.typer.UnpackPrefix(p, value.Type()) if err != nil { return err @@ -699,7 +667,7 @@ func (c *genericCodec) unmarshal( typeStack.Add(intfImplementorType) // Unmarshal into the struct - if err := c.unmarshal(p, intfImplementor, c.maxSliceLen, false /*=nullable*/, typeStack); err != nil { + if err := c.unmarshal(p, intfImplementor, typeStack); err != nil { return err } @@ -713,26 +681,19 @@ func (c *genericCodec) unmarshal( return fmt.Errorf("couldn't unmarshal struct: %w", err) } // Go through the fields and umarshal into them - for _, fieldDesc := range serializedFieldIndices { - if err := c.unmarshal(p, value.Field(fieldDesc.Index), fieldDesc.MaxSliceLen, fieldDesc.Nullable, typeStack); err != nil { + for _, fieldIndex := range serializedFieldIndices { + if err := c.unmarshal(p, value.Field(fieldIndex), typeStack); err != nil { return err } } return nil case reflect.Ptr: - if nullable { - isNil := p.UnpackBool() - if isNil || p.Err != nil { - return p.Err - } - } - // Get the type this pointer points to t := value.Type().Elem() // Create a new pointer to a new value of the underlying type v := reflect.New(t) // Fill the value - if err := c.unmarshal(p, v.Elem(), c.maxSliceLen, false /*=nullable*/, typeStack); err != nil { + if err := c.unmarshal(p, v.Elem(), typeStack); err != nil { return err } // Assign to the top-level struct's member @@ -743,7 +704,14 @@ func (c *genericCodec) unmarshal( if p.Err != nil { return fmt.Errorf("couldn't unmarshal map: %w", p.Err) } - if numElts32 > c.maxSliceLen { + if numElts32 > math.MaxInt32 { + return fmt.Errorf("%w; map length, %d, exceeds maximum length, %d", + codec.ErrMaxSliceLenExceeded, + numElts32, + math.MaxInt32, + ) + } + if time.Now().Before(c.durangoTime) && numElts32 > c.maxSliceLen { return fmt.Errorf("%w; map length, %d, exceeds maximum length, %d", codec.ErrMaxSliceLenExceeded, numElts32, @@ -767,7 +735,7 @@ func (c *genericCodec) unmarshal( keyStartOffset := p.Offset - if err := c.unmarshal(p, mapKey, c.maxSliceLen, false /*=nullable*/, typeStack); err != nil { + if err := c.unmarshal(p, mapKey, typeStack); err != nil { return err } @@ -785,7 +753,7 @@ func (c *genericCodec) unmarshal( // Get the value mapValue := reflect.New(mapValueType).Elem() - if err := c.unmarshal(p, mapValue, c.maxSliceLen, nullable, typeStack); err != nil { + if err := c.unmarshal(p, mapValue, typeStack); err != nil { return err } diff --git a/codec/reflectcodec/type_codec_test.go b/codec/reflectcodec/type_codec_test.go deleted file mode 100644 index 42b256c4a6c9..000000000000 --- a/codec/reflectcodec/type_codec_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package reflectcodec - -import ( - "reflect" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSizeWithNil(t *testing.T) { - require := require.New(t) - var x *int32 - y := int32(1) - c := genericCodec{} - _, _, err := c.size(reflect.ValueOf(x), false /*=nullable*/, nil /*=typeStack*/) - require.ErrorIs(err, errMarshalNil) - len, _, err := c.size(reflect.ValueOf(x), true /*=nullable*/, nil /*=typeStack*/) - require.Empty(err) - require.Equal(1, len) - x = &y - len, _, err = c.size(reflect.ValueOf(y), true /*=nullable*/, nil /*=typeStack*/) - require.Empty(err) - require.Equal(4, len) - len, _, err = c.size(reflect.ValueOf(x), true /*=nullable*/, nil /*=typeStack*/) - require.Empty(err) - require.Equal(5, len) -} diff --git a/codec/registry.go b/codec/registry.go index f0f1c2ff8157..de87e1a9b2aa 100644 --- a/codec/registry.go +++ b/codec/registry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package codec diff --git a/codec/test_codec.go b/codec/test_codec.go index 341912a823af..d58e2d818f9e 100644 --- a/codec/test_codec.go +++ b/codec/test_codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package codec @@ -8,6 +8,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/utils/wrappers" ) var ( @@ -23,7 +25,6 @@ var ( TestBigArray, TestPointerToStruct, TestSliceOfStruct, - TestStructWithNullable, TestInterface, TestSliceOfInterface, TestArrayOfInterface, @@ -40,7 +41,6 @@ var ( TestNegativeNumbers, TestTooLargeUnmarshal, TestUnmarshalInvalidInterface, - TestRestrictedSlice, TestExtraSpace, TestSliceLengthOverflow, TestMap, @@ -49,6 +49,15 @@ var ( MultipleTagsTests = []func(c GeneralCodec, t testing.TB){ TestMultipleTags, } + + EnforceSliceLenTests = []func(c GeneralCodec, t testing.TB){ + TestCanNotMarshalLargeSlices, + TestCanNotUnmarshalLargeSlices, + } + + IgnoreSliceLenTests = []func(c GeneralCodec, t testing.TB){ + TestCanMarshalLargeSlices, + } ) // The below structs and interfaces exist @@ -64,8 +73,7 @@ type Foo interface { } type MyInnerStruct struct { - Str string `serialize:"true"` - NumberNotProvided *int32 `serialize:"true,nullable"` + Str string `serialize:"true"` } func (*MyInnerStruct) Foo() int { @@ -88,15 +96,6 @@ type MyInnerStruct3 struct { F Foo `serialize:"true"` } -type MyStructWithNullable struct { - Interface any `serialize:"true,nullable"` - Int32 *int32 `serialize:"true,nullable"` - Int64 *int64 `serialize:"true,nullable"` - Int32Slice []*int32 `serialize:"true,nullable"` - Int32Array [2]*int32 `serialize:"true,nullable"` - Int32Map map[int32]*int32 `serialize:"true,nullable"` -} - type myStruct struct { InnerStruct MyInnerStruct `serialize:"true"` InnerStruct2 *MyInnerStruct `serialize:"true"` @@ -156,23 +155,21 @@ func TestStruct(codec GeneralCodec, t testing.TB) { myMap7["key"] = "value" myMap7[int32(1)] = int32(2) - number := int32(8) - myStructInstance := myStruct{ - InnerStruct: MyInnerStruct{"hello", nil}, - InnerStruct2: &MyInnerStruct{"yello", nil}, + InnerStruct: MyInnerStruct{"hello"}, + InnerStruct2: &MyInnerStruct{"yello"}, Member1: 1, Member2: 2, MySlice: []byte{1, 2, 3, 4}, MySlice2: []string{"one", "two", "three"}, - MySlice3: []MyInnerStruct{{"abc", nil}, {"ab", &number}, {"c", nil}}, + MySlice3: []MyInnerStruct{{"abc"}, {"ab"}, {"c"}}, MySlice4: []*MyInnerStruct2{{true}, {}}, MySlice5: []Foo{&MyInnerStruct2{true}, &MyInnerStruct2{}}, MyArray: [4]byte{5, 6, 7, 8}, MyArray2: [5]string{"four", "five", "six", "seven"}, - MyArray3: [3]MyInnerStruct{{"d", nil}, {"e", nil}, {"f", nil}}, + MyArray3: [3]MyInnerStruct{{"d"}, {"e"}, {"f"}}, MyArray4: [2]*MyInnerStruct2{{}, {true}}, - MyInterface: &MyInnerStruct{"yeet", &number}, + MyInterface: &MyInnerStruct{"yeet"}, InnerStruct3: MyInnerStruct3{ Str: "str", M1: MyInnerStruct{ @@ -427,79 +424,19 @@ func TestPointerToStruct(codec GeneralCodec, t testing.TB) { require.Equal(myPtr, myPtrUnmarshaled) } -func TestStructWithNullable(codec GeneralCodec, t testing.TB) { - require := require.New(t) - n1 := int32(5) - n2 := int64(10) - struct1 := MyStructWithNullable{ - Interface: nil, - Int32: &n1, - Int64: &n2, - Int32Slice: []*int32{ - nil, - nil, - &n1, - }, - Int32Array: [2]*int32{ - nil, - &n1, - }, - Int32Map: map[int32]*int32{ - 1: nil, - 2: &n1, - }, - } - - require.NoError(codec.RegisterType(&MyStructWithNullable{})) - manager := NewDefaultManager() - require.NoError(manager.RegisterCodec(0, codec)) - - bytes, err := manager.Marshal(0, struct1) - require.NoError(err) - - bytesLen, err := manager.Size(0, struct1) - require.NoError(err) - require.Len(bytes, bytesLen) - - var struct1Unmarshaled MyStructWithNullable - version, err := manager.Unmarshal(bytes, &struct1Unmarshaled) - require.NoError(err) - require.Zero(version) - require.Equal(struct1, struct1Unmarshaled) - - struct1 = MyStructWithNullable{ - Int32Slice: []*int32{}, - Int32Map: map[int32]*int32{}, - } - bytes, err = manager.Marshal(0, struct1) - require.NoError(err) - - bytesLen, err = manager.Size(0, struct1) - require.NoError(err) - require.Len(bytes, bytesLen) - - var struct1Unmarshaled2 MyStructWithNullable - version, err = manager.Unmarshal(bytes, &struct1Unmarshaled2) - require.NoError(err) - require.Zero(version) - require.Equal(struct1, struct1Unmarshaled2) -} - // Test marshalling a slice of structs func TestSliceOfStruct(codec GeneralCodec, t testing.TB) { require := require.New(t) - n1 := int32(-1) - n2 := int32(0xff) mySlice := []MyInnerStruct3{ { Str: "One", - M1: MyInnerStruct{"Two", &n1}, - F: &MyInnerStruct{"Three", &n2}, + M1: MyInnerStruct{"Two"}, + F: &MyInnerStruct{"Three"}, }, { Str: "Four", - M1: MyInnerStruct{"Five", nil}, - F: &MyInnerStruct{"Six", nil}, + M1: MyInnerStruct{"Five"}, + F: &MyInnerStruct{"Six"}, }, } require.NoError(codec.RegisterType(&MyInnerStruct{})) @@ -947,27 +884,6 @@ func TestUnmarshalInvalidInterface(codec GeneralCodec, t testing.TB) { } } -// Ensure deserializing slices that have been length restricted errors correctly -func TestRestrictedSlice(codec GeneralCodec, t testing.TB) { - require := require.New(t) - - type inner struct { - Bytes []byte `serialize:"true" len:"2"` - } - bytes := []byte{0, 0, 0, 0, 0, 3, 0, 1, 2} - - manager := NewDefaultManager() - require.NoError(manager.RegisterCodec(0, codec)) - - s := inner{} - _, err := manager.Unmarshal(bytes, &s) - require.ErrorIs(err, ErrMaxSliceLenExceeded) - - s.Bytes = []byte{0, 1, 2} - _, err = manager.Marshal(0, s) - require.ErrorIs(err, ErrMaxSliceLenExceeded) -} - // Test unmarshaling something with extra data func TestExtraSpace(codec GeneralCodec, t testing.TB) { require := require.New(t) @@ -982,12 +898,12 @@ func TestExtraSpace(codec GeneralCodec, t testing.TB) { require.ErrorIs(err, ErrExtraSpace) } -// Ensure deserializing slices that have been length restricted errors correctly +// Ensure deserializing slices whose lengths exceed MaxInt32 error correctly func TestSliceLengthOverflow(codec GeneralCodec, t testing.TB) { require := require.New(t) type inner struct { - Vals []uint32 `serialize:"true" len:"2"` + Vals []uint32 `serialize:"true"` } bytes := []byte{ // Codec Version: @@ -1115,6 +1031,52 @@ func TestMap(codec GeneralCodec, t testing.TB) { require.Len(outerArrayBytes, outerArraySize) } +func TestCanNotMarshalLargeSlices(codec GeneralCodec, t testing.TB) { + require := require.New(t) + + data := make([]uint16, 1_000_000) + + manager := NewManager(math.MaxInt) + require.NoError(manager.RegisterCodec(0, codec)) + + _, err := manager.Marshal(0, data) + require.ErrorIs(err, ErrMaxSliceLenExceeded) +} + +func TestCanNotUnmarshalLargeSlices(codec GeneralCodec, t testing.TB) { + require := require.New(t) + + writer := wrappers.Packer{ + Bytes: make([]byte, 2+4+2_000_000), + } + writer.PackShort(0) + writer.PackInt(1_000_000) + + manager := NewManager(math.MaxInt) + require.NoError(manager.RegisterCodec(0, codec)) + + var data []uint16 + _, err := manager.Unmarshal(writer.Bytes, &data) + require.ErrorIs(err, ErrMaxSliceLenExceeded) +} + +func TestCanMarshalLargeSlices(codec GeneralCodec, t testing.TB) { + require := require.New(t) + + data := make([]uint16, 1_000_000) + + manager := NewManager(math.MaxInt) + require.NoError(manager.RegisterCodec(0, codec)) + + bytes, err := manager.Marshal(0, data) + require.NoError(err) + + var unmarshalledData []uint16 + _, err = manager.Unmarshal(bytes, &unmarshalledData) + require.NoError(err) + require.Equal(data, unmarshalledData) +} + func FuzzStructUnmarshal(codec GeneralCodec, f *testing.F) { manager := NewDefaultManager() // Register the types that may be unmarshaled into interfaces diff --git a/config/config.go b/config/config.go index d894e832d889..09cc32d286b5 100644 --- a/config/config.go +++ b/config/config.go @@ -1,10 +1,9 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package config import ( - "context" "crypto/tls" "encoding/base64" "encoding/json" @@ -12,7 +11,6 @@ import ( "fmt" "io/fs" "math" - "net" "os" "path/filepath" "strings" @@ -25,7 +23,6 @@ import ( "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/ipcs" - "github.com/ava-labs/avalanchego/nat" "github.com/ava-labs/avalanchego/network" "github.com/ava-labs/avalanchego/network/dialer" "github.com/ava-labs/avalanchego/network/throttling" @@ -40,7 +37,6 @@ import ( "github.com/ava-labs/avalanchego/utils/compression" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/dynamicip" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/password" @@ -58,29 +54,41 @@ const ( chainConfigFileName = "config" chainUpgradeFileName = "upgrade" subnetConfigFileExt = ".json" - ipResolutionTimeout = 30 * time.Second ipcDeprecationMsg = "IPC API is deprecated" keystoreDeprecationMsg = "keystore API is deprecated" acceptedFrontierGossipDeprecationMsg = "push-based accepted frontier gossip is deprecated" + peerListPushGossipDeprecationMsg = "push-based peer list gossip is deprecated" ) var ( // Deprecated key --> deprecation message (i.e. which key replaces it) // TODO: deprecate "BootstrapIDsKey" and "BootstrapIPsKey" - deprecatedKeys = map[string]string{ - IpcAPIEnabledKey: ipcDeprecationMsg, - IpcsChainIDsKey: ipcDeprecationMsg, - IpcsPathKey: ipcDeprecationMsg, + commitThresholdDeprecationMsg = fmt.Sprintf("use --%s instead", SnowCommitThresholdKey) + deprecatedKeys = map[string]string{ + IpcAPIEnabledKey: ipcDeprecationMsg, + IpcsChainIDsKey: ipcDeprecationMsg, + IpcsPathKey: ipcDeprecationMsg, + KeystoreAPIEnabledKey: keystoreDeprecationMsg, + ConsensusGossipAcceptedFrontierValidatorSizeKey: acceptedFrontierGossipDeprecationMsg, ConsensusGossipAcceptedFrontierNonValidatorSizeKey: acceptedFrontierGossipDeprecationMsg, ConsensusGossipAcceptedFrontierPeerSizeKey: acceptedFrontierGossipDeprecationMsg, ConsensusGossipOnAcceptValidatorSizeKey: acceptedFrontierGossipDeprecationMsg, ConsensusGossipOnAcceptNonValidatorSizeKey: acceptedFrontierGossipDeprecationMsg, ConsensusGossipOnAcceptPeerSizeKey: acceptedFrontierGossipDeprecationMsg, + + NetworkPeerListValidatorGossipSizeKey: peerListPushGossipDeprecationMsg, + NetworkPeerListNonValidatorGossipSizeKey: peerListPushGossipDeprecationMsg, + NetworkPeerListPeersGossipSizeKey: peerListPushGossipDeprecationMsg, + NetworkPeerListGossipFreqKey: peerListPushGossipDeprecationMsg, + + SnowRogueCommitThresholdKey: commitThresholdDeprecationMsg, + SnowVirtuousCommitThresholdKey: commitThresholdDeprecationMsg, } + errConflictingACPOpinion = errors.New("supporting and objecting to the same ACP") errSybilProtectionDisabledStakerWeights = errors.New("sybil protection disabled weights must be positive") errSybilProtectionDisabledOnPublicNetwork = errors.New("sybil protection disabled on public network") errAuthPasswordTooWeak = errors.New("API auth password is not strong enough") @@ -101,21 +109,16 @@ var ( errCannotReadDirectory = errors.New("cannot read directory") errUnmarshalling = errors.New("unmarshalling failed") errFileDoesNotExist = errors.New("file does not exist") + errGzipDeprecatedMsg = errors.New("gzip compression is not supported, use zstd or no compression") ) func getConsensusConfig(v *viper.Viper) snowball.Parameters { p := snowball.Parameters{ - K: v.GetInt(SnowSampleSizeKey), - AlphaPreference: v.GetInt(SnowPreferenceQuorumSizeKey), - AlphaConfidence: v.GetInt(SnowConfidenceQuorumSizeKey), - // During the X-chain linearization we require BetaVirtuous and - // BetaRogue to be equal. Therefore we use the more conservative - // BetaRogue value for both BetaVirtuous and BetaRogue. - // - // TODO: After the X-chain linearization use the - // SnowVirtuousCommitThresholdKey as before. - BetaVirtuous: v.GetInt(SnowRogueCommitThresholdKey), - BetaRogue: v.GetInt(SnowRogueCommitThresholdKey), + K: v.GetInt(SnowSampleSizeKey), + AlphaPreference: v.GetInt(SnowPreferenceQuorumSizeKey), + AlphaConfidence: v.GetInt(SnowConfidenceQuorumSizeKey), + BetaVirtuous: v.GetInt(SnowCommitThresholdKey), + BetaRogue: v.GetInt(SnowCommitThresholdKey), ConcurrentRepolls: v.GetInt(SnowConcurrentRepollsKey), OptimalProcessing: v.GetInt(SnowOptimalProcessingKey), MaxOutstandingItems: v.GetInt(SnowMaxProcessingKey), @@ -125,6 +128,10 @@ func getConsensusConfig(v *viper.Viper) snowball.Parameters { p.AlphaPreference = v.GetInt(SnowQuorumSizeKey) p.AlphaConfidence = p.AlphaPreference } + if v.IsSet(SnowRogueCommitThresholdKey) { + p.BetaVirtuous = v.GetInt(SnowRogueCommitThresholdKey) + p.BetaRogue = v.GetInt(SnowRogueCommitThresholdKey) + } return p } @@ -340,12 +347,34 @@ func getNetworkConfig( if err != nil { return network.Config{}, err } + if compressionType == compression.TypeGzip { + return network.Config{}, errGzipDeprecatedMsg + } allowPrivateIPs := !constants.ProductionNetworkIDs.Contains(networkID) if v.IsSet(NetworkAllowPrivateIPsKey) { allowPrivateIPs = v.GetBool(NetworkAllowPrivateIPsKey) } + var supportedACPs set.Set[uint32] + for _, acp := range v.GetIntSlice(ACPSupportKey) { + if acp < 0 || acp > math.MaxInt32 { + return network.Config{}, fmt.Errorf("invalid ACP: %d", acp) + } + supportedACPs.Add(uint32(acp)) + } + + var objectedACPs set.Set[uint32] + for _, acp := range v.GetIntSlice(ACPObjectKey) { + if acp < 0 || acp > math.MaxInt32 { + return network.Config{}, fmt.Errorf("invalid ACP: %d", acp) + } + objectedACPs.Add(uint32(acp)) + } + if supportedACPs.Overlaps(objectedACPs) { + return network.Config{}, errConflictingACPOpinion + } + config := network.Config{ ThrottlerConfig: network.ThrottlerConfig{ MaxInboundConnsPerSec: maxInboundConnsPerSec, @@ -411,6 +440,8 @@ func getNetworkConfig( PeerListNonValidatorGossipSize: v.GetUint32(NetworkPeerListNonValidatorGossipSizeKey), PeerListPeersGossipSize: v.GetUint32(NetworkPeerListPeersGossipSizeKey), PeerListGossipFreq: v.GetDuration(NetworkPeerListGossipFreqKey), + PeerListPullGossipFreq: v.GetDuration(NetworkPeerListPullGossipFreqKey), + PeerListBloomResetFreq: v.GetDuration(NetworkPeerListBloomResetFreqKey), }, DelayConfig: network.DelayConfig{ @@ -425,6 +456,9 @@ func getNetworkConfig( UptimeMetricFreq: v.GetDuration(UptimeMetricFreqKey), MaximumInboundMessageTimeout: v.GetDuration(NetworkMaximumInboundTimeoutKey), + SupportedACPs: supportedACPs, + ObjectedACPs: objectedACPs, + RequireValidatorToConnect: v.GetBool(NetworkRequireValidatorToConnectKey), PeerReadBufferSize: int(v.GetUint(NetworkPeerReadBufferSizeKey)), PeerWriteBufferSize: int(v.GetUint(NetworkPeerWriteBufferSizeKey)), @@ -443,6 +477,10 @@ func getNetworkConfig( return network.Config{}, fmt.Errorf("%q must be >= 0", NetworkOutboundConnectionTimeoutKey) case config.PeerListGossipFreq < 0: return network.Config{}, fmt.Errorf("%s must be >= 0", NetworkPeerListGossipFreqKey) + case config.PeerListPullGossipFreq < 0: + return network.Config{}, fmt.Errorf("%s must be >= 0", NetworkPeerListPullGossipFreqKey) + case config.PeerListBloomResetFreq < 0: + return network.Config{}, fmt.Errorf("%s must be >= 0", NetworkPeerListBloomResetFreqKey) case config.ThrottlerConfig.InboundMsgThrottlerConfig.CPUThrottlerConfig.MaxRecheckDelay < constants.MinInboundThrottlerMaxRecheckDelay: return network.Config{}, fmt.Errorf("%s must be >= %d", InboundThrottlerCPUMaxRecheckDelayKey, constants.MinInboundThrottlerMaxRecheckDelay) case config.ThrottlerConfig.InboundMsgThrottlerConfig.DiskThrottlerConfig.MaxRecheckDelay < constants.MinInboundThrottlerMaxRecheckDelay: @@ -593,64 +631,19 @@ func getBootstrapConfig(v *viper.Viper, networkID uint32) (node.BootstrapConfig, } func getIPConfig(v *viper.Viper) (node.IPConfig, error) { - ipResolutionService := v.GetString(PublicIPResolutionServiceKey) - ipResolutionFreq := v.GetDuration(PublicIPResolutionFreqKey) - if ipResolutionFreq <= 0 { - return node.IPConfig{}, fmt.Errorf("%q must be > 0", PublicIPResolutionFreqKey) - } - - stakingPort := uint16(v.GetUint(StakingPortKey)) - publicIP := v.GetString(PublicIPKey) - if publicIP != "" && ipResolutionService != "" { - return node.IPConfig{}, fmt.Errorf("only one of --%s and --%s can be given", PublicIPKey, PublicIPResolutionServiceKey) - } - - // Define default configuration ipConfig := node.IPConfig{ - IPUpdater: dynamicip.NewNoUpdater(), - IPResolutionFreq: ipResolutionFreq, - Nat: nat.NewNoRouter(), - ListenHost: v.GetString(StakingHostKey), + PublicIP: v.GetString(PublicIPKey), + PublicIPResolutionService: v.GetString(PublicIPResolutionServiceKey), + PublicIPResolutionFreq: v.GetDuration(PublicIPResolutionFreqKey), + ListenHost: v.GetString(StakingHostKey), + ListenPort: uint16(v.GetUint(StakingPortKey)), } - - if publicIP != "" { - // User specified a specific public IP to use. - ip := net.ParseIP(publicIP) - if ip == nil { - return node.IPConfig{}, fmt.Errorf("invalid IP Address %s", publicIP) - } - ipConfig.IPPort = ips.NewDynamicIPPort(ip, stakingPort) - return ipConfig, nil - } - if ipResolutionService != "" { - // User specified to use dynamic IP resolution. - resolver, err := dynamicip.NewResolver(ipResolutionService) - if err != nil { - return node.IPConfig{}, fmt.Errorf("couldn't create IP resolver: %w", err) - } - - // Use that to resolve our public IP. - ctx, cancel := context.WithTimeout(context.Background(), ipResolutionTimeout) - defer cancel() - ip, err := resolver.Resolve(ctx) - if err != nil { - return node.IPConfig{}, fmt.Errorf("couldn't resolve public IP: %w", err) - } - ipConfig.IPPort = ips.NewDynamicIPPort(ip, stakingPort) - ipConfig.IPUpdater = dynamicip.NewUpdater(ipConfig.IPPort, resolver, ipResolutionFreq) - return ipConfig, nil + if ipConfig.PublicIPResolutionFreq <= 0 { + return node.IPConfig{}, fmt.Errorf("%q must be > 0", PublicIPResolutionFreqKey) } - - // User didn't specify a public IP to use, and they didn't specify a public IP resolution - // service to use. Try to resolve public IP with NAT traversal. - nat := nat.GetRouter() - ip, err := nat.ExternalIP() - if err != nil { - return node.IPConfig{}, fmt.Errorf("public IP / IP resolution service not given and failed to resolve IP with NAT: %w", err) + if ipConfig.PublicIP != "" && ipConfig.PublicIPResolutionService != "" { + return node.IPConfig{}, fmt.Errorf("only one of --%s and --%s can be given", PublicIPKey, PublicIPResolutionServiceKey) } - ipConfig.IPPort = ips.NewDynamicIPPort(ip, stakingPort) - ipConfig.Nat = nat - ipConfig.AttemptedNATTraversal = true return ipConfig, nil } diff --git a/config/config_test.go b/config/config_test.go index 037b8ac450cc..4c64e448ac13 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package config diff --git a/config/flags.go b/config/flags.go index 6e381e1c6a85..1cbd89dfcf8c 100644 --- a/config/flags.go +++ b/config/flags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package config @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/utils/compression" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/dynamicip" "github.com/ava-labs/avalanchego/utils/ulimit" "github.com/ava-labs/avalanchego/utils/units" ) @@ -92,6 +93,10 @@ func addNodeFlags(fs *pflag.FlagSet) { // Network ID fs.String(NetworkNameKey, constants.MainnetName, "Network ID this node will connect to") + // ACP flagging + fs.IntSlice(ACPSupportKey, nil, "ACPs to support adoption") + fs.IntSlice(ACPObjectKey, nil, "ACPs to object adoption") + // AVAX fees fs.Uint64(TxFeeKey, genesis.LocalParams.TxFee, "Transaction fee, in nAVAX") fs.Uint64(CreateAssetTxFeeKey, genesis.LocalParams.CreateAssetTxFee, "Transaction fee, in nAVAX, for transactions that create new assets") @@ -127,11 +132,13 @@ func addNodeFlags(fs *pflag.FlagSet) { fs.Uint(NetworkPeerListNonValidatorGossipSizeKey, constants.DefaultNetworkPeerListNonValidatorGossipSize, "Number of non-validators that the node will gossip peer list to") fs.Uint(NetworkPeerListPeersGossipSizeKey, constants.DefaultNetworkPeerListPeersGossipSize, "Number of total peers (including non-validators and validators) that the node will gossip peer list to") fs.Duration(NetworkPeerListGossipFreqKey, constants.DefaultNetworkPeerListGossipFreq, "Frequency to gossip peers to other nodes") + fs.Duration(NetworkPeerListPullGossipFreqKey, constants.DefaultNetworkPeerListPullGossipFreq, "Frequency to request peers from other nodes") + fs.Duration(NetworkPeerListBloomResetFreqKey, constants.DefaultNetworkPeerListBloomResetFreq, "Frequency to recalculate the bloom filter used to request new peers from other nodes") // Public IP Resolution - fs.String(PublicIPKey, "", "Public IP of this node for P2P communication. If empty, try to discover with NAT") + fs.String(PublicIPKey, "", "Public IP of this node for P2P communication") fs.Duration(PublicIPResolutionFreqKey, 5*time.Minute, "Frequency at which this node resolves/updates its public IP and renew NAT mappings, if applicable") - fs.String(PublicIPResolutionServiceKey, "", fmt.Sprintf("Only acceptable values are 'ifconfigco', 'opendns' or 'ifconfigme'. When provided, the node will use that service to periodically resolve/update its public IP. Ignored if %s is set", PublicIPKey)) + fs.String(PublicIPResolutionServiceKey, "", fmt.Sprintf("Only acceptable values are %q, %q or %q. When provided, the node will use that service to periodically resolve/update its public IP", dynamicip.OpenDNSName, dynamicip.IFConfigCoName, dynamicip.IFConfigMeName)) // Inbound Connection Throttling fs.Duration(NetworkInboundConnUpgradeThrottlerCooldownKey, constants.DefaultInboundConnUpgradeThrottlerCooldown, "Upgrade an inbound connection from a given IP at most once per this duration. If 0, don't rate-limit inbound connection upgrades") @@ -150,7 +157,7 @@ func addNodeFlags(fs *pflag.FlagSet) { fs.Duration(NetworkPingTimeoutKey, constants.DefaultPingPongTimeout, "Timeout value for Ping-Pong with a peer") fs.Duration(NetworkPingFrequencyKey, constants.DefaultPingFrequency, "Frequency of pinging other peers") - fs.String(NetworkCompressionTypeKey, constants.DefaultNetworkCompressionType.String(), fmt.Sprintf("Compression type for outbound messages. Must be one of [%s, %s, %s]", compression.TypeGzip, compression.TypeZstd, compression.TypeNone)) + fs.String(NetworkCompressionTypeKey, constants.DefaultNetworkCompressionType.String(), fmt.Sprintf("Compression type for outbound messages. Must be one of [%s, %s]", compression.TypeZstd, compression.TypeNone)) fs.Duration(NetworkMaxClockDifferenceKey, constants.DefaultNetworkMaxClockDifference, "Max allowed clock difference value between this node and peers") // Note: The default value is set to false here because the default @@ -302,10 +309,12 @@ func addNodeFlags(fs *pflag.FlagSet) { fs.Int(SnowQuorumSizeKey, snowball.DefaultParameters.AlphaConfidence, "Threshold of nodes required to update this node's preference and increase its confidence in a network poll") fs.Int(SnowPreferenceQuorumSizeKey, snowball.DefaultParameters.AlphaPreference, fmt.Sprintf("Threshold of nodes required to update this node's preference in a network poll. Ignored if %s is provided", SnowQuorumSizeKey)) fs.Int(SnowConfidenceQuorumSizeKey, snowball.DefaultParameters.AlphaConfidence, fmt.Sprintf("Threshold of nodes required to increase this node's confidence in a network poll. Ignored if %s is provided", SnowQuorumSizeKey)) - // TODO: Replace this temporary flag description after the X-chain - // linearization with "Beta value to use for virtuous transactions" + + fs.Int(SnowCommitThresholdKey, snowball.DefaultParameters.BetaRogue, "Beta value to use for transactions") + // TODO: Remove these once enough time has passed with SnowCommitThresholdKey fs.Int(SnowVirtuousCommitThresholdKey, snowball.DefaultParameters.BetaVirtuous, "This flag is temporarily ignored due to the X-chain linearization") fs.Int(SnowRogueCommitThresholdKey, snowball.DefaultParameters.BetaRogue, "Beta value to use for rogue transactions") + fs.Int(SnowConcurrentRepollsKey, snowball.DefaultParameters.ConcurrentRepolls, "Minimum number of concurrent polls for finalizing consensus") fs.Int(SnowOptimalProcessingKey, snowball.DefaultParameters.OptimalProcessing, "Optimal number of processing containers in consensus") fs.Int(SnowMaxProcessingKey, snowball.DefaultParameters.MaxOutstandingItems, "Maximum number of processing items to be considered healthy") diff --git a/config/keys.go b/config/keys.go index c627abfc1b57..b2ccc16fc63d 100644 --- a/config/keys.go +++ b/config/keys.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package config @@ -13,6 +13,8 @@ const ( GenesisFileKey = "genesis-file" GenesisFileContentKey = "genesis-file-content" NetworkNameKey = "network-id" + ACPSupportKey = "acp-support" + ACPObjectKey = "acp-object" TxFeeKey = "tx-fee" CreateAssetTxFeeKey = "create-asset-tx-fee" CreateSubnetTxFeeKey = "create-subnet-tx-fee" @@ -92,6 +94,8 @@ const ( NetworkPeerListNonValidatorGossipSizeKey = "network-peer-list-non-validator-gossip-size" NetworkPeerListPeersGossipSizeKey = "network-peer-list-peers-gossip-size" NetworkPeerListGossipFreqKey = "network-peer-list-gossip-frequency" + NetworkPeerListPullGossipFreqKey = "network-peer-list-pull-gossip-frequency" + NetworkPeerListBloomResetFreqKey = "network-peer-list-bloom-reset-frequency" NetworkInitialReconnectDelayKey = "network-initial-reconnect-delay" NetworkReadHandshakeTimeoutKey = "network-read-handshake-timeout" NetworkPingTimeoutKey = "network-ping-timeout" @@ -128,6 +132,7 @@ const ( SnowConfidenceQuorumSizeKey = "snow-confidence-quorum-size" SnowVirtuousCommitThresholdKey = "snow-virtuous-commit-threshold" SnowRogueCommitThresholdKey = "snow-rogue-commit-threshold" + SnowCommitThresholdKey = "snow-commit-threshold" SnowConcurrentRepollsKey = "snow-concurrent-repolls" SnowOptimalProcessingKey = "snow-optimal-processing" SnowMaxProcessingKey = "snow-max-processing" diff --git a/config/viper.go b/config/viper.go index 1e236ea32001..59ecf1941687 100644 --- a/config/viper.go +++ b/config/viper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package config diff --git a/database/batch.go b/database/batch.go index b097dc60eea7..8699a90c2960 100644 --- a/database/batch.go +++ b/database/batch.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // For ease of implementation, our database's interface matches Ethereum's diff --git a/database/benchmark_database.go b/database/benchmark_database.go index 949c071f84f5..b27eec902caa 100644 --- a/database/benchmark_database.go +++ b/database/benchmark_database.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package database diff --git a/database/common.go b/database/common.go index a27b0d27d4d0..651b8fe5719c 100644 --- a/database/common.go +++ b/database/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package database diff --git a/database/corruptabledb/db.go b/database/corruptabledb/db.go index cb46953f6858..d5bd6a711353 100644 --- a/database/corruptabledb/db.go +++ b/database/corruptabledb/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package corruptabledb diff --git a/database/corruptabledb/db_test.go b/database/corruptabledb/db_test.go index b58c65b4b162..566a6b084e19 100644 --- a/database/corruptabledb/db_test.go +++ b/database/corruptabledb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package corruptabledb diff --git a/database/database.go b/database/database.go index d0c274131d75..938c7f631b93 100644 --- a/database/database.go +++ b/database/database.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // For ease of implementation, our database's interface matches Ethereum's diff --git a/database/encdb/codec.go b/database/encdb/codec.go new file mode 100644 index 000000000000..62223b4fdd2f --- /dev/null +++ b/database/encdb/codec.go @@ -0,0 +1,24 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package encdb + +import ( + "time" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/codec/linearcodec" +) + +const CodecVersion = 0 + +var Codec codec.Manager + +func init() { + lc := linearcodec.NewDefault(time.Time{}) + Codec = codec.NewDefaultManager() + + if err := Codec.RegisterCodec(CodecVersion, lc); err != nil { + panic(err) + } +} diff --git a/database/encdb/db.go b/database/encdb/db.go index 42518bef9fc9..1b225b38f3cd 100644 --- a/database/encdb/db.go +++ b/database/encdb/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package encdb @@ -13,16 +13,10 @@ import ( "golang.org/x/exp/slices" - "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/utils/hashing" ) -const ( - codecVersion = 0 -) - var ( _ database.Database = (*Database)(nil) _ database.Batch = (*batch)(nil) @@ -32,7 +26,6 @@ var ( // Database encrypts all values that are provided type Database struct { lock sync.RWMutex - codec codec.Manager cipher cipher.AEAD db database.Database closed bool @@ -42,16 +35,10 @@ type Database struct { func New(password []byte, db database.Database) (*Database, error) { h := hashing.ComputeHash256(password) aead, err := chacha20poly1305.NewX(h) - if err != nil { - return nil, err - } - c := linearcodec.NewDefault() - manager := codec.NewDefaultManager() return &Database{ - codec: manager, cipher: aead, db: db, - }, manager.RegisterCodec(codecVersion, c) + }, err } func (db *Database) Has(key []byte) (bool, error) { @@ -297,7 +284,7 @@ func (db *Database) encrypt(plaintext []byte) ([]byte, error) { return nil, err } ciphertext := db.cipher.Seal(nil, nonce, plaintext, nil) - return db.codec.Marshal(codecVersion, &encryptedValue{ + return Codec.Marshal(CodecVersion, &encryptedValue{ Ciphertext: ciphertext, Nonce: nonce, }) @@ -305,7 +292,7 @@ func (db *Database) encrypt(plaintext []byte) ([]byte, error) { func (db *Database) decrypt(ciphertext []byte) ([]byte, error) { val := encryptedValue{} - if _, err := db.codec.Unmarshal(ciphertext, &val); err != nil { + if _, err := Codec.Unmarshal(ciphertext, &val); err != nil { return nil, err } return db.cipher.Open(nil, val.Nonce, val.Ciphertext, nil) diff --git a/database/encdb/db_test.go b/database/encdb/db_test.go index f49dd7ebb28d..871460a90a41 100644 --- a/database/encdb/db_test.go +++ b/database/encdb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package encdb diff --git a/database/errors.go b/database/errors.go index ee46521b6499..24f93aa8da27 100644 --- a/database/errors.go +++ b/database/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package database diff --git a/database/helpers.go b/database/helpers.go index d4261920dcdf..7e66c58fa770 100644 --- a/database/helpers.go +++ b/database/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package database diff --git a/database/helpers_test.go b/database/helpers_test.go index 79e37ea74b67..1ad3ccc2b20d 100644 --- a/database/helpers_test.go +++ b/database/helpers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package database diff --git a/database/iterator.go b/database/iterator.go index c83ceac49639..75126006d4ac 100644 --- a/database/iterator.go +++ b/database/iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // For ease of implementation, our database's interface matches Ethereum's diff --git a/database/leveldb/db.go b/database/leveldb/db.go index ab08db75a1c8..8ae825ef2fb1 100644 --- a/database/leveldb/db.go +++ b/database/leveldb/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package leveldb diff --git a/database/leveldb/db_test.go b/database/leveldb/db_test.go index 23733dacaff4..bf70739ce222 100644 --- a/database/leveldb/db_test.go +++ b/database/leveldb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package leveldb diff --git a/database/leveldb/metrics.go b/database/leveldb/metrics.go index 11bca8ddb07e..055579c8e6f4 100644 --- a/database/leveldb/metrics.go +++ b/database/leveldb/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package leveldb diff --git a/database/linkeddb/codec.go b/database/linkeddb/codec.go index 7780690b2e92..f1982e1c7cfd 100644 --- a/database/linkeddb/codec.go +++ b/database/linkeddb/codec.go @@ -1,29 +1,25 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package linkeddb import ( "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" ) -const ( - codecVersion = 0 -) +const CodecVersion = 0 -// c does serialization and deserialization -var ( - c codec.Manager -) +var Codec codec.Manager func init() { - lc := linearcodec.NewCustomMaxLength(math.MaxUint32) - c = codec.NewManager(math.MaxInt32) + lc := linearcodec.NewDefault(time.Time{}) + Codec = codec.NewManager(math.MaxInt32) - if err := c.RegisterCodec(codecVersion, lc); err != nil { + if err := Codec.RegisterCodec(CodecVersion, lc); err != nil { panic(err) } } diff --git a/database/linkeddb/linkeddb.go b/database/linkeddb/linkeddb.go index 597e1258367e..f40498812cda 100644 --- a/database/linkeddb/linkeddb.go +++ b/database/linkeddb/linkeddb.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package linkeddb @@ -316,7 +316,7 @@ func (ldb *linkedDB) getNode(key []byte) (node, error) { return node{}, err } n := node{} - _, err = c.Unmarshal(nodeBytes, &n) + _, err = Codec.Unmarshal(nodeBytes, &n) if err == nil { ldb.nodeCache.Put(keyStr, &n) } @@ -325,7 +325,7 @@ func (ldb *linkedDB) getNode(key []byte) (node, error) { func (ldb *linkedDB) putNode(key []byte, n node) error { ldb.updatedNodes[string(key)] = &n - nodeBytes, err := c.Marshal(codecVersion, n) + nodeBytes, err := Codec.Marshal(CodecVersion, n) if err != nil { return err } diff --git a/database/linkeddb/linkeddb_test.go b/database/linkeddb/linkeddb_test.go index 6f9cba4501de..815cac730b05 100644 --- a/database/linkeddb/linkeddb_test.go +++ b/database/linkeddb/linkeddb_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package linkeddb diff --git a/database/memdb/db.go b/database/memdb/db.go index 92b687afc0a5..914739f9e206 100644 --- a/database/memdb/db.go +++ b/database/memdb/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package memdb diff --git a/database/memdb/db_test.go b/database/memdb/db_test.go index 10d8ebe2be25..21c97909c7fb 100644 --- a/database/memdb/db_test.go +++ b/database/memdb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package memdb diff --git a/database/meterdb/db.go b/database/meterdb/db.go index a2640ca2dc00..fd3b3b77d7a8 100644 --- a/database/meterdb/db.go +++ b/database/meterdb/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package meterdb diff --git a/database/meterdb/db_test.go b/database/meterdb/db_test.go index a54d25c21542..cd36a6fb34f2 100644 --- a/database/meterdb/db_test.go +++ b/database/meterdb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package meterdb diff --git a/database/meterdb/metrics.go b/database/meterdb/metrics.go index a0a20e9d5f26..40cdf4566413 100644 --- a/database/meterdb/metrics.go +++ b/database/meterdb/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package meterdb diff --git a/database/mock_batch.go b/database/mock_batch.go index b20ae92473f1..e3762514954f 100644 --- a/database/mock_batch.go +++ b/database/mock_batch.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/database (interfaces: Batch) +// +// Generated by this command: +// +// mockgen -package=database -destination=database/mock_batch.go github.com/ava-labs/avalanchego/database Batch +// // Package database is a generated GoMock package. package database @@ -45,7 +47,7 @@ func (m *MockBatch) Delete(arg0 []byte) error { } // Delete indicates an expected call of Delete. -func (mr *MockBatchMockRecorder) Delete(arg0 interface{}) *gomock.Call { +func (mr *MockBatchMockRecorder) Delete(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockBatch)(nil).Delete), arg0) } @@ -73,7 +75,7 @@ func (m *MockBatch) Put(arg0, arg1 []byte) error { } // Put indicates an expected call of Put. -func (mr *MockBatchMockRecorder) Put(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockBatchMockRecorder) Put(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockBatch)(nil).Put), arg0, arg1) } @@ -87,7 +89,7 @@ func (m *MockBatch) Replay(arg0 KeyValueWriterDeleter) error { } // Replay indicates an expected call of Replay. -func (mr *MockBatchMockRecorder) Replay(arg0 interface{}) *gomock.Call { +func (mr *MockBatchMockRecorder) Replay(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Replay", reflect.TypeOf((*MockBatch)(nil).Replay), arg0) } diff --git a/database/mock_iterator.go b/database/mock_iterator.go index 4fa36ae24f69..77856c92ea5e 100644 --- a/database/mock_iterator.go +++ b/database/mock_iterator.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/database (interfaces: Iterator) +// +// Generated by this command: +// +// mockgen -package=database -destination=database/mock_iterator.go github.com/ava-labs/avalanchego/database Iterator +// // Package database is a generated GoMock package. package database diff --git a/database/pebble/batch.go b/database/pebble/batch.go index b6c9d283b64d..a53b962dc7be 100644 --- a/database/pebble/batch.go +++ b/database/pebble/batch.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pebble diff --git a/database/pebble/batch_test.go b/database/pebble/batch_test.go index a84134708956..66f4075558fd 100644 --- a/database/pebble/batch_test.go +++ b/database/pebble/batch_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pebble diff --git a/database/pebble/db.go b/database/pebble/db.go index 7aa718082a35..4838ff4b0ffe 100644 --- a/database/pebble/db.go +++ b/database/pebble/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pebble diff --git a/database/pebble/db_test.go b/database/pebble/db_test.go index 043caa23c813..2ea3c354f3d8 100644 --- a/database/pebble/db_test.go +++ b/database/pebble/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pebble diff --git a/database/pebble/iterator.go b/database/pebble/iterator.go index 115c122e30f4..5fc73f308a13 100644 --- a/database/pebble/iterator.go +++ b/database/pebble/iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pebble diff --git a/database/prefixdb/db.go b/database/prefixdb/db.go index f4ba04e31b06..64a644918241 100644 --- a/database/prefixdb/db.go +++ b/database/prefixdb/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package prefixdb diff --git a/database/prefixdb/db_test.go b/database/prefixdb/db_test.go index a0539b8e0105..109d8c4c9aba 100644 --- a/database/prefixdb/db_test.go +++ b/database/prefixdb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package prefixdb diff --git a/database/rpcdb/db_client.go b/database/rpcdb/db_client.go index 3e92a9a8477a..9f91667b41b6 100644 --- a/database/rpcdb/db_client.go +++ b/database/rpcdb/db_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcdb diff --git a/database/rpcdb/db_server.go b/database/rpcdb/db_server.go index e9e135738e89..6bcbd4e0276b 100644 --- a/database/rpcdb/db_server.go +++ b/database/rpcdb/db_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcdb diff --git a/database/rpcdb/db_test.go b/database/rpcdb/db_test.go index baabc9a69fbf..99323f8bd8c1 100644 --- a/database/rpcdb/db_test.go +++ b/database/rpcdb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcdb diff --git a/database/rpcdb/errors.go b/database/rpcdb/errors.go index 8a1fae2f0a1e..2cd759b6d612 100644 --- a/database/rpcdb/errors.go +++ b/database/rpcdb/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcdb diff --git a/database/test_database.go b/database/test_database.go index fc7e644ae5fd..1ce32bb54257 100644 --- a/database/test_database.go +++ b/database/test_database.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package database diff --git a/database/versiondb/db.go b/database/versiondb/db.go index d65dca947502..479e8af814d1 100644 --- a/database/versiondb/db.go +++ b/database/versiondb/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package versiondb diff --git a/database/versiondb/db_test.go b/database/versiondb/db_test.go index c2f57caacf61..c3093a9ee843 100644 --- a/database/versiondb/db_test.go +++ b/database/versiondb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package versiondb diff --git a/genesis/aliases.go b/genesis/aliases.go index b12e50d6f885..c6f8b3df2b74 100644 --- a/genesis/aliases.go +++ b/genesis/aliases.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/genesis/bootstrappers.go b/genesis/bootstrappers.go index 4aec33f9c3d9..b3e3a2a24f0c 100644 --- a/genesis/bootstrappers.go +++ b/genesis/bootstrappers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis @@ -36,10 +36,15 @@ type Bootstrapper struct { IP ips.IPDesc `json:"ip"` } +// GetBootstrappers returns all default bootstrappers for the provided network. +func GetBootstrappers(networkID uint32) []Bootstrapper { + networkName := constants.NetworkIDToNetworkName[networkID] + return bootstrappersPerNetwork[networkName] +} + // SampleBootstrappers returns the some beacons this node should connect to func SampleBootstrappers(networkID uint32, count int) []Bootstrapper { - networkName := constants.NetworkIDToNetworkName[networkID] - bootstrappers := bootstrappersPerNetwork[networkName] + bootstrappers := GetBootstrappers(networkID) count = math.Min(count, len(bootstrappers)) s := sampler.NewUniform() diff --git a/genesis/bootstrappers_test.go b/genesis/bootstrappers_test.go index d14eebbb559a..cde226f283dc 100644 --- a/genesis/bootstrappers_test.go +++ b/genesis/bootstrappers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/genesis/config.go b/genesis/config.go index a951e9e078fc..6e8ea15e7432 100644 --- a/genesis/config.go +++ b/genesis/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis @@ -53,9 +53,11 @@ func (a Allocation) Unparse(networkID uint32) (UnparsedAllocation, error) { return ua, err } -func (a Allocation) Less(other Allocation) bool { - return a.InitialAmount < other.InitialAmount || - (a.InitialAmount == other.InitialAmount && a.AVAXAddr.Less(other.AVAXAddr)) +func (a Allocation) Compare(other Allocation) int { + if amountCmp := utils.Compare(a.InitialAmount, other.InitialAmount); amountCmp != 0 { + return amountCmp + } + return a.AVAXAddr.Compare(other.AVAXAddr) } type Staker struct { diff --git a/genesis/config_test.go b/genesis/config_test.go index 455045dad70b..8a9bc96a9c94 100644 --- a/genesis/config_test.go +++ b/genesis/config_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis @@ -11,56 +11,43 @@ import ( "github.com/ava-labs/avalanchego/ids" ) -func TestAllocationLess(t *testing.T) { +func TestAllocationCompare(t *testing.T) { type test struct { name string alloc1 Allocation alloc2 Allocation - expected bool + expected int } tests := []test{ { name: "equal", alloc1: Allocation{}, alloc2: Allocation{}, - expected: false, + expected: 0, }, { - name: "first initial amount smaller", + name: "initial amount smaller", alloc1: Allocation{}, alloc2: Allocation{ InitialAmount: 1, }, - expected: true, + expected: -1, }, { - name: "first initial amount larger", - alloc1: Allocation{ - InitialAmount: 1, - }, - alloc2: Allocation{}, - expected: false, - }, - { - name: "first bytes smaller", + name: "bytes smaller", alloc1: Allocation{}, alloc2: Allocation{ AVAXAddr: ids.ShortID{1}, }, - expected: true, - }, - { - name: "first bytes larger", - alloc1: Allocation{ - AVAXAddr: ids.ShortID{1}, - }, - alloc2: Allocation{}, - expected: false, + expected: -1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.expected, tt.alloc1.Less(tt.alloc2)) + require := require.New(t) + + require.Equal(tt.expected, tt.alloc1.Compare(tt.alloc2)) + require.Equal(-tt.expected, tt.alloc2.Compare(tt.alloc1)) }) } } diff --git a/genesis/genesis.go b/genesis/genesis.go index 533795b56973..6a61b80aa559 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis @@ -552,9 +552,12 @@ func VMGenesis(genesisBytes []byte, vmID ids.ID) (*pchaintxs.Tx, error) { } func AVAXAssetID(avmGenesisBytes []byte) (ids.ID, error) { - parser, err := xchaintxs.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := xchaintxs.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) if err != nil { return ids.Empty, err } @@ -571,7 +574,7 @@ func AVAXAssetID(avmGenesisBytes []byte) (ids.ID, error) { genesisTx := genesis.Txs[0] tx := xchaintxs.Tx{Unsigned: &genesisTx.CreateAssetTx} - if err := parser.InitializeGenesisTx(&tx); err != nil { + if err := tx.Initialize(genesisCodec); err != nil { return ids.Empty, err } return tx.ID(), nil diff --git a/genesis/genesis_fuji.go b/genesis/genesis_fuji.go index a9bcaffcb956..27c43f79fd0b 100644 --- a/genesis/genesis_fuji.go +++ b/genesis/genesis_fuji.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/genesis/genesis_local.go b/genesis/genesis_local.go index de650009fd26..5a76aa25cfcf 100644 --- a/genesis/genesis_local.go +++ b/genesis/genesis_local.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/genesis/genesis_mainnet.go b/genesis/genesis_mainnet.go index 4e55c891be73..3808174ebbd1 100644 --- a/genesis/genesis_mainnet.go +++ b/genesis/genesis_mainnet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/genesis/genesis_test.go b/genesis/genesis_test.go index b18bc7f521a3..3257d6a0c271 100644 --- a/genesis/genesis_test.go +++ b/genesis/genesis_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/genesis/params.go b/genesis/params.go index 496a573c48f8..e2ae45c697e6 100644 --- a/genesis/params.go +++ b/genesis/params.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/genesis/unparsed_config.go b/genesis/unparsed_config.go index 647443bae482..2ace7647c363 100644 --- a/genesis/unparsed_config.go +++ b/genesis/unparsed_config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/go.mod b/go.mod index 383c70b22cf7..f85c1327317b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/DataDog/zstd v1.5.2 github.com/Microsoft/go-winio v0.5.2 github.com/NYTimes/gziphandler v1.1.1 - github.com/ava-labs/coreth v0.12.9-rc.9 + github.com/ava-labs/coreth v0.12.10-rc.5 github.com/ava-labs/ledger-avalanche/go v0.0.0-20231102202641-ae2ebdaeac34 github.com/btcsuite/btcd/btcutil v1.1.3 github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 @@ -24,7 +24,6 @@ require ( github.com/gorilla/rpc v1.2.0 github.com/gorilla/websocket v1.4.2 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/holiman/bloomfilter/v2 v2.0.3 github.com/huin/goupnp v1.0.3 github.com/jackpal/gateway v1.0.6 github.com/jackpal/go-nat-pmp v1.0.2 @@ -32,14 +31,13 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/mr-tron/base58 v1.2.0 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d - github.com/onsi/ginkgo/v2 v2.4.0 - github.com/onsi/gomega v1.24.0 + github.com/onsi/ginkgo/v2 v2.13.1 + github.com/onsi/gomega v1.29.0 github.com/pires/go-proxyproto v0.6.2 github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_model v0.3.0 github.com/rs/cors v1.7.0 github.com/shirou/gopsutil v3.21.11+incompatible - github.com/spaolacci/murmur3 v1.1.0 github.com/spf13/cast v1.5.0 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 @@ -56,13 +54,13 @@ require ( go.opentelemetry.io/otel/sdk v1.11.0 go.opentelemetry.io/otel/trace v1.11.0 go.uber.org/goleak v1.2.1 - go.uber.org/mock v0.2.0 + go.uber.org/mock v0.4.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.14.0 - golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df - golang.org/x/net v0.17.0 - golang.org/x/sync v0.3.0 - golang.org/x/term v0.13.0 + golang.org/x/crypto v0.17.0 + golang.org/x/exp v0.0.0-20231127185646-65229373498e + golang.org/x/net v0.19.0 + golang.org/x/sync v0.5.0 + golang.org/x/term v0.15.0 golang.org/x/time v0.0.0-20220922220347-f3bd1da661af gonum.org/v1/gonum v0.11.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 @@ -93,22 +91,23 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-stack/stack v1.8.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 // indirect github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/klauspost/compress v1.15.15 // indirect @@ -145,8 +144,9 @@ require ( go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.16.0 // indirect google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 04c29d1a591d..d963cb879e44 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/coreth v0.12.9-rc.9 h1:mvYxABdyPByXwwwIxnTBCiNO23dsE1Kfnd5H106lric= -github.com/ava-labs/coreth v0.12.9-rc.9/go.mod h1:yrf2vEah4Fgj6sJ4UpHewo4DLolwdpf2bJuLRT80PGw= +github.com/ava-labs/coreth v0.12.10-rc.5 h1:FMVvXHssvMQ3Eade7i85Wsx9tuD3kUOFMG8ktHeDTp8= +github.com/ava-labs/coreth v0.12.10-rc.5/go.mod h1:a58HbIBc9jscGc3aL8e7JuG8RfhBBOm63waq1q0YM+U= github.com/ava-labs/ledger-avalanche/go v0.0.0-20231102202641-ae2ebdaeac34 h1:mg9Uw6oZFJKytJxgxnl3uxZOs/SB8CVHg6Io4Tf99Zc= github.com/ava-labs/ledger-avalanche/go v0.0.0-20231102202641-ae2ebdaeac34/go.mod h1:pJxaT9bUgeRNVmNRgtCHb7sFDIRKy7CzTQVi8gGNT6g= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= @@ -217,8 +217,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= @@ -230,6 +230,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= @@ -295,8 +297,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -350,8 +353,6 @@ github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuW github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gto8n9hAmRxKxoar0iE6BLucjw= -github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e/go.mod h1:j9cQbcqHQujT0oKJ38PylVfqohClLr3CvDC+Qcg+lhU= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= @@ -477,16 +478,16 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU= +github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= -github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -551,8 +552,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= @@ -583,6 +582,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -669,8 +669,8 @@ go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU= -go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -692,8 +692,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -704,8 +704,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= +golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -732,6 +732,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -778,8 +779,8 @@ golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -802,8 +803,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -873,12 +874,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -889,8 +890,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -953,6 +954,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/header.yml b/header.yml index 19229afa4a64..c15feba2713b 100644 --- a/header.yml +++ b/header.yml @@ -1,4 +1,4 @@ header: | - // Copyright (C) 2019-{{YEAR}}, Ava Labs, Inc. All rights reserved. + // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. \ No newline at end of file diff --git a/ids/aliases.go b/ids/aliases.go index c7958e1c425e..484c6f8aa4b3 100644 --- a/ids/aliases.go +++ b/ids/aliases.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids diff --git a/ids/aliases_test.go b/ids/aliases_test.go index b25177242ff2..6c77d7443703 100644 --- a/ids/aliases_test.go +++ b/ids/aliases_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids diff --git a/ids/bits.go b/ids/bits.go index a884578f420b..bb3586704c4f 100644 --- a/ids/bits.go +++ b/ids/bits.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids diff --git a/ids/bits_test.go b/ids/bits_test.go index 4b5783dded46..feb381902000 100644 --- a/ids/bits_test.go +++ b/ids/bits_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids diff --git a/ids/galiasreader/alias_reader_client.go b/ids/galiasreader/alias_reader_client.go index aa77f9ec870f..319d7508cbd4 100644 --- a/ids/galiasreader/alias_reader_client.go +++ b/ids/galiasreader/alias_reader_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package galiasreader diff --git a/ids/galiasreader/alias_reader_server.go b/ids/galiasreader/alias_reader_server.go index 48f31bf74e3a..eeb9083ca1e4 100644 --- a/ids/galiasreader/alias_reader_server.go +++ b/ids/galiasreader/alias_reader_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package galiasreader diff --git a/ids/galiasreader/alias_reader_test.go b/ids/galiasreader/alias_reader_test.go index f268d10fc268..899c13a24ed2 100644 --- a/ids/galiasreader/alias_reader_test.go +++ b/ids/galiasreader/alias_reader_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package galiasreader diff --git a/ids/id.go b/ids/id.go index 68148018b078..6cda4b6ec854 100644 --- a/ids/id.go +++ b/ids/id.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids @@ -51,7 +51,7 @@ func (id ID) MarshalJSON() ([]byte, error) { if err != nil { return nil, err } - return []byte("\"" + str + "\""), nil + return []byte(`"` + str + `"`), nil } func (id *ID) UnmarshalJSON(b []byte) error { @@ -145,6 +145,6 @@ func (id ID) MarshalText() ([]byte, error) { return []byte(id.String()), nil } -func (id ID) Less(other ID) bool { - return bytes.Compare(id[:], other[:]) < 0 +func (id ID) Compare(other ID) int { + return bytes.Compare(id[:], other[:]) } diff --git a/ids/id_test.go b/ids/id_test.go index 3424b17633e6..930a323e614c 100644 --- a/ids/id_test.go +++ b/ids/id_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids import ( "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -101,11 +102,16 @@ func TestIDMarshalJSON(t *testing.T) { out []byte err error }{ - {"ID{}", ID{}, []byte("\"11111111111111111111111111111111LpoYY\""), nil}, { - "ID(\"ava labs\")", + "ID{}", + ID{}, + []byte(`"11111111111111111111111111111111LpoYY"`), + nil, + }, + { + `ID("ava labs")`, ID{'a', 'v', 'a', ' ', 'l', 'a', 'b', 's'}, - []byte("\"jvYi6Tn9idMi7BaymUVi9zWjg5tpmW7trfKG1AYJLKZJ2fsU7\""), + []byte(`"jvYi6Tn9idMi7BaymUVi9zWjg5tpmW7trfKG1AYJLKZJ2fsU7"`), nil, }, } @@ -127,10 +133,15 @@ func TestIDUnmarshalJSON(t *testing.T) { out ID err error }{ - {"ID{}", []byte("null"), ID{}, nil}, { - "ID(\"ava labs\")", - []byte("\"jvYi6Tn9idMi7BaymUVi9zWjg5tpmW7trfKG1AYJLKZJ2fsU7\""), + "ID{}", + []byte("null"), + ID{}, + nil, + }, + { + `ID("ava labs")`, + []byte(`"jvYi6Tn9idMi7BaymUVi9zWjg5tpmW7trfKG1AYJLKZJ2fsU7"`), ID{'a', 'v', 'a', ' ', 'l', 'a', 'b', 's'}, nil, }, @@ -200,26 +211,34 @@ func TestIDMapMarshalling(t *testing.T) { require.Equal(originalMap, unmarshalledMap) } -func TestIDLess(t *testing.T) { - require := require.New(t) +func TestIDCompare(t *testing.T) { + tests := []struct { + a ID + b ID + expected int + }{ + { + a: ID{1}, + b: ID{0}, + expected: 1, + }, + { + a: ID{1}, + b: ID{1}, + expected: 0, + }, + { + a: ID{1, 0}, + b: ID{1, 2}, + expected: -1, + }, + } + for _, test := range tests { + t.Run(fmt.Sprintf("%s_%s_%d", test.a, test.b, test.expected), func(t *testing.T) { + require := require.New(t) - id1 := ID{} - id2 := ID{} - require.False(id1.Less(id2)) - require.False(id2.Less(id1)) - - id1 = ID{1} - id2 = ID{0} - require.False(id1.Less(id2)) - require.True(id2.Less(id1)) - - id1 = ID{1} - id2 = ID{1} - require.False(id1.Less(id2)) - require.False(id2.Less(id1)) - - id1 = ID{1, 0} - id2 = ID{1, 2} - require.True(id1.Less(id2)) - require.False(id2.Less(id1)) + require.Equal(test.expected, test.a.Compare(test.b)) + require.Equal(-test.expected, test.b.Compare(test.a)) + }) + } } diff --git a/ids/node_id.go b/ids/node_id.go index 57d0c0b5bc69..7e9e94a12cdd 100644 --- a/ids/node_id.go +++ b/ids/node_id.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids @@ -37,7 +37,7 @@ func (id NodeID) Bytes() []byte { } func (id NodeID) MarshalJSON() ([]byte, error) { - return []byte("\"" + id.String() + "\""), nil + return []byte(`"` + id.String() + `"`), nil } func (id NodeID) MarshalText() ([]byte, error) { @@ -66,8 +66,8 @@ func (id *NodeID) UnmarshalText(text []byte) error { return id.UnmarshalJSON(text) } -func (id NodeID) Less(other NodeID) bool { - return bytes.Compare(id[:], other[:]) == -1 +func (id NodeID) Compare(other NodeID) int { + return bytes.Compare(id[:], other[:]) } // ToNodeID attempt to convert a byte slice into a node id diff --git a/ids/node_id_test.go b/ids/node_id_test.go index b92fb6e19053..2c94450f5b66 100644 --- a/ids/node_id_test.go +++ b/ids/node_id_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids import ( "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -67,11 +68,16 @@ func TestNodeIDMarshalJSON(t *testing.T) { out []byte err error }{ - {"NodeID{}", NodeID{}, []byte("\"NodeID-111111111111111111116DBWJs\""), nil}, { - "ID(\"ava labs\")", + "NodeID{}", + NodeID{}, + []byte(`"NodeID-111111111111111111116DBWJs"`), + nil, + }, + { + `ID("ava labs")`, NodeID{'a', 'v', 'a', ' ', 'l', 'a', 'b', 's'}, - []byte("\"NodeID-9tLMkeWFhWXd8QZc4rSiS5meuVXF5kRsz\""), + []byte(`"NodeID-9tLMkeWFhWXd8QZc4rSiS5meuVXF5kRsz"`), nil, }, } @@ -93,40 +99,45 @@ func TestNodeIDUnmarshalJSON(t *testing.T) { out NodeID expectedErr error }{ - {"NodeID{}", []byte("null"), NodeID{}, nil}, { - "NodeID(\"ava labs\")", - []byte("\"NodeID-9tLMkeWFhWXd8QZc4rSiS5meuVXF5kRsz\""), + "NodeID{}", + []byte("null"), + NodeID{}, + nil, + }, + { + `NodeID("ava labs")`, + []byte(`"NodeID-9tLMkeWFhWXd8QZc4rSiS5meuVXF5kRsz"`), NodeID{'a', 'v', 'a', ' ', 'l', 'a', 'b', 's'}, nil, }, { "missing start quote", - []byte("NodeID-9tLMkeWFhWXd8QZc4rSiS5meuVXF5kRsz\""), + []byte(`NodeID-9tLMkeWFhWXd8QZc4rSiS5meuVXF5kRsz"`), NodeID{}, errMissingQuotes, }, { "missing end quote", - []byte("\"NodeID-9tLMkeWFhWXd8QZc4rSiS5meuVXF5kRsz"), + []byte(`"NodeID-9tLMkeWFhWXd8QZc4rSiS5meuVXF5kRsz`), NodeID{}, errMissingQuotes, }, { "NodeID-", - []byte("\"NodeID-\""), + []byte(`"NodeID-"`), NodeID{}, errShortNodeID, }, { "NodeID-1", - []byte("\"NodeID-1\""), + []byte(`"NodeID-1"`), NodeID{}, cb58.ErrMissingChecksum, }, { "NodeID-9tLMkeWFhWXd8QZc4rSiS5meuVXF5kRsz1", - []byte("\"NodeID-1\""), + []byte(`"NodeID-1"`), NodeID{}, cb58.ErrMissingChecksum, }, @@ -174,26 +185,34 @@ func TestNodeIDMapMarshalling(t *testing.T) { require.Equal(originalMap, unmarshalledMap) } -func TestNodeIDLess(t *testing.T) { - require := require.New(t) +func TestNodeIDCompare(t *testing.T) { + tests := []struct { + a NodeID + b NodeID + expected int + }{ + { + a: NodeID{1}, + b: NodeID{0}, + expected: 1, + }, + { + a: NodeID{1}, + b: NodeID{1}, + expected: 0, + }, + { + a: NodeID{1, 0}, + b: NodeID{1, 2}, + expected: -1, + }, + } + for _, test := range tests { + t.Run(fmt.Sprintf("%s_%s_%d", test.a, test.b, test.expected), func(t *testing.T) { + require := require.New(t) - id1 := NodeID{} - id2 := NodeID{} - require.False(id1.Less(id2)) - require.False(id2.Less(id1)) - - id1 = NodeID{1} - id2 = NodeID{} - require.False(id1.Less(id2)) - require.True(id2.Less(id1)) - - id1 = NodeID{1} - id2 = NodeID{1} - require.False(id1.Less(id2)) - require.False(id2.Less(id1)) - - id1 = NodeID{1} - id2 = NodeID{1, 2} - require.True(id1.Less(id2)) - require.False(id2.Less(id1)) + require.Equal(test.expected, test.a.Compare(test.b)) + require.Equal(-test.expected, test.b.Compare(test.a)) + }) + } } diff --git a/ids/request_id.go b/ids/request_id.go index 779f819d9d4e..e1d9459866f7 100644 --- a/ids/request_id.go +++ b/ids/request_id.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids diff --git a/ids/short.go b/ids/short.go index 25b96f1755b0..7c01dca4785a 100644 --- a/ids/short.go +++ b/ids/short.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids @@ -54,7 +54,7 @@ func (id ShortID) MarshalJSON() ([]byte, error) { if err != nil { return nil, err } - return []byte("\"" + str + "\""), nil + return []byte(`"` + str + `"`), nil } func (id *ShortID) UnmarshalJSON(b []byte) error { @@ -110,8 +110,8 @@ func (id ShortID) MarshalText() ([]byte, error) { return []byte(id.String()), nil } -func (id ShortID) Less(other ShortID) bool { - return bytes.Compare(id[:], other[:]) == -1 +func (id ShortID) Compare(other ShortID) int { + return bytes.Compare(id[:], other[:]) } // ShortIDsToStrings converts an array of shortIDs to an array of their string diff --git a/ids/test_aliases.go b/ids/test_aliases.go index 7e2b4fb9790e..ce9991f5f737 100644 --- a/ids/test_aliases.go +++ b/ids/test_aliases.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids diff --git a/ids/test_generator.go b/ids/test_generator.go index 29ecfa8d409c..2df95ec0499f 100644 --- a/ids/test_generator.go +++ b/ids/test_generator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ids diff --git a/indexer/client.go b/indexer/client.go index 785018e3072c..821059a1d6c0 100644 --- a/indexer/client.go +++ b/indexer/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer diff --git a/indexer/client_test.go b/indexer/client_test.go index 004c8eeb283d..95124a2129ab 100644 --- a/indexer/client_test.go +++ b/indexer/client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer diff --git a/indexer/codec.go b/indexer/codec.go new file mode 100644 index 000000000000..afde47502ac3 --- /dev/null +++ b/indexer/codec.go @@ -0,0 +1,25 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package indexer + +import ( + "math" + "time" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/codec/linearcodec" +) + +const CodecVersion = 0 + +var Codec codec.Manager + +func init() { + lc := linearcodec.NewDefault(time.Time{}) + Codec = codec.NewManager(math.MaxInt) + + if err := Codec.RegisterCodec(CodecVersion, lc); err != nil { + panic(err) + } +} diff --git a/indexer/container.go b/indexer/container.go index c640fdd96d19..2bbb68e5db6f 100644 --- a/indexer/container.go +++ b/indexer/container.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer diff --git a/indexer/examples/p-chain/main.go b/indexer/examples/p-chain/main.go index fb9ec2d6f2f6..80f0e1aeed8e 100644 --- a/indexer/examples/p-chain/main.go +++ b/indexer/examples/p-chain/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main @@ -10,6 +10,7 @@ import ( "time" "github.com/ava-labs/avalanchego/indexer" + "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/wallet/subnet/primary" platformvmblock "github.com/ava-labs/avalanchego/vms/platformvm/block" @@ -34,7 +35,7 @@ func main() { } platformvmBlockBytes := container.Bytes - proposerVMBlock, err := proposervmblock.Parse(container.Bytes) + proposerVMBlock, err := proposervmblock.Parse(container.Bytes, version.DefaultUpgradeTime) if err == nil { platformvmBlockBytes = proposerVMBlock.Block() } diff --git a/indexer/examples/x-chain-blocks/main.go b/indexer/examples/x-chain-blocks/main.go index a995f9612bbd..e25693b62998 100644 --- a/indexer/examples/x-chain-blocks/main.go +++ b/indexer/examples/x-chain-blocks/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main @@ -10,6 +10,7 @@ import ( "time" "github.com/ava-labs/avalanchego/indexer" + "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/proposervm/block" "github.com/ava-labs/avalanchego/wallet/chain/x" "github.com/ava-labs/avalanchego/wallet/subnet/primary" @@ -32,7 +33,7 @@ func main() { continue } - proposerVMBlock, err := block.Parse(container.Bytes) + proposerVMBlock, err := block.Parse(container.Bytes, version.DefaultUpgradeTime) if err != nil { log.Fatalf("failed to parse proposervm block: %s\n", err) } diff --git a/indexer/index.go b/indexer/index.go index 5361754bf73d..86d75b37d4cb 100644 --- a/indexer/index.go +++ b/indexer/index.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer @@ -6,12 +6,10 @@ package indexer import ( "errors" "fmt" - "io" "sync" "go.uber.org/zap" - "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/database/versiondb" @@ -36,26 +34,15 @@ var ( errNumToFetchInvalid = fmt.Errorf("numToFetch must be in [1,%d]", MaxFetchedByRange) errNoContainerAtIndex = errors.New("no container at index") - _ Index = (*index)(nil) + _ snow.Acceptor = (*index)(nil) ) -// Index indexes containers in their order of acceptance -// Index is thread-safe. -// Index assumes that Accept is called before the container is committed to the -// database of the VM that the container exists in. -type Index interface { - snow.Acceptor - GetContainerByIndex(index uint64) (Container, error) - GetContainerRange(startIndex uint64, numToFetch uint64) ([]Container, error) - GetLastAccepted() (Container, error) - GetIndex(id ids.ID) (uint64, error) - GetContainerByID(id ids.ID) (Container, error) - io.Closer -} - -// indexer indexes all accepted transactions by the order in which they were accepted +// index indexes containers in their order of acceptance +// +// Invariant: index is thread-safe. +// Invariant: index assumes that Accept is called, before the container is +// committed to the database of the VM, in the order they were accepted. type index struct { - codec codec.Manager clock mockable.Clock lock sync.RWMutex // The index of the next accepted transaction @@ -71,21 +58,20 @@ type index struct { log logging.Logger } -// Returns a new, thread-safe Index. -// Closes [baseDB] on close. +// Create a new thread-safe index. +// +// Invariant: Closes [baseDB] on close. func newIndex( baseDB database.Database, log logging.Logger, - codec codec.Manager, clock mockable.Clock, -) (Index, error) { +) (*index, error) { vDB := versiondb.New(baseDB) indexToContainer := prefixdb.New(indexToContainerPrefix, vDB) containerToIndex := prefixdb.New(containerToIDPrefix, vDB) i := &index{ clock: clock, - codec: codec, baseDB: baseDB, vDB: vDB, indexToContainer: indexToContainer, @@ -150,7 +136,7 @@ func (i *index) Accept(ctx *snow.ConsensusContext, containerID ids.ID, container ) // Persist index --> Container nextAcceptedIndexBytes := database.PackUInt64(i.nextAcceptedIndex) - bytes, err := i.codec.Marshal(codecVersion, Container{ + bytes, err := Codec.Marshal(CodecVersion, Container{ ID: containerID, Bytes: containerBytes, Timestamp: i.clock.Time().UnixNano(), @@ -209,7 +195,7 @@ func (i *index) getContainerByIndexBytes(indexBytes []byte) (Container, error) { return Container{}, fmt.Errorf("couldn't read from database: %w", err) } var container Container - if _, err := i.codec.Unmarshal(containerBytes, &container); err != nil { + if _, err := Codec.Unmarshal(containerBytes, &container); err != nil { return Container{}, fmt.Errorf("couldn't unmarshal container: %w", err) } return container, nil diff --git a/indexer/index_test.go b/indexer/index_test.go index f31117c77b71..127aa64bda69 100644 --- a/indexer/index_test.go +++ b/indexer/index_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer @@ -8,12 +8,10 @@ import ( "github.com/stretchr/testify/require" - "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/versiondb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" @@ -24,15 +22,13 @@ func TestIndex(t *testing.T) { // Setup pageSize := uint64(64) require := require.New(t) - codec := codec.NewDefaultManager() - require.NoError(codec.RegisterCodec(codecVersion, linearcodec.NewDefault())) baseDB := memdb.New() db := versiondb.New(baseDB) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) - indexIntf, err := newIndex(db, logging.NoLog{}, codec, mockable.Clock{}) + idx, err := newIndex(db, logging.NoLog{}, mockable.Clock{}) require.NoError(err) - idx := indexIntf.(*index) // Populate "containers" with random IDs/bytes containers := map[ids.ID][]byte{} @@ -83,9 +79,8 @@ func TestIndex(t *testing.T) { require.NoError(db.Commit()) require.NoError(idx.Close()) db = versiondb.New(baseDB) - indexIntf, err = newIndex(db, logging.NoLog{}, codec, mockable.Clock{}) + idx, err = newIndex(db, logging.NoLog{}, mockable.Clock{}) require.NoError(err) - idx = indexIntf.(*index) // Get all of the containers containersList, err := idx.GetContainerRange(0, pageSize) @@ -113,13 +108,11 @@ func TestIndex(t *testing.T) { func TestIndexGetContainerByRangeMaxPageSize(t *testing.T) { // Setup require := require.New(t) - codec := codec.NewDefaultManager() - require.NoError(codec.RegisterCodec(codecVersion, linearcodec.NewDefault())) db := memdb.New() - ctx := snow.DefaultConsensusContextTest() - indexIntf, err := newIndex(db, logging.NoLog{}, codec, mockable.Clock{}) + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) + idx, err := newIndex(db, logging.NoLog{}, mockable.Clock{}) require.NoError(err) - idx := indexIntf.(*index) // Insert [MaxFetchedByRange] + 1 containers for i := uint64(0); i < MaxFetchedByRange+1; i++ { @@ -153,11 +146,10 @@ func TestIndexGetContainerByRangeMaxPageSize(t *testing.T) { func TestDontIndexSameContainerTwice(t *testing.T) { // Setup require := require.New(t) - codec := codec.NewDefaultManager() - require.NoError(codec.RegisterCodec(codecVersion, linearcodec.NewDefault())) db := memdb.New() - ctx := snow.DefaultConsensusContextTest() - idx, err := newIndex(db, logging.NoLog{}, codec, mockable.Clock{}) + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) + idx, err := newIndex(db, logging.NoLog{}, mockable.Clock{}) require.NoError(err) // Accept the same container twice diff --git a/indexer/indexer.go b/indexer/indexer.go index e6080c104c2a..6b16f8245ebc 100644 --- a/indexer/indexer.go +++ b/indexer/indexer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer @@ -6,7 +6,6 @@ package indexer import ( "fmt" "io" - "math" "sync" "github.com/gorilla/rpc/v2" @@ -15,8 +14,6 @@ import ( "github.com/ava-labs/avalanchego/api/server" "github.com/ava-labs/avalanchego/chains" - "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" @@ -32,26 +29,18 @@ import ( ) const ( - indexNamePrefix = "index-" - codecVersion = uint16(0) - // Max size, in bytes, of something serialized by this indexer - // Assumes no containers are larger than math.MaxUint32 - // wrappers.IntLen accounts for the size of the container bytes - // wrappers.LongLen accounts for the timestamp of the container - // ids.IDLen accounts for the container ID - // wrappers.ShortLen accounts for the codec version - codecMaxSize = int(constants.DefaultMaxMessageSize) + wrappers.IntLen + wrappers.LongLen + ids.IDLen + wrappers.ShortLen + indexNamePrefix = "index-" + txPrefix = 0x01 + vtxPrefix = 0x02 + blockPrefix = 0x03 + isIncompletePrefix = 0x04 + previouslyIndexedPrefix = 0x05 ) var ( - txPrefix = byte(0x01) - vtxPrefix = byte(0x02) - blockPrefix = byte(0x03) - isIncompletePrefix = byte(0x04) - previouslyIndexedPrefix = byte(0x05) - hasRunKey = []byte{0x07} - _ Indexer = (*indexer)(nil) + + hasRunKey = []byte{0x07} ) // Config for an indexer @@ -80,7 +69,6 @@ type Indexer interface { // NewIndexer returns a new Indexer and registers a new endpoint on the given API server. func NewIndexer(config Config) (Indexer, error) { indexer := &indexer{ - codec: codec.NewManager(codecMaxSize), log: config.Log, db: config.DB, allowIncompleteIndex: config.AllowIncompleteIndex, @@ -88,19 +76,13 @@ func NewIndexer(config Config) (Indexer, error) { blockAcceptorGroup: config.BlockAcceptorGroup, txAcceptorGroup: config.TxAcceptorGroup, vertexAcceptorGroup: config.VertexAcceptorGroup, - txIndices: map[ids.ID]Index{}, - vtxIndices: map[ids.ID]Index{}, - blockIndices: map[ids.ID]Index{}, + txIndices: map[ids.ID]*index{}, + vtxIndices: map[ids.ID]*index{}, + blockIndices: map[ids.ID]*index{}, pathAdder: config.APIServer, shutdownF: config.ShutdownF, } - if err := indexer.codec.RegisterCodec( - codecVersion, - linearcodec.NewCustomMaxLength(math.MaxUint32), - ); err != nil { - return nil, fmt.Errorf("couldn't register codec: %w", err) - } hasRun, err := indexer.hasRun() if err != nil { return nil, err @@ -110,7 +92,6 @@ func NewIndexer(config Config) (Indexer, error) { } type indexer struct { - codec codec.Manager clock mockable.Clock lock sync.RWMutex log logging.Logger @@ -134,11 +115,11 @@ type indexer struct { indexingEnabled bool // Chain ID --> index of blocks of that chain (if applicable) - blockIndices map[ids.ID]Index + blockIndices map[ids.ID]*index // Chain ID --> index of vertices of that chain (if applicable) - vtxIndices map[ids.ID]Index + vtxIndices map[ids.ID]*index // Chain ID --> index of txs of that chain (if applicable) - txIndices map[ids.ID]Index + txIndices map[ids.ID]*index // Notifies of newly accepted blocks blockAcceptorGroup snow.AcceptorGroup @@ -331,12 +312,12 @@ func (i *indexer) registerChainHelper( prefixEnd byte, name, endpoint string, acceptorGroup snow.AcceptorGroup, -) (Index, error) { +) (*index, error) { prefix := make([]byte, ids.IDLen+wrappers.ByteLen) copy(prefix, chainID[:]) prefix[ids.IDLen] = prefixEnd indexDB := prefixdb.New(prefix, i.db) - index, err := newIndex(indexDB, i.log, i.codec, i.clock) + index, err := newIndex(indexDB, i.log, i.clock) if err != nil { _ = indexDB.Close() return nil, err @@ -353,7 +334,7 @@ func (i *indexer) registerChainHelper( codec := json.NewCodec() apiServer.RegisterCodec(codec, "application/json") apiServer.RegisterCodec(codec, "application/json;charset=UTF-8") - if err := apiServer.RegisterService(&service{Index: index}, "index"); err != nil { + if err := apiServer.RegisterService(&service{index: index}, "index"); err != nil { _ = index.Close() return nil, err } diff --git a/indexer/indexer_test.go b/indexer/indexer_test.go index a8c2f474f743..348aa87d13a9 100644 --- a/indexer/indexer_test.go +++ b/indexer/indexer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer @@ -20,7 +20,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" + "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/logging" ) @@ -67,7 +68,6 @@ func TestNewIndexer(t *testing.T) { require.NoError(err) require.IsType(&indexer{}, idxrIntf) idxr := idxrIntf.(*indexer) - require.NotNil(idxr.codec) require.NotNil(idxr.log) require.NotNil(idxr.db) require.False(idxr.closed) @@ -153,8 +153,8 @@ func TestIndexer(t *testing.T) { idxr.clock.Set(now) // Assert state is right - chain1Ctx := snow.DefaultConsensusContextTest() - chain1Ctx.ChainID = ids.GenerateTestID() + snow1Ctx := snowtest.Context(t, snowtest.CChainID) + chain1Ctx := snowtest.ConsensusContext(snow1Ctx) isIncomplete, err := idxr.isIncomplete(chain1Ctx.ChainID) require.NoError(err) require.False(isIncomplete) @@ -163,7 +163,7 @@ func TestIndexer(t *testing.T) { require.False(previouslyIndexed) // Register this chain, creating a new index - chainVM := mocks.NewMockChainVM(ctrl) + chainVM := block.NewMockChainVM(ctrl) idxr.RegisterChain("chain1", chain1Ctx, chainVM) isIncomplete, err = idxr.isIncomplete(chain1Ctx.ChainID) require.NoError(err) @@ -258,8 +258,8 @@ func TestIndexer(t *testing.T) { require.Contains(server.endpoints, "/block") // Register a DAG chain - chain2Ctx := snow.DefaultConsensusContextTest() - chain2Ctx.ChainID = ids.GenerateTestID() + snow2Ctx := snowtest.Context(t, snowtest.XChainID) + chain2Ctx := snowtest.ConsensusContext(snow2Ctx) isIncomplete, err = idxr.isIncomplete(chain2Ctx.ChainID) require.NoError(err) require.False(isIncomplete) @@ -418,15 +418,15 @@ func TestIncompleteIndex(t *testing.T) { require.False(idxr.indexingEnabled) // Register a chain - chain1Ctx := snow.DefaultConsensusContextTest() - chain1Ctx.ChainID = ids.GenerateTestID() + snow1Ctx := snowtest.Context(t, snowtest.CChainID) + chain1Ctx := snowtest.ConsensusContext(snow1Ctx) isIncomplete, err := idxr.isIncomplete(chain1Ctx.ChainID) require.NoError(err) require.False(isIncomplete) previouslyIndexed, err := idxr.previouslyIndexed(chain1Ctx.ChainID) require.NoError(err) require.False(previouslyIndexed) - chainVM := mocks.NewMockChainVM(ctrl) + chainVM := block.NewMockChainVM(ctrl) idxr.RegisterChain("chain1", chain1Ctx, chainVM) isIncomplete, err = idxr.isIncomplete(chain1Ctx.ChainID) require.NoError(err) @@ -500,13 +500,14 @@ func TestIgnoreNonDefaultChains(t *testing.T) { require.IsType(&indexer{}, idxrIntf) idxr := idxrIntf.(*indexer) - // Assert state is right - chain1Ctx := snow.DefaultConsensusContextTest() - chain1Ctx.ChainID = ids.GenerateTestID() - chain1Ctx.SubnetID = ids.GenerateTestID() + // Create chain1Ctx for a random subnet + chain. + chain1Ctx := snowtest.ConsensusContext(&snow.Context{ + ChainID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + }) // RegisterChain should return without adding an index for this chain - chainVM := mocks.NewMockChainVM(ctrl) + chainVM := block.NewMockChainVM(ctrl) idxr.RegisterChain("chain1", chain1Ctx, chainVM) require.Empty(idxr.blockIndices) } diff --git a/indexer/service.go b/indexer/service.go index 98bc91e90787..83f9912f2fea 100644 --- a/indexer/service.go +++ b/indexer/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer @@ -15,7 +15,7 @@ import ( ) type service struct { - Index + index *index } type FormattedContainer struct { @@ -46,11 +46,11 @@ type GetLastAcceptedArgs struct { } func (s *service) GetLastAccepted(_ *http.Request, args *GetLastAcceptedArgs, reply *FormattedContainer) error { - container, err := s.Index.GetLastAccepted() + container, err := s.index.GetLastAccepted() if err != nil { return err } - index, err := s.Index.GetIndex(container.ID) + index, err := s.index.GetIndex(container.ID) if err != nil { return fmt.Errorf("couldn't get index: %w", err) } @@ -64,11 +64,11 @@ type GetContainerByIndexArgs struct { } func (s *service) GetContainerByIndex(_ *http.Request, args *GetContainerByIndexArgs, reply *FormattedContainer) error { - container, err := s.Index.GetContainerByIndex(uint64(args.Index)) + container, err := s.index.GetContainerByIndex(uint64(args.Index)) if err != nil { return err } - index, err := s.Index.GetIndex(container.ID) + index, err := s.index.GetIndex(container.ID) if err != nil { return fmt.Errorf("couldn't get index: %w", err) } @@ -92,14 +92,14 @@ type GetContainerRangeResponse struct { // If [n] > [MaxFetchedByRange], returns an error. // If we run out of transactions, returns the ones fetched before running out. func (s *service) GetContainerRange(_ *http.Request, args *GetContainerRangeArgs, reply *GetContainerRangeResponse) error { - containers, err := s.Index.GetContainerRange(uint64(args.StartIndex), uint64(args.NumToFetch)) + containers, err := s.index.GetContainerRange(uint64(args.StartIndex), uint64(args.NumToFetch)) if err != nil { return err } reply.Containers = make([]FormattedContainer, len(containers)) for i, container := range containers { - index, err := s.Index.GetIndex(container.ID) + index, err := s.index.GetIndex(container.ID) if err != nil { return fmt.Errorf("couldn't get index: %w", err) } @@ -120,7 +120,7 @@ type GetIndexResponse struct { } func (s *service) GetIndex(_ *http.Request, args *GetIndexArgs, reply *GetIndexResponse) error { - index, err := s.Index.GetIndex(args.ID) + index, err := s.index.GetIndex(args.ID) reply.Index = json.Uint64(index) return err } @@ -134,7 +134,7 @@ type IsAcceptedResponse struct { } func (s *service) IsAccepted(_ *http.Request, args *IsAcceptedArgs, reply *IsAcceptedResponse) error { - _, err := s.Index.GetIndex(args.ID) + _, err := s.index.GetIndex(args.ID) if err == nil { reply.IsAccepted = true return nil @@ -152,11 +152,11 @@ type GetContainerByIDArgs struct { } func (s *service) GetContainerByID(_ *http.Request, args *GetContainerByIDArgs, reply *FormattedContainer) error { - container, err := s.Index.GetContainerByID(args.ID) + container, err := s.index.GetContainerByID(args.ID) if err != nil { return err } - index, err := s.Index.GetIndex(container.ID) + index, err := s.index.GetIndex(container.ID) if err != nil { return fmt.Errorf("couldn't get index: %w", err) } diff --git a/ipcs/chainipc.go b/ipcs/chainipc.go index 56d4393360ff..bb78cc175327 100644 --- a/ipcs/chainipc.go +++ b/ipcs/chainipc.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ipcs diff --git a/ipcs/eventsocket.go b/ipcs/eventsocket.go index 109b42bb34af..0dbbe1c92e5a 100644 --- a/ipcs/eventsocket.go +++ b/ipcs/eventsocket.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ipcs diff --git a/ipcs/socket/socket.go b/ipcs/socket/socket.go index d3ca391dd019..77f2d6fdb8e2 100644 --- a/ipcs/socket/socket.go +++ b/ipcs/socket/socket.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package socket diff --git a/ipcs/socket/socket_test.go b/ipcs/socket/socket_test.go index a56329b28c3e..a2c1ec638754 100644 --- a/ipcs/socket/socket_test.go +++ b/ipcs/socket/socket_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package socket diff --git a/ipcs/socket/socket_unix.go b/ipcs/socket/socket_unix.go index 98f4ad492330..14d5aabde4e3 100644 --- a/ipcs/socket/socket_unix.go +++ b/ipcs/socket/socket_unix.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build !windows && !plan9 && !js diff --git a/ipcs/socket/socket_windows.go b/ipcs/socket/socket_windows.go index eb54ccf07066..99590cb674b4 100644 --- a/ipcs/socket/socket_windows.go +++ b/ipcs/socket/socket_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build windows diff --git a/main/main.go b/main/main.go index 2b07898f9072..549ef65f48cf 100644 --- a/main/main.go +++ b/main/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/message/creator.go b/message/creator.go index f1a6def2b21e..a5711375c331 100644 --- a/message/creator.go +++ b/message/creator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message diff --git a/message/fields.go b/message/fields.go index 87bffe518abd..08e744fab911 100644 --- a/message/fields.go +++ b/message/fields.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message diff --git a/message/inbound_msg_builder.go b/message/inbound_msg_builder.go index a287ade2d8cb..b32dbc5d480d 100644 --- a/message/inbound_msg_builder.go +++ b/message/inbound_msg_builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message @@ -284,6 +284,26 @@ func InboundAppRequest( } } +func InboundAppError( + nodeID ids.NodeID, + chainID ids.ID, + requestID uint32, + errorCode int32, + errorMessage string, +) InboundMessage { + return &inboundMessage{ + nodeID: nodeID, + op: AppErrorOp, + message: &p2p.AppError{ + ChainId: chainID[:], + RequestId: requestID, + ErrorCode: errorCode, + ErrorMessage: errorMessage, + }, + expiration: mockable.MaxTime, + } +} + func InboundAppResponse( chainID ids.ID, requestID uint32, @@ -304,7 +324,7 @@ func InboundAppResponse( func encodeIDs(ids []ids.ID, result [][]byte) { for i, id := range ids { - copy := id - result[i] = copy[:] + id := id + result[i] = id[:] } } diff --git a/message/inbound_msg_builder_test.go b/message/inbound_msg_builder_test.go index c9642c57250c..b14b3ddedab7 100644 --- a/message/inbound_msg_builder_test.go +++ b/message/inbound_msg_builder_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message @@ -13,6 +13,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/proto/pb/p2p" + "github.com/ava-labs/avalanchego/utils/compression" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/timer/mockable" ) @@ -396,3 +397,46 @@ func TestInboundMsgBuilder(t *testing.T) { }, ) } + +func TestAppError(t *testing.T) { + require := require.New(t) + + mb, err := newMsgBuilder( + logging.NoLog{}, + "", + prometheus.NewRegistry(), + time.Second, + ) + require.NoError(err) + + nodeID := ids.GenerateTestNodeID() + chainID := ids.GenerateTestID() + requestID := uint32(1) + errorCode := int32(2) + errorMessage := "hello world" + + want := &p2p.Message{ + Message: &p2p.Message_AppError{ + AppError: &p2p.AppError{ + ChainId: chainID[:], + RequestId: requestID, + ErrorCode: errorCode, + ErrorMessage: errorMessage, + }, + }, + } + + outMsg, err := mb.createOutbound(want, compression.TypeNone, false) + require.NoError(err) + + got, err := mb.parseInbound(outMsg.Bytes(), nodeID, func() {}) + require.NoError(err) + + require.Equal(nodeID, got.NodeID()) + require.Equal(AppErrorOp, got.Op()) + + msg, ok := got.Message().(*p2p.AppError) + require.True(ok) + require.Equal(errorCode, msg.ErrorCode) + require.Equal(errorMessage, msg.ErrorMessage) +} diff --git a/message/internal_msg_builder.go b/message/internal_msg_builder.go index 46e7b7e78405..38a95cb78a8c 100644 --- a/message/internal_msg_builder.go +++ b/message/internal_msg_builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //nolint:stylecheck // proto generates interfaces that fail linting @@ -52,10 +52,6 @@ var ( _ requestIDGetter = (*QueryFailed)(nil) _ engineTypeGetter = (*QueryFailed)(nil) - _ fmt.Stringer = (*AppRequestFailed)(nil) - _ chainIDGetter = (*AppRequestFailed)(nil) - _ requestIDGetter = (*AppRequestFailed)(nil) - _ fmt.Stringer = (*CrossChainAppRequest)(nil) _ sourceChainIDGetter = (*CrossChainAppRequest)(nil) _ chainIDGetter = (*CrossChainAppRequest)(nil) @@ -365,42 +361,6 @@ func InternalQueryFailed( } } -type AppRequestFailed struct { - ChainID ids.ID `json:"chain_id,omitempty"` - RequestID uint32 `json:"request_id,omitempty"` -} - -func (m *AppRequestFailed) String() string { - return fmt.Sprintf( - "ChainID: %s RequestID: %d", - m.ChainID, m.RequestID, - ) -} - -func (m *AppRequestFailed) GetChainId() []byte { - return m.ChainID[:] -} - -func (m *AppRequestFailed) GetRequestId() uint32 { - return m.RequestID -} - -func InternalAppRequestFailed( - nodeID ids.NodeID, - chainID ids.ID, - requestID uint32, -) InboundMessage { - return &inboundMessage{ - nodeID: nodeID, - op: AppRequestFailedOp, - message: &AppRequestFailed{ - ChainID: chainID, - RequestID: requestID, - }, - expiration: mockable.MaxTime, - } -} - type CrossChainAppRequest struct { SourceChainID ids.ID `json:"source_chain_id,omitempty"` DestinationChainID ids.ID `json:"destination_chain_id,omitempty"` @@ -452,6 +412,8 @@ type CrossChainAppRequestFailed struct { SourceChainID ids.ID `json:"source_chain_id,omitempty"` DestinationChainID ids.ID `json:"destination_chain_id,omitempty"` RequestID uint32 `json:"request_id,omitempty"` + ErrorCode int32 `json:"error_code,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` } func (m *CrossChainAppRequestFailed) String() string { @@ -473,19 +435,23 @@ func (m *CrossChainAppRequestFailed) GetRequestId() uint32 { return m.RequestID } -func InternalCrossChainAppRequestFailed( +func InternalCrossChainAppError( nodeID ids.NodeID, sourceChainID ids.ID, destinationChainID ids.ID, requestID uint32, + errorCode int32, + errorMessage string, ) InboundMessage { return &inboundMessage{ nodeID: nodeID, - op: CrossChainAppRequestFailedOp, + op: CrossChainAppErrorOp, message: &CrossChainAppRequestFailed{ SourceChainID: sourceChainID, DestinationChainID: destinationChainID, RequestID: requestID, + ErrorCode: errorCode, + ErrorMessage: errorMessage, }, expiration: mockable.MaxTime, } diff --git a/message/messages.go b/message/messages.go index 8d9671e63123..b8b5db69e958 100644 --- a/message/messages.go +++ b/message/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message @@ -134,8 +134,8 @@ func (m *outboundMessage) BytesSavedCompression() int { type msgBuilder struct { log logging.Logger + // TODO: Remove gzip once v1.11.x is out. gzipCompressor compression.Compressor - gzipCompressTimeMetrics map[Op]metric.Averager gzipDecompressTimeMetrics map[Op]metric.Averager zstdCompressor compression.Compressor @@ -164,7 +164,6 @@ func newMsgBuilder( log: log, gzipCompressor: gzipCompressor, - gzipCompressTimeMetrics: make(map[Op]metric.Averager, len(ExternalOps)), gzipDecompressTimeMetrics: make(map[Op]metric.Averager, len(ExternalOps)), zstdCompressor: zstdCompressor, @@ -176,13 +175,6 @@ func newMsgBuilder( errs := wrappers.Errs{} for _, op := range ExternalOps { - mb.gzipCompressTimeMetrics[op] = metric.NewAveragerWithErrs( - namespace, - fmt.Sprintf("gzip_%s_compress_time", op), - fmt.Sprintf("time (in ns) to compress %s messages with gzip", op), - metrics, - &errs, - ) mb.gzipDecompressTimeMetrics[op] = metric.NewAveragerWithErrs( namespace, fmt.Sprintf("gzip_%s_decompress_time", op), @@ -236,17 +228,6 @@ func (mb *msgBuilder) marshal( switch compressionType { case compression.TypeNone: return uncompressedMsgBytes, 0, op, nil - case compression.TypeGzip: - compressedBytes, err := mb.gzipCompressor.Compress(uncompressedMsgBytes) - if err != nil { - return nil, 0, 0, err - } - compressedMsg = p2p.Message{ - Message: &p2p.Message_CompressedGzip{ - CompressedGzip: compressedBytes, - }, - } - opToCompressTimeMetrics = mb.gzipCompressTimeMetrics case compression.TypeZstd: compressedBytes, err := mb.zstdCompressor.Compress(uncompressedMsgBytes) if err != nil { diff --git a/message/messages_benchmark_test.go b/message/messages_benchmark_test.go index 8d0939a67348..48ae10acf5dd 100644 --- a/message/messages_benchmark_test.go +++ b/message/messages_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message @@ -26,35 +26,35 @@ var ( dummyOnFinishedHandling = func() {} ) -// Benchmarks marshal-ing "Version" message. +// Benchmarks marshal-ing "Handshake" message. // // e.g., // // $ go install -v golang.org/x/tools/cmd/benchcmp@latest // $ go install -v golang.org/x/perf/cmd/benchstat@latest // -// $ go test -run=NONE -bench=BenchmarkMarshalVersion > /tmp/cpu.before.txt -// $ USE_BUILDER=true go test -run=NONE -bench=BenchmarkMarshalVersion > /tmp/cpu.after.txt +// $ go test -run=NONE -bench=BenchmarkMarshalHandshake > /tmp/cpu.before.txt +// $ USE_BUILDER=true go test -run=NONE -bench=BenchmarkMarshalHandshake > /tmp/cpu.after.txt // $ benchcmp /tmp/cpu.before.txt /tmp/cpu.after.txt // $ benchstat -alpha 0.03 -geomean /tmp/cpu.before.txt /tmp/cpu.after.txt // -// $ go test -run=NONE -bench=BenchmarkMarshalVersion -benchmem > /tmp/mem.before.txt -// $ USE_BUILDER=true go test -run=NONE -bench=BenchmarkMarshalVersion -benchmem > /tmp/mem.after.txt +// $ go test -run=NONE -bench=BenchmarkMarshalHandshake -benchmem > /tmp/mem.before.txt +// $ USE_BUILDER=true go test -run=NONE -bench=BenchmarkMarshalHandshake -benchmem > /tmp/mem.after.txt // $ benchcmp /tmp/mem.before.txt /tmp/mem.after.txt // $ benchstat -alpha 0.03 -geomean /tmp/mem.before.txt /tmp/mem.after.txt -func BenchmarkMarshalVersion(b *testing.B) { +func BenchmarkMarshalHandshake(b *testing.B) { require := require.New(b) id := ids.GenerateTestID() msg := p2p.Message{ - Message: &p2p.Message_Version{ - Version: &p2p.Version{ + Message: &p2p.Message_Handshake{ + Handshake: &p2p.Handshake{ NetworkId: uint32(1337), MyTime: uint64(time.Now().Unix()), IpAddr: []byte(net.IPv4(1, 2, 3, 4).To16()), IpPort: 0, MyVersion: "v1.2.3", - MyVersionTime: uint64(time.Now().Unix()), + IpSigningTime: uint64(time.Now().Unix()), Sig: []byte{'y', 'e', 'e', 't'}, TrackedSubnets: [][]byte{id[:]}, }, @@ -87,30 +87,30 @@ func BenchmarkMarshalVersion(b *testing.B) { // $ go install -v golang.org/x/tools/cmd/benchcmp@latest // $ go install -v golang.org/x/perf/cmd/benchstat@latest // -// $ go test -run=NONE -bench=BenchmarkUnmarshalVersion > /tmp/cpu.before.txt -// $ USE_BUILDER=true go test -run=NONE -bench=BenchmarkUnmarshalVersion > /tmp/cpu.after.txt +// $ go test -run=NONE -bench=BenchmarkUnmarshalHandshake > /tmp/cpu.before.txt +// $ USE_BUILDER=true go test -run=NONE -bench=BenchmarkUnmarshalHandshake > /tmp/cpu.after.txt // $ benchcmp /tmp/cpu.before.txt /tmp/cpu.after.txt // $ benchstat -alpha 0.03 -geomean /tmp/cpu.before.txt /tmp/cpu.after.txt // -// $ go test -run=NONE -bench=BenchmarkUnmarshalVersion -benchmem > /tmp/mem.before.txt -// $ USE_BUILDER=true go test -run=NONE -bench=BenchmarkUnmarshalVersion -benchmem > /tmp/mem.after.txt +// $ go test -run=NONE -bench=BenchmarkUnmarshalHandshake -benchmem > /tmp/mem.before.txt +// $ USE_BUILDER=true go test -run=NONE -bench=BenchmarkUnmarshalHandshake -benchmem > /tmp/mem.after.txt // $ benchcmp /tmp/mem.before.txt /tmp/mem.after.txt // $ benchstat -alpha 0.03 -geomean /tmp/mem.before.txt /tmp/mem.after.txt -func BenchmarkUnmarshalVersion(b *testing.B) { +func BenchmarkUnmarshalHandshake(b *testing.B) { require := require.New(b) b.StopTimer() id := ids.GenerateTestID() msg := p2p.Message{ - Message: &p2p.Message_Version{ - Version: &p2p.Version{ + Message: &p2p.Message_Handshake{ + Handshake: &p2p.Handshake{ NetworkId: uint32(1337), MyTime: uint64(time.Now().Unix()), IpAddr: []byte(net.IPv4(1, 2, 3, 4).To16()), IpPort: 0, MyVersion: "v1.2.3", - MyVersionTime: uint64(time.Now().Unix()), + IpSigningTime: uint64(time.Now().Unix()), Sig: []byte{'y', 'e', 'e', 't'}, TrackedSubnets: [][]byte{id[:]}, }, diff --git a/message/messages_test.go b/message/messages_test.go index a7cf74c95c48..27c0fb5fb646 100644 --- a/message/messages_test.go +++ b/message/messages_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message @@ -122,17 +122,17 @@ func TestMessage(t *testing.T) { bytesSaved: false, }, { - desc: "version message with no compression", - op: VersionOp, + desc: "Handshake message with no compression", + op: HandshakeOp, msg: &p2p.Message{ - Message: &p2p.Message_Version{ - Version: &p2p.Version{ + Message: &p2p.Message_Handshake{ + Handshake: &p2p.Handshake{ NetworkId: uint32(1337), MyTime: uint64(nowUnix), IpAddr: []byte(net.IPv6zero), IpPort: 9651, MyVersion: "v1.2.3", - MyVersionTime: uint64(nowUnix), + IpSigningTime: uint64(nowUnix), Sig: []byte{'y', 'e', 'e', 't'}, TrackedSubnets: [][]byte{testID[:]}, }, @@ -143,55 +143,67 @@ func TestMessage(t *testing.T) { bytesSaved: false, }, { - desc: "peer_list message with no compression", - op: PeerListOp, + desc: "get_peer_list message with no compression", + op: GetPeerListOp, msg: &p2p.Message{ - Message: &p2p.Message_PeerList{ - PeerList: &p2p.PeerList{ - ClaimedIpPorts: []*p2p.ClaimedIpPort{ - { - X509Certificate: testTLSCert.Certificate[0], - IpAddr: []byte(net.IPv4zero), - IpPort: 10, - Timestamp: 1, - Signature: []byte{0}, - }, + Message: &p2p.Message_GetPeerList{ + GetPeerList: &p2p.GetPeerList{ + KnownPeers: &p2p.BloomFilter{ + Filter: make([]byte, 2048), + Salt: make([]byte, 32), }, }, }, }, compressionType: compression.TypeNone, - bypassThrottling: true, + bypassThrottling: false, bytesSaved: false, }, { - desc: "peer_list message with gzip compression", + desc: "get_peer_list message with zstd compression", + op: GetPeerListOp, + msg: &p2p.Message{ + Message: &p2p.Message_GetPeerList{ + GetPeerList: &p2p.GetPeerList{ + KnownPeers: &p2p.BloomFilter{ + Filter: make([]byte, 2048), + Salt: make([]byte, 32), + }, + }, + }, + }, + compressionType: compression.TypeZstd, + bypassThrottling: false, + bytesSaved: true, + }, + { + desc: "peer_list message with no compression", op: PeerListOp, msg: &p2p.Message{ - Message: &p2p.Message_PeerList{ - PeerList: &p2p.PeerList{ + Message: &p2p.Message_PeerList_{ + PeerList_: &p2p.PeerList{ ClaimedIpPorts: []*p2p.ClaimedIpPort{ { X509Certificate: testTLSCert.Certificate[0], - IpAddr: []byte(net.IPv6zero), - IpPort: 9651, - Timestamp: uint64(nowUnix), - Signature: compressibleContainers[0], + IpAddr: []byte(net.IPv4zero), + IpPort: 10, + Timestamp: 1, + Signature: []byte{0}, }, }, }, }, }, - compressionType: compression.TypeGzip, + compressionType: compression.TypeNone, bypassThrottling: true, - bytesSaved: true, + bytesSaved: false, }, { desc: "peer_list message with zstd compression", op: PeerListOp, msg: &p2p.Message{ - Message: &p2p.Message_PeerList{ - PeerList: &p2p.PeerList{ + Message: &p2p.Message_PeerList_{ + PeerList_: &p2p.PeerList{ ClaimedIpPorts: []*p2p.ClaimedIpPort{ { X509Certificate: testTLSCert.Certificate[0], @@ -208,25 +220,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: true, }, - { - desc: "peer_list_ack message with no compression", - op: PeerListAckOp, - msg: &p2p.Message{ - Message: &p2p.Message_PeerListAck{ - PeerListAck: &p2p.PeerListAck{ - PeerAcks: []*p2p.PeerAck{ - { - TxId: testID[:], - Timestamp: 1, - }, - }, - }, - }, - }, - compressionType: compression.TypeNone, - bypassThrottling: false, - bytesSaved: false, - }, { desc: "get_state_summary_frontier message with no compression", op: GetStateSummaryFrontierOp, @@ -259,22 +252,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: false, }, - { - desc: "state_summary_frontier message with gzip compression", - op: StateSummaryFrontierOp, - msg: &p2p.Message{ - Message: &p2p.Message_StateSummaryFrontier_{ - StateSummaryFrontier_: &p2p.StateSummaryFrontier{ - ChainId: testID[:], - RequestId: 1, - Summary: compressibleContainers[0], - }, - }, - }, - compressionType: compression.TypeGzip, - bypassThrottling: true, - bytesSaved: true, - }, { desc: "state_summary_frontier message with zstd compression", op: StateSummaryFrontierOp, @@ -308,23 +285,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: false, }, - { - desc: "get_accepted_state_summary message with gzip compression", - op: GetAcceptedStateSummaryOp, - msg: &p2p.Message{ - Message: &p2p.Message_GetAcceptedStateSummary{ - GetAcceptedStateSummary: &p2p.GetAcceptedStateSummary{ - ChainId: testID[:], - RequestId: 1, - Deadline: 1, - Heights: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - }, - }, - compressionType: compression.TypeGzip, - bypassThrottling: true, - bytesSaved: false, - }, { desc: "get_accepted_state_summary message with zstd compression", op: GetAcceptedStateSummaryOp, @@ -358,22 +318,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: false, }, - { - desc: "accepted_state_summary message with gzip compression", - op: AcceptedStateSummaryOp, - msg: &p2p.Message{ - Message: &p2p.Message_AcceptedStateSummary_{ - AcceptedStateSummary_: &p2p.AcceptedStateSummary{ - ChainId: testID[:], - RequestId: 1, - SummaryIds: [][]byte{testID[:], testID[:], testID[:], testID[:], testID[:], testID[:], testID[:], testID[:], testID[:]}, - }, - }, - }, - compressionType: compression.TypeGzip, - bypassThrottling: true, - bytesSaved: true, - }, { desc: "accepted_state_summary message with zstd compression", op: AcceptedStateSummaryOp, @@ -491,22 +435,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: false, }, - { - desc: "ancestors message with gzip compression", - op: AncestorsOp, - msg: &p2p.Message{ - Message: &p2p.Message_Ancestors_{ - Ancestors_: &p2p.Ancestors{ - ChainId: testID[:], - RequestId: 12345, - Containers: compressibleContainers, - }, - }, - }, - compressionType: compression.TypeGzip, - bypassThrottling: true, - bytesSaved: true, - }, { desc: "ancestors message with zstd compression", op: AncestorsOp, @@ -558,23 +486,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: false, }, - { - desc: "put message with gzip compression", - op: PutOp, - msg: &p2p.Message{ - Message: &p2p.Message_Put{ - Put: &p2p.Put{ - ChainId: testID[:], - RequestId: 1, - Container: compressibleContainers[0], - EngineType: p2p.EngineType_ENGINE_TYPE_AVALANCHE, - }, - }, - }, - compressionType: compression.TypeGzip, - bypassThrottling: true, - bytesSaved: true, - }, { desc: "put message with zstd compression", op: PutOp, @@ -610,24 +521,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: false, }, - { - desc: "push_query message with gzip compression", - op: PushQueryOp, - msg: &p2p.Message{ - Message: &p2p.Message_PushQuery{ - PushQuery: &p2p.PushQuery{ - ChainId: testID[:], - RequestId: 1, - Deadline: 1, - Container: compressibleContainers[0], - EngineType: p2p.EngineType_ENGINE_TYPE_AVALANCHE, - }, - }, - }, - compressionType: compression.TypeGzip, - bypassThrottling: true, - bytesSaved: true, - }, { desc: "push_query message with zstd compression", op: PushQueryOp, @@ -697,23 +590,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: false, }, - { - desc: "app_request message with gzip compression", - op: AppRequestOp, - msg: &p2p.Message{ - Message: &p2p.Message_AppRequest{ - AppRequest: &p2p.AppRequest{ - ChainId: testID[:], - RequestId: 1, - Deadline: 1, - AppBytes: compressibleContainers[0], - }, - }, - }, - compressionType: compression.TypeGzip, - bypassThrottling: true, - bytesSaved: true, - }, { desc: "app_request message with zstd compression", op: AppRequestOp, @@ -747,22 +623,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: false, }, - { - desc: "app_response message with gzip compression", - op: AppResponseOp, - msg: &p2p.Message{ - Message: &p2p.Message_AppResponse{ - AppResponse: &p2p.AppResponse{ - ChainId: testID[:], - RequestId: 1, - AppBytes: compressibleContainers[0], - }, - }, - }, - compressionType: compression.TypeGzip, - bypassThrottling: true, - bytesSaved: true, - }, { desc: "app_response message with zstd compression", op: AppResponseOp, @@ -794,21 +654,6 @@ func TestMessage(t *testing.T) { bypassThrottling: true, bytesSaved: false, }, - { - desc: "app_gossip message with gzip compression", - op: AppGossipOp, - msg: &p2p.Message{ - Message: &p2p.Message_AppGossip{ - AppGossip: &p2p.AppGossip{ - ChainId: testID[:], - AppBytes: compressibleContainers[0], - }, - }, - }, - compressionType: compression.TypeGzip, - bypassThrottling: true, - bytesSaved: true, - }, { desc: "app_gossip message with zstd compression", op: AppGossipOp, @@ -836,8 +681,9 @@ func TestMessage(t *testing.T) { require.Equal(tv.bypassThrottling, encodedMsg.BypassThrottling()) require.Equal(tv.op, encodedMsg.Op()) - bytesSaved := encodedMsg.BytesSavedCompression() - require.Equal(tv.bytesSaved, bytesSaved > 0) + if bytesSaved := encodedMsg.BytesSavedCompression(); tv.bytesSaved { + require.Greater(bytesSaved, 0) + } parsedMsg, err := mb.parseInbound(encodedMsg.Bytes(), ids.EmptyNodeID, func() {}) require.NoError(err) diff --git a/message/mock_message.go b/message/mock_message.go index 938a90edbb8e..ea6b9a67afcf 100644 --- a/message/mock_message.go +++ b/message/mock_message.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/message (interfaces: OutboundMessage) +// +// Generated by this command: +// +// mockgen -package=message -destination=message/mock_message.go github.com/ava-labs/avalanchego/message OutboundMessage +// // Package message is a generated GoMock package. package message diff --git a/message/mock_outbound_message_builder.go b/message/mock_outbound_message_builder.go index 6c128123cc3c..0d053f71090b 100644 --- a/message/mock_outbound_message_builder.go +++ b/message/mock_outbound_message_builder.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/message (interfaces: OutboundMsgBuilder) +// +// Generated by this command: +// +// mockgen -package=message -destination=message/mock_outbound_message_builder.go github.com/ava-labs/avalanchego/message OutboundMsgBuilder +// // Package message is a generated GoMock package. package message @@ -50,7 +52,7 @@ func (m *MockOutboundMsgBuilder) Accepted(arg0 ids.ID, arg1 uint32, arg2 []ids.I } // Accepted indicates an expected call of Accepted. -func (mr *MockOutboundMsgBuilderMockRecorder) Accepted(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) Accepted(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accepted", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Accepted), arg0, arg1, arg2) } @@ -65,7 +67,7 @@ func (m *MockOutboundMsgBuilder) AcceptedFrontier(arg0 ids.ID, arg1 uint32, arg2 } // AcceptedFrontier indicates an expected call of AcceptedFrontier. -func (mr *MockOutboundMsgBuilderMockRecorder) AcceptedFrontier(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) AcceptedFrontier(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptedFrontier", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).AcceptedFrontier), arg0, arg1, arg2) } @@ -80,7 +82,7 @@ func (m *MockOutboundMsgBuilder) AcceptedStateSummary(arg0 ids.ID, arg1 uint32, } // AcceptedStateSummary indicates an expected call of AcceptedStateSummary. -func (mr *MockOutboundMsgBuilderMockRecorder) AcceptedStateSummary(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) AcceptedStateSummary(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptedStateSummary", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).AcceptedStateSummary), arg0, arg1, arg2) } @@ -95,7 +97,7 @@ func (m *MockOutboundMsgBuilder) Ancestors(arg0 ids.ID, arg1 uint32, arg2 [][]by } // Ancestors indicates an expected call of Ancestors. -func (mr *MockOutboundMsgBuilderMockRecorder) Ancestors(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) Ancestors(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ancestors", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Ancestors), arg0, arg1, arg2) } @@ -110,7 +112,7 @@ func (m *MockOutboundMsgBuilder) AppGossip(arg0 ids.ID, arg1 []byte) (OutboundMe } // AppGossip indicates an expected call of AppGossip. -func (mr *MockOutboundMsgBuilderMockRecorder) AppGossip(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) AppGossip(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppGossip", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).AppGossip), arg0, arg1) } @@ -125,7 +127,7 @@ func (m *MockOutboundMsgBuilder) AppRequest(arg0 ids.ID, arg1 uint32, arg2 time. } // AppRequest indicates an expected call of AppRequest. -func (mr *MockOutboundMsgBuilderMockRecorder) AppRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) AppRequest(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppRequest", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).AppRequest), arg0, arg1, arg2, arg3) } @@ -140,7 +142,7 @@ func (m *MockOutboundMsgBuilder) AppResponse(arg0 ids.ID, arg1 uint32, arg2 []by } // AppResponse indicates an expected call of AppResponse. -func (mr *MockOutboundMsgBuilderMockRecorder) AppResponse(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) AppResponse(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppResponse", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).AppResponse), arg0, arg1, arg2) } @@ -155,7 +157,7 @@ func (m *MockOutboundMsgBuilder) Chits(arg0 ids.ID, arg1 uint32, arg2, arg3, arg } // Chits indicates an expected call of Chits. -func (mr *MockOutboundMsgBuilderMockRecorder) Chits(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) Chits(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Chits", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Chits), arg0, arg1, arg2, arg3, arg4) } @@ -170,7 +172,7 @@ func (m *MockOutboundMsgBuilder) Get(arg0 ids.ID, arg1 uint32, arg2 time.Duratio } // Get indicates an expected call of Get. -func (mr *MockOutboundMsgBuilderMockRecorder) Get(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) Get(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Get), arg0, arg1, arg2, arg3, arg4) } @@ -185,7 +187,7 @@ func (m *MockOutboundMsgBuilder) GetAccepted(arg0 ids.ID, arg1 uint32, arg2 time } // GetAccepted indicates an expected call of GetAccepted. -func (mr *MockOutboundMsgBuilderMockRecorder) GetAccepted(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) GetAccepted(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccepted", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).GetAccepted), arg0, arg1, arg2, arg3, arg4) } @@ -200,7 +202,7 @@ func (m *MockOutboundMsgBuilder) GetAcceptedFrontier(arg0 ids.ID, arg1 uint32, a } // GetAcceptedFrontier indicates an expected call of GetAcceptedFrontier. -func (mr *MockOutboundMsgBuilderMockRecorder) GetAcceptedFrontier(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) GetAcceptedFrontier(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAcceptedFrontier", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).GetAcceptedFrontier), arg0, arg1, arg2, arg3) } @@ -215,7 +217,7 @@ func (m *MockOutboundMsgBuilder) GetAcceptedStateSummary(arg0 ids.ID, arg1 uint3 } // GetAcceptedStateSummary indicates an expected call of GetAcceptedStateSummary. -func (mr *MockOutboundMsgBuilderMockRecorder) GetAcceptedStateSummary(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) GetAcceptedStateSummary(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAcceptedStateSummary", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).GetAcceptedStateSummary), arg0, arg1, arg2, arg3) } @@ -230,11 +232,26 @@ func (m *MockOutboundMsgBuilder) GetAncestors(arg0 ids.ID, arg1 uint32, arg2 tim } // GetAncestors indicates an expected call of GetAncestors. -func (mr *MockOutboundMsgBuilderMockRecorder) GetAncestors(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) GetAncestors(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAncestors", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).GetAncestors), arg0, arg1, arg2, arg3, arg4) } +// GetPeerList mocks base method. +func (m *MockOutboundMsgBuilder) GetPeerList(arg0, arg1 []byte) (OutboundMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPeerList", arg0, arg1) + ret0, _ := ret[0].(OutboundMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPeerList indicates an expected call of GetPeerList. +func (mr *MockOutboundMsgBuilderMockRecorder) GetPeerList(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerList", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).GetPeerList), arg0, arg1) +} + // GetStateSummaryFrontier mocks base method. func (m *MockOutboundMsgBuilder) GetStateSummaryFrontier(arg0 ids.ID, arg1 uint32, arg2 time.Duration) (OutboundMessage, error) { m.ctrl.T.Helper() @@ -245,39 +262,39 @@ func (m *MockOutboundMsgBuilder) GetStateSummaryFrontier(arg0 ids.ID, arg1 uint3 } // GetStateSummaryFrontier indicates an expected call of GetStateSummaryFrontier. -func (mr *MockOutboundMsgBuilderMockRecorder) GetStateSummaryFrontier(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) GetStateSummaryFrontier(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStateSummaryFrontier", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).GetStateSummaryFrontier), arg0, arg1, arg2) } -// PeerList mocks base method. -func (m *MockOutboundMsgBuilder) PeerList(arg0 []ips.ClaimedIPPort, arg1 bool) (OutboundMessage, error) { +// Handshake mocks base method. +func (m *MockOutboundMsgBuilder) Handshake(arg0 uint32, arg1 uint64, arg2 ips.IPPort, arg3, arg4 string, arg5, arg6, arg7 uint32, arg8 uint64, arg9 []byte, arg10 []ids.ID, arg11, arg12 []uint32, arg13, arg14 []byte) (OutboundMessage, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PeerList", arg0, arg1) + ret := m.ctrl.Call(m, "Handshake", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) ret0, _ := ret[0].(OutboundMessage) ret1, _ := ret[1].(error) return ret0, ret1 } -// PeerList indicates an expected call of PeerList. -func (mr *MockOutboundMsgBuilderMockRecorder) PeerList(arg0, arg1 interface{}) *gomock.Call { +// Handshake indicates an expected call of Handshake. +func (mr *MockOutboundMsgBuilderMockRecorder) Handshake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerList", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).PeerList), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handshake", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Handshake), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) } -// PeerListAck mocks base method. -func (m *MockOutboundMsgBuilder) PeerListAck(arg0 []*p2p.PeerAck) (OutboundMessage, error) { +// PeerList mocks base method. +func (m *MockOutboundMsgBuilder) PeerList(arg0 []*ips.ClaimedIPPort, arg1 bool) (OutboundMessage, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PeerListAck", arg0) + ret := m.ctrl.Call(m, "PeerList", arg0, arg1) ret0, _ := ret[0].(OutboundMessage) ret1, _ := ret[1].(error) return ret0, ret1 } -// PeerListAck indicates an expected call of PeerListAck. -func (mr *MockOutboundMsgBuilderMockRecorder) PeerListAck(arg0 interface{}) *gomock.Call { +// PeerList indicates an expected call of PeerList. +func (mr *MockOutboundMsgBuilderMockRecorder) PeerList(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerListAck", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).PeerListAck), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeerList", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).PeerList), arg0, arg1) } // Ping mocks base method. @@ -290,7 +307,7 @@ func (m *MockOutboundMsgBuilder) Ping(arg0 uint32, arg1 []*p2p.SubnetUptime) (Ou } // Ping indicates an expected call of Ping. -func (mr *MockOutboundMsgBuilderMockRecorder) Ping(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) Ping(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Ping), arg0, arg1) } @@ -305,7 +322,7 @@ func (m *MockOutboundMsgBuilder) Pong(arg0 uint32, arg1 []*p2p.SubnetUptime) (Ou } // Pong indicates an expected call of Pong. -func (mr *MockOutboundMsgBuilderMockRecorder) Pong(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) Pong(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Pong", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Pong), arg0, arg1) } @@ -320,7 +337,7 @@ func (m *MockOutboundMsgBuilder) PullQuery(arg0 ids.ID, arg1 uint32, arg2 time.D } // PullQuery indicates an expected call of PullQuery. -func (mr *MockOutboundMsgBuilderMockRecorder) PullQuery(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) PullQuery(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PullQuery", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).PullQuery), arg0, arg1, arg2, arg3, arg4, arg5) } @@ -335,7 +352,7 @@ func (m *MockOutboundMsgBuilder) PushQuery(arg0 ids.ID, arg1 uint32, arg2 time.D } // PushQuery indicates an expected call of PushQuery. -func (mr *MockOutboundMsgBuilderMockRecorder) PushQuery(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) PushQuery(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushQuery", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).PushQuery), arg0, arg1, arg2, arg3, arg4, arg5) } @@ -350,7 +367,7 @@ func (m *MockOutboundMsgBuilder) Put(arg0 ids.ID, arg1 uint32, arg2 []byte, arg3 } // Put indicates an expected call of Put. -func (mr *MockOutboundMsgBuilderMockRecorder) Put(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) Put(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Put), arg0, arg1, arg2, arg3) } @@ -365,22 +382,7 @@ func (m *MockOutboundMsgBuilder) StateSummaryFrontier(arg0 ids.ID, arg1 uint32, } // StateSummaryFrontier indicates an expected call of StateSummaryFrontier. -func (mr *MockOutboundMsgBuilderMockRecorder) StateSummaryFrontier(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) StateSummaryFrontier(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSummaryFrontier", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).StateSummaryFrontier), arg0, arg1, arg2) } - -// Version mocks base method. -func (m *MockOutboundMsgBuilder) Version(arg0 uint32, arg1 uint64, arg2 ips.IPPort, arg3 string, arg4 uint64, arg5 []byte, arg6 []ids.ID) (OutboundMessage, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Version", arg0, arg1, arg2, arg3, arg4, arg5, arg6) - ret0, _ := ret[0].(OutboundMessage) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Version indicates an expected call of Version. -func (mr *MockOutboundMsgBuilderMockRecorder) Version(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Version), arg0, arg1, arg2, arg3, arg4, arg5, arg6) -} diff --git a/message/ops.go b/message/ops.go index e069553abd42..0c58eb60690b 100644 --- a/message/ops.go +++ b/message/ops.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message @@ -21,9 +21,9 @@ const ( // Handshake: PingOp Op = iota PongOp - VersionOp + HandshakeOp + GetPeerListOp PeerListOp - PeerListAckOp // State sync: GetStateSummaryFrontierOp GetStateSummaryFrontierFailedOp @@ -51,12 +51,12 @@ const ( ChitsOp // Application: AppRequestOp - AppRequestFailedOp + AppErrorOp AppResponseOp AppGossipOp // Cross chain: CrossChainAppRequestOp - CrossChainAppRequestFailedOp + CrossChainAppErrorOp CrossChainAppResponseOp // Internal: ConnectedOp @@ -71,9 +71,9 @@ var ( HandshakeOps = []Op{ PingOp, PongOp, - VersionOp, + HandshakeOp, + GetPeerListOp, PeerListOp, - PeerListAckOp, } // List of all consensus request message types @@ -115,9 +115,9 @@ var ( GetAncestorsFailedOp, GetFailedOp, QueryFailedOp, - AppRequestFailedOp, + AppErrorOp, CrossChainAppRequestOp, - CrossChainAppRequestFailedOp, + CrossChainAppErrorOp, CrossChainAppResponseOp, ConnectedOp, ConnectedSubnetOp, @@ -165,12 +165,12 @@ var ( AsynchronousOps = []Op{ // Application AppRequestOp, - AppRequestFailedOp, + AppErrorOp, AppGossipOp, AppResponseOp, // Cross chain CrossChainAppRequestOp, - CrossChainAppRequestFailedOp, + CrossChainAppErrorOp, CrossChainAppResponseOp, } @@ -182,8 +182,8 @@ var ( GetAncestorsFailedOp: AncestorsOp, GetFailedOp: PutOp, QueryFailedOp: ChitsOp, - AppRequestFailedOp: AppResponseOp, - CrossChainAppRequestFailedOp: CrossChainAppResponseOp, + AppErrorOp: AppResponseOp, + CrossChainAppErrorOp: CrossChainAppResponseOp, } UnrequestedOps = set.Of( GetAcceptedFrontierOp, @@ -209,12 +209,12 @@ func (op Op) String() string { return "ping" case PongOp: return "pong" - case VersionOp: - return "version" + case HandshakeOp: + return "handshake" + case GetPeerListOp: + return "get_peerlist" case PeerListOp: return "peerlist" - case PeerListAckOp: - return "peerlist_ack" // State sync case GetStateSummaryFrontierOp: return "get_state_summary_frontier" @@ -265,8 +265,8 @@ func (op Op) String() string { // Application case AppRequestOp: return "app_request" - case AppRequestFailedOp: - return "app_request_failed" + case AppErrorOp: + return "app_error" case AppResponseOp: return "app_response" case AppGossipOp: @@ -274,8 +274,8 @@ func (op Op) String() string { // Cross chain case CrossChainAppRequestOp: return "cross_chain_app_request" - case CrossChainAppRequestFailedOp: - return "cross_chain_app_request_failed" + case CrossChainAppErrorOp: + return "cross_chain_app_error" case CrossChainAppResponseOp: return "cross_chain_app_response" // Internal @@ -303,12 +303,12 @@ func Unwrap(m *p2p.Message) (fmt.Stringer, error) { return msg.Ping, nil case *p2p.Message_Pong: return msg.Pong, nil - case *p2p.Message_Version: - return msg.Version, nil - case *p2p.Message_PeerList: - return msg.PeerList, nil - case *p2p.Message_PeerListAck: - return msg.PeerListAck, nil + case *p2p.Message_Handshake: + return msg.Handshake, nil + case *p2p.Message_GetPeerList: + return msg.GetPeerList, nil + case *p2p.Message_PeerList_: + return msg.PeerList_, nil // State sync: case *p2p.Message_GetStateSummaryFrontier: return msg.GetStateSummaryFrontier, nil @@ -347,6 +347,8 @@ func Unwrap(m *p2p.Message) (fmt.Stringer, error) { return msg.AppRequest, nil case *p2p.Message_AppResponse: return msg.AppResponse, nil + case *p2p.Message_AppError: + return msg.AppError, nil case *p2p.Message_AppGossip: return msg.AppGossip, nil default: @@ -360,12 +362,12 @@ func ToOp(m *p2p.Message) (Op, error) { return PingOp, nil case *p2p.Message_Pong: return PongOp, nil - case *p2p.Message_Version: - return VersionOp, nil - case *p2p.Message_PeerList: + case *p2p.Message_Handshake: + return HandshakeOp, nil + case *p2p.Message_GetPeerList: + return GetPeerListOp, nil + case *p2p.Message_PeerList_: return PeerListOp, nil - case *p2p.Message_PeerListAck: - return PeerListAckOp, nil case *p2p.Message_GetStateSummaryFrontier: return GetStateSummaryFrontierOp, nil case *p2p.Message_StateSummaryFrontier_: @@ -400,6 +402,8 @@ func ToOp(m *p2p.Message) (Op, error) { return AppRequestOp, nil case *p2p.Message_AppResponse: return AppResponseOp, nil + case *p2p.Message_AppError: + return AppErrorOp, nil case *p2p.Message_AppGossip: return AppGossipOp, nil default: diff --git a/message/outbound_msg_builder.go b/message/outbound_msg_builder.go index f38a1d98ffed..150c9f4f3a65 100644 --- a/message/outbound_msg_builder.go +++ b/message/outbound_msg_builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message @@ -18,23 +18,32 @@ var _ OutboundMsgBuilder = (*outMsgBuilder)(nil) // with a reference count of 1. Once the reference count hits 0, the message // bytes should no longer be accessed. type OutboundMsgBuilder interface { - Version( + Handshake( networkID uint32, myTime uint64, ip ips.IPPort, myVersion string, - myVersionTime uint64, + client string, + major uint32, + minor uint32, + patch uint32, + ipSigningTime uint64, sig []byte, trackedSubnets []ids.ID, + supportedACPs []uint32, + objectedACPs []uint32, + knownPeersFilter []byte, + knownPeersSalt []byte, ) (OutboundMessage, error) - PeerList( - peers []ips.ClaimedIPPort, - bypassThrottling bool, + GetPeerList( + knownPeersFilter []byte, + knownPeersSalt []byte, ) (OutboundMessage, error) - PeerListAck( - peerAcks []*p2p.PeerAck, + PeerList( + peers []*ips.ClaimedIPPort, + bypassThrottling bool, ) (OutboundMessage, error) Ping( @@ -224,29 +233,49 @@ func (b *outMsgBuilder) Pong( ) } -func (b *outMsgBuilder) Version( +func (b *outMsgBuilder) Handshake( networkID uint32, myTime uint64, ip ips.IPPort, myVersion string, - myVersionTime uint64, + client string, + major uint32, + minor uint32, + patch uint32, + ipSigningTime uint64, sig []byte, trackedSubnets []ids.ID, + supportedACPs []uint32, + objectedACPs []uint32, + knownPeersFilter []byte, + knownPeersSalt []byte, ) (OutboundMessage, error) { subnetIDBytes := make([][]byte, len(trackedSubnets)) encodeIDs(trackedSubnets, subnetIDBytes) return b.builder.createOutbound( &p2p.Message{ - Message: &p2p.Message_Version{ - Version: &p2p.Version{ + Message: &p2p.Message_Handshake{ + Handshake: &p2p.Handshake{ NetworkId: networkID, MyTime: myTime, IpAddr: ip.IP.To16(), IpPort: uint32(ip.Port), MyVersion: myVersion, - MyVersionTime: myVersionTime, + IpSigningTime: ipSigningTime, Sig: sig, TrackedSubnets: subnetIDBytes, + Client: &p2p.Client{ + Name: client, + Major: major, + Minor: minor, + Patch: patch, + }, + SupportedAcps: supportedACPs, + ObjectedAcps: objectedACPs, + KnownPeers: &p2p.BloomFilter{ + Filter: knownPeersFilter, + Salt: knownPeersSalt, + }, }, }, }, @@ -255,7 +284,27 @@ func (b *outMsgBuilder) Version( ) } -func (b *outMsgBuilder) PeerList(peers []ips.ClaimedIPPort, bypassThrottling bool) (OutboundMessage, error) { +func (b *outMsgBuilder) GetPeerList( + knownPeersFilter []byte, + knownPeersSalt []byte, +) (OutboundMessage, error) { + return b.builder.createOutbound( + &p2p.Message{ + Message: &p2p.Message_GetPeerList{ + GetPeerList: &p2p.GetPeerList{ + KnownPeers: &p2p.BloomFilter{ + Filter: knownPeersFilter, + Salt: knownPeersSalt, + }, + }, + }, + }, + b.compressionType, + false, + ) +} + +func (b *outMsgBuilder) PeerList(peers []*ips.ClaimedIPPort, bypassThrottling bool) (OutboundMessage, error) { claimIPPorts := make([]*p2p.ClaimedIpPort, len(peers)) for i, p := range peers { claimIPPorts[i] = &p2p.ClaimedIpPort{ @@ -264,13 +313,13 @@ func (b *outMsgBuilder) PeerList(peers []ips.ClaimedIPPort, bypassThrottling boo IpPort: uint32(p.IPPort.Port), Timestamp: p.Timestamp, Signature: p.Signature, - TxId: p.TxID[:], + TxId: ids.Empty[:], } } return b.builder.createOutbound( &p2p.Message{ - Message: &p2p.Message_PeerList{ - PeerList: &p2p.PeerList{ + Message: &p2p.Message_PeerList_{ + PeerList_: &p2p.PeerList{ ClaimedIpPorts: claimIPPorts, }, }, @@ -280,20 +329,6 @@ func (b *outMsgBuilder) PeerList(peers []ips.ClaimedIPPort, bypassThrottling boo ) } -func (b *outMsgBuilder) PeerListAck(peerAcks []*p2p.PeerAck) (OutboundMessage, error) { - return b.builder.createOutbound( - &p2p.Message{ - Message: &p2p.Message_PeerListAck{ - PeerListAck: &p2p.PeerListAck{ - PeerAcks: peerAcks, - }, - }, - }, - compression.TypeNone, - false, - ) -} - func (b *outMsgBuilder) GetStateSummaryFrontier( chainID ids.ID, requestID uint32, diff --git a/message/outbound_msg_builder_test.go b/message/outbound_msg_builder_test.go index 50f273bf4b13..39d22442b0a4 100644 --- a/message/outbound_msg_builder_test.go +++ b/message/outbound_msg_builder_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message @@ -29,7 +29,6 @@ func Test_newOutboundBuilder(t *testing.T) { for _, compressionType := range []compression.Type{ compression.TypeNone, - compression.TypeGzip, compression.TypeZstd, } { t.Run(compressionType.String(), func(t *testing.T) { diff --git a/nat/nat.go b/nat/nat.go index 33749ca0e572..a6e37078e7a6 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nat @@ -53,8 +53,8 @@ type Mapper struct { } // NewPortMapper returns an initialized mapper -func NewPortMapper(log logging.Logger, r Router) Mapper { - return Mapper{ +func NewPortMapper(log logging.Logger, r Router) *Mapper { + return &Mapper{ log: log, r: r, closer: make(chan struct{}), diff --git a/nat/no_router.go b/nat/no_router.go index 5c894c8c673c..19c68dac5538 100644 --- a/nat/no_router.go +++ b/nat/no_router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nat diff --git a/nat/pmp.go b/nat/pmp.go index ad2032ec1493..ecee9793f934 100644 --- a/nat/pmp.go +++ b/nat/pmp.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nat diff --git a/nat/upnp.go b/nat/upnp.go index 2571048e367e..aa26d6d82fc6 100644 --- a/nat/upnp.go +++ b/nat/upnp.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nat diff --git a/network/README.md b/network/README.md index 5364d9db39e5..303d1f56ab82 100644 --- a/network/README.md +++ b/network/README.md @@ -46,7 +46,7 @@ When starting an Avalanche node, a node needs to be able to initiate some proces In Avalanche, nodes connect to an initial set of bootstrapper nodes known as **beacons** (this is user-configurable). Once connected to a set of beacons, a node is able to discover other nodes in the network. Over time, a node eventually discovers other peers in the network through `PeerList` messages it receives through: - The handshake initiated between two peers when attempting to connect to a peer (see [Connecting](#connecting)). -- Periodic `PeerList` gossip messages that every peer sends to the peers it's connected to (see [Connected](#connected)). +- Responses to periodically sent `GetPeerList` messages requesting a `PeerList` of unknown peers (see [Connected](#connected)). #### Connecting @@ -54,32 +54,31 @@ In Avalanche, nodes connect to an initial set of bootstrapper nodes known as **b Upon connection to any peer, a handshake is performed between the node attempting to establish the outbound connection to the peer and the peer receiving the inbound connection. -When attempting to establish the connection, the first message that the node attempting to connect to the peer in the network is a `Version` message describing compatibility of the candidate node with the peer. As an example, nodes that are attempting to connect with an incompatible version of AvalancheGo or a significantly skewed local clock are rejected by the peer. +When attempting to establish the connection, the first message that the node attempting to connect to the peer in the network is a `Handshake` message describing compatibility of the candidate node with the peer. As an example, nodes that are attempting to connect with an incompatible version of AvalancheGo or a significantly skewed local clock are rejected by the peer. ```mermaid sequenceDiagram Note over Node,Peer: Initiate Handshake Note left of Node: I want to connect to you! -Note over Node,Peer: Version message +Note over Node,Peer: Handshake message Node->>Peer: AvalancheGo v1.0.0 Note right of Peer: My version v1.9.4 is incompatible with your version v1.0.0. Peer-xNode: Connection dropped Note over Node,Peer: Handshake Failed ``` -If the `Version` message is successfully received and the peer decides that it wants a connection with this node, it replies with a `PeerList` message that contains metadata about other peers that allows a node to connect to them. Upon reception of a `PeerList` message, a node will attempt to connect to any peers that the node is not already connected to to allow the node to discover more peers in the network. +If the `Handshake` message is successfully received and the peer decides that it wants a connection with this node, it replies with a `PeerList` message that contains metadata about other peers that allows a node to connect to them. Upon reception of a `PeerList` message, a node will attempt to connect to any peers that the node is not already connected to to allow the node to discover more peers in the network. ```mermaid sequenceDiagram Note over Node,Peer: Initiate Handshake Note left of Node: I want to connect to you! -Note over Node,Peer: Version message +Note over Node,Peer: Handshake message Node->>Peer: AvalancheGo v1.9.4 Note right of Peer: LGTM! Note over Node,Peer: PeerList message Peer->>Node: Peer-X, Peer-Y, Peer-Z Note over Node,Peer: Handshake Complete -Node->>Peer: ACK Peer-X, Peer-Y, Peer-Z ``` Once the node attempting to join the network receives this `PeerList` message, the handshake is complete and the node is now connected to the peer. The node attempts to connect to the new peers discovered in the `PeerList` message. Each connection results in another peer handshake, which results in the node incrementally discovering more and more peers in the network as more and more `PeerList` messages are exchanged. @@ -90,73 +89,55 @@ Some peers aren't discovered through the `PeerList` messages exchanged through p ```mermaid sequenceDiagram -Node ->> Peer-1: Version - v1.9.5 +Node ->> Peer-1: Handshake - v1.9.5 Peer-1 ->> Node: PeerList - Peer-2 -Node ->> Peer-1: ACK - Peer-2 Note left of Node: Node is connected to Peer-1 and now tries to connect to Peer-2. -Node ->> Peer-2: Version - v1.9.5 +Node ->> Peer-2: Handshake - v1.9.5 Peer-2 ->> Node: PeerList - Peer-1 -Node ->> Peer-2: ACK - Peer-1 Note left of Node: Peer-3 was never sampled, so we haven't connected yet! Node --> Peer-3: No connection ``` -To guarantee that a node can discover all peers, each node periodically gossips a sample of the peers it knows about to other peers. +To guarantee that a node can discover all peers, each node periodically sends a `GetPeerList` message to a random peer. ##### PeerList Gossip ###### Messages -A `PeerList` is the message that is used to communicate the presence of peers in the network. Each `PeerList` message contains networking-level metadata about the peer that provides the necessary information to connect to it, alongside the corresponding transaction id that added that peer to the validator set. Transaction ids are unique hashes that only add a single validator, so it is guaranteed that there is a 1:1 mapping between a validator and its associated transaction id. +A `GetPeerList` message requests that the peer sends a `PeerList` message. `GetPeerList` messages contain a bloom filter of already known peers to reduce useless bandwidth on `PeerList` messages. The bloom filter reduces bandwidth by enabling the `PeerList` message to only include peers that aren't already known. -`PeerListAck` messages are sent in response to `PeerList` messages to allow a peer to confirm which peers it will actually attempt to connect to. Because nodes only gossip peers they believe another peer doesn't already know about to optimize bandwidth, `PeerListAck` messages are important to confirm that a peer will attempt to connect to someone. Without this, a node might gossip a peer to another peer and assume a connection between the two is being established, and not re-gossip the peer in future gossip cycles. If the connection was never actually wanted by the peer being gossiped to due to a transient reason, that peer would never be able to re-discover the gossiped peer and could be isolated from a subset of the network. +A `PeerList` is the message that is used to communicate the presence of peers in the network. Each `PeerList` message contains signed networking-level metadata about a peer that provides the necessary information to connect to it. -Once a `PeerListAck` message is received from a peer, the node that sent the original `PeerList` message marks the corresponding acknowledged validators as already having been transmitted to the peer, so that it's excluded from subsequent iterations of `PeerList` gossip. +Once peer metadata is received, the node will add that data to its bloom filter to prevent learning about it again. ###### Gossip Handshake messages provide a node with some knowledge of peers in the network, but offers no guarantee that learning about a subset of peers from each peer the node connects with will result in the node learning about every peer in the network. -In order to provide a probabilistic guarantee that all peers in the network will eventually learn of one another, each node periodically gossips a sample of the peers that they're aware of to a sample of the peers that they're connected to. Over time, this probabilistically guarantees that every peer will eventually learn of every other peer. +To provide an eventual guarantee that all peers learn of one another, each node periodically requests peers from a random peer. -To optimize bandwidth usage, each node tracks which peers are guaranteed to know of which peers. A node learns this information by tracking both inbound and outbound `PeerList` gossip. +To optimize bandwidth, each node tracks the most recent IPs of validators. The validator's nodeID and timestamp are inserted into a bloom filter which is used to select only necessary IPs to gossip. -- Inbound - - If a node ever receives `PeerList` from a peer, that peer _must_ have known about the peers in that `PeerList` message in order to have gossiped them. -- Outbound - - If a node sends a `PeerList` to a peer and the peer replies with an `PeerListAck` message, then all peers in the `PeerListAck` must be known by the peer. +As the number of entries increases in the bloom filter, the probability of a false positive increases. False positives can cause recent IPs not to be gossiped when they otherwise should be, slowing down the rate of `PeerList` gossip. To prevent the bloom filter from having too many false positives, a new bloom filter is periodically generated and the number of entries a validator is allowed to have in the bloom filter is capped. Generating the new bloom filter both removes stale entries and modifies the hash functions to avoid persistent hash collisions. -To efficiently track which peers know of which peers, the peers that each peer is aware of is represented in a [bit set](https://en.wikipedia.org/wiki/Bit_array). A peer is represented by either a `0` if it isn't known by the peer yet, or a `1` if it is known by the peer. - -An node follows the following steps for every cycle of `PeerList` gossip: - -1. Get a sample of peers in the network that the node is connected to -2. For each peer: - 1. Figure out which peers the node hasn't gossiped to them yet. - 2. Take a random sample of these unknown peers. - 3. Send a message describing these peers to the peer. +A node follows the following steps for of `PeerList` gossip: ```mermaid sequenceDiagram -Note left of Node: Initialize gossip bit set for Peer-123 -Note left of Node: Peer-123: [0, 0, 0] -Node->>Peer-123: PeerList - Peer-1 -Peer-123->>Node: PeerListAck - Peer-1 -Note left of Node: Peer-123: [1, 0, 0] -Node->>Peer-123: PeerList - Peer-3 -Peer-123->>Node: PeerListAck - Peer-3 -Note left of Node: Peer-123: [1, 0, 1] -Node->>Peer-123: PeerList - Peer-2 -Peer-123->>Node: PeerListAck - Peer-2 -Note left of Node: Peer-123: [1, 1, 1] -Note left of Node: No more gossip left to send to Peer-123! +Note left of Node: Initialize bloom filter +Note left of Node: Bloom: [0, 0, 0] +Node->>Peer-123: GetPeerList [0, 0, 0] +Note right of Peer-123: Any peers can be sent. +Peer-123->>Node: PeerList - Peer-1 +Note left of Node: Bloom: [1, 0, 0] +Node->>Peer-123: GetPeerList [1, 0, 0] +Note right of Peer-123: Either Peer-2 or Peer-3 can be sent. +Peer-123->>Node: PeerList - Peer-3 +Note left of Node: Bloom: [1, 0, 1] +Node->>Peer-123: GetPeerList [1, 0, 1] +Note right of Peer-123: Only Peer-2 can be sent. +Peer-123->>Node: PeerList - Peer-2 +Note left of Node: Bloom: [1, 1, 1] +Node->>Peer-123: GetPeerList [1, 1, 1] +Note right of Peer-123: There are no more peers left to send! ``` - -Because network state is generally expected to be stable (i.e nodes are not continuously flickering online/offline), as more and more gossip messages are exchanged nodes eventually realize that the peers that they are connected to have learned about every other peer. - -A node eventually stops gossiping peers when there's no more new peers to gossip about. `PeerList` gossip only resumes once: - -1. a new peer joins -2. a peer disconnects and reconnects -3. a new validator joins the network -4. a validator's IP is updated diff --git a/network/certs_test.go b/network/certs_test.go index 10b92586ed5c..d172d68650fc 100644 --- a/network/certs_test.go +++ b/network/certs_test.go @@ -1,26 +1,83 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network import ( "crypto/tls" + "net" "sync" "testing" + _ "embed" + "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/staking" + "github.com/ava-labs/avalanchego/utils/ips" ) var ( + //go:embed test_cert_1.crt + testCertBytes1 []byte + //go:embed test_key_1.key + testKeyBytes1 []byte + //go:embed test_cert_2.crt + testCertBytes2 []byte + //go:embed test_key_2.key + testKeyBytes2 []byte + //go:embed test_cert_3.crt + testCertBytes3 []byte + //go:embed test_key_3.key + testKeyBytes3 []byte + + ip *ips.ClaimedIPPort + otherIP *ips.ClaimedIPPort + certLock sync.Mutex tlsCerts []*tls.Certificate tlsConfigs []*tls.Config ) +func init() { + cert1, err := staking.LoadTLSCertFromBytes(testKeyBytes1, testCertBytes1) + if err != nil { + panic(err) + } + cert2, err := staking.LoadTLSCertFromBytes(testKeyBytes2, testCertBytes2) + if err != nil { + panic(err) + } + cert3, err := staking.LoadTLSCertFromBytes(testKeyBytes3, testCertBytes3) + if err != nil { + panic(err) + } + tlsCerts = []*tls.Certificate{ + cert1, cert2, cert3, + } + + ip = ips.NewClaimedIPPort( + staking.CertificateFromX509(cert1.Leaf), + ips.IPPort{ + IP: net.IPv4(127, 0, 0, 1), + Port: 9651, + }, + 1, // timestamp + nil, // signature + ) + otherIP = ips.NewClaimedIPPort( + staking.CertificateFromX509(cert2.Leaf), + ips.IPPort{ + IP: net.IPv4(127, 0, 0, 1), + Port: 9651, + }, + 1, // timestamp + nil, // signature + ) +} + func getTLS(t *testing.T, index int) (ids.NodeID, *tls.Certificate, *tls.Config) { certLock.Lock() defer certLock.Unlock() @@ -28,9 +85,11 @@ func getTLS(t *testing.T, index int) (ids.NodeID, *tls.Certificate, *tls.Config) for len(tlsCerts) <= index { cert, err := staking.NewTLSCert() require.NoError(t, err) - tlsConfig := peer.TLSConfig(*cert, nil) - tlsCerts = append(tlsCerts, cert) + } + for len(tlsConfigs) <= index { + cert := tlsCerts[len(tlsConfigs)] + tlsConfig := peer.TLSConfig(*cert, nil) tlsConfigs = append(tlsConfigs, tlsConfig) } diff --git a/network/config.go b/network/config.go index 1ca1addc0117..5cb014741f56 100644 --- a/network/config.go +++ b/network/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/dialer" - "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/network/throttling" "github.com/ava-labs/avalanchego/snow/networking/tracker" "github.com/ava-labs/avalanchego/snow/uptime" @@ -73,6 +72,14 @@ type PeerListGossipConfig struct { // PeerListGossipFreq is the frequency that this node will attempt to gossip // signed IPs to its peers. PeerListGossipFreq time.Duration `json:"peerListGossipFreq"` + + // PeerListPullGossipFreq is the frequency that this node will attempt to + // request signed IPs from its peers. + PeerListPullGossipFreq time.Duration `json:"peerListPullGossipFreq"` + + // PeerListBloomResetFreq is how frequently this node will recalculate the + // IP tracker's bloom filter. + PeerListBloomResetFreq time.Duration `json:"peerListBloomResetFreq"` } type TimeoutConfig struct { @@ -126,6 +133,9 @@ type Config struct { PingFrequency time.Duration `json:"pingFrequency"` AllowPrivateIPs bool `json:"allowPrivateIPs"` + SupportedACPs set.Set[uint32] `json:"supportedACPs"` + ObjectedACPs set.Set[uint32] `json:"objectedACPs"` + // The compression type to use when compressing outbound messages. // Assumes all peers support this compression type. CompressionType compression.Type `json:"compressionType"` @@ -179,7 +189,4 @@ type Config struct { // Specifies how much disk usage each peer can cause before // we rate-limit them. DiskTargeter tracker.Targeter `json:"-"` - - // Tracks which validators have been sent to which peers - GossipTracker peer.GossipTracker `json:"-"` } diff --git a/network/conn_test.go b/network/conn_test.go index 4394cd885e3c..6a44c6153992 100644 --- a/network/conn_test.go +++ b/network/conn_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network diff --git a/network/dialer/dialer.go b/network/dialer/dialer.go index 22e8c3ba1bfe..109b63cc2002 100644 --- a/network/dialer/dialer.go +++ b/network/dialer/dialer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package dialer diff --git a/network/dialer/dialer_test.go b/network/dialer/dialer_test.go index 95011996bcdf..a824b8b03e08 100644 --- a/network/dialer/dialer_test.go +++ b/network/dialer/dialer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package dialer diff --git a/network/dialer_test.go b/network/dialer_test.go index b6f2eef15def..7a60d056d66d 100644 --- a/network/dialer_test.go +++ b/network/dialer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -33,7 +33,7 @@ func (d *testDialer) NewListener() (ips.DynamicIPPort, *testListener) { // Uses a private IP to easily enable testing AllowPrivateIPs ip := ips.NewDynamicIPPort( net.IPv4(10, 0, 0, 0), - uint16(len(d.listeners)), + uint16(len(d.listeners)+1), ) staticIP := ip.IPPort() listener := newTestListener(staticIP) @@ -55,22 +55,22 @@ func (d *testDialer) Dial(ctx context.Context, ip ips.IPPort) (net.Conn, error) Conn: serverConn, localAddr: &net.TCPAddr{ IP: net.IPv6loopback, - Port: 0, + Port: 1, }, remoteAddr: &net.TCPAddr{ IP: net.IPv6loopback, - Port: 1, + Port: 2, }, } client := &testConn{ Conn: clientConn, localAddr: &net.TCPAddr{ IP: net.IPv6loopback, - Port: 2, + Port: 3, }, remoteAddr: &net.TCPAddr{ IP: net.IPv6loopback, - Port: 3, + Port: 4, }, } select { diff --git a/network/example_test.go b/network/example_test.go index 8f20900a7e8d..bfac03fba44f 100644 --- a/network/example_test.go +++ b/network/example_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network diff --git a/network/handler_test.go b/network/handler_test.go index 64350b3b289a..08c99a0d4e70 100644 --- a/network/handler_test.go +++ b/network/handler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network diff --git a/network/ip_tracker.go b/network/ip_tracker.go new file mode 100644 index 000000000000..60502dbbf4df --- /dev/null +++ b/network/ip_tracker.go @@ -0,0 +1,437 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "crypto/rand" + "sync" + + "github.com/prometheus/client_golang/prometheus" + + "go.uber.org/zap" + + "golang.org/x/exp/maps" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/utils/bloom" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/ips" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/utils/sampler" + "github.com/ava-labs/avalanchego/utils/set" +) + +const ( + saltSize = 32 + minCountEstimate = 128 + targetFalsePositiveProbability = .001 + maxFalsePositiveProbability = .01 + // By setting maxIPEntriesPerValidator > 1, we allow validators to update + // their IP at least once per bloom filter reset. + maxIPEntriesPerValidator = 2 +) + +var _ validators.SetCallbackListener = (*ipTracker)(nil) + +func newIPTracker( + log logging.Logger, + namespace string, + registerer prometheus.Registerer, +) (*ipTracker, error) { + tracker := &ipTracker{ + log: log, + numValidatorIPs: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "validator_ips", + Help: "Number of known validator IPs", + }), + numGossipable: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "gossipable_ips", + Help: "Number of IPs this node is willing to gossip", + }), + bloomCount: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "ip_bloom_count", + Help: "Number of IP entries added to the bloom", + }), + bloomNumHashes: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "ip_bloom_hashes", + Help: "Number of hashes in the IP bloom", + }), + bloomNumEntries: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "ip_bloom_entries", + Help: "Number of entry slots in the IP bloom", + }), + bloomMaxCount: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "ip_bloom_max_count", + Help: "Maximum number of IP entries that can be added to the bloom before resetting", + }), + bloomResetCount: prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: namespace, + Name: "ip_bloom_reset_count", + Help: "Number times the IP bloom has been reset", + }), + connected: make(map[ids.NodeID]*ips.ClaimedIPPort), + mostRecentValidatorIPs: make(map[ids.NodeID]*ips.ClaimedIPPort), + gossipableIndicies: make(map[ids.NodeID]int), + bloomAdditions: make(map[ids.NodeID]int), + } + err := utils.Err( + registerer.Register(tracker.numValidatorIPs), + registerer.Register(tracker.numGossipable), + registerer.Register(tracker.bloomCount), + registerer.Register(tracker.bloomNumHashes), + registerer.Register(tracker.bloomNumEntries), + registerer.Register(tracker.bloomMaxCount), + registerer.Register(tracker.bloomResetCount), + ) + if err != nil { + return nil, err + } + return tracker, tracker.resetBloom() +} + +type ipTracker struct { + log logging.Logger + numValidatorIPs prometheus.Gauge + numGossipable prometheus.Gauge + bloomCount prometheus.Gauge + bloomNumHashes prometheus.Gauge + bloomNumEntries prometheus.Gauge + bloomMaxCount prometheus.Gauge + bloomResetCount prometheus.Counter + + lock sync.RWMutex + // Manually tracked nodes are always treated like validators + manuallyTracked set.Set[ids.NodeID] + // Connected tracks the currently connected peers, including validators and + // non-validators. The IP is not necessarily the same IP as in + // mostRecentIPs. + connected map[ids.NodeID]*ips.ClaimedIPPort + mostRecentValidatorIPs map[ids.NodeID]*ips.ClaimedIPPort + validators set.Set[ids.NodeID] + + // An IP is marked as gossipable if: + // - The node is a validator + // - The node is connected + // - The IP the node connected with is its latest IP + gossipableIndicies map[ids.NodeID]int + gossipableIPs []*ips.ClaimedIPPort + + // The bloom filter contains the most recent validator IPs to avoid + // unnecessary IP gossip. + bloom *bloom.Filter + // To prevent validators from causing the bloom filter to have too many + // false positives, we limit each validator to maxIPEntriesPerValidator in + // the bloom filter. + bloomAdditions map[ids.NodeID]int // Number of IPs added to the bloom + bloomSalt []byte + maxBloomCount int +} + +func (i *ipTracker) ManuallyTrack(nodeID ids.NodeID) { + i.lock.Lock() + defer i.lock.Unlock() + + // We treat manually tracked nodes as if they were validators. + if !i.validators.Contains(nodeID) { + i.onValidatorAdded(nodeID) + } + // Now that the node is marked as a validator, freeze it's validation + // status. Future calls to OnValidatorAdded or OnValidatorRemoved will be + // treated as noops. + i.manuallyTracked.Add(nodeID) +} + +func (i *ipTracker) WantsConnection(nodeID ids.NodeID) bool { + i.lock.RLock() + defer i.lock.RUnlock() + + return i.validators.Contains(nodeID) +} + +func (i *ipTracker) ShouldVerifyIP(ip *ips.ClaimedIPPort) bool { + i.lock.RLock() + defer i.lock.RUnlock() + + if !i.validators.Contains(ip.NodeID) { + return false + } + + prevIP, ok := i.mostRecentValidatorIPs[ip.NodeID] + return !ok || // This would be the first IP + prevIP.Timestamp < ip.Timestamp // This would be a newer IP +} + +// AddIP returns true if the addition of the provided IP updated the most +// recently known IP of a validator. +func (i *ipTracker) AddIP(ip *ips.ClaimedIPPort) bool { + i.lock.Lock() + defer i.lock.Unlock() + + if !i.validators.Contains(ip.NodeID) { + return false + } + + prevIP, ok := i.mostRecentValidatorIPs[ip.NodeID] + if !ok { + // This is the first IP we've heard from the validator, so it is the + // most recent. + i.updateMostRecentValidatorIP(ip) + // Because we didn't previously have an IP, we know we aren't currently + // connected to them. + return true + } + + if prevIP.Timestamp >= ip.Timestamp { + // This IP is not newer than the previously known IP. + return false + } + + i.updateMostRecentValidatorIP(ip) + i.removeGossipableIP(ip.NodeID) + return true +} + +func (i *ipTracker) GetIP(nodeID ids.NodeID) (*ips.ClaimedIPPort, bool) { + i.lock.RLock() + defer i.lock.RUnlock() + + ip, ok := i.mostRecentValidatorIPs[nodeID] + return ip, ok +} + +func (i *ipTracker) Connected(ip *ips.ClaimedIPPort) { + i.lock.Lock() + defer i.lock.Unlock() + + i.connected[ip.NodeID] = ip + if !i.validators.Contains(ip.NodeID) { + return + } + + prevIP, ok := i.mostRecentValidatorIPs[ip.NodeID] + if !ok { + // This is the first IP we've heard from the validator, so it is the + // most recent. + i.updateMostRecentValidatorIP(ip) + i.addGossipableIP(ip) + return + } + + if prevIP.Timestamp > ip.Timestamp { + // There is a more up-to-date IP than the one that was used to connect. + return + } + + if prevIP.Timestamp < ip.Timestamp { + i.updateMostRecentValidatorIP(ip) + } + i.addGossipableIP(ip) +} + +func (i *ipTracker) Disconnected(nodeID ids.NodeID) { + i.lock.Lock() + defer i.lock.Unlock() + + delete(i.connected, nodeID) + i.removeGossipableIP(nodeID) +} + +func (i *ipTracker) OnValidatorAdded(nodeID ids.NodeID, _ *bls.PublicKey, _ ids.ID, _ uint64) { + i.lock.Lock() + defer i.lock.Unlock() + + i.onValidatorAdded(nodeID) +} + +func (i *ipTracker) onValidatorAdded(nodeID ids.NodeID) { + if i.manuallyTracked.Contains(nodeID) { + return + } + + i.validators.Add(nodeID) + ip, connected := i.connected[nodeID] + if !connected { + return + } + + // Because we only track validator IPs, the from the connection is + // guaranteed to be the most up-to-date IP that we know. + i.updateMostRecentValidatorIP(ip) + i.addGossipableIP(ip) +} + +func (*ipTracker) OnValidatorWeightChanged(ids.NodeID, uint64, uint64) {} + +func (i *ipTracker) OnValidatorRemoved(nodeID ids.NodeID, _ uint64) { + i.lock.Lock() + defer i.lock.Unlock() + + if i.manuallyTracked.Contains(nodeID) { + return + } + + delete(i.mostRecentValidatorIPs, nodeID) + i.numValidatorIPs.Set(float64(len(i.mostRecentValidatorIPs))) + + i.validators.Remove(nodeID) + i.removeGossipableIP(nodeID) +} + +func (i *ipTracker) updateMostRecentValidatorIP(ip *ips.ClaimedIPPort) { + i.mostRecentValidatorIPs[ip.NodeID] = ip + i.numValidatorIPs.Set(float64(len(i.mostRecentValidatorIPs))) + + oldCount := i.bloomAdditions[ip.NodeID] + if oldCount >= maxIPEntriesPerValidator { + return + } + + // If the validator set is growing rapidly, we should increase the size of + // the bloom filter. + if count := i.bloom.Count(); count >= i.maxBloomCount { + if err := i.resetBloom(); err != nil { + i.log.Error("failed to reset validator tracker bloom filter", + zap.Int("maxCount", i.maxBloomCount), + zap.Int("currentCount", count), + zap.Error(err), + ) + } else { + i.log.Info("reset validator tracker bloom filter", + zap.Int("currentCount", count), + ) + } + return + } + + i.bloomAdditions[ip.NodeID] = oldCount + 1 + bloom.Add(i.bloom, ip.GossipID[:], i.bloomSalt) + i.bloomCount.Inc() +} + +func (i *ipTracker) addGossipableIP(ip *ips.ClaimedIPPort) { + i.gossipableIndicies[ip.NodeID] = len(i.gossipableIPs) + i.gossipableIPs = append(i.gossipableIPs, ip) + i.numGossipable.Inc() +} + +func (i *ipTracker) removeGossipableIP(nodeID ids.NodeID) { + indexToRemove, wasGossipable := i.gossipableIndicies[nodeID] + if !wasGossipable { + return + } + + newNumGossipable := len(i.gossipableIPs) - 1 + if newNumGossipable != indexToRemove { + replacementIP := i.gossipableIPs[newNumGossipable] + i.gossipableIndicies[replacementIP.NodeID] = indexToRemove + i.gossipableIPs[indexToRemove] = replacementIP + } + + delete(i.gossipableIndicies, nodeID) + i.gossipableIPs[newNumGossipable] = nil + i.gossipableIPs = i.gossipableIPs[:newNumGossipable] + i.numGossipable.Dec() +} + +// GetGossipableIPs returns the latest IPs of connected validators. The returned +// IPs will not contain [exceptNodeID] or any IPs contained in [exceptIPs]. If +// the number of eligible IPs to return low, it's possible that every IP will be +// iterated over while handling this call. +func (i *ipTracker) GetGossipableIPs( + exceptNodeID ids.NodeID, + exceptIPs *bloom.ReadFilter, + salt []byte, + maxNumIPs int, +) []*ips.ClaimedIPPort { + var ( + uniform = sampler.NewUniform() + ips = make([]*ips.ClaimedIPPort, 0, maxNumIPs) + ) + + i.lock.RLock() + defer i.lock.RUnlock() + + uniform.Initialize(uint64(len(i.gossipableIPs))) + for len(ips) < maxNumIPs { + index, err := uniform.Next() + if err != nil { + return ips + } + + ip := i.gossipableIPs[index] + if ip.NodeID == exceptNodeID { + continue + } + + if !bloom.Contains(exceptIPs, ip.GossipID[:], salt) { + ips = append(ips, ip) + } + } + return ips +} + +// ResetBloom prunes the current bloom filter. This must be called periodically +// to ensure that validators that change their IPs are updated correctly and +// that validators that left the validator set are removed. +func (i *ipTracker) ResetBloom() error { + i.lock.Lock() + defer i.lock.Unlock() + + return i.resetBloom() +} + +// Bloom returns the binary representation of the bloom filter along with the +// random salt. +func (i *ipTracker) Bloom() ([]byte, []byte) { + i.lock.RLock() + defer i.lock.RUnlock() + + return i.bloom.Marshal(), i.bloomSalt +} + +// resetBloom creates a new bloom filter with a reasonable size for the current +// validator set size. This function additionally populates the new bloom filter +// with the current most recently known IPs of validators. +func (i *ipTracker) resetBloom() error { + newSalt := make([]byte, saltSize) + _, err := rand.Reader.Read(newSalt) + if err != nil { + return err + } + + count := math.Max(maxIPEntriesPerValidator*i.validators.Len(), minCountEstimate) + numHashes, numEntries := bloom.OptimalParameters( + count, + targetFalsePositiveProbability, + ) + newFilter, err := bloom.New(numHashes, numEntries) + if err != nil { + return err + } + + i.bloom = newFilter + maps.Clear(i.bloomAdditions) + i.bloomSalt = newSalt + i.maxBloomCount = bloom.EstimateCount(numHashes, numEntries, maxFalsePositiveProbability) + + for nodeID, ip := range i.mostRecentValidatorIPs { + bloom.Add(newFilter, ip.GossipID[:], newSalt) + i.bloomAdditions[nodeID] = 1 + } + i.bloomCount.Set(float64(len(i.mostRecentValidatorIPs))) + i.bloomNumHashes.Set(float64(numHashes)) + i.bloomNumEntries.Set(float64(numEntries)) + i.bloomMaxCount.Set(float64(i.maxBloomCount)) + i.bloomResetCount.Inc() + return nil +} diff --git a/network/ip_tracker_test.go b/network/ip_tracker_test.go new file mode 100644 index 000000000000..921f2f33d655 --- /dev/null +++ b/network/ip_tracker_test.go @@ -0,0 +1,711 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/bloom" + "github.com/ava-labs/avalanchego/utils/ips" + "github.com/ava-labs/avalanchego/utils/logging" +) + +func newTestIPTracker(t *testing.T) *ipTracker { + tracker, err := newIPTracker(logging.NoLog{}, "", prometheus.NewRegistry()) + require.NoError(t, err) + return tracker +} + +func newerTestIP(ip *ips.ClaimedIPPort) *ips.ClaimedIPPort { + return ips.NewClaimedIPPort( + ip.Cert, + ip.IPPort, + ip.Timestamp+1, + ip.Signature, + ) +} + +func requireEqual(t *testing.T, expected, actual *ipTracker) { + require := require.New(t) + require.Equal(expected.manuallyTracked, actual.manuallyTracked) + require.Equal(expected.connected, actual.connected) + require.Equal(expected.mostRecentValidatorIPs, actual.mostRecentValidatorIPs) + require.Equal(expected.validators, actual.validators) + require.Equal(expected.gossipableIndicies, actual.gossipableIndicies) + require.Equal(expected.gossipableIPs, actual.gossipableIPs) + require.Equal(expected.bloomAdditions, actual.bloomAdditions) + require.Equal(expected.maxBloomCount, actual.maxBloomCount) +} + +func requireMetricsConsistent(t *testing.T, tracker *ipTracker) { + require := require.New(t) + require.Equal(float64(len(tracker.mostRecentValidatorIPs)), testutil.ToFloat64(tracker.numValidatorIPs)) + require.Equal(float64(len(tracker.gossipableIPs)), testutil.ToFloat64(tracker.numGossipable)) + require.Equal(float64(tracker.bloom.Count()), testutil.ToFloat64(tracker.bloomCount)) + require.Equal(float64(tracker.maxBloomCount), testutil.ToFloat64(tracker.bloomMaxCount)) +} + +func TestIPTracker_ManuallyTrack(t *testing.T) { + tests := []struct { + name string + initialState *ipTracker + nodeID ids.NodeID + expectedState *ipTracker + }{ + { + name: "non-connected non-validator", + initialState: newTestIPTracker(t), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.validators.Add(ip.NodeID) + tracker.manuallyTracked.Add(ip.NodeID) + return tracker + }(), + }, + { + name: "connected non-validator", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.Connected(ip) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.Connected(ip) + tracker.mostRecentValidatorIPs[ip.NodeID] = ip + tracker.bloomAdditions[ip.NodeID] = 1 + tracker.gossipableIndicies[ip.NodeID] = 0 + tracker.gossipableIPs = []*ips.ClaimedIPPort{ + ip, + } + tracker.validators.Add(ip.NodeID) + tracker.manuallyTracked.Add(ip.NodeID) + return tracker + }(), + }, + { + name: "non-connected validator", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.manuallyTracked.Add(ip.NodeID) + return tracker + }(), + }, + { + name: "connected validator", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.Connected(ip) + tracker.onValidatorAdded(ip.NodeID) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.Connected(ip) + tracker.onValidatorAdded(ip.NodeID) + tracker.manuallyTracked.Add(ip.NodeID) + return tracker + }(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.initialState.ManuallyTrack(test.nodeID) + requireEqual(t, test.expectedState, test.initialState) + requireMetricsConsistent(t, test.initialState) + }) + } +} + +func TestIPTracker_AddIP(t *testing.T) { + newerIP := newerTestIP(ip) + tests := []struct { + name string + initialState *ipTracker + ip *ips.ClaimedIPPort + expectedUpdated bool + expectedState *ipTracker + }{ + { + name: "non-validator", + initialState: newTestIPTracker(t), + ip: ip, + expectedUpdated: false, + expectedState: newTestIPTracker(t), + }, + { + name: "first known IP", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + return tracker + }(), + ip: ip, + expectedUpdated: true, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.mostRecentValidatorIPs[ip.NodeID] = ip + tracker.bloomAdditions[ip.NodeID] = 1 + return tracker + }(), + }, + { + name: "older IP", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(newerIP.NodeID) + require.True(t, tracker.AddIP(newerIP)) + return tracker + }(), + ip: ip, + expectedUpdated: false, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(newerIP.NodeID) + require.True(t, tracker.AddIP(newerIP)) + return tracker + }(), + }, + { + name: "same IP", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + return tracker + }(), + ip: ip, + expectedUpdated: false, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + return tracker + }(), + }, + { + name: "disconnected newer IP", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + return tracker + }(), + ip: newerIP, + expectedUpdated: true, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + tracker.mostRecentValidatorIPs[newerIP.NodeID] = newerIP + tracker.bloomAdditions[newerIP.NodeID] = 2 + return tracker + }(), + }, + { + name: "connected newer IP", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + return tracker + }(), + ip: newerIP, + expectedUpdated: true, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + tracker.mostRecentValidatorIPs[newerIP.NodeID] = newerIP + tracker.bloomAdditions[newerIP.NodeID] = 2 + delete(tracker.gossipableIndicies, newerIP.NodeID) + tracker.gossipableIPs = tracker.gossipableIPs[:0] + return tracker + }(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + updated := test.initialState.AddIP(test.ip) + require.Equal(t, test.expectedUpdated, updated) + requireEqual(t, test.expectedState, test.initialState) + requireMetricsConsistent(t, test.initialState) + }) + } +} + +func TestIPTracker_Connected(t *testing.T) { + newerIP := newerTestIP(ip) + tests := []struct { + name string + initialState *ipTracker + ip *ips.ClaimedIPPort + expectedState *ipTracker + }{ + { + name: "non-validator", + initialState: newTestIPTracker(t), + ip: ip, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.connected[ip.NodeID] = ip + return tracker + }(), + }, + { + name: "first known IP", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + return tracker + }(), + ip: ip, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.connected[ip.NodeID] = ip + tracker.mostRecentValidatorIPs[ip.NodeID] = ip + tracker.bloomAdditions[ip.NodeID] = 1 + tracker.gossipableIndicies[ip.NodeID] = 0 + tracker.gossipableIPs = []*ips.ClaimedIPPort{ + ip, + } + return tracker + }(), + }, + { + name: "connected with older IP", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(newerIP.NodeID) + require.True(t, tracker.AddIP(newerIP)) + return tracker + }(), + ip: ip, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(newerIP.NodeID) + require.True(t, tracker.AddIP(newerIP)) + tracker.connected[ip.NodeID] = ip + return tracker + }(), + }, + { + name: "connected with newer IP", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + return tracker + }(), + ip: newerIP, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + tracker.connected[newerIP.NodeID] = newerIP + tracker.mostRecentValidatorIPs[newerIP.NodeID] = newerIP + tracker.bloomAdditions[newerIP.NodeID] = 2 + tracker.gossipableIndicies[newerIP.NodeID] = 0 + tracker.gossipableIPs = []*ips.ClaimedIPPort{ + newerIP, + } + return tracker + }(), + }, + { + name: "connected with same IP", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + return tracker + }(), + ip: ip, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + tracker.connected[ip.NodeID] = ip + tracker.gossipableIndicies[ip.NodeID] = 0 + tracker.gossipableIPs = []*ips.ClaimedIPPort{ + ip, + } + return tracker + }(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.initialState.Connected(test.ip) + requireEqual(t, test.expectedState, test.initialState) + requireMetricsConsistent(t, test.initialState) + }) + } +} + +func TestIPTracker_Disconnected(t *testing.T) { + tests := []struct { + name string + initialState *ipTracker + nodeID ids.NodeID + expectedState *ipTracker + }{ + { + name: "not gossipable", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.Connected(ip) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: newTestIPTracker(t), + }, + { + name: "latest gossipable", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + delete(tracker.connected, ip.NodeID) + delete(tracker.gossipableIndicies, ip.NodeID) + tracker.gossipableIPs = tracker.gossipableIPs[:0] + return tracker + }(), + }, + { + name: "non-latest gossipable", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + tracker.onValidatorAdded(otherIP.NodeID) + tracker.Connected(otherIP) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + tracker.onValidatorAdded(otherIP.NodeID) + tracker.Connected(otherIP) + delete(tracker.connected, ip.NodeID) + tracker.gossipableIndicies = map[ids.NodeID]int{ + otherIP.NodeID: 0, + } + tracker.gossipableIPs = []*ips.ClaimedIPPort{ + otherIP, + } + return tracker + }(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.initialState.Disconnected(test.nodeID) + requireEqual(t, test.expectedState, test.initialState) + requireMetricsConsistent(t, test.initialState) + }) + } +} + +func TestIPTracker_OnValidatorAdded(t *testing.T) { + tests := []struct { + name string + initialState *ipTracker + nodeID ids.NodeID + expectedState *ipTracker + }{ + { + name: "manually tracked", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.ManuallyTrack(ip.NodeID) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.ManuallyTrack(ip.NodeID) + return tracker + }(), + }, + { + name: "disconnected", + initialState: newTestIPTracker(t), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.validators.Add(ip.NodeID) + return tracker + }(), + }, + { + name: "connected", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.Connected(ip) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.Connected(ip) + tracker.validators.Add(ip.NodeID) + tracker.mostRecentValidatorIPs[ip.NodeID] = ip + tracker.bloomAdditions[ip.NodeID] = 1 + tracker.gossipableIndicies[ip.NodeID] = 0 + tracker.gossipableIPs = []*ips.ClaimedIPPort{ + ip, + } + return tracker + }(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.initialState.OnValidatorAdded(test.nodeID, nil, ids.Empty, 0) + requireEqual(t, test.expectedState, test.initialState) + requireMetricsConsistent(t, test.initialState) + }) + } +} + +func TestIPTracker_OnValidatorRemoved(t *testing.T) { + tests := []struct { + name string + initialState *ipTracker + nodeID ids.NodeID + expectedState *ipTracker + }{ + { + name: "manually tracked", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.ManuallyTrack(ip.NodeID) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.ManuallyTrack(ip.NodeID) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + return tracker + }(), + }, + { + name: "not gossipable", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(t, tracker.AddIP(ip)) + delete(tracker.mostRecentValidatorIPs, ip.NodeID) + tracker.validators.Remove(ip.NodeID) + return tracker + }(), + }, + { + name: "latest gossipable", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + delete(tracker.mostRecentValidatorIPs, ip.NodeID) + tracker.validators.Remove(ip.NodeID) + delete(tracker.gossipableIndicies, ip.NodeID) + tracker.gossipableIPs = tracker.gossipableIPs[:0] + return tracker + }(), + }, + { + name: "non-latest gossipable", + initialState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + tracker.onValidatorAdded(otherIP.NodeID) + tracker.Connected(otherIP) + return tracker + }(), + nodeID: ip.NodeID, + expectedState: func() *ipTracker { + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + tracker.Connected(ip) + tracker.onValidatorAdded(otherIP.NodeID) + tracker.Connected(otherIP) + delete(tracker.mostRecentValidatorIPs, ip.NodeID) + tracker.validators.Remove(ip.NodeID) + tracker.gossipableIndicies = map[ids.NodeID]int{ + otherIP.NodeID: 0, + } + tracker.gossipableIPs = []*ips.ClaimedIPPort{ + otherIP, + } + return tracker + }(), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.initialState.OnValidatorRemoved(test.nodeID, 0) + requireEqual(t, test.expectedState, test.initialState) + requireMetricsConsistent(t, test.initialState) + }) + } +} + +func TestIPTracker_GetGossipableIPs(t *testing.T) { + require := require.New(t) + + tracker := newTestIPTracker(t) + tracker.Connected(ip) + tracker.Connected(otherIP) + tracker.onValidatorAdded(ip.NodeID) + tracker.onValidatorAdded(otherIP.NodeID) + + gossipableIPs := tracker.GetGossipableIPs(ids.EmptyNodeID, bloom.EmptyFilter, nil, 2) + require.ElementsMatch([]*ips.ClaimedIPPort{ip, otherIP}, gossipableIPs) + + gossipableIPs = tracker.GetGossipableIPs(ip.NodeID, bloom.EmptyFilter, nil, 2) + require.Equal([]*ips.ClaimedIPPort{otherIP}, gossipableIPs) + + gossipableIPs = tracker.GetGossipableIPs(ids.EmptyNodeID, bloom.FullFilter, nil, 2) + require.Empty(gossipableIPs) + + filter, err := bloom.New(8, 1024) + require.NoError(err) + bloom.Add(filter, ip.GossipID[:], nil) + + readFilter, err := bloom.Parse(filter.Marshal()) + require.NoError(err) + + gossipableIPs = tracker.GetGossipableIPs(ip.NodeID, readFilter, nil, 2) + require.Equal([]*ips.ClaimedIPPort{otherIP}, gossipableIPs) +} + +func TestIPTracker_BloomFiltersEverything(t *testing.T) { + require := require.New(t) + + tracker := newTestIPTracker(t) + tracker.Connected(ip) + tracker.Connected(otherIP) + tracker.onValidatorAdded(ip.NodeID) + tracker.onValidatorAdded(otherIP.NodeID) + + bloomBytes, salt := tracker.Bloom() + readFilter, err := bloom.Parse(bloomBytes) + require.NoError(err) + + gossipableIPs := tracker.GetGossipableIPs(ids.EmptyNodeID, readFilter, salt, 2) + require.Empty(gossipableIPs) + + require.NoError(tracker.ResetBloom()) +} + +func TestIPTracker_BloomGrowsWithValidatorSet(t *testing.T) { + require := require.New(t) + + tracker := newTestIPTracker(t) + initialMaxBloomCount := tracker.maxBloomCount + for i := 0; i < 2048; i++ { + tracker.onValidatorAdded(ids.GenerateTestNodeID()) + } + requireMetricsConsistent(t, tracker) + + require.NoError(tracker.ResetBloom()) + require.Greater(tracker.maxBloomCount, initialMaxBloomCount) + requireMetricsConsistent(t, tracker) +} + +func TestIPTracker_BloomResetsDynamically(t *testing.T) { + require := require.New(t) + + tracker := newTestIPTracker(t) + tracker.Connected(ip) + tracker.onValidatorAdded(ip.NodeID) + tracker.OnValidatorRemoved(ip.NodeID, 0) + tracker.maxBloomCount = 1 + tracker.Connected(otherIP) + tracker.onValidatorAdded(otherIP.NodeID) + requireMetricsConsistent(t, tracker) + + bloomBytes, salt := tracker.Bloom() + readFilter, err := bloom.Parse(bloomBytes) + require.NoError(err) + + require.False(bloom.Contains(readFilter, ip.GossipID[:], salt)) + require.True(bloom.Contains(readFilter, otherIP.GossipID[:], salt)) +} + +func TestIPTracker_PreventBloomFilterAddition(t *testing.T) { + require := require.New(t) + + newerIP := newerTestIP(ip) + newestIP := newerTestIP(newerIP) + + tracker := newTestIPTracker(t) + tracker.onValidatorAdded(ip.NodeID) + require.True(tracker.AddIP(ip)) + require.True(tracker.AddIP(newerIP)) + require.True(tracker.AddIP(newestIP)) + require.Equal(maxIPEntriesPerValidator, tracker.bloomAdditions[ip.NodeID]) + requireMetricsConsistent(t, tracker) +} + +func TestIPTracker_ShouldVerifyIP(t *testing.T) { + require := require.New(t) + + newerIP := newerTestIP(ip) + + tracker := newTestIPTracker(t) + require.False(tracker.ShouldVerifyIP(ip)) + tracker.onValidatorAdded(ip.NodeID) + require.True(tracker.ShouldVerifyIP(ip)) + require.True(tracker.AddIP(ip)) + require.False(tracker.ShouldVerifyIP(ip)) + require.True(tracker.ShouldVerifyIP(newerIP)) +} diff --git a/network/listener_test.go b/network/listener_test.go index 1b15b0062536..5d6073c6b383 100644 --- a/network/listener_test.go +++ b/network/listener_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network diff --git a/network/metrics.go b/network/metrics.go index 3e566a31c99f..e2a3a363b403 100644 --- a/network/metrics.go +++ b/network/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network diff --git a/network/network.go b/network/network.go index 3f89e0ea00ef..1e13ed45b9c2 100644 --- a/network/network.go +++ b/network/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -20,22 +20,20 @@ import ( "go.uber.org/zap" - "golang.org/x/exp/maps" - "github.com/ava-labs/avalanchego/api/health" + "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/message" "github.com/ava-labs/avalanchego/network/dialer" "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/network/throttling" - "github.com/ava-labs/avalanchego/proto/pb/p2p" "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/snow/networking/sender" "github.com/ava-labs/avalanchego/subnets" + "github.com/ava-labs/avalanchego/utils/bloom" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/sampler" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/version" @@ -79,12 +77,6 @@ type Network interface { // or the network is closed. Dispatch() error - // WantsConnection returns true if this node is willing to attempt to - // connect to the provided nodeID. If the node is attempting to connect to - // the minimum number of peers, then it should only connect if the peer is a - // validator or beacon. - WantsConnection(ids.NodeID) bool - // Attempt to connect to this IP. The network will never stop attempting to // connect to this ID. ManuallyTrack(nodeID ids.NodeID, ip ips.IPPort) @@ -146,18 +138,13 @@ type network struct { // Cancelled on close onCloseCtx context.Context // Call [onCloseCtxCancel] to cancel [onCloseCtx] during close() - onCloseCtxCancel func() + onCloseCtxCancel context.CancelFunc sendFailRateCalculator safemath.Averager // Tracks which peers know about which peers - gossipTracker peer.GossipTracker - peersLock sync.RWMutex - // peerIPs contains the most up to date set of signed IPs for nodes we are - // currently connected or attempting to connect to. - // Note: The txID provided inside of a claimed IP is not verified and should - // not be accessed from this map. - peerIPs map[ids.NodeID]*ips.ClaimedIPPort + ipTracker *ipTracker + peersLock sync.RWMutex // trackedIPs contains the set of IPs that we are currently attempting to // connect to. An entry is added to this set when we first start attempting // to connect to the peer. An entry is deleted from this set once we have @@ -167,10 +154,6 @@ type network struct { connectedPeers peer.Set closing bool - // Tracks special peers that the network should always track - manuallyTrackedIDsLock sync.RWMutex - manuallyTrackedIDs set.Set[ids.NodeID] - // router is notified about all peer [Connected] and [Disconnected] events // as well as all non-handshake peer messages. // @@ -254,6 +237,18 @@ func NewNetwork( return nil, fmt.Errorf("initializing network metrics failed with: %w", err) } + ipTracker, err := newIPTracker(log, config.Namespace, metricsRegisterer) + if err != nil { + return nil, fmt.Errorf("initializing ip tracker failed with: %w", err) + } + config.Validators.RegisterCallbackListener(constants.PrimaryNetworkID, ipTracker) + + // Track all default bootstrappers to ensure their current IPs are gossiped + // like validator IPs. + for _, bootstrapper := range genesis.GetBootstrappers(config.NetworkID) { + ipTracker.ManuallyTrack(bootstrapper.ID) + } + peerConfig := &peer.Config{ ReadBufferSize: config.PeerReadBufferSize, WriteBufferSize: config.PeerWriteBufferSize, @@ -271,11 +266,19 @@ func NewNetwork( PingFrequency: config.PingFrequency, PongTimeout: config.PingPongTimeout, MaxClockDifference: config.MaxClockDifference, + SupportedACPs: config.SupportedACPs.List(), + ObjectedACPs: config.ObjectedACPs.List(), ResourceTracker: config.ResourceTracker, UptimeCalculator: config.UptimeCalculator, IPSigner: peer.NewIPSigner(config.MyIPPort, config.TLSKey), } + // Invariant: We delay the activation of durango during the TLS handshake to + // avoid gossiping any TLS certs that anyone else in the network may + // consider invalid. Recall that if a peer gossips an invalid cert, the + // connection is terminated. + durangoTime := version.GetDurangoTime(config.NetworkID) + durangoTimeWithClockSkew := durangoTime.Add(config.MaxClockDifference) onCloseCtx, cancel := context.WithCancel(context.Background()) n := &network{ config: config, @@ -286,8 +289,8 @@ func NewNetwork( inboundConnUpgradeThrottler: throttling.NewInboundConnUpgradeThrottler(log, config.ThrottlerConfig.InboundConnUpgradeThrottlerConfig), listener: listener, dialer: dialer, - serverUpgrader: peer.NewTLSServerUpgrader(config.TLSConfig, metrics.tlsConnRejected), - clientUpgrader: peer.NewTLSClientUpgrader(config.TLSConfig, metrics.tlsConnRejected), + serverUpgrader: peer.NewTLSServerUpgrader(config.TLSConfig, metrics.tlsConnRejected, durangoTimeWithClockSkew), + clientUpgrader: peer.NewTLSClientUpgrader(config.TLSConfig, metrics.tlsConnRejected, durangoTimeWithClockSkew), onCloseCtx: onCloseCtx, onCloseCtxCancel: cancel, @@ -298,9 +301,8 @@ func NewNetwork( time.Now(), )), - peerIPs: make(map[ids.NodeID]*ips.ClaimedIPPort), trackedIPs: make(map[ids.NodeID]*trackedIP), - gossipTracker: config.GossipTracker, + ipTracker: ipTracker, connectingPeers: peer.NewSet(), connectedPeers: peer.NewSet(), router: router, @@ -422,32 +424,6 @@ func (n *network) Connected(nodeID ids.NodeID) { return } - peerIP := peer.IP() - newIP := &ips.ClaimedIPPort{ - Cert: peer.Cert(), - IPPort: peerIP.IPPort, - Timestamp: peerIP.Timestamp, - Signature: peerIP.Signature, - } - prevIP, ok := n.peerIPs[nodeID] - if !ok { - // If the IP wasn't previously tracked, then we never could have - // gossiped it. This means we don't need to reset the validator's - // tracked set. - n.peerIPs[nodeID] = newIP - } else if prevIP.Timestamp < newIP.Timestamp { - // The previous IP was stale, so we should gossip the newer IP. - n.peerIPs[nodeID] = newIP - - if !prevIP.IPPort.Equal(newIP.IPPort) { - // This IP is actually different, so we should gossip it. - n.peerConfig.Log.Debug("resetting gossip due to ip change", - zap.Stringer("nodeID", nodeID), - ) - _ = n.gossipTracker.ResetValidator(nodeID) - } - } - if tracked, ok := n.trackedIPs[nodeID]; ok { tracked.stopTracking() delete(n.trackedIPs, nodeID) @@ -456,6 +432,15 @@ func (n *network) Connected(nodeID ids.NodeID) { n.connectedPeers.Add(peer) n.peersLock.Unlock() + peerIP := peer.IP() + newIP := ips.NewClaimedIPPort( + peer.Cert(), + peerIP.IPPort, + peerIP.Timestamp, + peerIP.Signature, + ) + n.ipTracker.Connected(newIP) + n.metrics.markConnected(peer) peerVersion := peer.Version() @@ -473,177 +458,15 @@ func (n *network) AllowConnection(nodeID ids.NodeID) bool { if !n.config.RequireValidatorToConnect { return true } - _, isValidator := n.config.Validators.GetValidator(constants.PrimaryNetworkID, n.config.MyNodeID) - return isValidator || n.WantsConnection(nodeID) -} - -func (n *network) Track(peerID ids.NodeID, claimedIPPorts []*ips.ClaimedIPPort) ([]*p2p.PeerAck, error) { - // Perform all signature verification and hashing before grabbing the peer - // lock. - // Note: Avoiding signature verification when the IP isn't needed is a - // **significant** performance optimization. - // Note: To avoid signature verification when the IP isn't needed, we - // optimistically filter out IPs. This can result in us not tracking an IP - // that we otherwise would have. This case can only happen if the node - // became a validator between the time we verified the signature and when we - // processed the IP; which should be very rare. - ipAuths, err := n.authenticateIPs(claimedIPPorts) - if err != nil { - n.peerConfig.Log.Debug("authenticating claimed IPs failed", - zap.Stringer("nodeID", peerID), - zap.Error(err), - ) - return nil, err - } - - // Information for them to update about us - ipLen := len(claimedIPPorts) - newestTimestamp := make(map[ids.ID]uint64, ipLen) - // Information for us to update about them - txIDsWithUpToDateIP := make([]ids.ID, 0, ipLen) - - // Atomically modify peer data - n.peersLock.Lock() - defer n.peersLock.Unlock() - for i, ip := range claimedIPPorts { - ipAuth := ipAuths[i] - nodeID := ipAuth.nodeID - // Invariant: [ip] is only used to modify local node state if - // [verifiedIP] is true. - // Note: modifying peer-level state is allowed regardless of - // [verifiedIP]. - verifiedIP := ipAuth.verified - - // Re-fetch latest info for a [nodeID] in case it changed since we last - // held [peersLock]. - prevIP, previouslyTracked, shouldUpdateOurIP, shouldDial := n.peerIPStatus(nodeID, ip) - tracked, isTracked := n.trackedIPs[nodeID] - - // Evaluate if the gossiped IP is useful to us or to the peer that - // shared it with us. - switch { - case previouslyTracked && prevIP.Timestamp > ip.Timestamp: - // Our previous IP was more up to date. We should tell the peer - // not to gossip their IP to us. We should still gossip our IP to - // them. - newestTimestamp[ip.TxID] = prevIP.Timestamp - - n.metrics.numUselessPeerListBytes.Add(float64(ip.BytesLen())) - case previouslyTracked && prevIP.Timestamp == ip.Timestamp: - // Our previous IP was equally fresh. We should tell the peer - // not to gossip this IP to us. We should not gossip our IP to them. - newestTimestamp[ip.TxID] = prevIP.Timestamp - txIDsWithUpToDateIP = append(txIDsWithUpToDateIP, ip.TxID) - - n.metrics.numUselessPeerListBytes.Add(float64(ip.BytesLen())) - case verifiedIP && shouldUpdateOurIP: - // This IP is more up to date. We should tell the peer not to gossip - // this IP to us. We should not gossip our IP to them. - newestTimestamp[ip.TxID] = ip.Timestamp - txIDsWithUpToDateIP = append(txIDsWithUpToDateIP, ip.TxID) - - // In the future, we should gossip this IP rather than the old IP. - n.peerIPs[nodeID] = ip - - // If the new IP is equal to the old IP, there is no reason to - // refresh the references to it. This can happen when a node - // restarts but does not change their IP. - if prevIP.IPPort.Equal(ip.IPPort) { - continue - } - - // We should gossip this new IP to all our peers. - n.peerConfig.Log.Debug("resetting gossip due to ip change", - zap.Stringer("nodeID", nodeID), - ) - _ = n.gossipTracker.ResetValidator(nodeID) - - // We should update any existing outbound connection attempts. - if isTracked { - // Stop tracking the old IP and start tracking the new one. - tracked := tracked.trackNewIP(ip.IPPort) - n.trackedIPs[nodeID] = tracked - n.dial(nodeID, tracked) - } - case verifiedIP && shouldDial: - // Invariant: [isTracked] is false here. - - // This is the first we've heard of this IP and we want to connect - // to it. We should tell the peer not to gossip this IP to us again. - newestTimestamp[ip.TxID] = ip.Timestamp - // We should not gossip this IP back to them. - txIDsWithUpToDateIP = append(txIDsWithUpToDateIP, ip.TxID) - - // We don't need to reset gossip about this validator because - // we've never gossiped it before. - n.peerIPs[nodeID] = ip - - tracked := newTrackedIP(ip.IPPort) - n.trackedIPs[nodeID] = tracked - n.dial(nodeID, tracked) - default: - // This IP isn't desired - n.metrics.numUselessPeerListBytes.Add(float64(ip.BytesLen())) - } - } - - txIDsToAck := maps.Keys(newestTimestamp) - txIDsToAck, ok := n.gossipTracker.AddKnown(peerID, txIDsWithUpToDateIP, txIDsToAck) - if !ok { - n.peerConfig.Log.Error("failed to update known peers", - zap.Stringer("nodeID", peerID), - ) - return nil, nil - } - - peerAcks := make([]*p2p.PeerAck, len(txIDsToAck)) - for i, txID := range txIDsToAck { - txID := txID - peerAcks[i] = &p2p.PeerAck{ - TxId: txID[:], - // By responding with the highest timestamp, not just the timestamp - // the peer provided us, we may be able to avoid some unnecessary - // gossip in the case that the peer is about to update this - // validator's IP. - Timestamp: newestTimestamp[txID], - } - } - return peerAcks, nil + _, iAmAValidator := n.config.Validators.GetValidator(constants.PrimaryNetworkID, n.config.MyNodeID) + return iAmAValidator || n.ipTracker.WantsConnection(nodeID) } -func (n *network) MarkTracked(peerID ids.NodeID, ips []*p2p.PeerAck) error { - txIDs := make([]ids.ID, 0, len(ips)) - - n.peersLock.RLock() - defer n.peersLock.RUnlock() - - for _, ip := range ips { - txID, err := ids.ToID(ip.TxId) - if err != nil { +func (n *network) Track(claimedIPPorts []*ips.ClaimedIPPort) error { + for _, ip := range claimedIPPorts { + if err := n.track(ip); err != nil { return err } - - // If [txID]'s corresponding nodeID isn't known, then they must no - // longer be a validator. Therefore we wouldn't gossip their IP anyways. - nodeID, ok := n.gossipTracker.GetNodeID(txID) - if !ok { - continue - } - - // If the peer returns a lower timestamp than I currently have, then I - // have updated the IP since I sent the PeerList message this is in - // response to. That means that I should re-gossip this node's IP to the - // peer. - myIP, previouslyTracked := n.peerIPs[nodeID] - if previouslyTracked && myIP.Timestamp <= ip.Timestamp { - txIDs = append(txIDs, txID) - } - } - - if _, ok := n.gossipTracker.AddKnown(peerID, txIDs, nil); !ok { - n.peerConfig.Log.Error("failed to update known peers", - zap.Stringer("nodeID", peerID), - ) } return nil } @@ -654,13 +477,6 @@ func (n *network) MarkTracked(peerID ids.NodeID, ips []*p2p.PeerAck) error { // call. Note that this is from the perspective of a single peer object, because // a peer with the same ID can reconnect to this network instance. func (n *network) Disconnected(nodeID ids.NodeID) { - if !n.gossipTracker.StopTrackingPeer(nodeID) { - n.peerConfig.Log.Error( - "stopped non-existent peer tracker", - zap.Stringer("nodeID", nodeID), - ) - } - n.peersLock.RLock() _, connecting := n.connectingPeers.GetByID(nodeID) peer, connected := n.connectedPeers.GetByID(nodeID) @@ -674,57 +490,17 @@ func (n *network) Disconnected(nodeID ids.NodeID) { } } -func (n *network) Peers(peerID ids.NodeID) ([]ips.ClaimedIPPort, error) { - // Only select validators that we haven't already sent to this peer - unknownValidators, ok := n.gossipTracker.GetUnknown(peerID) - if !ok { - n.peerConfig.Log.Debug( - "unable to find peer to gossip to", - zap.Stringer("nodeID", peerID), - ) - return nil, nil - } - - // We select a random sample of validators to gossip to avoid starving out a - // validator from being gossiped for an extended period of time. - s := sampler.NewUniform() - s.Initialize(uint64(len(unknownValidators))) - - // Calculate the unknown information we need to send to this peer. - validatorIPs := make([]ips.ClaimedIPPort, 0, int(n.config.PeerListNumValidatorIPs)) - for i := 0; i < len(unknownValidators) && len(validatorIPs) < int(n.config.PeerListNumValidatorIPs); i++ { - drawn, err := s.Next() - if err != nil { - return nil, err - } - - validator := unknownValidators[drawn] - n.peersLock.RLock() - _, isConnected := n.connectedPeers.GetByID(validator.NodeID) - peerIP := n.peerIPs[validator.NodeID] - n.peersLock.RUnlock() - if !isConnected { - n.peerConfig.Log.Verbo( - "unable to find validator in connected peers", - zap.Stringer("nodeID", validator.NodeID), - ) - continue - } - - // Note: peerIP isn't used directly here because the TxID may be - // incorrect. - validatorIPs = append(validatorIPs, - ips.ClaimedIPPort{ - Cert: peerIP.Cert, - IPPort: peerIP.IPPort, - Timestamp: peerIP.Timestamp, - Signature: peerIP.Signature, - TxID: validator.TxID, - }, - ) - } +func (n *network) KnownPeers() ([]byte, []byte) { + return n.ipTracker.Bloom() +} - return validatorIPs, nil +func (n *network) Peers(except ids.NodeID, knownPeers *bloom.ReadFilter, salt []byte) []*ips.ClaimedIPPort { + return n.ipTracker.GetGossipableIPs( + except, + knownPeers, + salt, + int(n.config.PeerListNumValidatorIPs), + ) } // Dispatch starts accepting connections from other nodes attempting to connect @@ -804,21 +580,8 @@ func (n *network) Dispatch() error { return errs.Err } -func (n *network) WantsConnection(nodeID ids.NodeID) bool { - if _, ok := n.config.Validators.GetValidator(constants.PrimaryNetworkID, nodeID); ok { - return true - } - - n.manuallyTrackedIDsLock.RLock() - defer n.manuallyTrackedIDsLock.RUnlock() - - return n.manuallyTrackedIDs.Contains(nodeID) -} - func (n *network) ManuallyTrack(nodeID ids.NodeID, ip ips.IPPort) { - n.manuallyTrackedIDsLock.Lock() - n.manuallyTrackedIDs.Add(nodeID) - n.manuallyTrackedIDsLock.Unlock() + n.ipTracker.ManuallyTrack(nodeID) n.peersLock.Lock() defer n.peersLock.Unlock() @@ -839,6 +602,58 @@ func (n *network) ManuallyTrack(nodeID ids.NodeID, ip ips.IPPort) { } } +func (n *network) track(ip *ips.ClaimedIPPort) error { + // To avoid signature verification when the IP isn't needed, we + // optimistically filter out IPs. This can result in us not tracking an IP + // that we otherwise would have. This case can only happen if the node + // became a validator between the time we verified the signature and when we + // processed the IP; which should be very rare. + // + // Note: Avoiding signature verification when the IP isn't needed is a + // **significant** performance optimization. + if !n.ipTracker.ShouldVerifyIP(ip) { + n.metrics.numUselessPeerListBytes.Add(float64(ip.Size())) + return nil + } + + // Perform all signature verification and hashing before grabbing the peer + // lock. + signedIP := peer.SignedIP{ + UnsignedIP: peer.UnsignedIP{ + IPPort: ip.IPPort, + Timestamp: ip.Timestamp, + }, + Signature: ip.Signature, + } + if err := signedIP.Verify(ip.Cert); err != nil { + return err + } + + n.peersLock.Lock() + defer n.peersLock.Unlock() + + if !n.ipTracker.AddIP(ip) { + return nil + } + + if _, connected := n.connectedPeers.GetByID(ip.NodeID); connected { + // If I'm currently connected to [nodeID] then I'll attempt to dial them + // when we disconnect. + return nil + } + + tracked, isTracked := n.trackedIPs[ip.NodeID] + if isTracked { + // Stop tracking the old IP and start tracking the new one. + tracked = tracked.trackNewIP(ip.IPPort) + } else { + tracked = newTrackedIP(ip.IPPort) + } + n.trackedIPs[ip.NodeID] = tracked + n.dial(ip.NodeID, tracked) + return nil +} + // getPeers returns a slice of connected peers from a set of [nodeIDs]. // // - [nodeIDs] the IDs of the peers that should be returned if they are @@ -963,13 +778,12 @@ func (n *network) disconnectedFromConnecting(nodeID ids.NodeID) { // The peer that is disconnecting from us didn't finish the handshake tracked, ok := n.trackedIPs[nodeID] if ok { - if n.WantsConnection(nodeID) { + if n.ipTracker.WantsConnection(nodeID) { tracked := tracked.trackNewIP(tracked.ip) n.trackedIPs[nodeID] = tracked n.dial(nodeID, tracked) } else { tracked.stopTracking() - delete(n.peerIPs, nodeID) delete(n.trackedIPs, nodeID) } } @@ -978,6 +792,7 @@ func (n *network) disconnectedFromConnecting(nodeID ids.NodeID) { } func (n *network) disconnectedFromConnected(peer peer.Peer, nodeID ids.NodeID) { + n.ipTracker.Disconnected(nodeID) n.router.Disconnected(nodeID) n.peersLock.Lock() @@ -986,66 +801,15 @@ func (n *network) disconnectedFromConnected(peer peer.Peer, nodeID ids.NodeID) { n.connectedPeers.Remove(nodeID) // The peer that is disconnecting from us finished the handshake - if n.WantsConnection(nodeID) { - prevIP := n.peerIPs[nodeID] - tracked := newTrackedIP(prevIP.IPPort) + if ip, wantsConnection := n.ipTracker.GetIP(nodeID); wantsConnection { + tracked := newTrackedIP(ip.IPPort) n.trackedIPs[nodeID] = tracked n.dial(nodeID, tracked) - } else { - delete(n.peerIPs, nodeID) } n.metrics.markDisconnected(peer) } -// ipAuth is a helper struct used to convey information about an -// [*ips.ClaimedIPPort]. -type ipAuth struct { - nodeID ids.NodeID - verified bool -} - -func (n *network) authenticateIPs(ips []*ips.ClaimedIPPort) ([]*ipAuth, error) { - ipAuths := make([]*ipAuth, len(ips)) - for i, ip := range ips { - nodeID := ids.NodeIDFromCert(ip.Cert) - n.peersLock.RLock() - _, _, shouldUpdateOurIP, shouldDial := n.peerIPStatus(nodeID, ip) - n.peersLock.RUnlock() - if !shouldUpdateOurIP && !shouldDial { - ipAuths[i] = &ipAuth{ - nodeID: nodeID, - } - continue - } - - // Verify signature if needed - signedIP := peer.SignedIP{ - UnsignedIP: peer.UnsignedIP{ - IPPort: ip.IPPort, - Timestamp: ip.Timestamp, - }, - Signature: ip.Signature, - } - if err := signedIP.Verify(ip.Cert); err != nil { - return nil, err - } - ipAuths[i] = &ipAuth{ - nodeID: nodeID, - verified: true, - } - } - return ipAuths, nil -} - -// peerIPStatus assumes the caller holds [peersLock] -func (n *network) peerIPStatus(nodeID ids.NodeID, ip *ips.ClaimedIPPort) (*ips.ClaimedIPPort, bool, bool, bool) { - prevIP, previouslyTracked := n.peerIPs[nodeID] - shouldUpdateOurIP := previouslyTracked && prevIP.Timestamp < ip.Timestamp - shouldDial := !previouslyTracked && n.WantsConnection(nodeID) - return prevIP, previouslyTracked, shouldUpdateOurIP, shouldDial -} - // dial will spin up a new goroutine and attempt to establish a connection with // [nodeID] at [ip]. // @@ -1066,6 +830,10 @@ func (n *network) peerIPStatus(nodeID ids.NodeID, ip *ips.ClaimedIPPort) (*ips.C // there is a randomized exponential backoff to avoid spamming connection // attempts. func (n *network) dial(nodeID ids.NodeID, ip *trackedIP) { + n.peerConfig.Log.Verbo("attempting to dial node", + zap.Stringer("nodeID", nodeID), + zap.Stringer("ip", ip.ip), + ) go func() { n.metrics.numTracked.Inc() defer n.metrics.numTracked.Dec() @@ -1088,13 +856,12 @@ func (n *network) dial(nodeID ids.NodeID, ip *trackedIP) { // trackedIPs and this goroutine. This prevents a memory leak when // the tracked nodeID leaves the validator set and is never able to // be connected to. - if !n.WantsConnection(nodeID) { + if !n.ipTracker.WantsConnection(nodeID) { // Typically [n.trackedIPs[nodeID]] will already equal [ip], but // the reference to [ip] is refreshed to avoid any potential // race conditions before removing the entry. if ip, exists := n.trackedIPs[nodeID]; exists { ip.stopTracking() - delete(n.peerIPs, nodeID) delete(n.trackedIPs, nodeID) } n.peersLock.Unlock() @@ -1139,7 +906,7 @@ func (n *network) dial(nodeID ids.NodeID, ip *trackedIP) { n.peerConfig.Log.Verbo("skipping connection dial", zap.String("reason", "outbound connections to private IPs are prohibited"), zap.Stringer("nodeID", nodeID), - zap.Stringer("peerIP", ip.ip.IP), + zap.Stringer("peerIP", ip.ip), zap.Duration("delay", ip.delay), ) continue @@ -1149,7 +916,8 @@ func (n *network) dial(nodeID ids.NodeID, ip *trackedIP) { if err != nil { n.peerConfig.Log.Verbo( "failed to reach peer, attempting again", - zap.Stringer("peerIP", ip.ip.IP), + zap.Stringer("nodeID", nodeID), + zap.Stringer("peerIP", ip.ip), zap.Duration("delay", ip.delay), ) continue @@ -1157,14 +925,16 @@ func (n *network) dial(nodeID ids.NodeID, ip *trackedIP) { n.peerConfig.Log.Verbo("starting to upgrade connection", zap.String("direction", "outbound"), - zap.Stringer("peerIP", ip.ip.IP), + zap.Stringer("nodeID", nodeID), + zap.Stringer("peerIP", ip.ip), ) err = n.upgrade(conn, n.clientUpgrader) if err != nil { n.peerConfig.Log.Verbo( "failed to upgrade, attempting again", - zap.Stringer("peerIP", ip.ip.IP), + zap.Stringer("nodeID", nodeID), + zap.Stringer("peerIP", ip.ip), zap.Duration("delay", ip.delay), ) continue @@ -1268,13 +1038,6 @@ func (n *network) upgrade(conn net.Conn, upgrader peer.Upgrader) error { zap.Stringer("nodeID", nodeID), ) - if !n.gossipTracker.StartTrackingPeer(nodeID) { - n.peerConfig.Log.Error( - "started duplicate peer tracker", - zap.Stringer("nodeID", nodeID), - ) - } - // peer.Start requires there is only ever one peer instance running with the // same [peerConfig.InboundMsgThrottler]. This is guaranteed by the above // de-duplications for [connectingPeers] and [connectedPeers]. @@ -1323,7 +1086,6 @@ func (n *network) StartClose() { for nodeID, tracked := range n.trackedIPs { tracked.stopTracking() - delete(n.peerIPs, nodeID) delete(n.trackedIPs, nodeID) } @@ -1395,10 +1157,13 @@ func (n *network) NodeUptime(subnetID ids.ID) (UptimeResult, error) { } func (n *network) runTimers() { - gossipPeerlists := time.NewTicker(n.config.PeerListGossipFreq) + pushGossipPeerlists := time.NewTicker(n.config.PeerListGossipFreq) + pullGossipPeerlists := time.NewTicker(n.config.PeerListPullGossipFreq) + resetPeerListBloom := time.NewTicker(n.config.PeerListBloomResetFreq) updateUptimes := time.NewTicker(n.config.UptimeMetricFreq) defer func() { - gossipPeerlists.Stop() + pushGossipPeerlists.Stop() + resetPeerListBloom.Stop() updateUptimes.Stop() }() @@ -1406,8 +1171,18 @@ func (n *network) runTimers() { select { case <-n.onCloseCtx.Done(): return - case <-gossipPeerlists.C: - n.gossipPeerLists() + case <-pushGossipPeerlists.C: + n.pushGossipPeerLists() + case <-pullGossipPeerlists.C: + n.pullGossipPeerLists() + case <-resetPeerListBloom.C: + if err := n.ipTracker.ResetBloom(); err != nil { + n.peerConfig.Log.Error("failed to reset ip tracker bloom filter", + zap.Error(err), + ) + } else { + n.peerConfig.Log.Debug("reset ip tracker bloom filter") + } case <-updateUptimes.C: primaryUptime, err := n.NodeUptime(constants.PrimaryNetworkID) if err != nil { @@ -1434,8 +1209,8 @@ func (n *network) runTimers() { } } -// gossipPeerLists gossips validators to peers in the network -func (n *network) gossipPeerLists() { +// pushGossipPeerLists gossips validators to peers in the network +func (n *network) pushGossipPeerLists() { peers := n.samplePeers( constants.PrimaryNetworkID, int(n.config.PeerListValidatorGossipSize), @@ -1449,6 +1224,21 @@ func (n *network) gossipPeerLists() { } } +// pullGossipPeerLists requests validators from peers in the network +func (n *network) pullGossipPeerLists() { + peers := n.samplePeers( + constants.PrimaryNetworkID, + 1, // numValidatorsToSample + 0, // numNonValidatorsToSample + 0, // numPeersToSample + subnets.NoOpAllower, + ) + + for _, p := range peers { + p.StartSendGetPeerList() + } +} + func (n *network) getLastReceived() (time.Time, bool) { lastReceived := atomic.LoadInt64(&n.peerConfig.LastReceived) if lastReceived == 0 { diff --git a/network/network_test.go b/network/network_test.go index 65d4809101fb..90c063a99bb7 100644 --- a/network/network_test.go +++ b/network/network_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -54,6 +54,8 @@ var ( PeerListNonValidatorGossipSize: 100, PeerListPeersGossipSize: 100, PeerListGossipFreq: time.Second, + PeerListPullGossipFreq: time.Second, + PeerListBloomResetFreq: constants.DefaultNetworkPeerListBloomResetFreq, } defaultTimeoutConfig = TimeoutConfig{ PingPongTimeout: 30 * time.Second, @@ -215,27 +217,16 @@ func newFullyConnectedTestNetwork(t *testing.T, handlers []router.InboundHandler msgCreator := newMessageCreator(t) registry := prometheus.NewRegistry() - g, err := peer.NewGossipTracker(registry, "foobar") - require.NoError(err) - - log := logging.NoLog{} - gossipTrackerCallback := peer.GossipTrackerCallback{ - Log: log, - GossipTracker: g, - } - beacons := validators.NewManager() require.NoError(beacons.AddStaker(constants.PrimaryNetworkID, nodeIDs[0], nil, ids.GenerateTestID(), 1)) vdrs := validators.NewManager() - vdrs.RegisterCallbackListener(constants.PrimaryNetworkID, &gossipTrackerCallback) for _, nodeID := range nodeIDs { require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, nodeID, nil, ids.GenerateTestID(), 1)) } config := config - config.GossipTracker = g config.Beacons = beacons config.Validators = vdrs @@ -244,7 +235,7 @@ func newFullyConnectedTestNetwork(t *testing.T, handlers []router.InboundHandler config, msgCreator, registry, - log, + logging.NoLog{}, listeners[i], dialer, &testHandler{ @@ -405,15 +396,17 @@ func TestTrackVerifiesSignatures(t *testing.T) { nodeID, tlsCert, _ := getTLS(t, 1) require.NoError(network.config.Validators.AddStaker(constants.PrimaryNetworkID, nodeID, nil, ids.Empty, 1)) - _, err := network.Track(ids.EmptyNodeID, []*ips.ClaimedIPPort{{ - Cert: staking.CertificateFromX509(tlsCert.Leaf), - IPPort: ips.IPPort{ - IP: net.IPv4(123, 132, 123, 123), - Port: 10000, - }, - Timestamp: 1000, - Signature: nil, - }}) + err := network.Track([]*ips.ClaimedIPPort{ + ips.NewClaimedIPPort( + staking.CertificateFromX509(tlsCert.Leaf), + ips.IPPort{ + IP: net.IPv4(123, 132, 123, 123), + Port: 10000, + }, + 1000, // timestamp + nil, // signature + ), + }) // The signature is wrong so this peer tracking info isn't useful. require.ErrorIs(err, rsa.ErrVerification) @@ -437,27 +430,16 @@ func TestTrackDoesNotDialPrivateIPs(t *testing.T) { msgCreator := newMessageCreator(t) registry := prometheus.NewRegistry() - g, err := peer.NewGossipTracker(registry, "foobar") - require.NoError(err) - - log := logging.NoLog{} - gossipTrackerCallback := peer.GossipTrackerCallback{ - Log: log, - GossipTracker: g, - } - beacons := validators.NewManager() require.NoError(beacons.AddStaker(constants.PrimaryNetworkID, nodeIDs[0], nil, ids.GenerateTestID(), 1)) vdrs := validators.NewManager() - vdrs.RegisterCallbackListener(constants.PrimaryNetworkID, &gossipTrackerCallback) for _, nodeID := range nodeIDs { require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, nodeID, nil, ids.GenerateTestID(), 1)) } config := config - config.GossipTracker = g config.Beacons = beacons config.Validators = vdrs config.AllowPrivateIPs = false @@ -466,7 +448,7 @@ func TestTrackDoesNotDialPrivateIPs(t *testing.T) { config, msgCreator, registry, - log, + logging.NoLog{}, listeners[i], dialer, &testHandler{ @@ -532,23 +514,11 @@ func TestDialDeletesNonValidators(t *testing.T) { msgCreator := newMessageCreator(t) registry := prometheus.NewRegistry() - g, err := peer.NewGossipTracker(registry, "foobar") - require.NoError(err) - - log := logging.NoLog{} - gossipTrackerCallback := peer.GossipTrackerCallback{ - Log: log, - GossipTracker: g, - } - beacons := validators.NewManager() require.NoError(beacons.AddStaker(constants.PrimaryNetworkID, nodeIDs[0], nil, ids.GenerateTestID(), 1)) - vdrs.RegisterCallbackListener(constants.PrimaryNetworkID, &gossipTrackerCallback) - config := config - config.GossipTracker = g config.Beacons = beacons config.Validators = vdrs config.AllowPrivateIPs = false @@ -557,7 +527,7 @@ func TestDialDeletesNonValidators(t *testing.T) { config, msgCreator, registry, - log, + logging.NoLog{}, listeners[i], dialer, &testHandler{ @@ -581,16 +551,15 @@ func TestDialDeletesNonValidators(t *testing.T) { wg.Add(len(networks)) for i, net := range networks { if i != 0 { - peerAcks, err := net.Track(config.MyNodeID, []*ips.ClaimedIPPort{{ - Cert: staking.CertificateFromX509(config.TLSConfig.Certificates[0].Leaf), - IPPort: ip.IPPort, - Timestamp: ip.Timestamp, - Signature: ip.Signature, - }}) + err := net.Track([]*ips.ClaimedIPPort{ + ips.NewClaimedIPPort( + staking.CertificateFromX509(config.TLSConfig.Certificates[0].Leaf), + ip.IPPort, + ip.Timestamp, + ip.Signature, + ), + }) require.NoError(err) - // peerAcks is empty because we aren't actually connected to - // MyNodeID yet - require.Empty(peerAcks) } go func(net Network) { @@ -694,25 +663,14 @@ func TestAllowConnectionAsAValidator(t *testing.T) { msgCreator := newMessageCreator(t) registry := prometheus.NewRegistry() - g, err := peer.NewGossipTracker(registry, "foobar") - require.NoError(err) - - log := logging.NoLog{} - gossipTrackerCallback := peer.GossipTrackerCallback{ - Log: log, - GossipTracker: g, - } - beacons := validators.NewManager() require.NoError(beacons.AddStaker(constants.PrimaryNetworkID, nodeIDs[0], nil, ids.GenerateTestID(), 1)) vdrs := validators.NewManager() - vdrs.RegisterCallbackListener(constants.PrimaryNetworkID, &gossipTrackerCallback) require.NoError(vdrs.AddStaker(constants.PrimaryNetworkID, nodeIDs[0], nil, ids.GenerateTestID(), 1)) config := config - config.GossipTracker = g config.Beacons = beacons config.Validators = vdrs config.RequireValidatorToConnect = true @@ -721,7 +679,7 @@ func TestAllowConnectionAsAValidator(t *testing.T) { config, msgCreator, registry, - log, + logging.NoLog{}, listeners[i], dialer, &testHandler{ diff --git a/network/p2p/client.go b/network/p2p/client.go index 1c3c9bee01da..6107f8abb7d9 100644 --- a/network/p2p/client.go +++ b/network/p2p/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p @@ -14,9 +14,8 @@ import ( ) var ( - ErrAppRequestFailed = errors.New("app request failed") - ErrRequestPending = errors.New("request pending") - ErrNoPeers = errors.New("no peers") + ErrRequestPending = errors.New("request pending") + ErrNoPeers = errors.New("no peers") ) // AppResponseCallback is called upon receiving an AppResponse for an AppRequest @@ -41,6 +40,7 @@ type CrossChainAppResponseCallback func( type Client struct { handlerID uint64 + handlerIDStr string handlerPrefix []byte router *router sender common.AppSender @@ -96,8 +96,8 @@ func (c *Client) AppRequest( } c.router.pendingAppRequests[requestID] = pendingAppRequest{ - AppResponseCallback: onResponse, - metrics: c.router.handlers[c.handlerID].metrics, + handlerID: c.handlerIDStr, + callback: onResponse, } c.router.requestID += 2 } @@ -159,8 +159,8 @@ func (c *Client) CrossChainAppRequest( } c.router.pendingCrossChainAppRequests[requestID] = pendingCrossChainAppRequest{ - CrossChainAppResponseCallback: onResponse, - metrics: c.router.handlers[c.handlerID].metrics, + handlerID: c.handlerIDStr, + callback: onResponse, } c.router.requestID += 2 diff --git a/network/p2p/gossip/bloom.go b/network/p2p/gossip/bloom.go index 5b1c6bc8c390..5c477826c9b6 100644 --- a/network/p2p/gossip/bloom.go +++ b/network/p2p/gossip/bloom.go @@ -1,79 +1,98 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gossip import ( "crypto/rand" - "encoding/binary" - "hash" - - bloomfilter "github.com/holiman/bloomfilter/v2" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/bloom" + "github.com/ava-labs/avalanchego/utils/math" ) -var _ hash.Hash64 = (*hasher)(nil) - -// NewBloomFilter returns a new instance of a bloom filter with at most -// [maxExpectedElements] elements anticipated at any moment, and a false -// positive probability of [falsePositiveProbability]. +// NewBloomFilter returns a new instance of a bloom filter with at least [minTargetElements] elements +// anticipated at any moment, and a false positive probability of [targetFalsePositiveProbability]. If the +// false positive probability exceeds [resetFalsePositiveProbability], the bloom filter will be reset. +// +// Invariant: The returned bloom filter is not safe to reset concurrently with +// other operations. However, it is otherwise safe to access concurrently. func NewBloomFilter( - maxExpectedElements uint64, - falsePositiveProbability float64, + minTargetElements int, + targetFalsePositiveProbability, + resetFalsePositiveProbability float64, ) (*BloomFilter, error) { - bloom, err := bloomfilter.NewOptimal( - maxExpectedElements, - falsePositiveProbability, + numHashes, numEntries := bloom.OptimalParameters( + minTargetElements, + targetFalsePositiveProbability, ) + b, err := bloom.New(numHashes, numEntries) if err != nil { return nil, err } salt, err := randomSalt() return &BloomFilter{ - Bloom: bloom, - Salt: salt, + minTargetElements: minTargetElements, + targetFalsePositiveProbability: targetFalsePositiveProbability, + resetFalsePositiveProbability: resetFalsePositiveProbability, + + maxCount: bloom.EstimateCount(numHashes, numEntries, resetFalsePositiveProbability), + bloom: b, + salt: salt, }, err } type BloomFilter struct { - Bloom *bloomfilter.Filter - // Salt is provided to eventually unblock collisions in Bloom. It's possible + minTargetElements int + targetFalsePositiveProbability float64 + resetFalsePositiveProbability float64 + + maxCount int + bloom *bloom.Filter + // salt is provided to eventually unblock collisions in Bloom. It's possible // that conflicting Gossipable items collide in the bloom filter, so a salt // is generated to eventually resolve collisions. - Salt ids.ID + salt ids.ID } func (b *BloomFilter) Add(gossipable Gossipable) { - h := gossipable.GetID() - salted := &hasher{ - hash: h[:], - salt: b.Salt, - } - b.Bloom.Add(salted) + h := gossipable.GossipID() + bloom.Add(b.bloom, h[:], b.salt[:]) } func (b *BloomFilter) Has(gossipable Gossipable) bool { - h := gossipable.GetID() - salted := &hasher{ - hash: h[:], - salt: b.Salt, - } - return b.Bloom.Contains(salted) + h := gossipable.GossipID() + return bloom.Contains(b.bloom, h[:], b.salt[:]) } -// ResetBloomFilterIfNeeded resets a bloom filter if it breaches a target false -// positive probability. Returns true if the bloom filter was reset. +func (b *BloomFilter) Marshal() ([]byte, []byte) { + bloomBytes := b.bloom.Marshal() + // salt must be copied here to ensure the bytes aren't overwritten if salt + // is later modified. + salt := b.salt + return bloomBytes, salt[:] +} + +// ResetBloomFilterIfNeeded resets a bloom filter if it breaches [targetFalsePositiveProbability]. +// +// If [targetElements] exceeds [minTargetElements], the size of the bloom filter will grow to maintain +// the same [targetFalsePositiveProbability]. +// +// Returns true if the bloom filter was reset. func ResetBloomFilterIfNeeded( bloomFilter *BloomFilter, - falsePositiveProbability float64, + targetElements int, ) (bool, error) { - if bloomFilter.Bloom.FalsePosititveProbability() < falsePositiveProbability { + if bloomFilter.bloom.Count() <= bloomFilter.maxCount { return false, nil } - newBloom, err := bloomfilter.New(bloomFilter.Bloom.M(), bloomFilter.Bloom.K()) + numHashes, numEntries := bloom.OptimalParameters( + math.Max(bloomFilter.minTargetElements, targetElements), + bloomFilter.targetFalsePositiveProbability, + ) + newBloom, err := bloom.New(numHashes, numEntries) if err != nil { return false, err } @@ -81,9 +100,9 @@ func ResetBloomFilterIfNeeded( if err != nil { return false, err } - - bloomFilter.Bloom = newBloom - bloomFilter.Salt = salt + bloomFilter.maxCount = bloom.EstimateCount(numHashes, numEntries, bloomFilter.resetFalsePositiveProbability) + bloomFilter.bloom = newBloom + bloomFilter.salt = salt return true, nil } @@ -92,39 +111,3 @@ func randomSalt() (ids.ID, error) { _, err := rand.Read(salt[:]) return salt, err } - -type hasher struct { - hash []byte - salt ids.ID -} - -func (h *hasher) Write(p []byte) (n int, err error) { - h.hash = append(h.hash, p...) - return len(p), nil -} - -func (h *hasher) Sum(b []byte) []byte { - h.hash = append(h.hash, b...) - return h.hash -} - -func (h *hasher) Reset() { - h.hash = ids.Empty[:] -} - -func (*hasher) BlockSize() int { - return ids.IDLen -} - -func (h *hasher) Sum64() uint64 { - salted := ids.ID{} - for i := 0; i < len(h.hash) && i < ids.IDLen; i++ { - salted[i] = h.hash[i] ^ h.salt[i] - } - - return binary.BigEndian.Uint64(salted[:]) -} - -func (h *hasher) Size() int { - return len(h.hash) -} diff --git a/network/p2p/gossip/bloom_test.go b/network/p2p/gossip/bloom_test.go index 860d2d5e936e..128ea97824c0 100644 --- a/network/p2p/gossip/bloom_test.go +++ b/network/p2p/gossip/bloom_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gossip @@ -6,39 +6,54 @@ package gossip import ( "testing" - bloomfilter "github.com/holiman/bloomfilter/v2" - "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/bloom" ) func TestBloomFilterRefresh(t *testing.T) { tests := []struct { - name string - falsePositiveProbability float64 - add []*testTx - expected []*testTx + name string + minTargetElements int + targetFalsePositiveProbability float64 + resetFalsePositiveProbability float64 + reset bool + add []*testTx + expected []*testTx }{ { - name: "no refresh", - falsePositiveProbability: 1, + name: "no refresh", + minTargetElements: 1, + targetFalsePositiveProbability: 0.01, + resetFalsePositiveProbability: 1, + reset: false, // maxCount = 9223372036854775807 add: []*testTx{ {id: ids.ID{0}}, + {id: ids.ID{1}}, + {id: ids.ID{2}}, }, expected: []*testTx{ {id: ids.ID{0}}, + {id: ids.ID{1}}, + {id: ids.ID{2}}, }, }, { - name: "refresh", - falsePositiveProbability: 0.1, + name: "refresh", + minTargetElements: 1, + targetFalsePositiveProbability: 0.01, + resetFalsePositiveProbability: 0.0000000000000001, // maxCount = 1 + reset: true, add: []*testTx{ {id: ids.ID{0}}, {id: ids.ID{1}}, + {id: ids.ID{2}}, }, expected: []*testTx{ - {id: ids.ID{1}}, + {id: ids.ID{2}}, }, }, } @@ -46,20 +61,38 @@ func TestBloomFilterRefresh(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require := require.New(t) - b, err := bloomfilter.New(10, 1) + numHashes, numEntries := bloom.OptimalParameters( + tt.minTargetElements, + tt.targetFalsePositiveProbability, + ) + b, err := bloom.New(numHashes, numEntries) require.NoError(err) bloom := BloomFilter{ - Bloom: b, + bloom: b, + maxCount: bloom.EstimateCount(numHashes, numEntries, tt.resetFalsePositiveProbability), + minTargetElements: tt.minTargetElements, + targetFalsePositiveProbability: tt.targetFalsePositiveProbability, + resetFalsePositiveProbability: tt.resetFalsePositiveProbability, } + var didReset bool for _, item := range tt.add { - _, err = ResetBloomFilterIfNeeded(&bloom, tt.falsePositiveProbability) + bloomBytes, saltBytes := bloom.Marshal() + initialBloomBytes := slices.Clone(bloomBytes) + initialSaltBytes := slices.Clone(saltBytes) + + reset, err := ResetBloomFilterIfNeeded(&bloom, len(tt.add)) require.NoError(err) + if reset { + didReset = reset + } bloom.Add(item) - } - require.Equal(uint64(len(tt.expected)), bloom.Bloom.N()) + require.Equal(initialBloomBytes, bloomBytes) + require.Equal(initialSaltBytes, saltBytes) + } + require.Equal(tt.reset, didReset) for _, expected := range tt.expected { require.True(bloom.Has(expected)) } diff --git a/network/p2p/gossip/gossip.go b/network/p2p/gossip/gossip.go index 94d49260da40..97e90e6e99af 100644 --- a/network/p2p/gossip/gossip.go +++ b/network/p2p/gossip/gossip.go @@ -1,10 +1,13 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gossip import ( "context" + "errors" + "fmt" + "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -17,12 +20,33 @@ import ( "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/utils/buffer" "github.com/ava-labs/avalanchego/utils/logging" ) +const ( + typeLabel = "type" + pushType = "push" + pullType = "pull" +) + var ( _ Gossiper = (*ValidatorGossiper)(nil) - _ Gossiper = (*PullGossiper[testTx, *testTx])(nil) + _ Gossiper = (*PullGossiper[*testTx])(nil) + _ Gossiper = (*NoOpGossiper)(nil) + _ Gossiper = (*TestGossiper)(nil) + + _ Accumulator[*testTx] = (*PushGossiper[*testTx])(nil) + _ Accumulator[*testTx] = (*NoOpAccumulator[*testTx])(nil) + _ Accumulator[*testTx] = (*TestAccumulator[*testTx])(nil) + + metricLabels = []string{typeLabel} + pushLabels = prometheus.Labels{ + typeLabel: pushType, + } + pullLabels = prometheus.Labels{ + typeLabel: pullType, + } ) // Gossiper gossips Gossipables to other nodes @@ -31,11 +55,11 @@ type Gossiper interface { Gossip(ctx context.Context) error } -// GossipableAny exists to help create non-nil pointers to a concrete Gossipable -// ref: https://stackoverflow.com/questions/69573113/how-can-i-instantiate-a-non-nil-pointer-of-type-argument-with-generic-go -type GossipableAny[T any] interface { - *T - Gossipable +// Accumulator allows a caller to accumulate gossipables to be gossiped +type Accumulator[T Gossipable] interface { + Gossiper + // Add queues gossipables to be gossiped + Add(gossipables ...T) } // ValidatorGossiper only calls [Gossip] if the given node is a validator @@ -46,65 +70,88 @@ type ValidatorGossiper struct { Validators p2p.ValidatorSet } -func (v ValidatorGossiper) Gossip(ctx context.Context) error { - if !v.Validators.Has(ctx, v.NodeID) { - return nil - } - - return v.Gossiper.Gossip(ctx) +// Metrics that are tracked across a gossip protocol. A given protocol should +// only use a single instance of Metrics. +type Metrics struct { + sentCount *prometheus.CounterVec + sentBytes *prometheus.CounterVec + receivedCount *prometheus.CounterVec + receivedBytes *prometheus.CounterVec } -type Config struct { - Namespace string - PollSize int -} - -func NewPullGossiper[T any, U GossipableAny[T]]( - config Config, - log logging.Logger, - set Set[U], - client *p2p.Client, +// NewMetrics returns a common set of metrics +func NewMetrics( metrics prometheus.Registerer, -) (*PullGossiper[T, U], error) { - p := &PullGossiper[T, U]{ - config: config, - log: log, - set: set, - client: client, - receivedN: prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: config.Namespace, - Name: "gossip_received_n", + namespace string, +) (Metrics, error) { + m := Metrics{ + sentCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "gossip_sent_count", + Help: "amount of gossip sent (n)", + }, metricLabels), + sentBytes: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "gossip_sent_bytes", + Help: "amount of gossip sent (bytes)", + }, metricLabels), + receivedCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "gossip_received_count", Help: "amount of gossip received (n)", - }), - receivedBytes: prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: config.Namespace, + }, metricLabels), + receivedBytes: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, Name: "gossip_received_bytes", Help: "amount of gossip received (bytes)", - }), + }, metricLabels), } - err := utils.Err( - metrics.Register(p.receivedN), - metrics.Register(p.receivedBytes), + metrics.Register(m.sentCount), + metrics.Register(m.sentBytes), + metrics.Register(m.receivedCount), + metrics.Register(m.receivedBytes), ) - return p, err + return m, err } -type PullGossiper[T any, U GossipableAny[T]] struct { - config Config - log logging.Logger - set Set[U] - client *p2p.Client - receivedN prometheus.Counter - receivedBytes prometheus.Counter +func (v ValidatorGossiper) Gossip(ctx context.Context) error { + if !v.Validators.Has(ctx, v.NodeID) { + return nil + } + + return v.Gossiper.Gossip(ctx) } -func (p *PullGossiper[_, _]) Gossip(ctx context.Context) error { - bloom, salt, err := p.set.GetFilter() - if err != nil { - return err +func NewPullGossiper[T Gossipable]( + log logging.Logger, + marshaller Marshaller[T], + set Set[T], + client *p2p.Client, + metrics Metrics, + pollSize int, +) *PullGossiper[T] { + return &PullGossiper[T]{ + log: log, + marshaller: marshaller, + set: set, + client: client, + metrics: metrics, + pollSize: pollSize, } +} +type PullGossiper[T Gossipable] struct { + log logging.Logger + marshaller Marshaller[T] + set Set[T] + client *p2p.Client + metrics Metrics + pollSize int +} + +func (p *PullGossiper[_]) Gossip(ctx context.Context) error { + bloom, salt := p.set.GetFilter() request := &sdk.PullGossipRequest{ Filter: bloom, Salt: salt, @@ -114,8 +161,9 @@ func (p *PullGossiper[_, _]) Gossip(ctx context.Context) error { return err } - for i := 0; i < p.config.PollSize; i++ { - if err := p.client.AppRequestAny(ctx, msgBytes, p.handleResponse); err != nil { + for i := 0; i < p.pollSize; i++ { + err := p.client.AppRequestAny(ctx, msgBytes, p.handleResponse) + if err != nil && !errors.Is(err, p2p.ErrNoPeers) { return err } } @@ -123,7 +171,7 @@ func (p *PullGossiper[_, _]) Gossip(ctx context.Context) error { return nil } -func (p *PullGossiper[T, U]) handleResponse( +func (p *PullGossiper[_]) handleResponse( _ context.Context, nodeID ids.NodeID, responseBytes []byte, @@ -148,8 +196,8 @@ func (p *PullGossiper[T, U]) handleResponse( for _, bytes := range response.Gossip { receivedBytes += len(bytes) - gossipable := U(new(T)) - if err := gossipable.Unmarshal(bytes); err != nil { + gossipable, err := p.marshaller.UnmarshalGossip(bytes) + if err != nil { p.log.Debug( "failed to unmarshal gossip", zap.Stringer("nodeID", nodeID), @@ -158,7 +206,7 @@ func (p *PullGossiper[T, U]) handleResponse( continue } - hash := gossipable.GetID() + hash := gossipable.GossipID() p.log.Debug( "received gossip", zap.Stringer("nodeID", nodeID), @@ -175,8 +223,104 @@ func (p *PullGossiper[T, U]) handleResponse( } } - p.receivedN.Add(float64(len(response.Gossip))) - p.receivedBytes.Add(float64(receivedBytes)) + receivedCountMetric, err := p.metrics.receivedCount.GetMetricWith(pullLabels) + if err != nil { + p.log.Error("failed to get received count metric", zap.Error(err)) + return + } + + receivedBytesMetric, err := p.metrics.receivedBytes.GetMetricWith(pullLabels) + if err != nil { + p.log.Error("failed to get received bytes metric", zap.Error(err)) + return + } + + receivedCountMetric.Add(float64(len(response.Gossip))) + receivedBytesMetric.Add(float64(receivedBytes)) +} + +// NewPushGossiper returns an instance of PushGossiper +func NewPushGossiper[T Gossipable](marshaller Marshaller[T], client *p2p.Client, metrics Metrics, targetGossipSize int) *PushGossiper[T] { + return &PushGossiper[T]{ + marshaller: marshaller, + client: client, + metrics: metrics, + targetGossipSize: targetGossipSize, + pending: buffer.NewUnboundedDeque[T](0), + } +} + +// PushGossiper broadcasts gossip to peers randomly in the network +type PushGossiper[T Gossipable] struct { + marshaller Marshaller[T] + client *p2p.Client + metrics Metrics + targetGossipSize int + + lock sync.Mutex + pending buffer.Deque[T] +} + +// Gossip flushes any queued gossipables +func (p *PushGossiper[T]) Gossip(ctx context.Context) error { + p.lock.Lock() + defer p.lock.Unlock() + + if p.pending.Len() == 0 { + return nil + } + + msg := &sdk.PushGossip{ + Gossip: make([][]byte, 0, p.pending.Len()), + } + + sentBytes := 0 + for sentBytes < p.targetGossipSize { + gossipable, ok := p.pending.PeekLeft() + if !ok { + break + } + + bytes, err := p.marshaller.MarshalGossip(gossipable) + if err != nil { + // remove this item so we don't get stuck in a loop + _, _ = p.pending.PopLeft() + return err + } + + msg.Gossip = append(msg.Gossip, bytes) + sentBytes += len(bytes) + p.pending.PopLeft() + } + + msgBytes, err := proto.Marshal(msg) + if err != nil { + return err + } + + sentCountMetric, err := p.metrics.sentCount.GetMetricWith(pushLabels) + if err != nil { + return fmt.Errorf("failed to get sent count metric: %w", err) + } + + sentBytesMetric, err := p.metrics.sentBytes.GetMetricWith(pushLabels) + if err != nil { + return fmt.Errorf("failed to get sent bytes metric: %w", err) + } + + sentCountMetric.Add(float64(len(msg.Gossip))) + sentBytesMetric.Add(float64(sentBytes)) + + return p.client.AppGossip(ctx, msgBytes) +} + +func (p *PushGossiper[T]) Add(gossipables ...T) { + p.lock.Lock() + defer p.lock.Unlock() + + for _, gossipable := range gossipables { + p.pending.PushRight(gossipable) + } } // Every calls [Gossip] every [frequency] amount of time. @@ -196,3 +340,46 @@ func Every(ctx context.Context, log logging.Logger, gossiper Gossiper, frequency } } } + +type NoOpGossiper struct{} + +func (NoOpGossiper) Gossip(context.Context) error { + return nil +} + +type NoOpAccumulator[T Gossipable] struct{} + +func (NoOpAccumulator[_]) Gossip(context.Context) error { + return nil +} + +func (NoOpAccumulator[T]) Add(...T) {} + +type TestGossiper struct { + GossipF func(ctx context.Context) error +} + +func (t *TestGossiper) Gossip(ctx context.Context) error { + return t.GossipF(ctx) +} + +type TestAccumulator[T Gossipable] struct { + GossipF func(ctx context.Context) error + AddF func(...T) +} + +func (t TestAccumulator[T]) Gossip(ctx context.Context) error { + if t.GossipF == nil { + return nil + } + + return t.GossipF(ctx) +} + +func (t TestAccumulator[T]) Add(gossipables ...T) { + if t.AddF == nil { + return + } + + t.AddF(gossipables...) +} diff --git a/network/p2p/gossip/gossip_test.go b/network/p2p/gossip/gossip_test.go index d30fac0008e7..1c0941bd7eba 100644 --- a/network/p2p/gossip/gossip_test.go +++ b/network/p2p/gossip/gossip_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gossip @@ -11,32 +11,30 @@ import ( "github.com/prometheus/client_golang/prometheus" + "golang.org/x/exp/maps" + + "google.golang.org/protobuf/proto" + "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/proto/pb/sdk" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/units" ) -var ( - _ p2p.ValidatorSet = (*testValidatorSet)(nil) - _ Gossiper = (*testGossiper)(nil) -) - -func TestGossiperShutdown(t *testing.T) { - require := require.New(t) - - metrics := prometheus.NewRegistry() - gossiper, err := NewPullGossiper[testTx]( - Config{}, +func TestGossiperShutdown(*testing.T) { + gossiper := NewPullGossiper[*testTx]( logging.NoLog{}, nil, nil, - metrics, + nil, + Metrics{}, + 0, ) - require.NoError(err) ctx, cancel := context.WithCancel(context.Background()) wg := &sync.WaitGroup{} @@ -54,7 +52,7 @@ func TestGossiperShutdown(t *testing.T) { func TestGossiperGossip(t *testing.T) { tests := []struct { name string - config HandlerConfig + targetResponseSize int requester []*testTx // what we have responder []*testTx // what the peer we're requesting gossip from has expectedPossibleValues []*testTx // possible values we can have @@ -64,48 +62,38 @@ func TestGossiperGossip(t *testing.T) { name: "no gossip - no one knows anything", }, { - name: "no gossip - requester knows more than responder", - config: HandlerConfig{ - TargetResponseSize: 1024, - }, + name: "no gossip - requester knows more than responder", + targetResponseSize: 1024, requester: []*testTx{{id: ids.ID{0}}}, expectedPossibleValues: []*testTx{{id: ids.ID{0}}}, expectedLen: 1, }, { - name: "no gossip - requester knows everything responder knows", - config: HandlerConfig{ - TargetResponseSize: 1024, - }, + name: "no gossip - requester knows everything responder knows", + targetResponseSize: 1024, requester: []*testTx{{id: ids.ID{0}}}, responder: []*testTx{{id: ids.ID{0}}}, expectedPossibleValues: []*testTx{{id: ids.ID{0}}}, expectedLen: 1, }, { - name: "gossip - requester knows nothing", - config: HandlerConfig{ - TargetResponseSize: 1024, - }, + name: "gossip - requester knows nothing", + targetResponseSize: 1024, responder: []*testTx{{id: ids.ID{0}}}, expectedPossibleValues: []*testTx{{id: ids.ID{0}}}, expectedLen: 1, }, { - name: "gossip - requester knows less than responder", - config: HandlerConfig{ - TargetResponseSize: 1024, - }, + name: "gossip - requester knows less than responder", + targetResponseSize: 1024, requester: []*testTx{{id: ids.ID{0}}}, responder: []*testTx{{id: ids.ID{0}}, {id: ids.ID{1}}}, expectedPossibleValues: []*testTx{{id: ids.ID{0}}, {id: ids.ID{1}}}, expectedLen: 2, }, { - name: "gossip - target response size exceeded", - config: HandlerConfig{ - TargetResponseSize: 32, - }, + name: "gossip - target response size exceeded", + targetResponseSize: 32, responder: []*testTx{{id: ids.ID{0}}, {id: ids.ID{1}}, {id: ids.ID{2}}}, expectedPossibleValues: []*testTx{{id: ids.ID{0}}, {id: ids.ID{1}}, {id: ids.ID{2}}}, expectedLen: 2, @@ -115,65 +103,66 @@ func TestGossiperGossip(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require := require.New(t) + ctx := context.Background() + + responseSender := &common.FakeSender{ + SentAppResponse: make(chan []byte, 1), + } + responseNetwork, err := p2p.NewNetwork(logging.NoLog{}, responseSender, prometheus.NewRegistry(), "") + require.NoError(err) - responseSender := &common.SenderTest{} - responseNetwork := p2p.NewNetwork(logging.NoLog{}, responseSender, prometheus.NewRegistry(), "") - responseBloom, err := NewBloomFilter(1000, 0.01) + responseBloom, err := NewBloomFilter(1000, 0.01, 0.05) require.NoError(err) - responseSet := testSet{ - set: set.Set[*testTx]{}, + responseSet := &testSet{ + txs: make(map[ids.ID]*testTx), bloom: responseBloom, } for _, item := range tt.responder { require.NoError(responseSet.Add(item)) } - handler, err := NewHandler[*testTx](responseSet, tt.config, prometheus.NewRegistry()) + metrics, err := NewMetrics(prometheus.NewRegistry(), "") require.NoError(err) - _, err = responseNetwork.NewAppProtocol(0x0, handler) + marshaller := testMarshaller{} + handler := NewHandler[*testTx]( + logging.NoLog{}, + marshaller, + NoOpAccumulator[*testTx]{}, + responseSet, + metrics, + tt.targetResponseSize, + ) require.NoError(err) + require.NoError(responseNetwork.AddHandler(0x0, handler)) - requestSender := &common.SenderTest{ - SendAppRequestF: func(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error { - go func() { - require.NoError(responseNetwork.AppRequest(ctx, ids.EmptyNodeID, requestID, time.Time{}, request)) - }() - return nil - }, + requestSender := &common.FakeSender{ + SentAppRequest: make(chan []byte, 1), } - requestNetwork := p2p.NewNetwork(logging.NoLog{}, requestSender, prometheus.NewRegistry(), "") + requestNetwork, err := p2p.NewNetwork(logging.NoLog{}, requestSender, prometheus.NewRegistry(), "") + require.NoError(err) require.NoError(requestNetwork.Connected(context.Background(), ids.EmptyNodeID, nil)) - gossiped := make(chan struct{}) - responseSender.SendAppResponseF = func(ctx context.Context, nodeID ids.NodeID, requestID uint32, appResponseBytes []byte) error { - require.NoError(requestNetwork.AppResponse(ctx, nodeID, requestID, appResponseBytes)) - close(gossiped) - return nil - } - - bloom, err := NewBloomFilter(1000, 0.01) + bloom, err := NewBloomFilter(1000, 0.01, 0.05) require.NoError(err) - requestSet := testSet{ - set: set.Set[*testTx]{}, + requestSet := &testSet{ + txs: make(map[ids.ID]*testTx), bloom: bloom, } for _, item := range tt.requester { require.NoError(requestSet.Add(item)) } - requestClient, err := requestNetwork.NewAppProtocol(0x0, nil) - require.NoError(err) + requestClient := requestNetwork.NewClient(0x0) - config := Config{ - PollSize: 1, - } - gossiper, err := NewPullGossiper[testTx, *testTx]( - config, + require.NoError(err) + gossiper := NewPullGossiper[*testTx]( logging.NoLog{}, + marshaller, requestSet, requestClient, - prometheus.NewRegistry(), + metrics, + 1, ) require.NoError(err) received := set.Set[*testTx]{} @@ -181,11 +170,12 @@ func TestGossiperGossip(t *testing.T) { received.Add(tx) } - require.NoError(gossiper.Gossip(context.Background())) - <-gossiped + require.NoError(gossiper.Gossip(ctx)) + require.NoError(responseNetwork.AppRequest(ctx, ids.EmptyNodeID, 1, time.Time{}, <-requestSender.SentAppRequest)) + require.NoError(requestNetwork.AppResponse(ctx, ids.EmptyNodeID, 1, <-responseSender.SentAppResponse)) - require.Len(requestSet.set, tt.expectedLen) - require.Subset(tt.expectedPossibleValues, requestSet.set.List()) + require.Len(requestSet.txs, tt.expectedLen) + require.Subset(tt.expectedPossibleValues, maps.Values(requestSet.txs)) // we should not receive anything that we already had before we // requested the gossip @@ -199,8 +189,8 @@ func TestGossiperGossip(t *testing.T) { func TestEvery(*testing.T) { ctx, cancel := context.WithCancel(context.Background()) calls := 0 - gossiper := &testGossiper{ - gossipF: func(context.Context) error { + gossiper := &TestGossiper{ + GossipF: func(context.Context) error { if calls >= 10 { cancel() return nil @@ -226,8 +216,8 @@ func TestValidatorGossiper(t *testing.T) { calls := 0 gossiper := ValidatorGossiper{ - Gossiper: &testGossiper{ - gossipF: func(context.Context) error { + Gossiper: &TestGossiper{ + GossipF: func(context.Context) error { calls++ return nil }, @@ -246,12 +236,225 @@ func TestValidatorGossiper(t *testing.T) { require.Equal(2, calls) } -type testGossiper struct { - gossipF func(ctx context.Context) error +// Tests that the outgoing gossip is equivalent to what was accumulated +func TestPushGossiper(t *testing.T) { + tests := []struct { + name string + cycles [][]*testTx + }{ + { + name: "single cycle", + cycles: [][]*testTx{ + { + &testTx{ + id: ids.ID{0}, + }, + &testTx{ + id: ids.ID{1}, + }, + &testTx{ + id: ids.ID{2}, + }, + }, + }, + }, + { + name: "multiple cycles", + cycles: [][]*testTx{ + { + &testTx{ + id: ids.ID{0}, + }, + }, + { + &testTx{ + id: ids.ID{1}, + }, + &testTx{ + id: ids.ID{2}, + }, + }, + { + &testTx{ + id: ids.ID{3}, + }, + &testTx{ + id: ids.ID{4}, + }, + &testTx{ + id: ids.ID{5}, + }, + }, + { + &testTx{ + id: ids.ID{6}, + }, + &testTx{ + id: ids.ID{7}, + }, + &testTx{ + id: ids.ID{8}, + }, + &testTx{ + id: ids.ID{9}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + ctx := context.Background() + + sender := &common.FakeSender{ + SentAppGossip: make(chan []byte, 1), + } + network, err := p2p.NewNetwork( + logging.NoLog{}, + sender, + prometheus.NewRegistry(), + "", + ) + require.NoError(err) + client := network.NewClient(0) + metrics, err := NewMetrics(prometheus.NewRegistry(), "") + require.NoError(err) + marshaller := testMarshaller{} + gossiper := NewPushGossiper[*testTx]( + marshaller, + client, + metrics, + units.MiB, + ) + + for _, gossipables := range tt.cycles { + gossiper.Add(gossipables...) + require.NoError(gossiper.Gossip(ctx)) + + want := &sdk.PushGossip{ + Gossip: make([][]byte, 0, len(tt.cycles)), + } + + for _, gossipable := range gossipables { + bytes, err := marshaller.MarshalGossip(gossipable) + require.NoError(err) + + want.Gossip = append(want.Gossip, bytes) + } + + // remove the handler prefix + sentMsg := <-sender.SentAppGossip + got := &sdk.PushGossip{} + require.NoError(proto.Unmarshal(sentMsg[1:], got)) + + require.Equal(want.Gossip, got.Gossip) + } + }) + } } -func (t *testGossiper) Gossip(ctx context.Context) error { - return t.gossipF(ctx) +// Tests that gossip to a peer should forward the gossip if it was not +// previously known +func TestPushGossipE2E(t *testing.T) { + require := require.New(t) + + // tx known by both the sender and the receiver which should not be + // forwarded + knownTx := &testTx{id: ids.GenerateTestID()} + + log := logging.NoLog{} + bloom, err := NewBloomFilter(100, 0.01, 0.05) + require.NoError(err) + set := &testSet{ + txs: make(map[ids.ID]*testTx), + bloom: bloom, + } + require.NoError(set.Add(knownTx)) + + forwarder := &common.FakeSender{ + SentAppGossip: make(chan []byte, 1), + } + forwarderNetwork, err := p2p.NewNetwork(log, forwarder, prometheus.NewRegistry(), "") + require.NoError(err) + handlerID := uint64(123) + client := forwarderNetwork.NewClient(handlerID) + + metrics, err := NewMetrics(prometheus.NewRegistry(), "") + require.NoError(err) + marshaller := testMarshaller{} + forwarderGossiper := NewPushGossiper[*testTx]( + marshaller, + client, + metrics, + units.MiB, + ) + + handler := NewHandler[*testTx]( + log, + marshaller, + forwarderGossiper, + set, + metrics, + 0, + ) + require.NoError(err) + require.NoError(forwarderNetwork.AddHandler(handlerID, handler)) + + issuer := &common.FakeSender{ + SentAppGossip: make(chan []byte, 1), + } + issuerNetwork, err := p2p.NewNetwork(log, issuer, prometheus.NewRegistry(), "") + require.NoError(err) + issuerClient := issuerNetwork.NewClient(handlerID) + require.NoError(err) + issuerGossiper := NewPushGossiper[*testTx]( + marshaller, + issuerClient, + metrics, + units.MiB, + ) + + want := []*testTx{ + {id: ids.GenerateTestID()}, + {id: ids.GenerateTestID()}, + {id: ids.GenerateTestID()}, + } + + // gossip both some unseen txs and one the receiver already knows about + var gossiped []*testTx + gossiped = append(gossiped, want...) + gossiped = append(gossiped, knownTx) + + issuerGossiper.Add(gossiped...) + addedToSet := make([]*testTx, 0, len(want)) + set.onAdd = func(tx *testTx) { + addedToSet = append(addedToSet, tx) + } + + ctx := context.Background() + require.NoError(issuerGossiper.Gossip(ctx)) + + // make sure that we only add new txs someone gossips to us + require.NoError(forwarderNetwork.AppGossip(ctx, ids.EmptyNodeID, <-issuer.SentAppGossip)) + require.Equal(want, addedToSet) + + // make sure that we only forward txs we have not already seen before + forwardedBytes := <-forwarder.SentAppGossip + forwardedMsg := &sdk.PushGossip{} + require.NoError(proto.Unmarshal(forwardedBytes[1:], forwardedMsg)) + require.Len(forwardedMsg.Gossip, len(want)) + + gotForwarded := make([]*testTx, 0, len(addedToSet)) + + for _, bytes := range forwardedMsg.Gossip { + tx, err := marshaller.UnmarshalGossip(bytes) + require.NoError(err) + gotForwarded = append(gotForwarded, tx) + } + + require.Equal(want, gotForwarded) } type testValidatorSet struct { diff --git a/network/p2p/gossip/gossipable.go b/network/p2p/gossip/gossipable.go index 84c37e2d6b84..238c62b4641c 100644 --- a/network/p2p/gossip/gossipable.go +++ b/network/p2p/gossip/gossipable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gossip @@ -7,18 +7,23 @@ import "github.com/ava-labs/avalanchego/ids" // Gossipable is an item that can be gossiped across the network type Gossipable interface { - GetID() ids.ID - Marshal() ([]byte, error) - Unmarshal(bytes []byte) error + GossipID() ids.ID +} + +// Marshaller handles parsing logic for a concrete Gossipable type +type Marshaller[T Gossipable] interface { + MarshalGossip(T) ([]byte, error) + UnmarshalGossip([]byte) (T, error) } // Set holds a set of known Gossipable items type Set[T Gossipable] interface { - // Add adds a Gossipable to the set + // Add adds a Gossipable to the set. Returns an error if gossipable was not + // added. Add(gossipable T) error // Iterate iterates over elements until [f] returns false Iterate(f func(gossipable T) bool) // GetFilter returns the byte representation of bloom filter and its // corresponding salt. - GetFilter() (bloom []byte, salt []byte, err error) + GetFilter() (bloom []byte, salt []byte) } diff --git a/network/p2p/gossip/handler.go b/network/p2p/gossip/handler.go index ecaf58434bc2..15ef1fe16684 100644 --- a/network/p2p/gossip/handler.go +++ b/network/p2p/gossip/handler.go @@ -1,71 +1,53 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gossip import ( "context" - "errors" + "fmt" "time" - bloomfilter "github.com/holiman/bloomfilter/v2" - - "github.com/prometheus/client_golang/prometheus" + "go.uber.org/zap" "google.golang.org/protobuf/proto" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/proto/pb/sdk" - "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/utils/bloom" + "github.com/ava-labs/avalanchego/utils/logging" ) -var ( - _ p2p.Handler = (*Handler[Gossipable])(nil) - - ErrInvalidID = errors.New("invalid id") -) - -type HandlerConfig struct { - Namespace string - TargetResponseSize int -} +var _ p2p.Handler = (*Handler[*testTx])(nil) func NewHandler[T Gossipable]( + log logging.Logger, + marshaller Marshaller[T], + accumulator Accumulator[T], set Set[T], - config HandlerConfig, - metrics prometheus.Registerer, -) (*Handler[T], error) { - h := &Handler[T]{ + metrics Metrics, + targetResponseSize int, +) *Handler[T] { + return &Handler[T]{ Handler: p2p.NoOpHandler{}, + log: log, + marshaller: marshaller, + accumulator: accumulator, set: set, - targetResponseSize: config.TargetResponseSize, - sentN: prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: config.Namespace, - Name: "gossip_sent_n", - Help: "amount of gossip sent (n)", - }), - sentBytes: prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: config.Namespace, - Name: "gossip_sent_bytes", - Help: "amount of gossip sent (bytes)", - }), + metrics: metrics, + targetResponseSize: targetResponseSize, } - - err := utils.Err( - metrics.Register(h.sentN), - metrics.Register(h.sentBytes), - ) - return h, err } type Handler[T Gossipable] struct { p2p.Handler + marshaller Marshaller[T] + accumulator Accumulator[T] + log logging.Logger set Set[T] + metrics Metrics targetResponseSize int - - sentN prometheus.Counter - sentBytes prometheus.Counter } func (h Handler[T]) AppRequest(_ context.Context, _ ids.NodeID, _ time.Time, requestBytes []byte) ([]byte, error) { @@ -79,24 +61,23 @@ func (h Handler[T]) AppRequest(_ context.Context, _ ids.NodeID, _ time.Time, req return nil, err } - filter := &BloomFilter{ - Bloom: &bloomfilter.Filter{}, - Salt: salt, - } - if err := filter.Bloom.UnmarshalBinary(request.Filter); err != nil { + filter, err := bloom.Parse(request.Filter) + if err != nil { return nil, err } responseSize := 0 gossipBytes := make([][]byte, 0) h.set.Iterate(func(gossipable T) bool { + gossipID := gossipable.GossipID() + // filter out what the requesting peer already knows about - if filter.Has(gossipable) { + if bloom.Contains(filter, gossipID[:], salt[:]) { return true } var bytes []byte - bytes, err = gossipable.Marshal() + bytes, err = h.marshaller.MarshalGossip(gossipable) if err != nil { return false } @@ -117,8 +98,72 @@ func (h Handler[T]) AppRequest(_ context.Context, _ ids.NodeID, _ time.Time, req Gossip: gossipBytes, } - h.sentN.Add(float64(len(response.Gossip))) - h.sentBytes.Add(float64(responseSize)) + sentCountMetric, err := h.metrics.sentCount.GetMetricWith(pullLabels) + if err != nil { + return nil, fmt.Errorf("failed to get sent count metric: %w", err) + } + + sentBytesMetric, err := h.metrics.sentBytes.GetMetricWith(pullLabels) + if err != nil { + return nil, fmt.Errorf("failed to get sent bytes metric: %w", err) + } + + sentCountMetric.Add(float64(len(response.Gossip))) + sentBytesMetric.Add(float64(responseSize)) return proto.Marshal(response) } + +func (h Handler[_]) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) { + msg := &sdk.PushGossip{} + if err := proto.Unmarshal(gossipBytes, msg); err != nil { + h.log.Debug("failed to unmarshal gossip", zap.Error(err)) + return + } + + receivedBytes := 0 + for _, bytes := range msg.Gossip { + receivedBytes += len(bytes) + gossipable, err := h.marshaller.UnmarshalGossip(bytes) + if err != nil { + h.log.Debug("failed to unmarshal gossip", + zap.Stringer("nodeID", nodeID), + zap.Error(err), + ) + continue + } + + if err := h.set.Add(gossipable); err != nil { + h.log.Debug( + "failed to add gossip to the known set", + zap.Stringer("nodeID", nodeID), + zap.Stringer("id", gossipable.GossipID()), + zap.Error(err), + ) + continue + } + + // continue gossiping messages we have not seen to other peers + h.accumulator.Add(gossipable) + } + + if err := h.accumulator.Gossip(ctx); err != nil { + h.log.Error("failed to forward gossip", zap.Error(err)) + return + } + + receivedCountMetric, err := h.metrics.receivedCount.GetMetricWith(pushLabels) + if err != nil { + h.log.Error("failed to get received count metric", zap.Error(err)) + return + } + + receivedBytesMetric, err := h.metrics.receivedBytes.GetMetricWith(pushLabels) + if err != nil { + h.log.Error("failed to get received bytes metric", zap.Error(err)) + return + } + + receivedCountMetric.Add(float64(len(msg.Gossip))) + receivedBytesMetric.Add(float64(receivedBytes)) +} diff --git a/network/p2p/gossip/test_gossip.go b/network/p2p/gossip/test_gossip.go index ba114adf3774..03098399462e 100644 --- a/network/p2p/gossip/test_gossip.go +++ b/network/p2p/gossip/test_gossip.go @@ -1,44 +1,53 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gossip import ( + "fmt" + "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/set" ) var ( - _ Gossipable = (*testTx)(nil) - _ Set[*testTx] = (*testSet)(nil) + _ Gossipable = (*testTx)(nil) + _ Set[*testTx] = (*testSet)(nil) + _ Marshaller[*testTx] = (*testMarshaller)(nil) ) type testTx struct { id ids.ID } -func (t *testTx) GetID() ids.ID { +func (t *testTx) GossipID() ids.ID { return t.id } -func (t *testTx) Marshal() ([]byte, error) { - return t.id[:], nil +type testMarshaller struct{} + +func (testMarshaller) MarshalGossip(tx *testTx) ([]byte, error) { + return tx.id[:], nil } -func (t *testTx) Unmarshal(bytes []byte) error { - t.id = ids.ID{} - copy(t.id[:], bytes) - return nil +func (testMarshaller) UnmarshalGossip(bytes []byte) (*testTx, error) { + id, err := ids.ToID(bytes) + return &testTx{ + id: id, + }, err } type testSet struct { - set set.Set[*testTx] + txs map[ids.ID]*testTx bloom *BloomFilter onAdd func(tx *testTx) } -func (t testSet) Add(gossipable *testTx) error { - t.set.Add(gossipable) +func (t *testSet) Add(gossipable *testTx) error { + if _, ok := t.txs[gossipable.id]; ok { + return fmt.Errorf("%s already present", gossipable.id) + } + + t.txs[gossipable.id] = gossipable t.bloom.Add(gossipable) if t.onAdd != nil { t.onAdd(gossipable) @@ -47,15 +56,14 @@ func (t testSet) Add(gossipable *testTx) error { return nil } -func (t testSet) Iterate(f func(gossipable *testTx) bool) { - for tx := range t.set { +func (t *testSet) Iterate(f func(gossipable *testTx) bool) { + for _, tx := range t.txs { if !f(tx) { return } } } -func (t testSet) GetFilter() ([]byte, []byte, error) { - bloom, err := t.bloom.Bloom.MarshalBinary() - return bloom, t.bloom.Salt[:], err +func (t *testSet) GetFilter() ([]byte, []byte) { + return t.bloom.Marshal() } diff --git a/network/p2p/handler.go b/network/p2p/handler.go index b85195a5255c..3ff4de29037e 100644 --- a/network/p2p/handler.go +++ b/network/p2p/handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p @@ -20,6 +20,7 @@ var ( ErrNotValidator = errors.New("not a validator") _ Handler = (*NoOpHandler)(nil) + _ Handler = (*TestHandler)(nil) _ Handler = (*ValidatorHandler)(nil) ) @@ -63,28 +64,48 @@ func (NoOpHandler) CrossChainAppRequest(context.Context, ids.ID, time.Time, []by return nil, nil } +func NewValidatorHandler( + handler Handler, + validatorSet ValidatorSet, + log logging.Logger, +) *ValidatorHandler { + return &ValidatorHandler{ + handler: handler, + validatorSet: validatorSet, + log: log, + } +} + // ValidatorHandler drops messages from non-validators type ValidatorHandler struct { - Handler - ValidatorSet ValidatorSet - Log logging.Logger + handler Handler + validatorSet ValidatorSet + log logging.Logger } func (v ValidatorHandler) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) { - if !v.ValidatorSet.Has(ctx, nodeID) { - v.Log.Debug("dropping message", zap.Stringer("nodeID", nodeID)) + if !v.validatorSet.Has(ctx, nodeID) { + v.log.Debug( + "dropping message", + zap.Stringer("nodeID", nodeID), + zap.String("reason", "not a validator"), + ) return } - v.Handler.AppGossip(ctx, nodeID, gossipBytes) + v.handler.AppGossip(ctx, nodeID, gossipBytes) } func (v ValidatorHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, error) { - if !v.ValidatorSet.Has(ctx, nodeID) { + if !v.validatorSet.Has(ctx, nodeID) { return nil, ErrNotValidator } - return v.Handler.AppRequest(ctx, nodeID, deadline, requestBytes) + return v.handler.AppRequest(ctx, nodeID, deadline, requestBytes) +} + +func (v ValidatorHandler) CrossChainAppRequest(ctx context.Context, chainID ids.ID, deadline time.Time, requestBytes []byte) ([]byte, error) { + return v.handler.CrossChainAppRequest(ctx, chainID, deadline, requestBytes) } // responder automatically sends the response for a given request @@ -131,3 +152,33 @@ func (r *responder) CrossChainAppRequest(ctx context.Context, chainID ids.ID, re return r.sender.SendCrossChainAppResponse(ctx, chainID, requestID, appResponse) } + +type TestHandler struct { + AppGossipF func(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) + AppRequestF func(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, error) + CrossChainAppRequestF func(ctx context.Context, chainID ids.ID, deadline time.Time, requestBytes []byte) ([]byte, error) +} + +func (t TestHandler) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) { + if t.AppGossipF == nil { + return + } + + t.AppGossipF(ctx, nodeID, gossipBytes) +} + +func (t TestHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, error) { + if t.AppRequestF == nil { + return nil, nil + } + + return t.AppRequestF(ctx, nodeID, deadline, requestBytes) +} + +func (t TestHandler) CrossChainAppRequest(ctx context.Context, chainID ids.ID, deadline time.Time, requestBytes []byte) ([]byte, error) { + if t.CrossChainAppRequestF == nil { + return nil, nil + } + + return t.CrossChainAppRequestF(ctx, chainID, deadline, requestBytes) +} diff --git a/network/p2p/handler_test.go b/network/p2p/handler_test.go index b7a1b6bec899..0633b70f00a8 100644 --- a/network/p2p/handler_test.go +++ b/network/p2p/handler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p @@ -55,15 +55,15 @@ func TestValidatorHandlerAppGossip(t *testing.T) { require := require.New(t) called := false - handler := ValidatorHandler{ - Handler: testHandler{ - appGossipF: func(context.Context, ids.NodeID, []byte) { + handler := NewValidatorHandler( + &TestHandler{ + AppGossipF: func(context.Context, ids.NodeID, []byte) { called = true }, }, - ValidatorSet: tt.validatorSet, - Log: logging.NoLog{}, - } + tt.validatorSet, + logging.NoLog{}, + ) handler.AppGossip(context.Background(), tt.nodeID, []byte("foobar")) require.Equal(tt.expected, called) @@ -100,11 +100,11 @@ func TestValidatorHandlerAppRequest(t *testing.T) { t.Run(tt.name, func(t *testing.T) { require := require.New(t) - handler := ValidatorHandler{ - Handler: NoOpHandler{}, - ValidatorSet: tt.validatorSet, - Log: logging.NoLog{}, - } + handler := NewValidatorHandler( + NoOpHandler{}, + tt.validatorSet, + logging.NoLog{}, + ) _, err := handler.AppRequest(context.Background(), tt.nodeID, time.Time{}, []byte("foobar")) require.ErrorIs(err, tt.expected) diff --git a/network/p2p/mocks/mock_handler.go b/network/p2p/mocks/mock_handler.go deleted file mode 100644 index 0d4147d23183..000000000000 --- a/network/p2p/mocks/mock_handler.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/network/p2p (interfaces: Handler) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - context "context" - reflect "reflect" - time "time" - - ids "github.com/ava-labs/avalanchego/ids" - gomock "go.uber.org/mock/gomock" -) - -// MockHandler is a mock of Handler interface. -type MockHandler struct { - ctrl *gomock.Controller - recorder *MockHandlerMockRecorder -} - -// MockHandlerMockRecorder is the mock recorder for MockHandler. -type MockHandlerMockRecorder struct { - mock *MockHandler -} - -// NewMockHandler creates a new mock instance. -func NewMockHandler(ctrl *gomock.Controller) *MockHandler { - mock := &MockHandler{ctrl: ctrl} - mock.recorder = &MockHandlerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockHandler) EXPECT() *MockHandlerMockRecorder { - return m.recorder -} - -// AppGossip mocks base method. -func (m *MockHandler) AppGossip(arg0 context.Context, arg1 ids.NodeID, arg2 []byte) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "AppGossip", arg0, arg1, arg2) -} - -// AppGossip indicates an expected call of AppGossip. -func (mr *MockHandlerMockRecorder) AppGossip(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppGossip", reflect.TypeOf((*MockHandler)(nil).AppGossip), arg0, arg1, arg2) -} - -// AppRequest mocks base method. -func (m *MockHandler) AppRequest(arg0 context.Context, arg1 ids.NodeID, arg2 time.Time, arg3 []byte) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AppRequest", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AppRequest indicates an expected call of AppRequest. -func (mr *MockHandlerMockRecorder) AppRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppRequest", reflect.TypeOf((*MockHandler)(nil).AppRequest), arg0, arg1, arg2, arg3) -} - -// CrossChainAppRequest mocks base method. -func (m *MockHandler) CrossChainAppRequest(arg0 context.Context, arg1 ids.ID, arg2 time.Time, arg3 []byte) ([]byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CrossChainAppRequest", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].([]byte) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CrossChainAppRequest indicates an expected call of CrossChainAppRequest. -func (mr *MockHandlerMockRecorder) CrossChainAppRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CrossChainAppRequest", reflect.TypeOf((*MockHandler)(nil).CrossChainAppRequest), arg0, arg1, arg2, arg3) -} diff --git a/network/p2p/network.go b/network/p2p/network.go index 444c2e4b9408..604d06db617f 100644 --- a/network/p2p/network.go +++ b/network/p2p/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p @@ -6,6 +6,7 @@ package p2p import ( "context" "encoding/binary" + "strconv" "sync" "time" @@ -14,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/version" @@ -23,6 +25,9 @@ var ( _ validators.Connector = (*Network)(nil) _ common.AppHandler = (*Network)(nil) _ NodeSampler = (*peerSampler)(nil) + + handlerLabel = "handlerID" + labelNames = []string{handlerLabel} ) // ClientOption configures Client @@ -53,17 +58,108 @@ type clientOptions struct { func NewNetwork( log logging.Logger, sender common.AppSender, - metrics prometheus.Registerer, + registerer prometheus.Registerer, namespace string, -) *Network { - return &Network{ - Peers: &Peers{}, - log: log, - sender: sender, - metrics: metrics, - namespace: namespace, - router: newRouter(log, sender, metrics, namespace), +) (*Network, error) { + metrics := metrics{ + appRequestTime: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "app_request_time", + Help: "app request time (ns)", + }, labelNames), + appRequestCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "app_request_count", + Help: "app request count (n)", + }, labelNames), + appResponseTime: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "app_response_time", + Help: "app response time (ns)", + }, labelNames), + appResponseCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "app_response_count", + Help: "app response count (n)", + }, labelNames), + appRequestFailedTime: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "app_request_failed_time", + Help: "app request failed time (ns)", + }, labelNames), + appRequestFailedCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "app_request_failed_count", + Help: "app request failed count (ns)", + }, labelNames), + appGossipTime: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "app_gossip_time", + Help: "app gossip time (ns)", + }, labelNames), + appGossipCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "app_gossip_count", + Help: "app gossip count (n)", + }, labelNames), + crossChainAppRequestTime: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "cross_chain_app_request_time", + Help: "cross chain app request time (ns)", + }, labelNames), + crossChainAppRequestCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "cross_chain_app_request_count", + Help: "cross chain app request count (n)", + }, labelNames), + crossChainAppResponseTime: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "cross_chain_app_response_time", + Help: "cross chain app response time (ns)", + }, labelNames), + crossChainAppResponseCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "cross_chain_app_response_count", + Help: "cross chain app response count (n)", + }, labelNames), + crossChainAppRequestFailedTime: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "cross_chain_app_request_failed_time", + Help: "cross chain app request failed time (ns)", + }, labelNames), + crossChainAppRequestFailedCount: prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: namespace, + Name: "cross_chain_app_request_failed_count", + Help: "cross chain app request failed count (n)", + }, labelNames), + } + + err := utils.Err( + registerer.Register(metrics.appRequestTime), + registerer.Register(metrics.appRequestCount), + registerer.Register(metrics.appResponseTime), + registerer.Register(metrics.appResponseCount), + registerer.Register(metrics.appRequestFailedTime), + registerer.Register(metrics.appRequestFailedCount), + registerer.Register(metrics.appGossipTime), + registerer.Register(metrics.appGossipCount), + registerer.Register(metrics.crossChainAppRequestTime), + registerer.Register(metrics.crossChainAppRequestCount), + registerer.Register(metrics.crossChainAppResponseTime), + registerer.Register(metrics.crossChainAppResponseCount), + registerer.Register(metrics.crossChainAppRequestFailedTime), + registerer.Register(metrics.crossChainAppRequestFailedCount), + ) + if err != nil { + return nil, err } + + return &Network{ + Peers: &Peers{}, + log: log, + sender: sender, + router: newRouter(log, sender, metrics), + }, nil } // Network exposes networking state and supports building p2p application @@ -71,10 +167,8 @@ func NewNetwork( type Network struct { Peers *Peers - log logging.Logger - sender common.AppSender - metrics prometheus.Registerer - namespace string + log logging.Logger + sender common.AppSender router *router } @@ -87,8 +181,8 @@ func (n *Network) AppResponse(ctx context.Context, nodeID ids.NodeID, requestID return n.router.AppResponse(ctx, nodeID, requestID, response) } -func (n *Network) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error { - return n.router.AppRequestFailed(ctx, nodeID, requestID) +func (n *Network) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *common.AppError) error { + return n.router.AppRequestFailed(ctx, nodeID, requestID, appErr) } func (n *Network) AppGossip(ctx context.Context, nodeID ids.NodeID, msg []byte) error { @@ -103,8 +197,8 @@ func (n *Network) CrossChainAppResponse(ctx context.Context, chainID ids.ID, req return n.router.CrossChainAppResponse(ctx, chainID, requestID, response) } -func (n *Network) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32) error { - return n.router.CrossChainAppRequestFailed(ctx, chainID, requestID) +func (n *Network) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *common.AppError) error { + return n.router.CrossChainAppRequestFailed(ctx, chainID, requestID, appErr) } func (n *Network) Connected(_ context.Context, nodeID ids.NodeID, _ *version.Application) error { @@ -117,16 +211,12 @@ func (n *Network) Disconnected(_ context.Context, nodeID ids.NodeID) error { return nil } -// NewAppProtocol reserves an identifier for an application protocol handler and -// returns a Client that can be used to send messages for the corresponding -// protocol. -func (n *Network) NewAppProtocol(handlerID uint64, handler Handler, options ...ClientOption) (*Client, error) { - if err := n.router.addHandler(handlerID, handler); err != nil { - return nil, err - } - +// NewClient returns a Client that can be used to send messages for the +// corresponding protocol. +func (n *Network) NewClient(handlerID uint64, options ...ClientOption) *Client { client := &Client{ handlerID: handlerID, + handlerIDStr: strconv.FormatUint(handlerID, 10), handlerPrefix: binary.AppendUvarint(nil, handlerID), sender: n.sender, router: n.router, @@ -141,7 +231,12 @@ func (n *Network) NewAppProtocol(handlerID uint64, handler Handler, options ...C option.apply(client.options) } - return client, nil + return client +} + +// AddHandler reserves an identifier for an application protocol +func (n *Network) AddHandler(handlerID uint64, handler Handler) error { + return n.router.addHandler(handlerID, handler) } // Peers contains metadata about the current set of connected peers diff --git a/network/p2p/network_test.go b/network/p2p/network_test.go index 590858a0c467..3bf902c38035 100644 --- a/network/p2p/network_test.go +++ b/network/p2p/network_test.go @@ -1,11 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p import ( "context" - "sync" "testing" "time" @@ -13,10 +12,7 @@ import ( "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/network/p2p/mocks" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/logging" @@ -25,289 +21,350 @@ import ( "github.com/ava-labs/avalanchego/version" ) +const ( + handlerID = 123 + handlerPrefix = byte(handlerID) +) + +var errFoo = &common.AppError{ + Code: 123, + Message: "foo", +} + +func TestMessageRouting(t *testing.T) { + require := require.New(t) + ctx := context.Background() + wantNodeID := ids.GenerateTestNodeID() + wantChainID := ids.GenerateTestID() + wantMsg := []byte("message") + + var appGossipCalled, appRequestCalled, crossChainAppRequestCalled bool + testHandler := &TestHandler{ + AppGossipF: func(_ context.Context, nodeID ids.NodeID, msg []byte) { + appGossipCalled = true + require.Equal(wantNodeID, nodeID) + require.Equal(wantMsg, msg) + }, + AppRequestF: func(_ context.Context, nodeID ids.NodeID, _ time.Time, msg []byte) ([]byte, error) { + appRequestCalled = true + require.Equal(wantNodeID, nodeID) + require.Equal(wantMsg, msg) + return nil, nil + }, + CrossChainAppRequestF: func(_ context.Context, chainID ids.ID, _ time.Time, msg []byte) ([]byte, error) { + crossChainAppRequestCalled = true + require.Equal(wantChainID, chainID) + require.Equal(wantMsg, msg) + return nil, nil + }, + } + + sender := &common.FakeSender{ + SentAppGossip: make(chan []byte, 1), + SentAppRequest: make(chan []byte, 1), + SentCrossChainAppRequest: make(chan []byte, 1), + } + + network, err := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + require.NoError(err) + require.NoError(network.AddHandler(1, testHandler)) + client := network.NewClient(1) + + require.NoError(client.AppGossip(ctx, wantMsg)) + require.NoError(network.AppGossip(ctx, wantNodeID, <-sender.SentAppGossip)) + require.True(appGossipCalled) + + require.NoError(client.AppRequest(ctx, set.Of(ids.EmptyNodeID), wantMsg, func(context.Context, ids.NodeID, []byte, error) {})) + require.NoError(network.AppRequest(ctx, wantNodeID, 1, time.Time{}, <-sender.SentAppRequest)) + require.True(appRequestCalled) + + require.NoError(client.CrossChainAppRequest(ctx, ids.Empty, wantMsg, func(context.Context, ids.ID, []byte, error) {})) + require.NoError(network.CrossChainAppRequest(ctx, wantChainID, 1, time.Time{}, <-sender.SentCrossChainAppRequest)) + require.True(crossChainAppRequestCalled) +} + +// Tests that the Client prefixes messages with the handler prefix +func TestClientPrefixesMessages(t *testing.T) { + require := require.New(t) + ctx := context.Background() + + sender := common.FakeSender{ + SentAppRequest: make(chan []byte, 1), + SentAppGossip: make(chan []byte, 1), + SentAppGossipSpecific: make(chan []byte, 1), + SentCrossChainAppRequest: make(chan []byte, 1), + } + + network, err := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + require.NoError(err) + require.NoError(network.Connected(ctx, ids.EmptyNodeID, nil)) + client := network.NewClient(handlerID) + + want := []byte("message") + + require.NoError(client.AppRequest( + ctx, + set.Of(ids.EmptyNodeID), + want, + func(context.Context, ids.NodeID, []byte, error) {}, + )) + gotAppRequest := <-sender.SentAppRequest + require.Equal(handlerPrefix, gotAppRequest[0]) + require.Equal(want, gotAppRequest[1:]) + + require.NoError(client.AppRequestAny( + ctx, + want, + func(context.Context, ids.NodeID, []byte, error) {}, + )) + gotAppRequest = <-sender.SentAppRequest + require.Equal(handlerPrefix, gotAppRequest[0]) + require.Equal(want, gotAppRequest[1:]) + + require.NoError(client.CrossChainAppRequest( + ctx, + ids.Empty, + want, + func(context.Context, ids.ID, []byte, error) {}, + )) + gotCrossChainAppRequest := <-sender.SentCrossChainAppRequest + require.Equal(handlerPrefix, gotCrossChainAppRequest[0]) + require.Equal(want, gotCrossChainAppRequest[1:]) + + require.NoError(client.AppGossip(ctx, want)) + gotAppGossip := <-sender.SentAppGossip + require.Equal(handlerPrefix, gotAppGossip[0]) + require.Equal(want, gotAppGossip[1:]) + + require.NoError(client.AppGossipSpecific(ctx, set.Of(ids.EmptyNodeID), want)) + gotAppGossip = <-sender.SentAppGossipSpecific + require.Equal(handlerPrefix, gotAppGossip[0]) + require.Equal(want, gotAppGossip[1:]) +} + +// Tests that the Client callback is called on a successful response func TestAppRequestResponse(t *testing.T) { - handlerID := uint64(0x0) - request := []byte("request") - response := []byte("response") - nodeID := ids.GenerateTestNodeID() - chainID := ids.GenerateTestID() + require := require.New(t) + ctx := context.Background() - ctxKey := new(string) - ctxVal := new(string) - *ctxKey = "foo" - *ctxVal = "bar" + sender := common.FakeSender{ + SentAppRequest: make(chan []byte, 1), + } + network, err := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + require.NoError(err) + client := network.NewClient(handlerID) - tests := []struct { - name string - requestFunc func(t *testing.T, network *Network, client *Client, sender *common.SenderTest, handler *mocks.MockHandler, wg *sync.WaitGroup) - }{ - { - name: "app request", - requestFunc: func(t *testing.T, network *Network, client *Client, sender *common.SenderTest, handler *mocks.MockHandler, wg *sync.WaitGroup) { - sender.SendAppRequestF = func(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error { - for range nodeIDs { - go func() { - require.NoError(t, network.AppRequest(ctx, nodeID, requestID, time.Time{}, request)) - }() - } + wantResponse := []byte("response") + wantNodeID := ids.GenerateTestNodeID() + done := make(chan struct{}) - return nil - } - sender.SendAppResponseF = func(ctx context.Context, _ ids.NodeID, requestID uint32, response []byte) error { - go func() { - ctx = context.WithValue(ctx, ctxKey, ctxVal) - require.NoError(t, network.AppResponse(ctx, nodeID, requestID, response)) - }() + callback := func(_ context.Context, gotNodeID ids.NodeID, gotResponse []byte, err error) { + require.Equal(wantNodeID, gotNodeID) + require.NoError(err) + require.Equal(wantResponse, gotResponse) - return nil - } - handler.EXPECT(). - AppRequest(context.Background(), nodeID, gomock.Any(), request). - DoAndReturn(func(context.Context, ids.NodeID, time.Time, []byte) ([]byte, error) { - return response, nil - }) - - callback := func(ctx context.Context, actualNodeID ids.NodeID, actualResponse []byte, err error) { - defer wg.Done() - - require.NoError(t, err) - require.Equal(t, ctxVal, ctx.Value(ctxKey)) - require.Equal(t, nodeID, actualNodeID) - require.Equal(t, response, actualResponse) - } + close(done) + } - require.NoError(t, client.AppRequestAny(context.Background(), request, callback)) - }, - }, - { - name: "app request failed", - requestFunc: func(t *testing.T, network *Network, client *Client, sender *common.SenderTest, handler *mocks.MockHandler, wg *sync.WaitGroup) { - sender.SendAppRequestF = func(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error { - for range nodeIDs { - go func() { - require.NoError(t, network.AppRequestFailed(ctx, nodeID, requestID)) - }() - } + want := []byte("request") + require.NoError(client.AppRequest(ctx, set.Of(wantNodeID), want, callback)) + got := <-sender.SentAppRequest + require.Equal(handlerPrefix, got[0]) + require.Equal(want, got[1:]) - return nil - } + require.NoError(network.AppResponse(ctx, wantNodeID, 1, wantResponse)) + <-done +} - callback := func(_ context.Context, actualNodeID ids.NodeID, actualResponse []byte, err error) { - defer wg.Done() +// Tests that the Client callback is given an error if the request fails +func TestAppRequestFailed(t *testing.T) { + require := require.New(t) + ctx := context.Background() - require.ErrorIs(t, err, ErrAppRequestFailed) - require.Equal(t, nodeID, actualNodeID) - require.Nil(t, actualResponse) - } + sender := common.FakeSender{ + SentAppRequest: make(chan []byte, 1), + } + network, err := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + require.NoError(err) + client := network.NewClient(handlerID) - require.NoError(t, client.AppRequest(context.Background(), set.Of(nodeID), request, callback)) - }, - }, - { - name: "cross-chain app request", - requestFunc: func(t *testing.T, network *Network, client *Client, sender *common.SenderTest, handler *mocks.MockHandler, wg *sync.WaitGroup) { - chainID := ids.GenerateTestID() - sender.SendCrossChainAppRequestF = func(ctx context.Context, chainID ids.ID, requestID uint32, request []byte) { - go func() { - require.NoError(t, network.CrossChainAppRequest(ctx, chainID, requestID, time.Time{}, request)) - }() - } - sender.SendCrossChainAppResponseF = func(ctx context.Context, chainID ids.ID, requestID uint32, response []byte) { - go func() { - ctx = context.WithValue(ctx, ctxKey, ctxVal) - require.NoError(t, network.CrossChainAppResponse(ctx, chainID, requestID, response)) - }() - } - handler.EXPECT(). - CrossChainAppRequest(context.Background(), chainID, gomock.Any(), request). - DoAndReturn(func(context.Context, ids.ID, time.Time, []byte) ([]byte, error) { - return response, nil - }) - - callback := func(ctx context.Context, actualChainID ids.ID, actualResponse []byte, err error) { - defer wg.Done() - require.NoError(t, err) - require.Equal(t, ctxVal, ctx.Value(ctxKey)) - require.Equal(t, chainID, actualChainID) - require.Equal(t, response, actualResponse) - } + wantNodeID := ids.GenerateTestNodeID() + done := make(chan struct{}) - require.NoError(t, client.CrossChainAppRequest(context.Background(), chainID, request, callback)) - }, - }, - { - name: "cross-chain app request failed", - requestFunc: func(t *testing.T, network *Network, client *Client, sender *common.SenderTest, handler *mocks.MockHandler, wg *sync.WaitGroup) { - sender.SendCrossChainAppRequestF = func(ctx context.Context, chainID ids.ID, requestID uint32, request []byte) { - go func() { - require.NoError(t, network.CrossChainAppRequestFailed(ctx, chainID, requestID)) - }() - } + callback := func(_ context.Context, gotNodeID ids.NodeID, gotResponse []byte, err error) { + require.Equal(wantNodeID, gotNodeID) + require.ErrorIs(err, errFoo) + require.Nil(gotResponse) - callback := func(_ context.Context, actualChainID ids.ID, actualResponse []byte, err error) { - defer wg.Done() + close(done) + } - require.ErrorIs(t, err, ErrAppRequestFailed) - require.Equal(t, chainID, actualChainID) - require.Nil(t, actualResponse) - } + require.NoError(client.AppRequest(ctx, set.Of(wantNodeID), []byte("request"), callback)) + <-sender.SentAppRequest - require.NoError(t, client.CrossChainAppRequest(context.Background(), chainID, request, callback)) - }, - }, - { - name: "app gossip", - requestFunc: func(t *testing.T, network *Network, client *Client, sender *common.SenderTest, handler *mocks.MockHandler, wg *sync.WaitGroup) { - sender.SendAppGossipF = func(ctx context.Context, gossip []byte) error { - go func() { - require.NoError(t, network.AppGossip(ctx, nodeID, gossip)) - }() + require.NoError(network.AppRequestFailed(ctx, wantNodeID, 1, errFoo)) + <-done +} - return nil - } - handler.EXPECT(). - AppGossip(context.Background(), nodeID, request). - DoAndReturn(func(context.Context, ids.NodeID, []byte) error { - defer wg.Done() - return nil - }) - - require.NoError(t, client.AppGossip(context.Background(), request)) - }, - }, - { - name: "app gossip specific", - requestFunc: func(t *testing.T, network *Network, client *Client, sender *common.SenderTest, handler *mocks.MockHandler, wg *sync.WaitGroup) { - sender.SendAppGossipSpecificF = func(ctx context.Context, nodeIDs set.Set[ids.NodeID], bytes []byte) error { - for n := range nodeIDs { - nodeID := n - go func() { - require.NoError(t, network.AppGossip(ctx, nodeID, bytes)) - }() - } +// Tests that the Client callback is called on a successful response +func TestCrossChainAppRequestResponse(t *testing.T) { + require := require.New(t) + ctx := context.Background() - return nil - } - handler.EXPECT(). - AppGossip(context.Background(), nodeID, request). - DoAndReturn(func(context.Context, ids.NodeID, []byte) error { - defer wg.Done() - return nil - }) - - require.NoError(t, client.AppGossipSpecific(context.Background(), set.Of(nodeID), request)) - }, - }, + sender := common.FakeSender{ + SentCrossChainAppRequest: make(chan []byte, 1), } + network, err := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + require.NoError(err) + client := network.NewClient(handlerID) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) + wantChainID := ids.GenerateTestID() + wantResponse := []byte("response") + done := make(chan struct{}) - sender := &common.SenderTest{} - handler := mocks.NewMockHandler(ctrl) - n := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") - require.NoError(n.Connected(context.Background(), nodeID, nil)) - client, err := n.NewAppProtocol(handlerID, handler) - require.NoError(err) + callback := func(_ context.Context, gotChainID ids.ID, gotResponse []byte, err error) { + require.Equal(wantChainID, gotChainID) + require.NoError(err) + require.Equal(wantResponse, gotResponse) - wg := &sync.WaitGroup{} - wg.Add(1) - tt.requestFunc(t, n, client, sender, handler, wg) - wg.Wait() - }) + close(done) } + + require.NoError(client.CrossChainAppRequest(ctx, wantChainID, []byte("request"), callback)) + <-sender.SentCrossChainAppRequest + + require.NoError(network.CrossChainAppResponse(ctx, wantChainID, 1, wantResponse)) + <-done } -func TestNetworkDropMessage(t *testing.T) { - unregistered := byte(0x0) +// Tests that the Client callback is given an error if the request fails +func TestCrossChainAppRequestFailed(t *testing.T) { + require := require.New(t) + ctx := context.Background() + + sender := common.FakeSender{ + SentCrossChainAppRequest: make(chan []byte, 1), + } + network, err := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + require.NoError(err) + client := network.NewClient(handlerID) + + wantChainID := ids.GenerateTestID() + done := make(chan struct{}) + + callback := func(_ context.Context, gotChainID ids.ID, gotResponse []byte, err error) { + require.Equal(wantChainID, gotChainID) + require.ErrorIs(err, errFoo) + require.Nil(gotResponse) + + close(done) + } + + require.NoError(client.CrossChainAppRequest(ctx, wantChainID, []byte("request"), callback)) + <-sender.SentCrossChainAppRequest + require.NoError(network.CrossChainAppRequestFailed(ctx, wantChainID, 1, errFoo)) + <-done +} + +// Messages for unregistered handlers should be dropped gracefully +func TestMessageForUnregisteredHandler(t *testing.T) { tests := []struct { - name string - requestFunc func(network *Network) error - err error + name string + msg []byte }{ { - name: "drop unregistered app request message", - requestFunc: func(network *Network) error { - return network.AppRequest(context.Background(), ids.GenerateTestNodeID(), 0, time.Time{}, []byte{unregistered}) - }, - err: nil, + name: "nil", + msg: nil, }, { - name: "drop empty app request message", - requestFunc: func(network *Network) error { - return network.AppRequest(context.Background(), ids.GenerateTestNodeID(), 0, time.Time{}, []byte{}) - }, - err: nil, + name: "empty", + msg: []byte{}, }, { - name: "drop unregistered cross-chain app request message", - requestFunc: func(network *Network) error { - return network.CrossChainAppRequest(context.Background(), ids.GenerateTestID(), 0, time.Time{}, []byte{unregistered}) - }, - err: nil, - }, - { - name: "drop empty cross-chain app request message", - requestFunc: func(network *Network) error { - return network.CrossChainAppRequest(context.Background(), ids.GenerateTestID(), 0, time.Time{}, []byte{}) - }, - err: nil, - }, - { - name: "drop unregistered gossip message", - requestFunc: func(network *Network) error { - return network.AppGossip(context.Background(), ids.GenerateTestNodeID(), []byte{unregistered}) - }, - err: nil, - }, - { - name: "drop empty gossip message", - requestFunc: func(network *Network) error { - return network.AppGossip(context.Background(), ids.GenerateTestNodeID(), []byte{}) - }, - err: nil, - }, - { - name: "drop unrequested app request failed", - requestFunc: func(network *Network) error { - return network.AppRequestFailed(context.Background(), ids.GenerateTestNodeID(), 0) - }, - err: ErrUnrequestedResponse, + name: "non-empty", + msg: []byte("foobar"), }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + ctx := context.Background() + handler := &TestHandler{ + AppGossipF: func(context.Context, ids.NodeID, []byte) { + require.Fail("should not be called") + }, + AppRequestF: func(context.Context, ids.NodeID, time.Time, []byte) ([]byte, error) { + require.Fail("should not be called") + return nil, nil + }, + CrossChainAppRequestF: func(context.Context, ids.ID, time.Time, []byte) ([]byte, error) { + require.Fail("should not be called") + return nil, nil + }, + } + network, err := NewNetwork(logging.NoLog{}, nil, prometheus.NewRegistry(), "") + require.NoError(err) + require.NoError(network.AddHandler(handlerID, handler)) + + require.Nil(network.AppRequest(ctx, ids.EmptyNodeID, 0, time.Time{}, tt.msg)) + require.Nil(network.AppGossip(ctx, ids.EmptyNodeID, tt.msg)) + require.Nil(network.CrossChainAppRequest(ctx, ids.Empty, 0, time.Time{}, tt.msg)) + }) + } +} + +// A response or timeout for a request we never made should return an error +func TestResponseForUnrequestedRequest(t *testing.T) { + tests := []struct { + name string + msg []byte + }{ { - name: "drop unrequested app response", - requestFunc: func(network *Network) error { - return network.AppResponse(context.Background(), ids.GenerateTestNodeID(), 0, nil) - }, - err: ErrUnrequestedResponse, + name: "nil", + msg: nil, }, { - name: "drop unrequested cross-chain request failed", - requestFunc: func(network *Network) error { - return network.CrossChainAppRequestFailed(context.Background(), ids.GenerateTestID(), 0) - }, - err: ErrUnrequestedResponse, + name: "empty", + msg: []byte{}, }, { - name: "drop unrequested cross-chain response", - requestFunc: func(network *Network) error { - return network.CrossChainAppResponse(context.Background(), ids.GenerateTestID(), 0, nil) - }, - err: ErrUnrequestedResponse, + name: "non-empty", + msg: []byte("foobar"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require := require.New(t) + ctx := context.Background() + handler := &TestHandler{ + AppGossipF: func(context.Context, ids.NodeID, []byte) { + require.Fail("should not be called") + }, + AppRequestF: func(context.Context, ids.NodeID, time.Time, []byte) ([]byte, error) { + require.Fail("should not be called") + return nil, nil + }, + CrossChainAppRequestF: func(context.Context, ids.ID, time.Time, []byte) ([]byte, error) { + require.Fail("should not be called") + return nil, nil + }, + } + network, err := NewNetwork(logging.NoLog{}, nil, prometheus.NewRegistry(), "") + require.NoError(err) + require.NoError(network.AddHandler(handlerID, handler)) - network := NewNetwork(logging.NoLog{}, &common.SenderTest{}, prometheus.NewRegistry(), "") + err = network.AppResponse(ctx, ids.EmptyNodeID, 0, []byte("foobar")) + require.ErrorIs(err, ErrUnrequestedResponse) + err = network.AppRequestFailed(ctx, ids.EmptyNodeID, 0, common.ErrTimeout) + require.ErrorIs(err, ErrUnrequestedResponse) + err = network.CrossChainAppResponse(ctx, ids.Empty, 0, []byte("foobar")) + require.ErrorIs(err, ErrUnrequestedResponse) + err = network.CrossChainAppRequestFailed(ctx, ids.Empty, 0, common.ErrTimeout) - err := tt.requestFunc(network) - require.ErrorIs(err, tt.err) + require.ErrorIs(err, ErrUnrequestedResponse) }) } } @@ -317,58 +374,26 @@ func TestNetworkDropMessage(t *testing.T) { // not attempt to issue another request until the previous one has cleared. func TestAppRequestDuplicateRequestIDs(t *testing.T) { require := require.New(t) - ctrl := gomock.NewController(t) + ctx := context.Background() - handler := mocks.NewMockHandler(ctrl) - sender := &common.SenderTest{ - SendAppResponseF: func(context.Context, ids.NodeID, uint32, []byte) error { - return nil - }, - } - network := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") - nodeID := ids.GenerateTestNodeID() - - requestSent := &sync.WaitGroup{} - sender.SendAppRequestF = func(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, request []byte) error { - for range nodeIDs { - requestSent.Add(1) - go func() { - require.NoError(network.AppRequest(ctx, nodeID, requestID, time.Time{}, request)) - requestSent.Done() - }() - } - - return nil + sender := &common.FakeSender{ + SentAppRequest: make(chan []byte, 1), } - timeout := &sync.WaitGroup{} - response := []byte("response") - handler.EXPECT().AppRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, nodeID ids.NodeID, deadline time.Time, request []byte) ([]byte, error) { - timeout.Wait() - return response, nil - }).AnyTimes() - - require.NoError(network.Connected(context.Background(), nodeID, nil)) - client, err := network.NewAppProtocol(0x1, handler) + network, err := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") require.NoError(err) + client := network.NewClient(0x1) - onResponse := func(ctx context.Context, nodeID ids.NodeID, got []byte, err error) { - require.NoError(err) - require.Equal(response, got) - } - - require.NoError(client.AppRequest(context.Background(), set.Of(nodeID), []byte{}, onResponse)) - requestSent.Wait() + noOpCallback := func(context.Context, ids.NodeID, []byte, error) {} + // create a request that never gets a response + network.router.requestID = 1 + require.NoError(client.AppRequest(ctx, set.Of(ids.EmptyNodeID), []byte{}, noOpCallback)) + <-sender.SentAppRequest // force the network to use the same requestID network.router.requestID = 1 - timeout.Add(1) - err = client.AppRequest(context.Background(), set.Of(nodeID), []byte{}, nil) - requestSent.Wait() + err = client.AppRequest(context.Background(), set.Of(ids.EmptyNodeID), []byte{}, noOpCallback) require.ErrorIs(err, ErrRequestPending) - - timeout.Done() } // Sample should always return up to [limit] peers, and less if fewer than @@ -437,7 +462,8 @@ func TestPeersSample(t *testing.T) { t.Run(tt.name, func(t *testing.T) { require := require.New(t) - network := NewNetwork(logging.NoLog{}, &common.SenderTest{}, prometheus.NewRegistry(), "") + network, err := NewNetwork(logging.NoLog{}, &common.FakeSender{}, prometheus.NewRegistry(), "") + require.NoError(err) for connected := range tt.connected { require.NoError(network.Connected(context.Background(), connected, nil)) @@ -481,23 +507,22 @@ func TestAppRequestAnyNodeSelection(t *testing.T) { sent := set.Set[ids.NodeID]{} sender := &common.SenderTest{ SendAppRequestF: func(_ context.Context, nodeIDs set.Set[ids.NodeID], _ uint32, _ []byte) error { - for nodeID := range nodeIDs { - sent.Add(nodeID) - } + sent = nodeIDs return nil }, } - n := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + n, err := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + require.NoError(err) for _, peer := range tt.peers { require.NoError(n.Connected(context.Background(), peer, &version.Application{})) } - client, err := n.NewAppProtocol(1, nil) - require.NoError(err) + client := n.NewClient(1) err = client.AppRequestAny(context.Background(), []byte("foobar"), nil) require.ErrorIs(err, tt.expected) + require.Subset(tt.peers, sent.List()) }) } } @@ -576,14 +601,14 @@ func TestNodeSamplerClientOption(t *testing.T) { return nil }, } - network := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + network, err := NewNetwork(logging.NoLog{}, sender, prometheus.NewRegistry(), "") + require.NoError(err) ctx := context.Background() for _, peer := range tt.peers { require.NoError(network.Connected(ctx, peer, nil)) } - client, err := network.NewAppProtocol(0x0, nil, tt.option(t, network)) - require.NoError(err) + client := network.NewClient(0, tt.option(t, network)) if err = client.AppRequestAny(ctx, []byte("request"), nil); err != nil { close(done) @@ -594,3 +619,13 @@ func TestNodeSamplerClientOption(t *testing.T) { }) } } + +// Tests that a given protocol can have more than one client +func TestMultipleClients(t *testing.T) { + require := require.New(t) + + n, err := NewNetwork(logging.NoLog{}, &common.SenderTest{}, prometheus.NewRegistry(), "") + require.NoError(err) + _ = n.NewClient(0) + _ = n.NewClient(0) +} diff --git a/network/p2p/node_sampler.go b/network/p2p/node_sampler.go index 057a175027a4..5bb3815e3b90 100644 --- a/network/p2p/node_sampler.go +++ b/network/p2p/node_sampler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p diff --git a/network/p2p/peer_tracker.go b/network/p2p/peer_tracker.go index 338d890bed22..c0eda693859b 100644 --- a/network/p2p/peer_tracker.go +++ b/network/p2p/peer_tracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p diff --git a/network/p2p/peer_tracker_test.go b/network/p2p/peer_tracker_test.go index d7c38b828e9d..bf771d177841 100644 --- a/network/p2p/peer_tracker_test.go +++ b/network/p2p/peer_tracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p diff --git a/network/p2p/router.go b/network/p2p/router.go index 110e9b6de627..82fdbf24fbc3 100644 --- a/network/p2p/router.go +++ b/network/p2p/router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p @@ -8,6 +8,7 @@ import ( "encoding/binary" "errors" "fmt" + "strconv" "sync" "time" @@ -19,7 +20,6 @@ import ( "github.com/ava-labs/avalanchego/message" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/metric" ) var ( @@ -29,40 +29,46 @@ var ( _ common.AppHandler = (*router)(nil) ) -type metrics struct { - appRequestTime metric.Averager - appRequestFailedTime metric.Averager - appResponseTime metric.Averager - appGossipTime metric.Averager - crossChainAppRequestTime metric.Averager - crossChainAppRequestFailedTime metric.Averager - crossChainAppResponseTime metric.Averager -} - type pendingAppRequest struct { - *metrics - AppResponseCallback + handlerID string + callback AppResponseCallback } type pendingCrossChainAppRequest struct { - *metrics - CrossChainAppResponseCallback + handlerID string + callback CrossChainAppResponseCallback } // meteredHandler emits metrics for a Handler type meteredHandler struct { *responder - *metrics + metrics +} + +type metrics struct { + appRequestTime *prometheus.CounterVec + appRequestCount *prometheus.CounterVec + appResponseTime *prometheus.CounterVec + appResponseCount *prometheus.CounterVec + appRequestFailedTime *prometheus.CounterVec + appRequestFailedCount *prometheus.CounterVec + appGossipTime *prometheus.CounterVec + appGossipCount *prometheus.CounterVec + crossChainAppRequestTime *prometheus.CounterVec + crossChainAppRequestCount *prometheus.CounterVec + crossChainAppResponseTime *prometheus.CounterVec + crossChainAppResponseCount *prometheus.CounterVec + crossChainAppRequestFailedTime *prometheus.CounterVec + crossChainAppRequestFailedCount *prometheus.CounterVec } // router routes incoming application messages to the corresponding registered // app handler. App messages must be made using the registered handler's // corresponding Client. type router struct { - log logging.Logger - sender common.AppSender - metrics prometheus.Registerer - namespace string + log logging.Logger + sender common.AppSender + metrics metrics lock sync.RWMutex handlers map[uint64]*meteredHandler @@ -75,14 +81,12 @@ type router struct { func newRouter( log logging.Logger, sender common.AppSender, - metrics prometheus.Registerer, - namespace string, + metrics metrics, ) *router { return &router{ log: log, sender: sender, metrics: metrics, - namespace: namespace, handlers: make(map[uint64]*meteredHandler), pendingAppRequests: make(map[uint32]pendingAppRequest), pendingCrossChainAppRequests: make(map[uint32]pendingCrossChainAppRequest), @@ -99,76 +103,6 @@ func (r *router) addHandler(handlerID uint64, handler Handler) error { return fmt.Errorf("failed to register handler id %d: %w", handlerID, ErrExistingAppProtocol) } - appRequestTime, err := metric.NewAverager( - r.namespace, - fmt.Sprintf("handler_%d_app_request", handlerID), - "app request time (ns)", - r.metrics, - ) - if err != nil { - return fmt.Errorf("failed to register app request metric for handler_%d: %w", handlerID, err) - } - - appRequestFailedTime, err := metric.NewAverager( - r.namespace, - fmt.Sprintf("handler_%d_app_request_failed", handlerID), - "app request failed time (ns)", - r.metrics, - ) - if err != nil { - return fmt.Errorf("failed to register app request failed metric for handler_%d: %w", handlerID, err) - } - - appResponseTime, err := metric.NewAverager( - r.namespace, - fmt.Sprintf("handler_%d_app_response", handlerID), - "app response time (ns)", - r.metrics, - ) - if err != nil { - return fmt.Errorf("failed to register app response metric for handler_%d: %w", handlerID, err) - } - - appGossipTime, err := metric.NewAverager( - r.namespace, - fmt.Sprintf("handler_%d_app_gossip", handlerID), - "app gossip time (ns)", - r.metrics, - ) - if err != nil { - return fmt.Errorf("failed to register app gossip metric for handler_%d: %w", handlerID, err) - } - - crossChainAppRequestTime, err := metric.NewAverager( - r.namespace, - fmt.Sprintf("handler_%d_cross_chain_app_request", handlerID), - "cross chain app request time (ns)", - r.metrics, - ) - if err != nil { - return fmt.Errorf("failed to register cross-chain app request metric for handler_%d: %w", handlerID, err) - } - - crossChainAppRequestFailedTime, err := metric.NewAverager( - r.namespace, - fmt.Sprintf("handler_%d_cross_chain_app_request_failed", handlerID), - "app request failed time (ns)", - r.metrics, - ) - if err != nil { - return fmt.Errorf("failed to register cross-chain app request failed metric for handler_%d: %w", handlerID, err) - } - - crossChainAppResponseTime, err := metric.NewAverager( - r.namespace, - fmt.Sprintf("handler_%d_cross_chain_app_response", handlerID), - "cross chain app response time (ns)", - r.metrics, - ) - if err != nil { - return fmt.Errorf("failed to register cross-chain app response metric for handler_%d: %w", handlerID, err) - } - r.handlers[handlerID] = &meteredHandler{ responder: &responder{ Handler: handler, @@ -176,15 +110,7 @@ func (r *router) addHandler(handlerID uint64, handler Handler) error { log: r.log, sender: r.sender, }, - metrics: &metrics{ - appRequestTime: appRequestTime, - appRequestFailedTime: appRequestFailedTime, - appResponseTime: appResponseTime, - appGossipTime: appGossipTime, - crossChainAppRequestTime: crossChainAppRequestTime, - crossChainAppRequestFailedTime: crossChainAppRequestFailedTime, - crossChainAppResponseTime: crossChainAppResponseTime, - }, + metrics: r.metrics, } return nil @@ -197,7 +123,7 @@ func (r *router) addHandler(handlerID uint64, handler Handler) error { // considered fatal func (r *router) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, deadline time.Time, request []byte) error { start := time.Now() - parsedMsg, handler, ok := r.parse(request) + parsedMsg, handler, handlerID, ok := r.parse(request) if !ok { r.log.Debug("failed to process message", zap.Stringer("messageOp", message.AppRequestOp), @@ -214,7 +140,23 @@ func (r *router) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID ui return err } - handler.metrics.appRequestTime.Observe(float64(time.Since(start))) + labels := prometheus.Labels{ + handlerLabel: handlerID, + } + + metricCount, err := r.metrics.appRequestCount.GetMetricWith(labels) + if err != nil { + return err + } + + metricTime, err := r.metrics.appRequestTime.GetMetricWith(labels) + if err != nil { + return err + } + + metricCount.Inc() + metricTime.Add(float64(time.Since(start))) + return nil } @@ -223,7 +165,7 @@ func (r *router) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID ui // // Any error condition propagated outside Handler application logic is // considered fatal -func (r *router) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error { +func (r *router) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *common.AppError) error { start := time.Now() pending, ok := r.clearAppRequest(requestID) if !ok { @@ -231,8 +173,25 @@ func (r *router) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, reques return ErrUnrequestedResponse } - pending.AppResponseCallback(ctx, nodeID, nil, ErrAppRequestFailed) - pending.appRequestFailedTime.Observe(float64(time.Since(start))) + pending.callback(ctx, nodeID, nil, appErr) + + labels := prometheus.Labels{ + handlerLabel: pending.handlerID, + } + + metricCount, err := r.metrics.appRequestFailedCount.GetMetricWith(labels) + if err != nil { + return err + } + + metricTime, err := r.metrics.appRequestFailedTime.GetMetricWith(labels) + if err != nil { + return err + } + + metricCount.Inc() + metricTime.Add(float64(time.Since(start))) + return nil } @@ -249,8 +208,25 @@ func (r *router) AppResponse(ctx context.Context, nodeID ids.NodeID, requestID u return ErrUnrequestedResponse } - pending.AppResponseCallback(ctx, nodeID, response, nil) - pending.appResponseTime.Observe(float64(time.Since(start))) + pending.callback(ctx, nodeID, response, nil) + + labels := prometheus.Labels{ + handlerLabel: pending.handlerID, + } + + metricCount, err := r.metrics.appResponseCount.GetMetricWith(labels) + if err != nil { + return err + } + + metricTime, err := r.metrics.appResponseTime.GetMetricWith(labels) + if err != nil { + return err + } + + metricCount.Inc() + metricTime.Add(float64(time.Since(start))) + return nil } @@ -261,7 +237,7 @@ func (r *router) AppResponse(ctx context.Context, nodeID ids.NodeID, requestID u // considered fatal func (r *router) AppGossip(ctx context.Context, nodeID ids.NodeID, gossip []byte) error { start := time.Now() - parsedMsg, handler, ok := r.parse(gossip) + parsedMsg, handler, handlerID, ok := r.parse(gossip) if !ok { r.log.Debug("failed to process message", zap.Stringer("messageOp", message.AppGossipOp), @@ -273,7 +249,23 @@ func (r *router) AppGossip(ctx context.Context, nodeID ids.NodeID, gossip []byte handler.AppGossip(ctx, nodeID, parsedMsg) - handler.metrics.appGossipTime.Observe(float64(time.Since(start))) + labels := prometheus.Labels{ + handlerLabel: handlerID, + } + + metricCount, err := r.metrics.appGossipCount.GetMetricWith(labels) + if err != nil { + return err + } + + metricTime, err := r.metrics.appGossipTime.GetMetricWith(labels) + if err != nil { + return err + } + + metricCount.Inc() + metricTime.Add(float64(time.Since(start))) + return nil } @@ -291,7 +283,7 @@ func (r *router) CrossChainAppRequest( msg []byte, ) error { start := time.Now() - parsedMsg, handler, ok := r.parse(msg) + parsedMsg, handler, handlerID, ok := r.parse(msg) if !ok { r.log.Debug("failed to process message", zap.Stringer("messageOp", message.CrossChainAppRequestOp), @@ -307,7 +299,23 @@ func (r *router) CrossChainAppRequest( return err } - handler.metrics.crossChainAppRequestTime.Observe(float64(time.Since(start))) + labels := prometheus.Labels{ + handlerLabel: handlerID, + } + + metricCount, err := r.metrics.crossChainAppRequestCount.GetMetricWith(labels) + if err != nil { + return err + } + + metricTime, err := r.metrics.crossChainAppRequestTime.GetMetricWith(labels) + if err != nil { + return err + } + + metricCount.Inc() + metricTime.Add(float64(time.Since(start))) + return nil } @@ -316,7 +324,7 @@ func (r *router) CrossChainAppRequest( // // Any error condition propagated outside Handler application logic is // considered fatal -func (r *router) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32) error { +func (r *router) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *common.AppError) error { start := time.Now() pending, ok := r.clearCrossChainAppRequest(requestID) if !ok { @@ -324,8 +332,25 @@ func (r *router) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, return ErrUnrequestedResponse } - pending.CrossChainAppResponseCallback(ctx, chainID, nil, ErrAppRequestFailed) - pending.crossChainAppRequestFailedTime.Observe(float64(time.Since(start))) + pending.callback(ctx, chainID, nil, appErr) + + labels := prometheus.Labels{ + handlerLabel: pending.handlerID, + } + + metricCount, err := r.metrics.crossChainAppRequestFailedCount.GetMetricWith(labels) + if err != nil { + return err + } + + metricTime, err := r.metrics.crossChainAppRequestFailedTime.GetMetricWith(labels) + if err != nil { + return err + } + + metricCount.Inc() + metricTime.Add(float64(time.Since(start))) + return nil } @@ -342,8 +367,25 @@ func (r *router) CrossChainAppResponse(ctx context.Context, chainID ids.ID, requ return ErrUnrequestedResponse } - pending.CrossChainAppResponseCallback(ctx, chainID, response, nil) - pending.crossChainAppResponseTime.Observe(float64(time.Since(start))) + pending.callback(ctx, chainID, response, nil) + + labels := prometheus.Labels{ + handlerLabel: pending.handlerID, + } + + metricCount, err := r.metrics.crossChainAppResponseCount.GetMetricWith(labels) + if err != nil { + return err + } + + metricTime, err := r.metrics.crossChainAppResponseTime.GetMetricWith(labels) + if err != nil { + return err + } + + metricCount.Inc() + metricTime.Add(float64(time.Since(start))) + return nil } @@ -353,20 +395,22 @@ func (r *router) CrossChainAppResponse(ctx context.Context, chainID ids.ID, requ // Returns: // - The unprefixed protocol message. // - The protocol responder. +// - The protocol metric name. // - A boolean indicating that parsing succeeded. // // Invariant: Assumes [r.lock] isn't held. -func (r *router) parse(msg []byte) ([]byte, *meteredHandler, bool) { +func (r *router) parse(msg []byte) ([]byte, *meteredHandler, string, bool) { handlerID, bytesRead := binary.Uvarint(msg) if bytesRead <= 0 { - return nil, nil, false + return nil, nil, "", false } r.lock.RLock() defer r.lock.RUnlock() + handlerStr := strconv.FormatUint(handlerID, 10) handler, ok := r.handlers[handlerID] - return msg[bytesRead:], handler, ok + return msg[bytesRead:], handler, handlerStr, ok } // Invariant: Assumes [r.lock] isn't held. diff --git a/network/p2p/throttler.go b/network/p2p/throttler.go index de173a655266..c8f34a7ee90f 100644 --- a/network/p2p/throttler.go +++ b/network/p2p/throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p diff --git a/network/p2p/throttler_handler.go b/network/p2p/throttler_handler.go index 4dd142c400c1..8fa3df93faee 100644 --- a/network/p2p/throttler_handler.go +++ b/network/p2p/throttler_handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p @@ -20,25 +20,41 @@ var ( _ Handler = (*ThrottlerHandler)(nil) ) +func NewThrottlerHandler(handler Handler, throttler Throttler, log logging.Logger) *ThrottlerHandler { + return &ThrottlerHandler{ + handler: handler, + throttler: throttler, + log: log, + } +} + type ThrottlerHandler struct { - Handler - Throttler Throttler - Log logging.Logger + handler Handler + throttler Throttler + log logging.Logger } func (t ThrottlerHandler) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) { - if !t.Throttler.Handle(nodeID) { - t.Log.Debug("dropping message", zap.Stringer("nodeID", nodeID)) + if !t.throttler.Handle(nodeID) { + t.log.Debug( + "dropping message", + zap.Stringer("nodeID", nodeID), + zap.String("reason", "throttled"), + ) return } - t.Handler.AppGossip(ctx, nodeID, gossipBytes) + t.handler.AppGossip(ctx, nodeID, gossipBytes) } func (t ThrottlerHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, error) { - if !t.Throttler.Handle(nodeID) { + if !t.throttler.Handle(nodeID) { return nil, fmt.Errorf("dropping message from %s: %w", nodeID, ErrThrottled) } - return t.Handler.AppRequest(ctx, nodeID, deadline, requestBytes) + return t.handler.AppRequest(ctx, nodeID, deadline, requestBytes) +} + +func (t ThrottlerHandler) CrossChainAppRequest(ctx context.Context, chainID ids.ID, deadline time.Time, requestBytes []byte) ([]byte, error) { + return t.handler.CrossChainAppRequest(ctx, chainID, deadline, requestBytes) } diff --git a/network/p2p/throttler_handler_test.go b/network/p2p/throttler_handler_test.go index 1e4ed9578bf6..1f5a07069d8e 100644 --- a/network/p2p/throttler_handler_test.go +++ b/network/p2p/throttler_handler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p @@ -14,7 +14,7 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" ) -var _ Handler = (*testHandler)(nil) +var _ Handler = (*TestHandler)(nil) func TestThrottlerHandlerAppGossip(t *testing.T) { tests := []struct { @@ -37,15 +37,15 @@ func TestThrottlerHandlerAppGossip(t *testing.T) { require := require.New(t) called := false - handler := ThrottlerHandler{ - Handler: testHandler{ - appGossipF: func(context.Context, ids.NodeID, []byte) { + handler := NewThrottlerHandler( + TestHandler{ + AppGossipF: func(context.Context, ids.NodeID, []byte) { called = true }, }, - Throttler: tt.Throttler, - Log: logging.NoLog{}, - } + tt.Throttler, + logging.NoLog{}, + ) handler.AppGossip(context.Background(), ids.GenerateTestNodeID(), []byte("foobar")) require.Equal(tt.expected, called) @@ -73,43 +73,13 @@ func TestThrottlerHandlerAppRequest(t *testing.T) { t.Run(tt.name, func(t *testing.T) { require := require.New(t) - handler := ThrottlerHandler{ - Handler: NoOpHandler{}, - Throttler: tt.Throttler, - Log: logging.NoLog{}, - } + handler := NewThrottlerHandler( + NoOpHandler{}, + tt.Throttler, + logging.NoLog{}, + ) _, err := handler.AppRequest(context.Background(), ids.GenerateTestNodeID(), time.Time{}, []byte("foobar")) require.ErrorIs(err, tt.expectedErr) }) } } - -type testHandler struct { - appGossipF func(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) - appRequestF func(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, error) - crossChainAppRequestF func(ctx context.Context, chainID ids.ID, deadline time.Time, requestBytes []byte) ([]byte, error) -} - -func (t testHandler) AppGossip(ctx context.Context, nodeID ids.NodeID, gossipBytes []byte) { - if t.appGossipF == nil { - return - } - - t.appGossipF(ctx, nodeID, gossipBytes) -} - -func (t testHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, deadline time.Time, requestBytes []byte) ([]byte, error) { - if t.appRequestF == nil { - return nil, nil - } - - return t.appRequestF(ctx, nodeID, deadline, requestBytes) -} - -func (t testHandler) CrossChainAppRequest(ctx context.Context, chainID ids.ID, deadline time.Time, requestBytes []byte) ([]byte, error) { - if t.crossChainAppRequestF == nil { - return nil, nil - } - - return t.crossChainAppRequestF(ctx, chainID, deadline, requestBytes) -} diff --git a/network/p2p/throttler_test.go b/network/p2p/throttler_test.go index c7b0153e671d..3c3c56360dc1 100644 --- a/network/p2p/throttler_test.go +++ b/network/p2p/throttler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p diff --git a/network/p2p/validators.go b/network/p2p/validators.go index a780c87f0d8c..3ece6559af42 100644 --- a/network/p2p/validators.go +++ b/network/p2p/validators.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p @@ -86,6 +86,8 @@ func (v *Validators) Sample(ctx context.Context, limit int) []ids.NodeID { v.refresh(ctx) + // TODO: Account for peer connectivity during the sampling of validators + // rather than filtering sampled validators. validatorIDs := v.validatorIDs.Sample(limit) sampled := validatorIDs[:0] diff --git a/network/p2p/validators_test.go b/network/p2p/validators_test.go index e721b4a978af..4671a20fdcae 100644 --- a/network/p2p/validators_test.go +++ b/network/p2p/validators_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p2p @@ -158,7 +158,7 @@ func TestValidatorsSample(t *testing.T) { ctrl := gomock.NewController(t) mockValidators := validators.NewMockState(ctrl) - calls := make([]*gomock.Call, 0) + calls := make([]any, 0) for _, call := range tt.calls { calls = append(calls, mockValidators.EXPECT(). GetCurrentHeight(gomock.Any()).Return(call.height, call.getCurrentHeightErr)) @@ -179,7 +179,9 @@ func TestValidatorsSample(t *testing.T) { } gomock.InOrder(calls...) - network := NewNetwork(logging.NoLog{}, &common.SenderTest{}, prometheus.NewRegistry(), "") + network, err := NewNetwork(logging.NoLog{}, &common.FakeSender{}, prometheus.NewRegistry(), "") + require.NoError(err) + ctx := context.Background() require.NoError(network.Connected(ctx, nodeID1, nil)) require.NoError(network.Connected(ctx, nodeID2, nil)) @@ -187,7 +189,7 @@ func TestValidatorsSample(t *testing.T) { v := NewValidators(network.Peers, network.log, subnetID, mockValidators, tt.maxStaleness) for _, call := range tt.calls { v.lastUpdated = call.time - sampled := v.Sample(context.Background(), call.limit) + sampled := v.Sample(ctx, call.limit) require.LessOrEqual(len(sampled), call.limit) require.Subset(call.expected, sampled) } diff --git a/network/peer/config.go b/network/peer/config.go index b4fd03db2166..0a01cf87fb92 100644 --- a/network/peer/config.go +++ b/network/peer/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer @@ -40,6 +40,9 @@ type Config struct { PongTimeout time.Duration MaxClockDifference time.Duration + SupportedACPs []uint32 + ObjectedACPs []uint32 + // Unix time of the last message sent and received respectively // Must only be accessed atomically LastSent, LastReceived int64 @@ -50,6 +53,6 @@ type Config struct { // Calculates uptime of peers UptimeCalculator uptime.Calculator - // Signs my IP so I can send my signed IP address in the Version message + // Signs my IP so I can send my signed IP address in the Handshake message IPSigner *IPSigner } diff --git a/network/peer/example_test.go b/network/peer/example_test.go index 75eaecee53d5..d6c8ba20c913 100644 --- a/network/peer/example_test.go +++ b/network/peer/example_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/gossip_tracker.go b/network/peer/gossip_tracker.go deleted file mode 100644 index 5676b0734fc8..000000000000 --- a/network/peer/gossip_tracker.go +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package peer - -import ( - "fmt" - "sync" - - "github.com/prometheus/client_golang/prometheus" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/set" -) - -// GossipTracker tracks the validators that we're currently aware of, as well as -// the validators we've told each peers about. This data is stored in a bitset -// to optimize space, where only N (num validators) bits will be used per peer. -// -// This is done by recording some state information of both what validators this -// node is aware of, and what validators we've told each peer about. -// As an example, say we track three peers and three validators (MSB first): -// -// trackedPeers: { -// p1: [1, 1, 1] // we have already told [p1] about all validators -// p2: [0, 1, 1] // [p2] doesn't know about [v3] -// p3: [0, 0, 1] // [p3] knows only about [v3] -// } -// -// GetUnknown computes the validators we haven't sent to a given peer. Ex: -// -// GetUnknown(p1) - [0, 0, 0] -// GetUnknown(p2) - [1, 0, 0] -// GetUnknown(p3) - [1, 1, 0] -// -// Using the gossipTracker, we can quickly compute the validators each peer -// doesn't know about using GetUnknown so that in subsequent PeerList gossip -// messages we only send information that this peer (most likely) doesn't -// already know about. The only case where we'll send a redundant set of -// bytes is if another remote peer gossips to the same peer we're trying to -// gossip to first. -type GossipTracker interface { - // Tracked returns if a peer is being tracked - // Returns: - // bool: False if [peerID] is not tracked. True otherwise. - Tracked(peerID ids.NodeID) bool - - // StartTrackingPeer starts tracking a peer - // Returns: - // bool: False if [peerID] was already tracked. True otherwise. - StartTrackingPeer(peerID ids.NodeID) bool - // StopTrackingPeer stops tracking a given peer - // Returns: - // bool: False if [peerID] was not tracked. True otherwise. - StopTrackingPeer(peerID ids.NodeID) bool - - // AddValidator adds a validator that can be gossiped about - // bool: False if a validator with the same node ID or txID as [validator] - // is present. True otherwise. - AddValidator(validator ValidatorID) bool - // GetNodeID maps a txID into a nodeIDs - // nodeID: The nodeID that was registered by [txID] - // bool: False if [validator] was not present. True otherwise. - GetNodeID(txID ids.ID) (ids.NodeID, bool) - // RemoveValidator removes a validator that can be gossiped about - // bool: False if [validator] was already not present. True otherwise. - RemoveValidator(validatorID ids.NodeID) bool - // ResetValidator resets known gossip status of [validatorID] to unknown - // for all peers - // bool: False if [validator] was not present. True otherwise. - ResetValidator(validatorID ids.NodeID) bool - - // AddKnown adds [knownTxIDs] to the txIDs known by [peerID] and filters - // [txIDs] for non-validators. - // Returns: - // txIDs: The txIDs in [txIDs] that are currently validators. - // bool: False if [peerID] is not tracked. True otherwise. - AddKnown( - peerID ids.NodeID, - knownTxIDs []ids.ID, - txIDs []ids.ID, - ) ([]ids.ID, bool) - // GetUnknown gets the peers that we haven't sent to this peer - // Returns: - // []ValidatorID: a slice of ValidatorIDs that [peerID] doesn't know about. - // bool: False if [peerID] is not tracked. True otherwise. - GetUnknown(peerID ids.NodeID) ([]ValidatorID, bool) -} - -type gossipTracker struct { - lock sync.RWMutex - // a mapping of txIDs => the validator added to the validiator set by that - // tx. - txIDsToNodeIDs map[ids.ID]ids.NodeID - // a mapping of validators => the index they occupy in the bitsets - nodeIDsToIndices map[ids.NodeID]int - // each validator in the index it occupies in the bitset - validatorIDs []ValidatorID - // a mapping of each peer => the validators they know about - trackedPeers map[ids.NodeID]set.Bits - - metrics gossipTrackerMetrics -} - -// NewGossipTracker returns an instance of gossipTracker -func NewGossipTracker( - registerer prometheus.Registerer, - namespace string, -) (GossipTracker, error) { - m, err := newGossipTrackerMetrics(registerer, fmt.Sprintf("%s_gossip_tracker", namespace)) - if err != nil { - return nil, err - } - - return &gossipTracker{ - txIDsToNodeIDs: make(map[ids.ID]ids.NodeID), - nodeIDsToIndices: make(map[ids.NodeID]int), - trackedPeers: make(map[ids.NodeID]set.Bits), - metrics: m, - }, nil -} - -func (g *gossipTracker) Tracked(peerID ids.NodeID) bool { - g.lock.RLock() - defer g.lock.RUnlock() - - _, ok := g.trackedPeers[peerID] - return ok -} - -func (g *gossipTracker) StartTrackingPeer(peerID ids.NodeID) bool { - g.lock.Lock() - defer g.lock.Unlock() - - // don't track the peer if it's already being tracked - if _, ok := g.trackedPeers[peerID]; ok { - return false - } - - // start tracking the peer. Initialize their bitset to zero since we - // haven't sent them anything yet. - g.trackedPeers[peerID] = set.NewBits() - - // emit metrics - g.metrics.trackedPeersSize.Set(float64(len(g.trackedPeers))) - - return true -} - -func (g *gossipTracker) StopTrackingPeer(peerID ids.NodeID) bool { - g.lock.Lock() - defer g.lock.Unlock() - - // only stop tracking peers that are actually being tracked - if _, ok := g.trackedPeers[peerID]; !ok { - return false - } - - // stop tracking the peer by removing them - delete(g.trackedPeers, peerID) - g.metrics.trackedPeersSize.Set(float64(len(g.trackedPeers))) - - return true -} - -func (g *gossipTracker) AddValidator(validator ValidatorID) bool { - g.lock.Lock() - defer g.lock.Unlock() - - // only add validators that are not already present - if _, ok := g.txIDsToNodeIDs[validator.TxID]; ok { - return false - } - if _, ok := g.nodeIDsToIndices[validator.NodeID]; ok { - return false - } - - // add the validator to the MSB of the bitset. - msb := len(g.validatorIDs) - g.txIDsToNodeIDs[validator.TxID] = validator.NodeID - g.nodeIDsToIndices[validator.NodeID] = msb - g.validatorIDs = append(g.validatorIDs, validator) - - // emit metrics - g.metrics.validatorsSize.Set(float64(len(g.validatorIDs))) - - return true -} - -func (g *gossipTracker) GetNodeID(txID ids.ID) (ids.NodeID, bool) { - g.lock.RLock() - defer g.lock.RUnlock() - - nodeID, ok := g.txIDsToNodeIDs[txID] - return nodeID, ok -} - -func (g *gossipTracker) RemoveValidator(validatorID ids.NodeID) bool { - g.lock.Lock() - defer g.lock.Unlock() - - // only remove validators that are already present - indexToRemove, ok := g.nodeIDsToIndices[validatorID] - if !ok { - return false - } - validatorToRemove := g.validatorIDs[indexToRemove] - - // swap the validator-to-be-removed with the validator in the last index - // if the element we're swapping with is ourselves, we can skip this swap - // since we only need to delete instead - lastIndex := len(g.validatorIDs) - 1 - if indexToRemove != lastIndex { - lastValidator := g.validatorIDs[lastIndex] - - g.nodeIDsToIndices[lastValidator.NodeID] = indexToRemove - g.validatorIDs[indexToRemove] = lastValidator - } - - delete(g.txIDsToNodeIDs, validatorToRemove.TxID) - delete(g.nodeIDsToIndices, validatorID) - g.validatorIDs = g.validatorIDs[:lastIndex] - - // Invariant: We must remove the validator from everyone else's validator - // bitsets to make sure that each validator occupies the same position in - // each bitset. - for _, knownPeers := range g.trackedPeers { - // swap the element to be removed with the msb - if indexToRemove != lastIndex { - if knownPeers.Contains(lastIndex) { - knownPeers.Add(indexToRemove) - } else { - knownPeers.Remove(indexToRemove) - } - } - knownPeers.Remove(lastIndex) - } - - // emit metrics - g.metrics.validatorsSize.Set(float64(len(g.validatorIDs))) - - return true -} - -func (g *gossipTracker) ResetValidator(validatorID ids.NodeID) bool { - g.lock.Lock() - defer g.lock.Unlock() - - // only reset validators that exist - indexToReset, ok := g.nodeIDsToIndices[validatorID] - if !ok { - return false - } - - for _, knownPeers := range g.trackedPeers { - knownPeers.Remove(indexToReset) - } - - return true -} - -// AddKnown invariants: -// -// 1. [peerID] SHOULD only be a nodeID that has been tracked with -// StartTrackingPeer(). -func (g *gossipTracker) AddKnown( - peerID ids.NodeID, - knownTxIDs []ids.ID, - txIDs []ids.ID, -) ([]ids.ID, bool) { - g.lock.Lock() - defer g.lock.Unlock() - - knownPeers, ok := g.trackedPeers[peerID] - if !ok { - return nil, false - } - for _, txID := range knownTxIDs { - nodeID, ok := g.txIDsToNodeIDs[txID] - if !ok { - // We don't know about this txID, this can happen due to differences - // between our current validator set and the peer's current - // validator set. - continue - } - - // Because we fetched the nodeID from [g.txIDsToNodeIDs], we are - // guaranteed that the index is populated. - index := g.nodeIDsToIndices[nodeID] - knownPeers.Add(index) - } - - validatorTxIDs := make([]ids.ID, 0, len(txIDs)) - for _, txID := range txIDs { - if _, ok := g.txIDsToNodeIDs[txID]; ok { - validatorTxIDs = append(validatorTxIDs, txID) - } - } - return validatorTxIDs, true -} - -func (g *gossipTracker) GetUnknown(peerID ids.NodeID) ([]ValidatorID, bool) { - g.lock.RLock() - defer g.lock.RUnlock() - - // return false if this peer isn't tracked - knownPeers, ok := g.trackedPeers[peerID] - if !ok { - return nil, false - } - - // Calculate the unknown information we need to send to this peer. We do - // this by computing the difference between the validators we know about - // and the validators we know we've sent to [peerID]. - result := make([]ValidatorID, 0, len(g.validatorIDs)) - for i, validatorID := range g.validatorIDs { - if !knownPeers.Contains(i) { - result = append(result, validatorID) - } - } - - return result, true -} diff --git a/network/peer/gossip_tracker_callback.go b/network/peer/gossip_tracker_callback.go deleted file mode 100644 index 28514ac163a6..000000000000 --- a/network/peer/gossip_tracker_callback.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package peer - -import ( - "go.uber.org/zap" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/logging" -) - -var _ validators.SetCallbackListener = (*GossipTrackerCallback)(nil) - -// GossipTrackerCallback synchronizes GossipTracker's validator state with the -// validator set it's registered to. -type GossipTrackerCallback struct { - Log logging.Logger - GossipTracker GossipTracker -} - -// OnValidatorAdded adds [validatorID] to the set of validators that can be -// gossiped about -func (g *GossipTrackerCallback) OnValidatorAdded( - nodeID ids.NodeID, - _ *bls.PublicKey, - txID ids.ID, - _ uint64, -) { - vdr := ValidatorID{ - NodeID: nodeID, - TxID: txID, - } - if !g.GossipTracker.AddValidator(vdr) { - g.Log.Error("failed to add a validator", - zap.Stringer("nodeID", nodeID), - zap.Stringer("txID", txID), - ) - } -} - -// OnValidatorRemoved removes [validatorID] from the set of validators that can -// be gossiped about. -func (g *GossipTrackerCallback) OnValidatorRemoved(nodeID ids.NodeID, _ uint64) { - if !g.GossipTracker.RemoveValidator(nodeID) { - g.Log.Error("failed to remove a validator", - zap.Stringer("nodeID", nodeID), - ) - } -} - -// OnValidatorWeightChanged does nothing because PeerList gossip doesn't care -// about validator weights. -func (*GossipTrackerCallback) OnValidatorWeightChanged(ids.NodeID, uint64, uint64) {} diff --git a/network/peer/gossip_tracker_metrics.go b/network/peer/gossip_tracker_metrics.go deleted file mode 100644 index e80f31765b9c..000000000000 --- a/network/peer/gossip_tracker_metrics.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package peer - -import ( - "github.com/prometheus/client_golang/prometheus" - - "github.com/ava-labs/avalanchego/utils" -) - -type gossipTrackerMetrics struct { - trackedPeersSize prometheus.Gauge - validatorsSize prometheus.Gauge -} - -func newGossipTrackerMetrics(registerer prometheus.Registerer, namespace string) (gossipTrackerMetrics, error) { - m := gossipTrackerMetrics{ - trackedPeersSize: prometheus.NewGauge( - prometheus.GaugeOpts{ - Namespace: namespace, - Name: "tracked_peers_size", - Help: "amount of peers that are being tracked", - }, - ), - validatorsSize: prometheus.NewGauge( - prometheus.GaugeOpts{ - Namespace: namespace, - Name: "validators_size", - Help: "number of validators this node is tracking", - }, - ), - } - - err := utils.Err( - registerer.Register(m.trackedPeersSize), - registerer.Register(m.validatorsSize), - ) - return m, err -} diff --git a/network/peer/gossip_tracker_test.go b/network/peer/gossip_tracker_test.go deleted file mode 100644 index 1bd420c4f433..000000000000 --- a/network/peer/gossip_tracker_test.go +++ /dev/null @@ -1,620 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package peer - -import ( - "testing" - - "github.com/prometheus/client_golang/prometheus" - - "github.com/stretchr/testify/require" - - "github.com/ava-labs/avalanchego/ids" -) - -var ( - // peers - p1 = ids.GenerateTestNodeID() - p2 = ids.GenerateTestNodeID() - p3 = ids.GenerateTestNodeID() - - // validators - v1 = ValidatorID{ - NodeID: ids.GenerateTestNodeID(), - TxID: ids.GenerateTestID(), - } - v2 = ValidatorID{ - NodeID: ids.GenerateTestNodeID(), - TxID: ids.GenerateTestID(), - } - v3 = ValidatorID{ - NodeID: ids.GenerateTestNodeID(), - TxID: ids.GenerateTestID(), - } -) - -func TestGossipTracker_Contains(t *testing.T) { - tests := []struct { - name string - track []ids.NodeID - contains ids.NodeID - expected bool - }{ - { - name: "empty", - track: []ids.NodeID{}, - contains: p1, - expected: false, - }, - { - name: "populated - does not contain", - track: []ids.NodeID{p1, p2}, - contains: p3, - expected: false, - }, - { - name: "populated - contains", - track: []ids.NodeID{p1, p2, p3}, - contains: p3, - expected: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - for _, add := range test.track { - require.True(g.StartTrackingPeer(add)) - } - - require.Equal(test.expected, g.Tracked(test.contains)) - }) - } -} - -func TestGossipTracker_StartTrackingPeer(t *testing.T) { - tests := []struct { - name string - toStartTracking []ids.NodeID - expected []bool - }{ - { - // Tracking new peers always works - name: "unique adds", - toStartTracking: []ids.NodeID{p1, p2, p3}, - expected: []bool{true, true, true}, - }, - { - // We shouldn't be able to track a peer more than once - name: "duplicate adds", - toStartTracking: []ids.NodeID{p1, p1, p1}, - expected: []bool{true, false, false}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - for i, p := range test.toStartTracking { - require.Equal(test.expected[i], g.StartTrackingPeer(p)) - require.True(g.Tracked(p)) - } - }) - } -} - -func TestGossipTracker_StopTrackingPeer(t *testing.T) { - tests := []struct { - name string - toStartTracking []ids.NodeID - expectedStartTracking []bool - toStopTracking []ids.NodeID - expectedStopTracking []bool - }{ - { - // We should be able to stop tracking that we are tracking - name: "stop tracking tracked peers", - toStartTracking: []ids.NodeID{p1, p2, p3}, - toStopTracking: []ids.NodeID{p1, p2, p3}, - expectedStopTracking: []bool{true, true, true}, - }, - { - // We shouldn't be able to stop tracking peers we've stopped tracking - name: "stop tracking twice", - toStartTracking: []ids.NodeID{p1}, - toStopTracking: []ids.NodeID{p1, p1}, - expectedStopTracking: []bool{true, false}, - }, - { - // We shouldn't be able to stop tracking peers we were never tracking - name: "remove non-existent elements", - toStartTracking: []ids.NodeID{}, - expectedStartTracking: []bool{}, - toStopTracking: []ids.NodeID{p1, p2, p3}, - expectedStopTracking: []bool{false, false, false}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - for _, add := range test.toStartTracking { - require.True(g.StartTrackingPeer(add)) - require.True(g.Tracked(add)) - } - - for i, p := range test.toStopTracking { - require.Equal(test.expectedStopTracking[i], g.StopTrackingPeer(p)) - } - }) - } -} - -func TestGossipTracker_AddValidator(t *testing.T) { - type args struct { - validator ValidatorID - } - - tests := []struct { - name string - validators []ValidatorID - args args - expected bool - }{ - { - name: "not present", - validators: []ValidatorID{}, - args: args{validator: v1}, - expected: true, - }, - { - name: "already present txID but with different nodeID", - validators: []ValidatorID{v1}, - args: args{validator: ValidatorID{ - NodeID: ids.GenerateTestNodeID(), - TxID: v1.TxID, - }}, - expected: false, - }, - { - name: "already present nodeID but with different txID", - validators: []ValidatorID{v1}, - args: args{validator: ValidatorID{ - NodeID: v1.NodeID, - TxID: ids.GenerateTestID(), - }}, - expected: false, - }, - { - name: "already present validatorID", - validators: []ValidatorID{v1}, - args: args{validator: v1}, - expected: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - for _, v := range test.validators { - require.True(g.AddValidator(v)) - } - - require.Equal(test.expected, g.AddValidator(test.args.validator)) - }) - } -} - -func TestGossipTracker_RemoveValidator(t *testing.T) { - type args struct { - id ids.NodeID - } - - tests := []struct { - name string - validators []ValidatorID - args args - expected bool - }{ - { - name: "not already present", - validators: []ValidatorID{}, - args: args{id: v1.NodeID}, - expected: false, - }, - { - name: "already present", - validators: []ValidatorID{v1}, - args: args{id: v1.NodeID}, - expected: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - for _, v := range test.validators { - require.True(g.AddValidator(v)) - } - - require.Equal(test.expected, g.RemoveValidator(test.args.id)) - }) - } -} - -func TestGossipTracker_ResetValidator(t *testing.T) { - type args struct { - id ids.NodeID - } - - tests := []struct { - name string - validators []ValidatorID - args args - expected bool - }{ - { - name: "non-existent validator", - validators: []ValidatorID{}, - args: args{id: v1.NodeID}, - expected: false, - }, - { - name: "existing validator", - validators: []ValidatorID{v1}, - args: args{id: v1.NodeID}, - expected: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - require.True(g.StartTrackingPeer(p1)) - - for _, v := range test.validators { - require.True(g.AddValidator(v)) - g.AddKnown(p1, []ids.ID{v.TxID}, nil) - - unknown, ok := g.GetUnknown(p1) - require.True(ok) - require.NotContains(unknown, v) - } - - require.Equal(test.expected, g.ResetValidator(test.args.id)) - - for _, v := range test.validators { - unknown, ok := g.GetUnknown(p1) - require.True(ok) - require.Contains(unknown, v) - } - }) - } -} - -func TestGossipTracker_AddKnown(t *testing.T) { - type args struct { - peerID ids.NodeID - txIDs []ids.ID - } - - tests := []struct { - name string - trackedPeers []ids.NodeID - validators []ValidatorID - args args - expectedTxIDs []ids.ID - expectedOk bool - }{ - { - // We should not be able to update an untracked peer - name: "untracked peer - empty", - trackedPeers: []ids.NodeID{}, - validators: []ValidatorID{}, - args: args{peerID: p1, txIDs: []ids.ID{}}, - expectedTxIDs: nil, - expectedOk: false, - }, - { - // We should not be able to update an untracked peer - name: "untracked peer - populated", - trackedPeers: []ids.NodeID{p2, p3}, - validators: []ValidatorID{}, - args: args{peerID: p1, txIDs: []ids.ID{}}, - expectedTxIDs: nil, - expectedOk: false, - }, - { - // We shouldn't be able to look up a peer that isn't tracked - name: "untracked peer - unknown validator", - trackedPeers: []ids.NodeID{}, - validators: []ValidatorID{}, - args: args{peerID: p1, txIDs: []ids.ID{v1.TxID}}, - expectedTxIDs: nil, - expectedOk: false, - }, - { - // We shouldn't fail on a validator that's not registered - name: "tracked peer - unknown validator", - trackedPeers: []ids.NodeID{p1}, - validators: []ValidatorID{}, - args: args{peerID: p1, txIDs: []ids.ID{v1.TxID}}, - expectedTxIDs: []ids.ID{}, - expectedOk: true, - }, - { - // We should be able to update a tracked validator - name: "update tracked validator", - trackedPeers: []ids.NodeID{p1, p2, p3}, - validators: []ValidatorID{v1}, - args: args{peerID: p1, txIDs: []ids.ID{v1.TxID}}, - expectedTxIDs: []ids.ID{v1.TxID}, - expectedOk: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - for _, p := range test.trackedPeers { - require.True(g.StartTrackingPeer(p)) - require.True(g.Tracked(p)) - } - - for _, v := range test.validators { - require.True(g.AddValidator(v)) - } - - txIDs, ok := g.AddKnown(test.args.peerID, test.args.txIDs, test.args.txIDs) - require.Equal(test.expectedOk, ok) - require.Equal(test.expectedTxIDs, txIDs) - }) - } -} - -func TestGossipTracker_GetUnknown(t *testing.T) { - tests := []struct { - name string - peerID ids.NodeID - peersToTrack []ids.NodeID - validators []ValidatorID - expectedUnknown []ValidatorID - expectedOk bool - }{ - { - name: "non tracked peer", - peerID: p1, - validators: []ValidatorID{v2}, - peersToTrack: []ids.NodeID{}, - expectedUnknown: nil, - expectedOk: false, - }, - { - name: "only validators", - peerID: p1, - peersToTrack: []ids.NodeID{p1}, - validators: []ValidatorID{v2}, - expectedUnknown: []ValidatorID{v2}, - expectedOk: true, - }, - { - name: "only non-validators", - peerID: p1, - peersToTrack: []ids.NodeID{p1, p2}, - validators: []ValidatorID{}, - expectedUnknown: []ValidatorID{}, - expectedOk: true, - }, - { - name: "validators and non-validators", - peerID: p1, - peersToTrack: []ids.NodeID{p1, p3}, - validators: []ValidatorID{v2}, - expectedUnknown: []ValidatorID{v2}, - expectedOk: true, - }, - { - name: "same as limit", - peerID: p1, - peersToTrack: []ids.NodeID{p1}, - validators: []ValidatorID{v2, v3}, - expectedUnknown: []ValidatorID{v2, v3}, - expectedOk: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - // add our validators - for _, validator := range test.validators { - require.True(g.AddValidator(validator)) - } - - // start tracking our peers - for _, nonValidator := range test.peersToTrack { - require.True(g.StartTrackingPeer(nonValidator)) - require.True(g.Tracked(nonValidator)) - } - - // get the unknown peers for this peer - result, ok := g.GetUnknown(test.peerID) - require.Equal(test.expectedOk, ok) - require.Len(result, len(test.expectedUnknown)) - for _, v := range test.expectedUnknown { - require.Contains(result, v) - } - }) - } -} - -func TestGossipTracker_E2E(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - // [v1, v2, v3] are validators - require.True(g.AddValidator(v1)) - require.True(g.AddValidator(v2)) - - // we should get an empty unknown since we're not tracking anything - unknown, ok := g.GetUnknown(p1) - require.False(ok) - require.Nil(unknown) - - // we should get a unknown of [v1, v2] since v1 and v2 are registered - require.True(g.StartTrackingPeer(p1)) - require.True(g.Tracked(p1)) - - // check p1's unknown - unknown, ok = g.GetUnknown(p1) - require.True(ok) - require.Contains(unknown, v1) - require.Contains(unknown, v2) - require.Len(unknown, 2) - - // Check p2's unknown. We should get nothing since we're not tracking it - // yet. - unknown, ok = g.GetUnknown(p2) - require.False(ok) - require.Nil(unknown) - - // Start tracking p2 - require.True(g.StartTrackingPeer(p2)) - - // check p2's unknown - unknown, ok = g.GetUnknown(p2) - require.True(ok) - require.Contains(unknown, v1) - require.Contains(unknown, v2) - require.Len(unknown, 2) - - // p1 now knows about v1, but not v2, so it should see [v2] in its unknown - // p2 still knows nothing, so it should see both - txIDs, ok := g.AddKnown(p1, []ids.ID{v1.TxID}, []ids.ID{v1.TxID}) - require.True(ok) - require.Equal([]ids.ID{v1.TxID}, txIDs) - - // p1 should have an unknown of [v2], since it knows v1 - unknown, ok = g.GetUnknown(p1) - require.True(ok) - require.Contains(unknown, v2) - require.Len(unknown, 1) - - // p2 should have a unknown of [v1, v2], since it knows nothing - unknown, ok = g.GetUnknown(p2) - require.True(ok) - require.Contains(unknown, v1) - require.Contains(unknown, v2) - require.Len(unknown, 2) - - // Add v3 - require.True(g.AddValidator(v3)) - - // track p3, who knows of v1, v2, and v3 - // p1 and p2 still don't know of v3 - require.True(g.StartTrackingPeer(p3)) - - txIDs, ok = g.AddKnown(p3, []ids.ID{v1.TxID, v2.TxID, v3.TxID}, []ids.ID{v1.TxID, v2.TxID, v3.TxID}) - require.True(ok) - require.Equal([]ids.ID{v1.TxID, v2.TxID, v3.TxID}, txIDs) - - // p1 doesn't know about [v2, v3] - unknown, ok = g.GetUnknown(p1) - require.True(ok) - require.Contains(unknown, v2) - require.Contains(unknown, v3) - require.Len(unknown, 2) - - // p2 doesn't know about [v1, v2, v3] - unknown, ok = g.GetUnknown(p2) - require.True(ok) - require.Contains(unknown, v1) - require.Contains(unknown, v2) - require.Contains(unknown, v3) - require.Len(unknown, 3) - - // p3 knows about everyone - unknown, ok = g.GetUnknown(p3) - require.True(ok) - require.Empty(unknown) - - // stop tracking p2 - require.True(g.StopTrackingPeer(p2)) - unknown, ok = g.GetUnknown(p2) - require.False(ok) - require.Nil(unknown) - - // p1 doesn't know about [v2, v3] because v2 is still registered as - // a validator - unknown, ok = g.GetUnknown(p1) - require.True(ok) - require.Contains(unknown, v2) - require.Contains(unknown, v3) - require.Len(unknown, 2) - - // Remove p2 from the validator set - require.True(g.RemoveValidator(v2.NodeID)) - - // p1 doesn't know about [v3] since v2 left the validator set - unknown, ok = g.GetUnknown(p1) - require.True(ok) - require.Contains(unknown, v3) - require.Len(unknown, 1) - - // p3 knows about everyone since it learned about v1 and v3 earlier. - unknown, ok = g.GetUnknown(p3) - require.Empty(unknown) - require.True(ok) -} - -func TestGossipTracker_Regression_IncorrectTxIDDeletion(t *testing.T) { - require := require.New(t) - - g, err := NewGossipTracker(prometheus.NewRegistry(), "foobar") - require.NoError(err) - - require.True(g.AddValidator(v1)) - require.True(g.AddValidator(v2)) - - require.True(g.RemoveValidator(v1.NodeID)) - - require.False(g.AddValidator(ValidatorID{ - NodeID: ids.GenerateTestNodeID(), - TxID: v2.TxID, - })) -} diff --git a/network/peer/info.go b/network/peer/info.go index 45f7a3cdd4a6..00ccaec7953b 100644 --- a/network/peer/info.go +++ b/network/peer/info.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/json" + "github.com/ava-labs/avalanchego/utils/set" ) type Info struct { @@ -19,5 +20,7 @@ type Info struct { LastReceived time.Time `json:"lastReceived"` ObservedUptime json.Uint32 `json:"observedUptime"` ObservedSubnetUptimes map[ids.ID]json.Uint32 `json:"observedSubnetUptimes"` - TrackedSubnets []ids.ID `json:"trackedSubnets"` + TrackedSubnets set.Set[ids.ID] `json:"trackedSubnets"` + SupportedACPs set.Set[uint32] `json:"supportedACPs"` + ObjectedACPs set.Set[uint32] `json:"objectedACPs"` } diff --git a/network/peer/ip.go b/network/peer/ip.go index 8fb9d744f974..0112374b9b80 100644 --- a/network/peer/ip.go +++ b/network/peer/ip.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/ip_signer.go b/network/peer/ip_signer.go index b524d3463619..cfe85f387819 100644 --- a/network/peer/ip_signer.go +++ b/network/peer/ip_signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/ip_signer_test.go b/network/peer/ip_signer_test.go index 382501a825bc..7e5314f5f58a 100644 --- a/network/peer/ip_signer_test.go +++ b/network/peer/ip_signer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/message_queue.go b/network/peer/message_queue.go index b9d38996723b..f2ccef6dc915 100644 --- a/network/peer/message_queue.go +++ b/network/peer/message_queue.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/message_queue_test.go b/network/peer/message_queue_test.go index 2e1e46f5e2f5..496f19425f20 100644 --- a/network/peer/message_queue_test.go +++ b/network/peer/message_queue_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/metrics.go b/network/peer/metrics.go index 602726131134..cad8797addfb 100644 --- a/network/peer/metrics.go +++ b/network/peer/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/mock_gossip_tracker.go b/network/peer/mock_gossip_tracker.go deleted file mode 100644 index ee7b8c21ca91..000000000000 --- a/network/peer/mock_gossip_tracker.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/network/peer (interfaces: GossipTracker) - -// Package peer is a generated GoMock package. -package peer - -import ( - reflect "reflect" - - ids "github.com/ava-labs/avalanchego/ids" - gomock "go.uber.org/mock/gomock" -) - -// MockGossipTracker is a mock of GossipTracker interface. -type MockGossipTracker struct { - ctrl *gomock.Controller - recorder *MockGossipTrackerMockRecorder -} - -// MockGossipTrackerMockRecorder is the mock recorder for MockGossipTracker. -type MockGossipTrackerMockRecorder struct { - mock *MockGossipTracker -} - -// NewMockGossipTracker creates a new mock instance. -func NewMockGossipTracker(ctrl *gomock.Controller) *MockGossipTracker { - mock := &MockGossipTracker{ctrl: ctrl} - mock.recorder = &MockGossipTrackerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockGossipTracker) EXPECT() *MockGossipTrackerMockRecorder { - return m.recorder -} - -// AddKnown mocks base method. -func (m *MockGossipTracker) AddKnown(arg0 ids.NodeID, arg1, arg2 []ids.ID) ([]ids.ID, bool) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddKnown", arg0, arg1, arg2) - ret0, _ := ret[0].([]ids.ID) - ret1, _ := ret[1].(bool) - return ret0, ret1 -} - -// AddKnown indicates an expected call of AddKnown. -func (mr *MockGossipTrackerMockRecorder) AddKnown(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddKnown", reflect.TypeOf((*MockGossipTracker)(nil).AddKnown), arg0, arg1, arg2) -} - -// AddValidator mocks base method. -func (m *MockGossipTracker) AddValidator(arg0 ValidatorID) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddValidator", arg0) - ret0, _ := ret[0].(bool) - return ret0 -} - -// AddValidator indicates an expected call of AddValidator. -func (mr *MockGossipTrackerMockRecorder) AddValidator(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddValidator", reflect.TypeOf((*MockGossipTracker)(nil).AddValidator), arg0) -} - -// GetNodeID mocks base method. -func (m *MockGossipTracker) GetNodeID(arg0 ids.ID) (ids.NodeID, bool) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetNodeID", arg0) - ret0, _ := ret[0].(ids.NodeID) - ret1, _ := ret[1].(bool) - return ret0, ret1 -} - -// GetNodeID indicates an expected call of GetNodeID. -func (mr *MockGossipTrackerMockRecorder) GetNodeID(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeID", reflect.TypeOf((*MockGossipTracker)(nil).GetNodeID), arg0) -} - -// GetUnknown mocks base method. -func (m *MockGossipTracker) GetUnknown(arg0 ids.NodeID) ([]ValidatorID, bool) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUnknown", arg0) - ret0, _ := ret[0].([]ValidatorID) - ret1, _ := ret[1].(bool) - return ret0, ret1 -} - -// GetUnknown indicates an expected call of GetUnknown. -func (mr *MockGossipTrackerMockRecorder) GetUnknown(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnknown", reflect.TypeOf((*MockGossipTracker)(nil).GetUnknown), arg0) -} - -// RemoveValidator mocks base method. -func (m *MockGossipTracker) RemoveValidator(arg0 ids.NodeID) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveValidator", arg0) - ret0, _ := ret[0].(bool) - return ret0 -} - -// RemoveValidator indicates an expected call of RemoveValidator. -func (mr *MockGossipTrackerMockRecorder) RemoveValidator(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveValidator", reflect.TypeOf((*MockGossipTracker)(nil).RemoveValidator), arg0) -} - -// ResetValidator mocks base method. -func (m *MockGossipTracker) ResetValidator(arg0 ids.NodeID) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ResetValidator", arg0) - ret0, _ := ret[0].(bool) - return ret0 -} - -// ResetValidator indicates an expected call of ResetValidator. -func (mr *MockGossipTrackerMockRecorder) ResetValidator(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetValidator", reflect.TypeOf((*MockGossipTracker)(nil).ResetValidator), arg0) -} - -// StartTrackingPeer mocks base method. -func (m *MockGossipTracker) StartTrackingPeer(arg0 ids.NodeID) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StartTrackingPeer", arg0) - ret0, _ := ret[0].(bool) - return ret0 -} - -// StartTrackingPeer indicates an expected call of StartTrackingPeer. -func (mr *MockGossipTrackerMockRecorder) StartTrackingPeer(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTrackingPeer", reflect.TypeOf((*MockGossipTracker)(nil).StartTrackingPeer), arg0) -} - -// StopTrackingPeer mocks base method. -func (m *MockGossipTracker) StopTrackingPeer(arg0 ids.NodeID) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StopTrackingPeer", arg0) - ret0, _ := ret[0].(bool) - return ret0 -} - -// StopTrackingPeer indicates an expected call of StopTrackingPeer. -func (mr *MockGossipTrackerMockRecorder) StopTrackingPeer(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopTrackingPeer", reflect.TypeOf((*MockGossipTracker)(nil).StopTrackingPeer), arg0) -} - -// Tracked mocks base method. -func (m *MockGossipTracker) Tracked(arg0 ids.NodeID) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Tracked", arg0) - ret0, _ := ret[0].(bool) - return ret0 -} - -// Tracked indicates an expected call of Tracked. -func (mr *MockGossipTrackerMockRecorder) Tracked(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Tracked", reflect.TypeOf((*MockGossipTracker)(nil).Tracked), arg0) -} diff --git a/network/peer/msg_length.go b/network/peer/msg_length.go index 27a48dea3060..625034913d9f 100644 --- a/network/peer/msg_length.go +++ b/network/peer/msg_length.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/msg_length_test.go b/network/peer/msg_length_test.go index 52767888c8c2..97866a7d95cf 100644 --- a/network/peer/msg_length_test.go +++ b/network/peer/msg_length_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/network.go b/network/peer/network.go index fc136f0bcb9c..b8fb01814546 100644 --- a/network/peer/network.go +++ b/network/peer/network.go @@ -1,11 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer import ( "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/proto/pb/p2p" + "github.com/ava-labs/avalanchego/utils/bloom" "github.com/ava-labs/avalanchego/utils/ips" ) @@ -19,15 +19,9 @@ type Network interface { // connection is no longer desired and should be terminated. AllowConnection(peerID ids.NodeID) bool - // Track allows the peer to notify the network of a potential new peer to - // connect to, given the [ips] of the peers it sent us during the peer - // handshake. - // - // Returns which IPs should not be gossipped to this node again. - Track(peerID ids.NodeID, ips []*ips.ClaimedIPPort) ([]*p2p.PeerAck, error) - - // MarkTracked stops sending gossip about [ips] to [peerID]. - MarkTracked(peerID ids.NodeID, ips []*p2p.PeerAck) error + // Track allows the peer to notify the network of potential new peers to + // connect to. + Track(ips []*ips.ClaimedIPPort) error // Disconnected is called when the peer finishes shutting down. It is not // guaranteed that [Connected] was called for the provided peer. However, it @@ -35,6 +29,13 @@ type Network interface { // for a given [Peer] object. Disconnected(peerID ids.NodeID) - // Peers returns peers that [peerID] might not know about. - Peers(peerID ids.NodeID) ([]ips.ClaimedIPPort, error) + // KnownPeers returns the bloom filter of the known peers. + KnownPeers() (bloomFilter []byte, salt []byte) + + // Peers returns peers that are not known. + Peers( + peerID ids.NodeID, + knownPeers *bloom.ReadFilter, + peerSalt []byte, + ) []*ips.ClaimedIPPort } diff --git a/network/peer/peer.go b/network/peer/peer.go index 503f97262882..255f13821f23 100644 --- a/network/peer/peer.go +++ b/network/peer/peer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer @@ -21,6 +21,7 @@ import ( "github.com/ava-labs/avalanchego/proto/pb/p2p" "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/utils/bloom" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/json" @@ -29,6 +30,10 @@ import ( "github.com/ava-labs/avalanchego/version" ) +// maxBloomSaltLen restricts the allowed size of the bloom salt to prevent +// excessively expensive bloom filter contains checks. +const maxBloomSaltLen = 32 + var ( errClosed = errors.New("closed") @@ -91,6 +96,11 @@ type Peer interface { // sent. StartSendPeerList() + // StartSendGetPeerList attempts to send a GetPeerList message to this peer + // on this peer's gossip routine. It is not guaranteed that a GetPeerList + // will be sent. + StartSendGetPeerList() + // StartClose will begin shutting down the peer. It will not block. StartClose() @@ -120,27 +130,30 @@ type peer struct { // queue of messages to send to this peer. messageQueue MessageQueue - // ip is the claimed IP the peer gave us in the Version message. + // ip is the claimed IP the peer gave us in the Handshake message. ip *SignedIP // version is the claimed version the peer is running that we received in - // the Version message. + // the Handshake message. version *version.Application - // trackedSubnets is the subset of subnetIDs the peer sent us in the Version + // trackedSubnets is the subset of subnetIDs the peer sent us in the Handshake // message that we are also tracking. trackedSubnets set.Set[ids.ID] + // options of ACPs provided in the Handshake message. + supportedACPs set.Set[uint32] + objectedACPs set.Set[uint32] observedUptimesLock sync.RWMutex // [observedUptimesLock] must be held while accessing [observedUptime] // Subnet ID --> Our uptime for the given subnet as perceived by the peer observedUptimes map[ids.ID]uint32 - // True if this peer has sent us a valid Version message and + // True if this peer has sent us a valid Handshake message and // is running a compatible version. // Only modified on the connection's reader routine. - gotVersion utils.Atomic[bool] + gotHandshake utils.Atomic[bool] // True if the peer: - // * Has sent us a Version message + // * Has sent us a Handshake message // * Has sent us a PeerList message // * Is running a compatible version // Only modified on the connection's reader routine. @@ -167,6 +180,10 @@ type peer struct { // peerListChan signals that we should attempt to send a PeerList to this // peer peerListChan chan struct{} + + // getPeerListChan signals that we should attempt to send a GetPeerList to + // this peer + getPeerListChan chan struct{} } // Start a new peer instance. @@ -194,6 +211,7 @@ func Start( onClosed: make(chan struct{}), observedUptimes: make(map[ids.ID]uint32), peerListChan: make(chan struct{}, 1), + getPeerListChan: make(chan struct{}, 1), } go p.readMessages() @@ -246,10 +264,9 @@ func (p *peer) Info() Info { publicIPStr = p.ip.IPPort.String() } - trackedSubnets := p.trackedSubnets.List() - uptimes := make(map[ids.ID]json.Uint32, len(trackedSubnets)) + uptimes := make(map[ids.ID]json.Uint32, p.trackedSubnets.Len()) - for _, subnetID := range trackedSubnets { + for subnetID := range p.trackedSubnets { uptime, exist := p.ObservedUptime(subnetID) if !exist { continue @@ -271,7 +288,9 @@ func (p *peer) Info() Info { LastReceived: p.LastReceived(), ObservedUptime: json.Uint32(primaryUptime), ObservedSubnetUptimes: uptimes, - TrackedSubnets: trackedSubnets, + TrackedSubnets: p.trackedSubnets, + SupportedACPs: p.supportedACPs, + ObjectedACPs: p.objectedACPs, } } @@ -306,6 +325,13 @@ func (p *peer) StartSendPeerList() { } } +func (p *peer) StartSendGetPeerList() { + select { + case p.getPeerListChan <- struct{}{}: + default: + } +} + func (p *peer) StartClose() { p.startClosingOnce.Do(func() { if err := p.conn.Close(); err != nil { @@ -487,7 +513,7 @@ func (p *peer) writeMessages() { writer := bufio.NewWriterSize(p.conn, p.Config.WriteBufferSize) - // Make sure that the version is the first message sent + // Make sure that the Handshake is the first message sent mySignedIP, err := p.IPSigner.GetSignedIP() if err != nil { p.Log.Error("failed to get signed IP", @@ -496,19 +522,44 @@ func (p *peer) writeMessages() { ) return } + if mySignedIP.Port == 0 { + p.Log.Error("signed IP has invalid port", + zap.Stringer("nodeID", p.id), + zap.Uint16("port", mySignedIP.Port), + ) + return + } + + myVersion := p.VersionCompatibility.Version() + legacyApplication := &version.Application{ + Name: version.LegacyAppName, + Major: myVersion.Major, + Minor: myVersion.Minor, + Patch: myVersion.Patch, + } + + knownPeersFilter, knownPeersSalt := p.Network.KnownPeers() - msg, err := p.MessageCreator.Version( + msg, err := p.MessageCreator.Handshake( p.NetworkID, p.Clock.Unix(), mySignedIP.IPPort, - p.VersionCompatibility.Version().String(), + legacyApplication.String(), + myVersion.Name, + uint32(myVersion.Major), + uint32(myVersion.Minor), + uint32(myVersion.Patch), mySignedIP.Timestamp, mySignedIP.Signature, p.MySubnets.List(), + p.SupportedACPs, + p.ObjectedACPs, + knownPeersFilter, + knownPeersSalt, ) if err != nil { p.Log.Error("failed to create message", - zap.Stringer("messageOp", message.VersionOp), + zap.Stringer("messageOp", message.HandshakeOp), zap.Stringer("nodeID", p.id), zap.Error(err), ) @@ -596,15 +647,7 @@ func (p *peer) sendNetworkMessages() { for { select { case <-p.peerListChan: - peerIPs, err := p.Config.Network.Peers(p.id) - if err != nil { - p.Log.Error("failed to get peers to gossip", - zap.Stringer("nodeID", p.id), - zap.Error(err), - ) - return - } - + peerIPs := p.Config.Network.Peers(p.id, bloom.EmptyFilter, nil) if len(peerIPs) == 0 { p.Log.Verbo( "skipping peer gossip as there are no unknown peers", @@ -629,6 +672,22 @@ func (p *peer) sendNetworkMessages() { zap.Stringer("nodeID", p.id), ) } + case <-p.getPeerListChan: + knownPeersFilter, knownPeersSalt := p.Config.Network.KnownPeers() + msg, err := p.Config.MessageCreator.GetPeerList(knownPeersFilter, knownPeersSalt) + if err != nil { + p.Log.Error("failed to create get peer list message", + zap.Stringer("nodeID", p.id), + zap.Error(err), + ) + continue + } + + if !p.Send(p.onClosingCtx, msg) { + p.Log.Debug("failed to send get peer list", + zap.Stringer("nodeID", p.id), + ) + } case <-sendPingsTicker.C: if !p.Network.AllowConnection(p.id) { p.Log.Debug("disconnecting from peer", @@ -678,16 +737,16 @@ func (p *peer) handle(msg message.InboundMessage) { p.handlePong(m) msg.OnFinishedHandling() return - case *p2p.Version: - p.handleVersion(m) + case *p2p.Handshake: + p.handleHandshake(m) msg.OnFinishedHandling() return - case *p2p.PeerList: - p.handlePeerList(m) + case *p2p.GetPeerList: + p.handleGetPeerList(m) msg.OnFinishedHandling() return - case *p2p.PeerListAck: - p.handlePeerListAck(m) + case *p2p.PeerList: + p.handlePeerList(m) msg.OnFinishedHandling() return } @@ -828,10 +887,10 @@ func (p *peer) observeUptime(subnetID ids.ID, uptime uint32) { p.observedUptimesLock.Unlock() } -func (p *peer) handleVersion(msg *p2p.Version) { - if p.gotVersion.Get() { +func (p *peer) handleHandshake(msg *p2p.Handshake) { + if p.gotHandshake.Get() { // TODO: this should never happen, should we close the connection here? - p.Log.Verbo("dropping duplicated version message", + p.Log.Verbo("dropping duplicated handshake message", zap.Stringer("nodeID", p.id), ) return @@ -870,48 +929,58 @@ func (p *peer) handleVersion(msg *p2p.Version) { return } - peerVersion, err := version.ParseApplication(msg.MyVersion) - if err != nil { - p.Log.Debug("failed to parse peer version", - zap.Stringer("nodeID", p.id), - zap.Error(err), - ) - p.StartClose() - return + if msg.Client != nil { + p.version = &version.Application{ + Name: msg.Client.Name, + Major: int(msg.Client.Major), + Minor: int(msg.Client.Minor), + Patch: int(msg.Client.Patch), + } + } else { + // Handle legacy version field + peerVersion, err := version.ParseLegacyApplication(msg.MyVersion) + if err != nil { + p.Log.Debug("failed to parse peer version", + zap.Stringer("nodeID", p.id), + zap.Error(err), + ) + p.StartClose() + return + } + p.version = peerVersion } - p.version = peerVersion - if p.VersionCompatibility.Version().Before(peerVersion) { + if p.VersionCompatibility.Version().Before(p.version) { if _, ok := p.Beacons.GetValidator(constants.PrimaryNetworkID, p.id); ok { p.Log.Info("beacon attempting to connect with newer version. You may want to update your client", zap.Stringer("nodeID", p.id), - zap.Stringer("beaconVersion", peerVersion), + zap.Stringer("beaconVersion", p.version), ) } else { p.Log.Debug("peer attempting to connect with newer version. You may want to update your client", zap.Stringer("nodeID", p.id), - zap.Stringer("peerVersion", peerVersion), + zap.Stringer("peerVersion", p.version), ) } } - if err := p.VersionCompatibility.Compatible(peerVersion); err != nil { + if err := p.VersionCompatibility.Compatible(p.version); err != nil { p.Log.Verbo("peer version not compatible", zap.Stringer("nodeID", p.id), - zap.Stringer("peerVersion", peerVersion), + zap.Stringer("peerVersion", p.version), zap.Error(err), ) p.StartClose() return } - // Note that it is expected that the [versionTime] can be in the past. We + // Note that it is expected that the [ipSigningTime] can be in the past. We // are just verifying that the claimed signing time isn't too far in the // future here. - if float64(msg.MyVersionTime)-float64(myTime) > p.MaxClockDifference.Seconds() { + if float64(msg.IpSigningTime)-float64(myTime) > p.MaxClockDifference.Seconds() { p.Log.Debug("peer attempting to connect with version timestamp too far in the future", zap.Stringer("nodeID", p.id), - zap.Uint64("versionTime", msg.MyVersionTime), + zap.Uint64("ipSigningTime", msg.IpSigningTime), ) p.StartClose() return @@ -934,17 +1003,81 @@ func (p *peer) handleVersion(msg *p2p.Version) { } } + for _, acp := range msg.SupportedAcps { + if constants.CurrentACPs.Contains(acp) { + p.supportedACPs.Add(acp) + } + } + for _, acp := range msg.ObjectedAcps { + if constants.CurrentACPs.Contains(acp) { + p.objectedACPs.Add(acp) + } + } + + if p.supportedACPs.Overlaps(p.objectedACPs) { + p.Log.Debug("message with invalid field", + zap.Stringer("nodeID", p.id), + zap.Stringer("messageOp", message.HandshakeOp), + zap.String("field", "ACPs"), + zap.Reflect("supportedACPs", p.supportedACPs), + zap.Reflect("objectedACPs", p.objectedACPs), + ) + p.StartClose() + return + } + + var ( + knownPeers = bloom.EmptyFilter + salt []byte + ) + if msg.KnownPeers != nil { + var err error + knownPeers, err = bloom.Parse(msg.KnownPeers.Filter) + if err != nil { + p.Log.Debug("message with invalid field", + zap.Stringer("nodeID", p.id), + zap.Stringer("messageOp", message.HandshakeOp), + zap.String("field", "KnownPeers.Filter"), + zap.Error(err), + ) + p.StartClose() + return + } + + salt = msg.KnownPeers.Salt + if saltLen := len(salt); saltLen > maxBloomSaltLen { + p.Log.Debug("message with invalid field", + zap.Stringer("nodeID", p.id), + zap.Stringer("messageOp", message.HandshakeOp), + zap.String("field", "KnownPeers.Salt"), + zap.Int("saltLen", saltLen), + ) + p.StartClose() + return + } + } + // "net.IP" type in Golang is 16-byte if ipLen := len(msg.IpAddr); ipLen != net.IPv6len { p.Log.Debug("message with invalid field", zap.Stringer("nodeID", p.id), - zap.Stringer("messageOp", message.VersionOp), + zap.Stringer("messageOp", message.HandshakeOp), zap.String("field", "IP"), zap.Int("ipLen", ipLen), ) p.StartClose() return } + if msg.IpPort == 0 { + p.Log.Debug("message with invalid field", + zap.Stringer("nodeID", p.id), + zap.Stringer("messageOp", message.HandshakeOp), + zap.String("field", "Port"), + zap.Uint32("port", msg.IpPort), + ) + p.StartClose() + return + } p.ip = &SignedIP{ UnsignedIP: UnsignedIP{ @@ -952,7 +1085,7 @@ func (p *peer) handleVersion(msg *p2p.Version) { IP: msg.IpAddr, Port: uint16(msg.IpPort), }, - Timestamp: msg.MyVersionTime, + Timestamp: msg.IpSigningTime, }, Signature: msg.Sig, } @@ -965,19 +1098,12 @@ func (p *peer) handleVersion(msg *p2p.Version) { return } - p.gotVersion.Set(true) + p.gotHandshake.Set(true) - peerIPs, err := p.Network.Peers(p.id) - if err != nil { - p.Log.Error("failed to get peers to gossip for handshake", - zap.Stringer("nodeID", p.id), - zap.Error(err), - ) - return - } + peerIPs := p.Network.Peers(p.id, knownPeers, salt) - // We bypass throttling here to ensure that the version message is - // acknowledged timely. + // We bypass throttling here to ensure that the handshake message is + // acknowledged correctly. peerListMsg, err := p.Config.MessageCreator.PeerList(peerIPs, true /*=bypassThrottling*/) if err != nil { p.Log.Error("failed to create peer list handshake message", @@ -998,9 +1124,68 @@ func (p *peer) handleVersion(msg *p2p.Version) { } } +func (p *peer) handleGetPeerList(msg *p2p.GetPeerList) { + if !p.finishedHandshake.Get() { + p.Log.Verbo("dropping get peer list message", + zap.Stringer("nodeID", p.id), + ) + return + } + + knownPeersMsg := msg.GetKnownPeers() + filter, err := bloom.Parse(knownPeersMsg.GetFilter()) + if err != nil { + p.Log.Debug("message with invalid field", + zap.Stringer("nodeID", p.id), + zap.Stringer("messageOp", message.GetPeerListOp), + zap.String("field", "KnownPeers.Filter"), + zap.Error(err), + ) + p.StartClose() + return + } + + salt := knownPeersMsg.GetSalt() + if saltLen := len(salt); saltLen > maxBloomSaltLen { + p.Log.Debug("message with invalid field", + zap.Stringer("nodeID", p.id), + zap.Stringer("messageOp", message.GetPeerListOp), + zap.String("field", "KnownPeers.Salt"), + zap.Int("saltLen", saltLen), + ) + p.StartClose() + return + } + + peerIPs := p.Network.Peers(p.id, filter, salt) + if len(peerIPs) == 0 { + p.Log.Debug("skipping sending of empty peer list", + zap.Stringer("nodeID", p.id), + ) + return + } + + // Bypass throttling is disabled here to follow the non-handshake message + // sending pattern. + peerListMsg, err := p.Config.MessageCreator.PeerList(peerIPs, false /*=bypassThrottling*/) + if err != nil { + p.Log.Error("failed to create peer list message", + zap.Stringer("nodeID", p.id), + zap.Error(err), + ) + return + } + + if !p.Send(p.onClosingCtx, peerListMsg) { + p.Log.Debug("failed to send peer list", + zap.Stringer("nodeID", p.id), + ) + } +} + func (p *peer) handlePeerList(msg *p2p.PeerList) { if !p.finishedHandshake.Get() { - if !p.gotVersion.Get() { + if !p.gotHandshake.Get() { return } @@ -1009,10 +1194,22 @@ func (p *peer) handlePeerList(msg *p2p.PeerList) { close(p.onFinishHandshake) } - // the peers this peer told us about - discoveredIPs := make([]*ips.ClaimedIPPort, len(msg.ClaimedIpPorts)) + // Invariant: We do not account for clock skew here, as the sender of the + // certificate is expected to account for clock skew during the activation + // of Durango. + durangoTime := version.GetDurangoTime(p.NetworkID) + beforeDurango := time.Now().Before(durangoTime) + discoveredIPs := make([]*ips.ClaimedIPPort, len(msg.ClaimedIpPorts)) // the peers this peer told us about for i, claimedIPPort := range msg.ClaimedIpPorts { - tlsCert, err := staking.ParseCertificate(claimedIPPort.X509Certificate) + var ( + tlsCert *staking.Certificate + err error + ) + if beforeDurango { + tlsCert, err = staking.ParseCertificate(claimedIPPort.X509Certificate) + } else { + tlsCert, err = staking.ParseCertificatePermissive(claimedIPPort.X509Certificate) + } if err != nil { p.Log.Debug("message with invalid field", zap.Stringer("nodeID", p.id), @@ -1028,40 +1225,36 @@ func (p *peer) handlePeerList(msg *p2p.PeerList) { if ipLen := len(claimedIPPort.IpAddr); ipLen != net.IPv6len { p.Log.Debug("message with invalid field", zap.Stringer("nodeID", p.id), - zap.Stringer("messageOp", message.VersionOp), + zap.Stringer("messageOp", message.PeerListOp), zap.String("field", "IP"), zap.Int("ipLen", ipLen), ) p.StartClose() return } - - txID, err := ids.ToID(claimedIPPort.TxId) - if err != nil { + if claimedIPPort.IpPort == 0 { p.Log.Debug("message with invalid field", zap.Stringer("nodeID", p.id), zap.Stringer("messageOp", message.PeerListOp), - zap.String("field", "txID"), - zap.Error(err), + zap.String("field", "Port"), + zap.Uint32("port", claimedIPPort.IpPort), ) - p.StartClose() - return + // TODO: After v1.11.x is activated, close the peer here. + continue } - discoveredIPs[i] = &ips.ClaimedIPPort{ - Cert: tlsCert, - IPPort: ips.IPPort{ + discoveredIPs[i] = ips.NewClaimedIPPort( + tlsCert, + ips.IPPort{ IP: claimedIPPort.IpAddr, Port: uint16(claimedIPPort.IpPort), }, - Timestamp: claimedIPPort.Timestamp, - Signature: claimedIPPort.Signature, - TxID: txID, - } + claimedIPPort.Timestamp, + claimedIPPort.Signature, + ) } - trackedPeers, err := p.Network.Track(p.id, discoveredIPs) - if err != nil { + if err := p.Network.Track(discoveredIPs); err != nil { p.Log.Debug("message with invalid field", zap.Stringer("nodeID", p.id), zap.Stringer("messageOp", message.PeerListOp), @@ -1069,42 +1262,6 @@ func (p *peer) handlePeerList(msg *p2p.PeerList) { zap.Error(err), ) p.StartClose() - return - } - if len(trackedPeers) == 0 { - p.Log.Debug("skipping peerlist ack as there were no tracked peers", - zap.Stringer("nodeID", p.id), - ) - return - } - - peerListAckMsg, err := p.Config.MessageCreator.PeerListAck(trackedPeers) - if err != nil { - p.Log.Error("failed to create message", - zap.Stringer("messageOp", message.PeerListAckOp), - zap.Stringer("nodeID", p.id), - zap.Error(err), - ) - return - } - - if !p.Send(p.onClosingCtx, peerListAckMsg) { - p.Log.Debug("failed to send peer list ack", - zap.Stringer("nodeID", p.id), - ) - } -} - -func (p *peer) handlePeerListAck(msg *p2p.PeerListAck) { - err := p.Network.MarkTracked(p.id, msg.PeerAcks) - if err != nil { - p.Log.Debug("message with invalid field", - zap.Stringer("nodeID", p.id), - zap.Stringer("messageOp", message.PeerListAckOp), - zap.String("field", "txID"), - zap.Error(err), - ) - p.StartClose() } } diff --git a/network/peer/peer_test.go b/network/peer/peer_test.go index 95c756b4d454..797a9634a863 100644 --- a/network/peer/peer_test.go +++ b/network/peer/peer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer @@ -112,7 +112,7 @@ func makeRawTestPeers(t *testing.T, trackedSubnets set.Set[ids.ID]) (*rawTestPee peerConfig0 := sharedConfig peerConfig1 := sharedConfig - ip0 := ips.NewDynamicIPPort(net.IPv6loopback, 0) + ip0 := ips.NewDynamicIPPort(net.IPv6loopback, 1) tls0 := tlsCert0.PrivateKey.(crypto.Signer) peerConfig0.IPSigner = NewIPSigner(ip0, tls0) @@ -122,7 +122,7 @@ func makeRawTestPeers(t *testing.T, trackedSubnets set.Set[ids.ID]) (*rawTestPee inboundMsgChan0 <- msg }) - ip1 := ips.NewDynamicIPPort(net.IPv6loopback, 1) + ip1 := ips.NewDynamicIPPort(net.IPv6loopback, 2) tls1 := tlsCert1.PrivateKey.(crypto.Signer) peerConfig1.IPSigner = NewIPSigner(ip1, tls1) diff --git a/network/peer/set.go b/network/peer/set.go index bc3fbe60743d..cbb9675ec305 100644 --- a/network/peer/set.go +++ b/network/peer/set.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/set_test.go b/network/peer/set_test.go index fb67d25ef05f..fbdbc3e84643 100644 --- a/network/peer/set_test.go +++ b/network/peer/set_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/test_network.go b/network/peer/test_network.go index 9bac6260bece..01a341ae9abc 100644 --- a/network/peer/test_network.go +++ b/network/peer/test_network.go @@ -1,11 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer import ( "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/proto/pb/p2p" + "github.com/ava-labs/avalanchego/utils/bloom" "github.com/ava-labs/avalanchego/utils/ips" ) @@ -19,16 +19,16 @@ func (testNetwork) AllowConnection(ids.NodeID) bool { return true } -func (testNetwork) Track(ids.NodeID, []*ips.ClaimedIPPort) ([]*p2p.PeerAck, error) { - return nil, nil -} - -func (testNetwork) MarkTracked(ids.NodeID, []*p2p.PeerAck) error { +func (testNetwork) Track([]*ips.ClaimedIPPort) error { return nil } func (testNetwork) Disconnected(ids.NodeID) {} -func (testNetwork) Peers(ids.NodeID) ([]ips.ClaimedIPPort, error) { - return nil, nil +func (testNetwork) KnownPeers() ([]byte, []byte) { + return bloom.EmptyFilter.Marshal(), nil +} + +func (testNetwork) Peers(ids.NodeID, *bloom.ReadFilter, []byte) []*ips.ClaimedIPPort { + return nil } diff --git a/network/peer/test_peer.go b/network/peer/test_peer.go index 62717e27dca1..04cfd93aaa7a 100644 --- a/network/peer/test_peer.go +++ b/network/peer/test_peer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer @@ -65,6 +65,7 @@ func StartTestPeer( clientUpgrader := NewTLSClientUpgrader( tlsConfg, prometheus.NewCounter(prometheus.CounterOpts{}), + version.GetDurangoTime(networkID), ) peerID, conn, cert, err := clientUpgrader.Upgrade(conn) @@ -102,7 +103,7 @@ func StartTestPeer( return nil, err } - signerIP := ips.NewDynamicIPPort(net.IPv6zero, 0) + signerIP := ips.NewDynamicIPPort(net.IPv6zero, 1) tls := tlsCert.PrivateKey.(crypto.Signer) peer := Start( diff --git a/network/peer/tls_config.go b/network/peer/tls_config.go index 733812db5f7e..7de848ed062a 100644 --- a/network/peer/tls_config.go +++ b/network/peer/tls_config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer diff --git a/network/peer/upgrader.go b/network/peer/upgrader.go index b601ee370947..9341922175cd 100644 --- a/network/peer/upgrader.go +++ b/network/peer/upgrader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package peer @@ -7,6 +7,7 @@ import ( "crypto/tls" "errors" "net" + "time" "github.com/prometheus/client_golang/prometheus" @@ -29,36 +30,40 @@ type Upgrader interface { type tlsServerUpgrader struct { config *tls.Config invalidCerts prometheus.Counter + durangoTime time.Time } -func NewTLSServerUpgrader(config *tls.Config, invalidCerts prometheus.Counter) Upgrader { +func NewTLSServerUpgrader(config *tls.Config, invalidCerts prometheus.Counter, durangoTime time.Time) Upgrader { return &tlsServerUpgrader{ config: config, invalidCerts: invalidCerts, + durangoTime: durangoTime, } } func (t *tlsServerUpgrader) Upgrade(conn net.Conn) (ids.NodeID, net.Conn, *staking.Certificate, error) { - return connToIDAndCert(tls.Server(conn, t.config), t.invalidCerts) + return connToIDAndCert(tls.Server(conn, t.config), t.invalidCerts, t.durangoTime) } type tlsClientUpgrader struct { config *tls.Config invalidCerts prometheus.Counter + durangoTime time.Time } -func NewTLSClientUpgrader(config *tls.Config, invalidCerts prometheus.Counter) Upgrader { +func NewTLSClientUpgrader(config *tls.Config, invalidCerts prometheus.Counter, durangoTime time.Time) Upgrader { return &tlsClientUpgrader{ config: config, invalidCerts: invalidCerts, + durangoTime: durangoTime, } } func (t *tlsClientUpgrader) Upgrade(conn net.Conn) (ids.NodeID, net.Conn, *staking.Certificate, error) { - return connToIDAndCert(tls.Client(conn, t.config), t.invalidCerts) + return connToIDAndCert(tls.Client(conn, t.config), t.invalidCerts, t.durangoTime) } -func connToIDAndCert(conn *tls.Conn, invalidCerts prometheus.Counter) (ids.NodeID, net.Conn, *staking.Certificate, error) { +func connToIDAndCert(conn *tls.Conn, invalidCerts prometheus.Counter, durangoTime time.Time) (ids.NodeID, net.Conn, *staking.Certificate, error) { if err := conn.Handshake(); err != nil { return ids.EmptyNodeID, nil, nil, err } @@ -72,17 +77,18 @@ func connToIDAndCert(conn *tls.Conn, invalidCerts prometheus.Counter) (ids.NodeI // Invariant: ParseCertificate is used rather than CertificateFromX509 to // ensure that signature verification can assume the certificate was // parseable according the staking package's parser. - peerCert, err := staking.ParseCertificate(tlsCert.Raw) - if err != nil { - invalidCerts.Inc() - return ids.EmptyNodeID, nil, nil, err + // + // TODO: Remove pre-Durango parsing after v1.11.x has activated. + var ( + peerCert *staking.Certificate + err error + ) + if time.Now().Before(durangoTime) { + peerCert, err = staking.ParseCertificate(tlsCert.Raw) + } else { + peerCert, err = staking.ParseCertificatePermissive(tlsCert.Raw) } - - // We validate the certificate here to attempt to make the validity of the - // peer certificate as clear as possible. Specifically, a node running a - // prior version using an invalid certificate should not be able to report - // healthy. - if err := staking.ValidateCertificate(peerCert); err != nil { + if err != nil { invalidCerts.Inc() return ids.EmptyNodeID, nil, nil, err } diff --git a/network/peer/validator_id.go b/network/peer/validator_id.go deleted file mode 100644 index 5471fda20118..000000000000 --- a/network/peer/validator_id.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package peer - -import "github.com/ava-labs/avalanchego/ids" - -// ValidatorID represents a validator that we gossip to other peers -type ValidatorID struct { - // The validator's ID - NodeID ids.NodeID - // The Tx that added this into the validator set - TxID ids.ID -} diff --git a/network/test_cert_1.crt b/network/test_cert_1.crt new file mode 100644 index 000000000000..2f2b95e658ad --- /dev/null +++ b/network/test_cert_1.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEnTCCAoWgAwIBAgIBADANBgkqhkiG9w0BAQsFADAAMCAXDTk5MTIzMTAwMDAw +MFoYDzIxMjQwMTA5MTQ0NTU4WjAAMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAqCOUESK8b5N894dVCSIs4mTfNTdhaL5cnw3ZXSbZlfquBRJOxhqHXutG +An9++OTWvevrssaXBxGT4oOT3N11dm4iKh7ewi3to+1Sfqq71blCVZtBDOeWpZx0 +WwhPO37Us26fCR7T2gStiTHY9qE0QV/9p15OCAFsRb94JuhF0OR0d6tRm0yQ6b7Y +NRzpaBw4MBxZD9h84+QDdhsTyxI0xk/NnbG74pykjsau0/YA9mNqHHSnL4DyD5qu +IKqRfD5HQHemx66I3jEXUB/GxTHhxz5uskIpS9AV3oclvVi14BjSEWgNkJX+nMi+ +tjuSKouAFpzJZzZme2DvmyAecxbNVBdajOTe2QRiG7HKh1OdMZabd2dUNv5S9/gd +bI53s4R++z/H4llsBfk6B2+/DmqDRauh4Mz9HTf0Pud7Nz2b7r77PnPTjHExgN3R +i+Yo6LskRCQTzzTVwW/RY+rNVux9UE6ZPLarDbXnSyetKMUS7qlz8NUerWjtkC6i +om570LfTGs3GxIqVgoGg0mXuji+EoG+XpYR3PRaeo8cAmfEu7T+SxgSfJAv7DyZv ++a2VTZcOPDI1KTLrM8Xovy17t5rd9cy1/75vxnKLiGDEhzWJmNl4IvIYbtihWWl5 +ksdFYbe9Dpvuh/wBCGoK+kmCirUM1DiizWn5TxJeS1qYI8I2sYMCAwEAAaMgMB4w +DgYDVR0PAQH/BAQDAgSwMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB +AABzczRjzfhlmV+bFDzAs7nszQlZREcoRuWe5qHy7VKLvZvIAaYlcApB34hH7nDq +T/8fS8g8rC+Tzw0iCPF21Z4AzSe76V6EU4VGWWe8l00nDszfvavE5BF24z8dCuVC +1gScC1tvG6FPT23koZ0BVmyueCIa7sdqFlDz8rrRpLWfUcLj38gxwWM2JVBHQUvV +j87lzpTNH+2nPiwrKISqUPFi4YvbWKe8T4bY2Elw7THiNLZGfgqOXVkeIVi4fs97 +Tc5uscZ4OpSTlrfJqMJEV8cMRvrDmhD/VWbJvnk7lyELPoHx6MUinBswBT51yvmY +bZh4AZ43GSvSyo/V7p9scytQP3zM1MeHpsFa0RHwGVFp2BmO1abvydAxX0NMWasv +WUzXCKliXsVD/qUeCU/CFnaBqpzBvm4AFBgwHzprwzP9Be/mz/TjTcsfrmoiyxlr +QjXNk9TnP9d+aeOJsRz+JSYyHETACO5PkCg+XCDyEOf+kQAzVb9Dp0oWaCovXciU +A5z0DSDzyKVBOQo0syb5NFsLZ2DeJemNbP+3kCNzBBASQ4VWAvRbLjPh3Oe8A5PZ +xezCvzRE05O6tYkz5C5hcKbpAjfP8G8RV6ERjLBICBfb7XI7T0hixhiNHlIKknkJ +F82B/zDt+qBFARw8A/qr44RF+vy3Ql4IS2ZcflAv2pTO +-----END CERTIFICATE----- diff --git a/network/test_cert_2.crt b/network/test_cert_2.crt new file mode 100644 index 000000000000..283e286be446 --- /dev/null +++ b/network/test_cert_2.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEnTCCAoWgAwIBAgIBADANBgkqhkiG9w0BAQsFADAAMCAXDTk5MTIzMTAwMDAw +MFoYDzIxMjQwMTA5MTQ0NTQ3WjAAMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEArT7afarml6cvCmAySAO8GQka1mcQIMACyEWy6KsqiccX+DoGh5ECyQSa +WFKWKGdQ32dAWGVlSkmmgJ1jtW749hSguysav3EPMaxe/ad5CV1MwyyccGS9U99M +z0UVuFEXVjN5W6UlcULp1oJDj07NzZP6ByRiDvnjzgeYb3jHwjqOBNwex1jLW6vp +oWD03zTanVQXZaaGcEISCI2CgDP3uXfd0NQpoGVpf9gMi0cdGu8gpqbLqBjzjzr8 +GDBQYGaWKFnlqe6X9nBUad/qNE3Zeb3ehSg+M2ecQzTZFWirfa6cGTtovu04RMML +9OLflQy3rTRST2HQ6z0gpVCP3V2Mg/LmAuWyhOLVYNkhEwkRHvddzFksRzQ+ghpP +cGfvI0dwxQV0CbEMVjd9zVEA6dOrMLI3st2922hqF23Al1+Hwcu1G/T3ybfSTwjd +YZ23IgkQF4r+RIXevzgOBBXfEwE8XERW2zNwUG5Sv5dxx+FgDjX0EGbrzgY6OeKT +D1SP/7WQLjwmGgwyNJYkAklvEKwU+dlGD5NpgvJ9fg8R1wUhp2HhSZ1l1OUVmRYw +YqUm7dTLK1CJU2BH2sRyZcUkwstjvgi688zfHNttGYmAnx6wGS12jWf+W4df+QNI +Ng6AdcJ5Ee0z0JAbTpZW/zX3CTSroow7igHnd4AwvKEVQFcyO/MCAwEAAaMgMB4w +DgYDVR0PAQH/BAQDAgSwMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB +ACePaZvjw2KiheheWNjzOv2B+7uLVe7oEbThEUQypEmTFK8wKaHwI4BGdBGEOr/N +LZ1M2wAYgwzMTEDJE+GEB2ZHIdH9cH5lu7ITsOMVcBSJttEJVhhEtbMwVJ9JC62j +AsW4VmHFpEik+xvinxedKczXOa21YJo4sv2TiFWFaSHqPeRo7HA1dxQYOwiLsS6e +JKIupMrn8IZz2YN5gFhbvQTBp2J3u6kxMIzN0a+BPARR4fwMn5lVMVvye/+8Kwtw +dZHSN1FYUcFqHagmhNlNkAOaGQklSFWtsVVQxQCFS2bxEImLj5kG16fCAsQoRC0J +ZS2OaRncrtB0r0Qu1JB5XJP9FLflSb57KIxBNVrl+iWdWikgBFE6cMthMwgLfQ99 +k8AMp6KrCjcxqegN+P30ct/JwahKPq2+SwtdHG3yrZ2TJEjhOtersrTnRK9zqm9v +lqS7JsiztjgqnhMs2eTdXygfEe0AoZihGTaaLYj37A9+2RECkuijkjBghG2NBnv6 +264lTghZyZcZgZNCgYglYC1bhifEorJpYf6TOOcDAi5UH8R7vi4x70vI6sIDrhga +d9E63EVe11QdIjceceMlNm42UTrhl0epMbL6FIzU+d91qBgd9qT6YqoYPFZSiYFy +2hArgLxH2fxTXatCAit5g1MEk0w1MiHVrPZ8lTU3U/ET +-----END CERTIFICATE----- diff --git a/network/test_cert_3.crt b/network/test_cert_3.crt new file mode 100644 index 000000000000..c0977191ec7b --- /dev/null +++ b/network/test_cert_3.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEnTCCAoWgAwIBAgIBADANBgkqhkiG9w0BAQsFADAAMCAXDTk5MTIzMTAwMDAw +MFoYDzIxMjQwMTA5MTQ0NTM0WjAAMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEA5aV76ivIZ1iWmW0OzGMCrmFQBnej9JntQ1jP9yiacKu7j5Z/bD/eqoyc +jRwoSiesErfnThAGy7H80glVw/XmC0fYNPVDPyzAEdNk46M3yEI8hAKI6aSkl1s1 +KVAHpQuNcG+3xIB39OOMx0XuycZ6gqzyMmjqeT0cThNDXTwGbodMVDAf0q220QAq +zB/lz0sjHPXlYh25LJ1yPtl+vlcfGrP+q+2ODR9rnI79PE7AZB4Xc6wUIca5XXkH +PS7zQ1Ida1xrf446MYCVuazLFhpzq8/nhkxNMzxdZsJaWavL+xkpjGxAySvj0jlu +QFGsmsxOIU/XgJD/VRqqyISXpl2wg0l8mpsU9fV7bEW1y6MIc7AARRgbbEPiDz8m +/O8mjEW3C16untLHB7LzPCCitTssGR65Shkj+Lw+aM4X5ZI+Xm8eHTRCek8T5Cl3 +Sm2UFkLk2mun6cwoyWWhwi6+EfW6ks0c7qSHtJTP8DgLrWxYmBuD9PKSHclpa4/5 +toj52YnT6fIBJWz5ggIdntRCaH8+0eWvwuvDsdPUL7JQFjJmfQOdMenlNqW2aEvx ++JZiYLJBWj9cjpI33P5CAfFEVM3IFlDHmMHRTQ/kKLcfvSDfuofEBoMt4tjf01Um +dfi8kFKWl9ba9I7CoQ13U4J1wkk6KxatZP7eGCmKRoq8w+Y38NsCAwEAAaMgMB4w +DgYDVR0PAQH/BAQDAgSwMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB +AKsvbN5/r4YPguetl+jIhqpr4TZM8GNZRGTqkKC8clRspBeihJqkNQWsnZiFkJTH +NhNAx+7tlJHqeGdojc2XjBAkc+//qYqXKHgihsO54bVG9oN9IPO+mpPumRRhGneH +jTUE/hLFqwA4ZPw5L1HtJ0m1yqg/HXf4aBXcVQ/YO8YN17ZgLpueYt+Chi1pP/Ku +TzHuoKuHst2T6uuZQZxcD+XJoXwdOt7mfPTh5y9/Psjn+qx833DNWSwF3O/lEghA +2yOb+5CFta2LLUHH894oj5SvgJ/5cvn4+NbyDCUv5ebvE98BMh72PLNRuIRV0gfO +XalMIZ+9Jm2TGXD0dWt9GeZ5z3h+nCEB6s3x0sqluaWG3lTUx+4T/aIxdGuvPFi6 +7DWm7TG7yxFGfbECyyXXL+B/gyHhE1Q93nE3wK9flSG+ljqFJS+8wytht52XhgwE +lV1AwHgxkbkFzNIwB0s7etR9+wBcQvFKqeCZrDeG1twKNcY1dv1D/OCUlBYJvL/X +YADeT2ZjFzHhWhv6TLVEAtqytT1o4qXh6VWeIrwfMG0VcQSiJyNxwO/aW5BOTM44 +EelDzvSjo/pRxqN/m44Iuf0Ran86DO7LmjNYh/04FN3oaL9cFIaT9BWXt/Xx2Fdw ++dg5bPSJ62ExVnnNRlY9lQECkSoRZK2epcICs+3YmmGX +-----END CERTIFICATE----- diff --git a/network/test_key_1.key b/network/test_key_1.key new file mode 100644 index 000000000000..c49775114d66 --- /dev/null +++ b/network/test_key_1.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCoI5QRIrxvk3z3 +h1UJIiziZN81N2FovlyfDdldJtmV+q4FEk7GGode60YCf3745Na96+uyxpcHEZPi +g5Pc3XV2biIqHt7CLe2j7VJ+qrvVuUJVm0EM55alnHRbCE87ftSzbp8JHtPaBK2J +Mdj2oTRBX/2nXk4IAWxFv3gm6EXQ5HR3q1GbTJDpvtg1HOloHDgwHFkP2Hzj5AN2 +GxPLEjTGT82dsbvinKSOxq7T9gD2Y2ocdKcvgPIPmq4gqpF8PkdAd6bHrojeMRdQ +H8bFMeHHPm6yQilL0BXehyW9WLXgGNIRaA2Qlf6cyL62O5Iqi4AWnMlnNmZ7YO+b +IB5zFs1UF1qM5N7ZBGIbscqHU50xlpt3Z1Q2/lL3+B1sjnezhH77P8fiWWwF+ToH +b78OaoNFq6HgzP0dN/Q+53s3PZvuvvs+c9OMcTGA3dGL5ijouyREJBPPNNXBb9Fj +6s1W7H1QTpk8tqsNtedLJ60oxRLuqXPw1R6taO2QLqKibnvQt9MazcbEipWCgaDS +Ze6OL4Sgb5elhHc9Fp6jxwCZ8S7tP5LGBJ8kC/sPJm/5rZVNlw48MjUpMuszxei/ +LXu3mt31zLX/vm/GcouIYMSHNYmY2Xgi8hhu2KFZaXmSx0Vht70Om+6H/AEIagr6 +SYKKtQzUOKLNaflPEl5LWpgjwjaxgwIDAQABAoICAHGe8U0PGyWPFlCzLDyq0of+ +wHNWxEWi9jYphqyTN1BJgVU+BOuMO9RhywKfI6+P/KmFBtbdqmuFblkQr1f+c4Uf +cYjjKYcwwDkZg7jDKYGI2pG9A51z1nJ9oodtuxUqZRQH+gKQyXq31Ik0nTg0wXo4 +ItH6QWLZi1AqzkgEiEFcUHQZ2mDGwdqjM7nYmsXW5AVm8qxpkCP0Dn6+V4bP+8fT +X9BjreK6Fd3B15y2zfmyPp+SGPRZ/7mZvnemq/+4mi+va43enPEBXY6wmoLhbYBV +6ToeyYdIy65/x3oHu4f/Xd2TYi9FnTRX18CPyvtjH6CoPNW5hlFztRcwAkOlsgQ7 +sZ+9FGAnRvz1lrBg80DeCHeSKVkDHmMQSINhPcPnlMJpxn6iiZjdvz/Bd+9RRqZl +xUI/lV3/Wueh8SeCQlFOj3fHBZEaq6QoC/VmmaeIiLEm1hj+ymuFxwOtA6AKWLb3 +59XnEkONeTfv9d2eQ7NOPU86n/zhWHUKodmBUEaxLDaUwRkS1Adb4rLuRwrMfn3a +2KkknYWzvyrlk8lDqKAMeQneFmpresGAXeIn0vt434eaGcK4a/IZ8PebuhZxGq1Z +bVbxVm0AsLmd9X3htR6MOiZswnVmA3JCw1AMKZpLMDRSbjV0uYuhBJQsN4Y/kyOK +l52JtymFNvbuRF+836+RAoIBAQDZ9wyihmgsEPLl7PHzfYo4pnTs1puoT5PS7GjO +iVm7UtOKaawsJxKX3cxzSFVXONs9hbPPzmsQEL3Xz+lUsgrSeXReF00KLRbfE2LM +dv9hlJVMQXEKnEkFYNNgETyZIJE3ZDDqdd2PDzNM8aKHlvLYREiETCwVn7r4x5QE +jIHC0gUjRJHqUgSdAMa+qvranPLxVV9mpJmL2RXjjb/OtJosFef9h5augSNI9tPS +EDLm4wMjyXr25Vu20/cusmTlOhCzi2d23hNHx8nPE0nCEVtZ2rnnWyH/ozqRnpXX +EPh0IeZQmebBhHWzkjIPaOa05Ua5rkVAQau8/FUUubjXytyZAoIBAQDFerIQwodP +V46WVC0LtSq4ju88x1vgDfT0NFE3H6hIX7Mc91zt0NGOhzv4crfjnoj+romNfQwD +0ymtudnnoaGPFBRrRF8T+26jfFpes7Ve5q/PpY78zJH1ZLwyKKX4dzgeY0Aj9FbO +q4dzh21oD7wyknRm0NTqOvgLAuxoBFZ4FTgudKNDzGymgIaQVT1+h0226og289WT +iptkpOZ/HcxQts2U3j3a87pJB0IFjIrBTtVqIyphdwRVDa929WGDITUPHa3aqykx +Ma/zvXvocAlIDITVwxXlS16DkSS+5jdN/CUj5h0O6FefGaJmk6/bFQIeXM4fRhRF +M0cs1mxXkNR7AoIBAQCFxYftn4wDr4tD7f44sE3Kou6UBMqXq+9PvmQ8jjOSMi0+ +f8h5eKmCp0+5WSV3WJ/FzG8lFMzEmWHKOAI+Rt85ee0fajGQE0g8NMuoLUhjfSt8 +F5XnKy/tqxVPmoSUflZhpo4W96u5B1021f4oNU5pyM6w04ci5lt8IBEKEan6Bae9 +k3HyW9AVA8r2bj1zOmwoDXt1pYPPPraeZ/rWRCVy9SbihPrHst4TA9nQzLxQ0/az +Wg6rxOxa8xB7imU+AjsJ1n7zhyxSG54SBwZ3outr5D/AbEAbgvSJNslDq1iw/bU6 +tpnXHxKV2R38MyeU0jpr7zb1Tti2Li+RfsKhPhHRAoIBAHfbpXH4r6mfaeKiCokd +l2VXE6tfEMtnjTIfAuAjLb9nnk3JcTTCVj5cpDCCaEwV7+4sPz6KFB3KL3TK5Y/q +ESXHOTF12QNGyvsdQbhS+JU2DKVKRgP3oetADd2fwESTD5OaB9cKuRlNELQ1EVlk +m4RSUaYJwAC+c8gzKQtk/pp5vpSrpGBFFfjk70dxBRbjxm5r4OsBibK4IOKwF1o1 +2sluek6NqRtYbMtgRVka2SjE0VFPMKzhUNbSrJnWCy5MnGilSdz7n8/E6ZdVfXwx +a+C4AHPBqWt3GFFgad4X2p9Rl7U3OJHQwUXGiEQcBVNCZ/vHti9TGIB7xApZxn5L +YDsCggEBAJ8RhrfEzm2YkyODFKFwgOszHQ3TNSvbC4+yLOUMSdzdKIyroOq0t53A +PSs046TINd+EDs9Pi6E69C+RYLim1NYMHeHFMzmKnQPXPwJVnYYUKInbIMURcuE9 +8FNBSKg3SUGz31SwG4bRIkJluMUp5oSAEUxWaxbUzLYkZex2uxnUGSd6TjddWKk1 ++SuoiZ3+W6yPWWh7TDKAR/oukBCmLIJI7dXSwv2DhagRpppdoMfqcnsCAgs/omB8 +Ku4y/jEkGbxLgo3Qd6U1o/QZlZG+9Q0iaxQS4dIpMxA3LwrL5txy00bm3JeWMB4H +MUZqfFgfj8ESxFBEeToOwr3Jq46vOwQ= +-----END PRIVATE KEY----- diff --git a/network/test_key_2.key b/network/test_key_2.key new file mode 100644 index 000000000000..bcc0a192b2b4 --- /dev/null +++ b/network/test_key_2.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCtPtp9quaXpy8K +YDJIA7wZCRrWZxAgwALIRbLoqyqJxxf4OgaHkQLJBJpYUpYoZ1DfZ0BYZWVKSaaA +nWO1bvj2FKC7Kxq/cQ8xrF79p3kJXUzDLJxwZL1T30zPRRW4URdWM3lbpSVxQunW +gkOPTs3Nk/oHJGIO+ePOB5hveMfCOo4E3B7HWMtbq+mhYPTfNNqdVBdlpoZwQhII +jYKAM/e5d93Q1CmgZWl/2AyLRx0a7yCmpsuoGPOPOvwYMFBgZpYoWeWp7pf2cFRp +3+o0Tdl5vd6FKD4zZ5xDNNkVaKt9rpwZO2i+7ThEwwv04t+VDLetNFJPYdDrPSCl +UI/dXYyD8uYC5bKE4tVg2SETCREe913MWSxHND6CGk9wZ+8jR3DFBXQJsQxWN33N +UQDp06swsjey3b3baGoXbcCXX4fBy7Ub9PfJt9JPCN1hnbciCRAXiv5Ehd6/OA4E +Fd8TATxcRFbbM3BQblK/l3HH4WAONfQQZuvOBjo54pMPVI//tZAuPCYaDDI0liQC +SW8QrBT52UYPk2mC8n1+DxHXBSGnYeFJnWXU5RWZFjBipSbt1MsrUIlTYEfaxHJl +xSTCy2O+CLrzzN8c220ZiYCfHrAZLXaNZ/5bh1/5A0g2DoB1wnkR7TPQkBtOllb/ +NfcJNKuijDuKAed3gDC8oRVAVzI78wIDAQABAoICAQCIgPu7BMuINoyUClPT9k1h +FJF22eIVS/VlQ7XCKgvsX1j9lwrKCnI9XUkXyorR7wYD4OEMRWhX7kwpDtoffP7h +NkOm9kGvEjA8nWqDRk/SFxeCuUXSMS4URd/JeM+yWQKgQxKeKTOlWGnTQPRmmFsE +XlIlCn/Q+QiLr+RmAK601VpNbfs6azZgVsZRB4opzQVr7XQ5/cnz7bszzfxDc67/ +DflSr7jUztMfjmXj3/aI4F3DsazKGE7gTkOP85GBQ5OQ27Rf/sTxwnRgr7Nj3us6 +R2ZrWNgZvMudEKjze3OUJd6M6wiPV258j4p+O7ybPlgDOzSXo6TvlUyBtUaFz04E +5S7bgimNUxEjFzTxkn9W/FTUeauvJcgDk+JmMZ+I9dFdMIuyksndywN9KdXBVxZH +1ZtO1P6JeFpxF7zQUmkH+/6RZd9PbQGlpNI06nAj98LVwqSDCO1aejLqoXYs9zqG +DOU4JdRm3qK0eshIghkvVOWIYhqKPkskQfbTFY+hasg82cGGFyzxqOsSiuW+CVIy +3iF3WyfKgvLMABoK/38zutsMT+/mOtA7rjErh1NJuwwWkkglmuwQMDqaWdOASs+v +MK8JjSi6zDpnbp70Prw5pUlHvvsD1iYWo7SOcpFos+U5zw1jHJJvnAatzcXWixuu +Xzbn2BtCqSFigW7waMy14QKCAQEAx/Nwy2xH9lVGfz8aO2CB0FGL9Ra3Jcv4HFJT +nw6/yvVLvRAwr87+/c+qbIzwLKbQXV/4vmNsqPrIJiazY+Tk739DjcW8YaMbejfr +ASPHtYbeF0FmVbxBHNZ/JSDSYUXdFZ7JlBiDSs3zhPlFBZYG2tU3JJZCR8+9J/Ss +JEIwL9UlapMznMwljFkLbvZ2oFstKkfdY61WxROOIwuGaKr0yRnNvMMp135JiB/O +dwh/NfROt4JzQ5O4ipMg6Wc73+OvBsOSQHYZQHl9NOaK1uomu5bUY7H8pLwGU7sw +LmPRzrGiu8dB+UUEyFkNI2xzwkjet+0UGupDyOfsCMf9hlzWmwKCAQEA3c8FeHkl +Il4GEB0VEw1NtC5x6i+s3NiPOlUmH+nOHgdaI7/BfljfTokQBGo+GkXkJ36yTEMh +L9Vtya3HtW4VEHNfPMjntPztn4XQvMZdSpu/k8rM44m+CB0DDLhFfwRr2cyUAwHz +xebXw8KhceqaWRp6ygJGx5Sk0gr7s7nhmIByjdx4tddEH/MahLklGdV7Vnp+yb3o +zNLVx/aDueknArgUb/zvZRcYWuNoGs9ac4pl0m6jan/x0ZcdBF0SU2bI6ltvF3WT +qwcvVnbJbBwq5PRuL4ZUqrqmXBbBAkpLJTx+kfPKD4bgcZTBnV2TxDbzze9CeieT +YCtg4u+khW7ZiQKCAQBrMIEuPD0TvEFPo8dvP1w4Dg9Gc0f5li/LFwNHCIQezIMu +togzJ3ehHvuQt7llZoPbGsDhZ7FvoQk9EpAmpCVqksHnNbK4cNUhHur3sHO2R7e1 +pdSzb3lEeWStxbuic+6CUZ5kqwNvTZsXlP3Acd344EZwcbDUiHQyAENsKKNmcRBe +4szPaM1UQMQVV0De1CIRQXdYoSsb+VDATsReRg9140Rcxg8fO881jz+CpmZzySWN +0PvzpTRP7XG+Th5V9tv0d1FnByigXMCXZGPXtKzQ8ZmoXFlBAp8tsfKxW8e005uW +qMogVDStJrgZXmFsLN5goVKe3yk5gcMSLgwmRIyzAoIBAQCoE6CkmsAd27uiaDc4 ++aLA/1TIzZmiu+NEo5NBKY1LyexvHHZGBJgqTcg6YDtw8zchCmuXSGMUeRk5cxrb +C3Cgx5wKVn7l8acqc18qPPIigATavBkn7o92XG2cLOJUjogfQVuDL+6GLxeeupRV +2x1cmakj/DegMq32j+YNWbRuOB8WClPaDyYLQ877dcR8X/2XGTmMLAEFfFoMrWtB +7D/oWo76EWNiae7FqH6RmkCDPwNLQxVHtW4LkQOm89PYKRHkLKbw0uKz/bzMOzUE +XA/Q8Lux/YuY19kJ/SACWUO6Eq4icObTfzQCPWO9mFRJog57JWttXyHZBOXk8Qzt +I4NpAoIBACurK0zJxaGUdTjmzaVipauyOZYFBsbzvCWsdSNodtZ/mw6n/qkj2N33 +vNCRLrsQAkDKATzWrscRg+xvl5/wIa4B3s8TZNIp3hL7bvI/NoR5bi5M0vcjdXEd +DeKeZsSBzEs5zivM3aWEF5MSR2zpJPNYyD0PnT6EvZOkMoq6LM3FJcouS1ChePLQ +wHEY5ZMqPODOcQ+EixNXl6FGdywaJYxKnG4liG9zdJ0lGNIivTA7gyM+JCbG4fs8 +73uGsbCpts5Y2xKFp3uK8HjWKbOCR3dE4mOZM8M/NlsUGNjSydXZMIJYWR8nvVmo +i3mHicYaTQxj0ruIz7JHOtFNVGi1sME= +-----END PRIVATE KEY----- diff --git a/network/test_key_3.key b/network/test_key_3.key new file mode 100644 index 000000000000..2cef238b67a9 --- /dev/null +++ b/network/test_key_3.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDlpXvqK8hnWJaZ +bQ7MYwKuYVAGd6P0me1DWM/3KJpwq7uPln9sP96qjJyNHChKJ6wSt+dOEAbLsfzS +CVXD9eYLR9g09UM/LMAR02TjozfIQjyEAojppKSXWzUpUAelC41wb7fEgHf044zH +Re7JxnqCrPIyaOp5PRxOE0NdPAZuh0xUMB/SrbbRACrMH+XPSyMc9eViHbksnXI+ +2X6+Vx8as/6r7Y4NH2ucjv08TsBkHhdzrBQhxrldeQc9LvNDUh1rXGt/jjoxgJW5 +rMsWGnOrz+eGTE0zPF1mwlpZq8v7GSmMbEDJK+PSOW5AUayazE4hT9eAkP9VGqrI +hJemXbCDSXyamxT19XtsRbXLowhzsABFGBtsQ+IPPyb87yaMRbcLXq6e0scHsvM8 +IKK1OywZHrlKGSP4vD5ozhflkj5ebx4dNEJ6TxPkKXdKbZQWQuTaa6fpzCjJZaHC +Lr4R9bqSzRzupIe0lM/wOAutbFiYG4P08pIdyWlrj/m2iPnZidPp8gElbPmCAh2e +1EJofz7R5a/C68Ox09QvslAWMmZ9A50x6eU2pbZoS/H4lmJgskFaP1yOkjfc/kIB +8URUzcgWUMeYwdFND+Qotx+9IN+6h8QGgy3i2N/TVSZ1+LyQUpaX1tr0jsKhDXdT +gnXCSTorFq1k/t4YKYpGirzD5jfw2wIDAQABAoICAQC/Rt32h29NvTj7JB5OWS2z +h3R7Xo2ev9Mi5EecSyKQNEpuZ+FMjcpubd47nrdkRLULhkhP+gNfCKpXW9Um+psY +zEemnJ7dcO2uK1B+VsWwtJLpNZ9KVIuPUjXuai1j6EJv423Ca2r++8WXeYVSZVJH +o7u8By09vIvl8B+M+eE1kNYfzVHETlLWtHfxO6RTy/a8OYhM+ArzwVSWStxJuBE9 +Ua0PETffcEtWxLbi04lmGrZX7315QKfG1ncUHBYc/blpYjpbrWCFON/9HpKtn2y3 +L91dPBKVWXNGkx1kUTb+t8+mmchAh6Ejyhgt1Jma+g8dqf4KpTs3bJXRnLcfqCvL +Kq+wCUGv7iVWlTmhlzLpneajLDdBxGfbkAgwPFOyZoJNrnh6hU60TPc1IV6YSLlB +GsxesK9QWUrg3BAN4iKD3FvDt0qeUPbPztxEZi1OzSYQDZUQBrBL+WHuD9NxeAYe +2yx1OlPMo73gK5GW/MHBCz77+NX2kVURlTvYW4TsmInCRvOTsVNkRPUJtiHYT7Ss +Y8SzS5F/u9sfjFAVowGgwtNfq8Rm6Q1QdPZltiUNBgiTekFNQEy7WhzVg6MlT5Ca +BRqUhN3+CFwxLZ9rSQL6gxfAHk9umb0ee4JU9JgcYjtb5AtyE6DmmcSZPSejjxit +HwZ/g5MDK7kk5fKMcnL7kQKCAQEA895z7T0c6y3rhWfEUMDdTlsPgAoxYNf+jXyJ +aQmtfnDP9tf8BdPpobfHp29e7JRaGGa9QWPaaemBPHXMmD+IegG9/E+PQdHQwFSG +OpI13uCBULt8a+MMUbTCg1V4uXqf2j1BUo9SFQ6aXh/Rg1gVBgsq1M6eyvel93io +0X+/cinsDEpB5HENZwBuRb0SP0RfCgQR9Yh+jIy2TwJDDNw3sG1TvIo9aK7blSwB +z/gwSDx1UUa2KReD4ChYcqgLFUj3F/uF2f20P/JuaUn7tU3HoCsbG0C+Cci/XSJ9 +gu8xYl64Vg16bO3CflqjucPTFXgyBOt0lIug77YYa9CgCUJvEwKCAQEA8RHqGghV +meDnRXvPmAEwtoT7IKBe+eYjGN6wc2o+QZzjeUFkyfOtaB8rqriUXqvihD2GD6XQ +O/cSNCqp5g6yUhBLo3b9BmCsQsvxkhMpwB/hdi5aYjn+CFQVD4rAso9yGwRBWoA0 +gQdGMKenOUhU/PtVKyTTUuY7rFD8RhYq0ZLqEgO7chn8QXCNPo7MfE/qF9vQBosP +ktiS0FG442PJp2B/lYKK6N2w77ZeCoLhQowaNN0/N36kX/n4bjBE2XFLNpSuHtlg +C7bV/RMR5i/3yB0eRVUDVlqC077qlC1w0tCNZvvi6kbWwIu/4pQTdcA8mAz5B7Lc +OwOMbA2GT4OIGQKCAQABoyS0Gwzup0hFhQTUZfcWZ5YbDfZ25/xVhtiFVANOLgO3 +bIvMnjebVliIzz6b6AMS1t2+aqU0wNSVS1UsUIDiENDtuLsFfhsgr3CXRBQIgwlb +OWcEcmnKwqPrrc85r5ETLgYaP8wVSBvRNfV6JEU/3SNUem6mfjMnDjBT97+ZTJ7B +Fl6K4hds8ZvL7BELS7I3pv9X3qq61tcCgMlidLgK/zDouyTeZw4iWkFI3Cm20nEX +MppWfEnuX1b4rhgk9HB0QMQNSp7DLyV+n3iJJxSIBsIP1Mdx2V8viOO+1UxHlMs4 +CK8hvBbqMkGXJbFtG3l6fvoxZR6XfWl8j9IDPebxAoIBAF07cnBy/LgwdQE4awb8 +ntxX/c+WdmTrjnNV3KQmWMGDba49jj9UkKIOPBMgo7EhhM9kA+8VT72BRncKcP7a +fDikuLwVjrHivXxv55N4+dKmAcp1DtuiVg7ehe6m2PO16olsUeIwZx3ntEuo61GK +GeRlR4ESEvCivj1cbNSmShUXXpNtAheU2Sxt3RJuo8MIHR7xEjkVmwZN4CnVEU5Q +D3M+LNmjzRlWc9GhlCk4iOn1yUTctFBAGE5OHLhwzo/R8ya+xcCEjVK6eXQQ5gFC +V+/64vQpdsr04lgGJC7+i/3cTnOfwxicIP4CjkmQvx3xJP4hNka189qW+r3nVSR3 +WDECggEAAQCCqF4J8C2keY+o/kYQBq0tHhrC28HgiVQuCGc4XruYQtDh4di/I72F +RsvgVHS29ApAlh29i29ws7K2bU6WIc+JR3nmwAHUtiJmxRZhn/c722AvRXF5YMH/ +u46bEURHF5sGz8vr5chX/R4LiF579xyNsB9KC3mPqdjW/L6ACQdrBJVAS9cwplO0 +D+YWxmCE1Ps2tQtz6ZN+LUC7WO6M24k8KW2y4Scue0/23uCllWFgS3/vxDdQDZWn ++7AvMYPh4Wrfdd0t0cU+c9rirFYVz+uo/QBUIZOIw64AvIUjZpHTbhcjz1mAqcgJ +eAOQk+OFUTNKeI9uJwoNYOguHsxt2w== +-----END PRIVATE KEY----- diff --git a/network/test_network.go b/network/test_network.go index d8795e14e044..8079e76240c1 100644 --- a/network/test_network.go +++ b/network/test_network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -18,7 +18,6 @@ import ( "github.com/ava-labs/avalanchego/network/dialer" "github.com/ava-labs/avalanchego/network/peer" "github.com/ava-labs/avalanchego/network/throttling" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/snow/networking/tracker" "github.com/ava-labs/avalanchego/snow/uptime" @@ -67,7 +66,7 @@ func (l *noopListener) Close() error { func (*noopListener) Addr() net.Addr { return &net.TCPAddr{ IP: net.IPv4zero, - Port: 0, + Port: 1, } } @@ -157,6 +156,8 @@ func NewTestNetwork( PeerListNonValidatorGossipSize: constants.DefaultNetworkPeerListNonValidatorGossipSize, PeerListPeersGossipSize: constants.DefaultNetworkPeerListPeersGossipSize, PeerListGossipFreq: constants.DefaultNetworkPeerListGossipFreq, + PeerListPullGossipFreq: constants.DefaultNetworkPeerListPullGossipFreq, + PeerListBloomResetFreq: constants.DefaultNetworkPeerListBloomResetFreq, }, DelayConfig: DelayConfig{ @@ -187,10 +188,8 @@ func NewTestNetwork( networkConfig.TLSConfig = tlsConfig networkConfig.TLSKey = tlsCert.PrivateKey.(crypto.Signer) - ctx := snow.DefaultConsensusContextTest() - beacons := validators.NewManager() networkConfig.Validators = currentValidators - networkConfig.Beacons = beacons + networkConfig.Beacons = validators.NewManager() // This never actually does anything because we never initialize the P-chain networkConfig.UptimeCalculator = uptime.NoOpCalculator @@ -207,7 +206,7 @@ func NewTestNetwork( return nil, err } networkConfig.CPUTargeter = tracker.NewTargeter( - ctx.Log, + logging.NoLog{}, &tracker.TargeterConfig{ VdrAlloc: float64(runtime.NumCPU()), MaxNonVdrUsage: .8 * float64(runtime.NumCPU()), @@ -217,7 +216,7 @@ func NewTestNetwork( networkConfig.ResourceTracker.CPUTracker(), ) networkConfig.DiskTargeter = tracker.NewTargeter( - ctx.Log, + logging.NoLog{}, &tracker.TargeterConfig{ VdrAlloc: 1000 * units.GiB, MaxNonVdrUsage: 1000 * units.GiB, @@ -227,12 +226,7 @@ func NewTestNetwork( networkConfig.ResourceTracker.DiskTracker(), ) - networkConfig.MyIPPort = ips.NewDynamicIPPort(net.IPv4zero, 0) - - networkConfig.GossipTracker, err = peer.NewGossipTracker(metrics, "") - if err != nil { - return nil, err - } + networkConfig.MyIPPort = ips.NewDynamicIPPort(net.IPv4zero, 1) return NewNetwork( &networkConfig, diff --git a/network/throttling/bandwidth_throttler.go b/network/throttling/bandwidth_throttler.go index 5adfcb0062a2..d8244eb37974 100644 --- a/network/throttling/bandwidth_throttler.go +++ b/network/throttling/bandwidth_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/bandwidth_throttler_test.go b/network/throttling/bandwidth_throttler_test.go index 5d51555baa9f..11a687f3a91b 100644 --- a/network/throttling/bandwidth_throttler_test.go +++ b/network/throttling/bandwidth_throttler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/common.go b/network/throttling/common.go index 9350fb4f684c..cedd5d732dbb 100644 --- a/network/throttling/common.go +++ b/network/throttling/common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/dial_throttler.go b/network/throttling/dial_throttler.go index 491c312b95ef..07c04aefb812 100644 --- a/network/throttling/dial_throttler.go +++ b/network/throttling/dial_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/dial_throttler_test.go b/network/throttling/dial_throttler_test.go index db1776e8e24b..1dd57c2e78ad 100644 --- a/network/throttling/dial_throttler_test.go +++ b/network/throttling/dial_throttler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_conn_throttler.go b/network/throttling/inbound_conn_throttler.go index 7f2206396ca8..5e1528074135 100644 --- a/network/throttling/inbound_conn_throttler.go +++ b/network/throttling/inbound_conn_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_conn_throttler_test.go b/network/throttling/inbound_conn_throttler_test.go index 0b5d1ccd7fb8..9e2fde15e825 100644 --- a/network/throttling/inbound_conn_throttler_test.go +++ b/network/throttling/inbound_conn_throttler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_conn_upgrade_throttler.go b/network/throttling/inbound_conn_upgrade_throttler.go index 9d058e29ba12..4df5ee39b776 100644 --- a/network/throttling/inbound_conn_upgrade_throttler.go +++ b/network/throttling/inbound_conn_upgrade_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_conn_upgrade_throttler_test.go b/network/throttling/inbound_conn_upgrade_throttler_test.go index d0e1fe93c84a..2f6cd926451e 100644 --- a/network/throttling/inbound_conn_upgrade_throttler_test.go +++ b/network/throttling/inbound_conn_upgrade_throttler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_msg_buffer_throttler.go b/network/throttling/inbound_msg_buffer_throttler.go index d06177839ea2..65306eea7d51 100644 --- a/network/throttling/inbound_msg_buffer_throttler.go +++ b/network/throttling/inbound_msg_buffer_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_msg_buffer_throttler_test.go b/network/throttling/inbound_msg_buffer_throttler_test.go index 76f399b6e94e..11d655c1c4fc 100644 --- a/network/throttling/inbound_msg_buffer_throttler_test.go +++ b/network/throttling/inbound_msg_buffer_throttler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_msg_byte_throttler.go b/network/throttling/inbound_msg_byte_throttler.go index 659d9f398309..459df7a11b5d 100644 --- a/network/throttling/inbound_msg_byte_throttler.go +++ b/network/throttling/inbound_msg_byte_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_msg_byte_throttler_test.go b/network/throttling/inbound_msg_byte_throttler_test.go index e71f0abba238..68a12965ff1e 100644 --- a/network/throttling/inbound_msg_byte_throttler_test.go +++ b/network/throttling/inbound_msg_byte_throttler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_msg_throttler.go b/network/throttling/inbound_msg_throttler.go index 3d79f640ae1a..86e20085466c 100644 --- a/network/throttling/inbound_msg_throttler.go +++ b/network/throttling/inbound_msg_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_resource_throttler.go b/network/throttling/inbound_resource_throttler.go index 42873fe42d6a..eb0e939b8d9e 100644 --- a/network/throttling/inbound_resource_throttler.go +++ b/network/throttling/inbound_resource_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/inbound_resource_throttler_test.go b/network/throttling/inbound_resource_throttler_test.go index eebbaf7dc851..bfc5a726c14b 100644 --- a/network/throttling/inbound_resource_throttler_test.go +++ b/network/throttling/inbound_resource_throttler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/no_inbound_msg_throttler.go b/network/throttling/no_inbound_msg_throttler.go index de6e03f81502..6f7af32fb135 100644 --- a/network/throttling/no_inbound_msg_throttler.go +++ b/network/throttling/no_inbound_msg_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/outbound_msg_throttler.go b/network/throttling/outbound_msg_throttler.go index 6f5ad24561f3..e1656c04ffe1 100644 --- a/network/throttling/outbound_msg_throttler.go +++ b/network/throttling/outbound_msg_throttler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/outbound_msg_throttler_test.go b/network/throttling/outbound_msg_throttler_test.go index 09d8b6f272ef..1930d935171b 100644 --- a/network/throttling/outbound_msg_throttler_test.go +++ b/network/throttling/outbound_msg_throttler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/throttling/release_func.go b/network/throttling/release_func.go index 0abe2bf4270d..e2cbcf1b1b20 100644 --- a/network/throttling/release_func.go +++ b/network/throttling/release_func.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package throttling diff --git a/network/tracked_ip.go b/network/tracked_ip.go index ca673f76b91d..6a95bbee5a47 100644 --- a/network/tracked_ip.go +++ b/network/tracked_ip.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network diff --git a/network/tracked_ip_test.go b/network/tracked_ip_test.go index bbf6267d86db..956f02cc19b4 100644 --- a/network/tracked_ip_test.go +++ b/network/tracked_ip_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network diff --git a/node/beacon_manager.go b/node/beacon_manager.go index af088f3b4845..9b6806fdf037 100644 --- a/node/beacon_manager.go +++ b/node/beacon_manager.go @@ -1,16 +1,16 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package node import ( + "sync" "sync/atomic" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/timer" "github.com/ava-labs/avalanchego/version" ) @@ -18,10 +18,11 @@ var _ router.Router = (*beaconManager)(nil) type beaconManager struct { router.Router - timer *timer.Timer - beacons validators.Manager - requiredConns int64 - numConns int64 + beacons validators.Manager + requiredConns int64 + numConns int64 + onSufficientlyConnected chan struct{} + onceOnSufficientlyConnected sync.Once } func (b *beaconManager) Connected(nodeID ids.NodeID, nodeVersion *version.Application, subnetID ids.ID) { @@ -29,7 +30,9 @@ func (b *beaconManager) Connected(nodeID ids.NodeID, nodeVersion *version.Applic if isBeacon && constants.PrimaryNetworkID == subnetID && atomic.AddInt64(&b.numConns, 1) >= b.requiredConns { - b.timer.Cancel() + b.onceOnSufficientlyConnected.Do(func() { + close(b.onSufficientlyConnected) + }) } b.Router.Connected(nodeID, nodeVersion, subnetID) } diff --git a/node/beacon_manager_test.go b/node/beacon_manager_test.go index 82be435e92f1..82b47efdacd6 100644 --- a/node/beacon_manager_test.go +++ b/node/beacon_manager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package node @@ -15,7 +15,6 @@ import ( "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/timer" "github.com/ava-labs/avalanchego/version" ) @@ -41,10 +40,10 @@ func TestBeaconManager_DataRace(t *testing.T) { mockRouter := router.NewMockRouter(ctrl) b := beaconManager{ - Router: mockRouter, - timer: timer.NewTimer(nil), - beacons: validatorSet, - requiredConns: numValidators, + Router: mockRouter, + beacons: validatorSet, + requiredConns: numValidators, + onSufficientlyConnected: make(chan struct{}), } // connect numValidators validators, each with a weight of 1 diff --git a/node/config.go b/node/config.go index 5839da75960a..a26ec4806fc6 100644 --- a/node/config.go +++ b/node/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package node @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/avalanchego/chains" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/nat" "github.com/ava-labs/avalanchego/network" "github.com/ava-labs/avalanchego/snow/networking/benchlist" "github.com/ava-labs/avalanchego/snow/networking/router" @@ -19,7 +18,6 @@ import ( "github.com/ava-labs/avalanchego/subnets" "github.com/ava-labs/avalanchego/trace" "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/dynamicip" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/profiler" @@ -74,19 +72,16 @@ type APIConfig struct { } type IPConfig struct { - IPPort ips.DynamicIPPort `json:"ip"` - IPUpdater dynamicip.Updater `json:"-"` - IPResolutionFreq time.Duration `json:"ipResolutionFrequency"` - // True if we attempted NAT traversal - AttemptedNATTraversal bool `json:"attemptedNATTraversal"` - // Tries to perform network address translation - Nat nat.Router `json:"-"` + PublicIP string `json:"publicIP"` + PublicIPResolutionService string `json:"publicIPResolutionService"` + PublicIPResolutionFreq time.Duration `json:"publicIPResolutionFreq"` // The host portion of the address to listen on. The port to // listen on will be sourced from IPPort. // // - If empty, listen on all interfaces (both ipv4 and ipv6). // - If populated, listen only on the specified address. ListenHost string `json:"listenHost"` + ListenPort uint16 `json:"listenPort"` } type StakingConfig struct { diff --git a/node/insecure_validator_manager.go b/node/insecure_validator_manager.go index d2cdab94cc89..0e23b8b90cc3 100644 --- a/node/insecure_validator_manager.go +++ b/node/insecure_validator_manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package node diff --git a/node/node.go b/node/node.go index 40eb8dc16bef..1aeaddcf6245 100644 --- a/node/node.go +++ b/node/node.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package node @@ -48,6 +48,7 @@ import ( "github.com/ava-labs/avalanchego/indexer" "github.com/ava-labs/avalanchego/ipcs" "github.com/ava-labs/avalanchego/message" + "github.com/ava-labs/avalanchego/nat" "github.com/ava-labs/avalanchego/network" "github.com/ava-labs/avalanchego/network/dialer" "github.com/ava-labs/avalanchego/network/peer" @@ -64,6 +65,7 @@ import ( "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/dynamicip" "github.com/ava-labs/avalanchego/utils/filesystem" "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/ips" @@ -73,13 +75,14 @@ import ( "github.com/ava-labs/avalanchego/utils/profiler" "github.com/ava-labs/avalanchego/utils/resource" "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/avalanchego/utils/timer" "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms" "github.com/ava-labs/avalanchego/vms/avm" "github.com/ava-labs/avalanchego/vms/nftfx" "github.com/ava-labs/avalanchego/vms/platformvm" + "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/signer" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/propertyfx" "github.com/ava-labs/avalanchego/vms/registry" "github.com/ava-labs/avalanchego/vms/rpcchainvm/runtime" @@ -90,6 +93,13 @@ import ( platformconfig "github.com/ava-labs/avalanchego/vms/platformvm/config" ) +const ( + stakingPortName = constants.AppName + "-staking" + httpPortName = constants.AppName + "-http" + + ipResolutionTimeout = 30 * time.Second +) + var ( genesisHashKey = []byte("genesisID") ungracefulShutdown = []byte("ungracefulShutdown") @@ -155,7 +165,7 @@ func New( } n.initMetrics() - + n.initNAT() if err := n.initAPIServer(); err != nil { // Start the API Server return nil, fmt.Errorf("couldn't initialize API server: %w", err) } @@ -192,6 +202,7 @@ func New( n.vdrs = validators.NewManager() if !n.Config.SybilProtectionEnabled { + logger.Warn("sybil control is not enforced") n.vdrs = newOverriddenManager(constants.PrimaryNetworkID, n.vdrs) } if err := n.initResourceManager(n.MetricsRegisterer); err != nil { @@ -265,6 +276,10 @@ type Node struct { // Storage for this node DB database.Database + router nat.Router + portMapper *nat.Mapper + ipUpdater dynamicip.Updater + // Profiles the process. Nil if continuous profiling is disabled. profiler profiler.ContinuousProfiler @@ -379,8 +394,6 @@ type Node struct { // Initialize the networking layer. // Assumes [n.vdrs], [n.CPUTracker], and [n.CPUTargeter] have been initialized. func (n *Node) initNetworking() error { - currentIPPort := n.Config.IPPort.IPPort() - // Providing either loopback address - `::1` for ipv6 and `127.0.0.1` for ipv4 - as the listen // host will avoid the need for a firewall exception on recent MacOS: // @@ -398,8 +411,7 @@ func (n *Node) initNetworking() error { // // 1: https://apple.stackexchange.com/questions/393715/do-you-want-the-application-main-to-accept-incoming-network-connections-pop // 2: https://github.com/golang/go/issues/56998 - listenAddress := net.JoinHostPort(n.Config.ListenHost, strconv.FormatUint(uint64(currentIPPort.Port), 10)) - + listenAddress := net.JoinHostPort(n.Config.ListenHost, strconv.FormatUint(uint64(n.Config.ListenPort), 10)) listener, err := net.Listen(constants.NetworkType, listenAddress) if err != nil { return err @@ -407,23 +419,67 @@ func (n *Node) initNetworking() error { // Wrap listener so it will only accept a certain number of incoming connections per second listener = throttling.NewThrottledListener(listener, n.Config.NetworkConfig.ThrottlerConfig.MaxInboundConnsPerSec) - ipPort, err := ips.ToIPPort(listener.Addr().String()) + // Record the bound address to enable inclusion in process context file. + n.stakingAddress = listener.Addr().String() + ipPort, err := ips.ToIPPort(n.stakingAddress) if err != nil { - n.Log.Info("initializing networking", - zap.Stringer("currentNodeIP", currentIPPort), - ) - } else { - ipPort = ips.IPPort{ - IP: currentIPPort.IP, - Port: ipPort.Port, + return err + } + + var dynamicIP ips.DynamicIPPort + switch { + case n.Config.PublicIP != "": + // Use the specified public IP. + ipPort.IP = net.ParseIP(n.Config.PublicIP) + if ipPort.IP == nil { + return fmt.Errorf("invalid IP Address: %s", n.Config.PublicIP) + } + dynamicIP = ips.NewDynamicIPPort(ipPort.IP, ipPort.Port) + n.ipUpdater = dynamicip.NewNoUpdater() + case n.Config.PublicIPResolutionService != "": + // Use dynamic IP resolution. + resolver, err := dynamicip.NewResolver(n.Config.PublicIPResolutionService) + if err != nil { + return fmt.Errorf("couldn't create IP resolver: %w", err) + } + + // Use that to resolve our public IP. + ctx, cancel := context.WithTimeout(context.Background(), ipResolutionTimeout) + ipPort.IP, err = resolver.Resolve(ctx) + cancel() + if err != nil { + return fmt.Errorf("couldn't resolve public IP: %w", err) + } + dynamicIP = ips.NewDynamicIPPort(ipPort.IP, ipPort.Port) + n.ipUpdater = dynamicip.NewUpdater(dynamicIP, resolver, n.Config.PublicIPResolutionFreq) + default: + ipPort.IP, err = n.router.ExternalIP() + if err != nil { + return fmt.Errorf("public IP / IP resolution service not given and failed to resolve IP with NAT: %w", err) } - n.Log.Info("initializing networking", - zap.Stringer("currentNodeIP", ipPort), + dynamicIP = ips.NewDynamicIPPort(ipPort.IP, ipPort.Port) + n.ipUpdater = dynamicip.NewNoUpdater() + } + + if ipPort.IP.IsLoopback() || ipPort.IP.IsPrivate() { + n.Log.Warn("P2P IP is private, you will not be publicly discoverable", + zap.Stringer("ip", ipPort), ) } - // Record the bound address to enable inclusion in process context file. - n.stakingAddress = listener.Addr().String() + // Regularly update our public IP and port mappings. + n.portMapper.Map( + ipPort.Port, + ipPort.Port, + stakingPortName, + dynamicIP, + n.Config.PublicIPResolutionFreq, + ) + go n.ipUpdater.Dispatch(n.Log) + + n.Log.Info("initializing networking", + zap.Stringer("ip", ipPort), + ) tlsKey, ok := n.Config.StakingTLSCert.PrivateKey.(crypto.Signer) if !ok { @@ -440,6 +496,25 @@ func (n *Node) initNetworking() error { ) } + // We allow nodes to gossip unknown ACPs in case the current ACPs constant + // becomes out of date. + var unknownACPs set.Set[uint32] + for acp := range n.Config.NetworkConfig.SupportedACPs { + if !constants.CurrentACPs.Contains(acp) { + unknownACPs.Add(acp) + } + } + for acp := range n.Config.NetworkConfig.ObjectedACPs { + if !constants.CurrentACPs.Contains(acp) { + unknownACPs.Add(acp) + } + } + if unknownACPs.Len() > 0 { + n.Log.Warn("gossipping unknown ACPs", + zap.Reflect("acps", unknownACPs), + ) + } + tlsConfig := peer.TLSConfig(n.Config.StakingTLSCert, n.tlsKeyLogWriterCloser) // Configure benchlist @@ -480,46 +555,38 @@ func (n *Node) initNetworking() error { requiredConns := (3*numBootstrappers + 3) / 4 if requiredConns > 0 { - // Set a timer that will fire after a given timeout unless we connect - // to a sufficient portion of nodes. If the timeout fires, the node will - // shutdown. - timer := timer.NewTimer(func() { - // If the timeout fires and we're already shutting down, nothing to do. - if !n.shuttingDown.Get() { + onSufficientlyConnected := make(chan struct{}) + consensusRouter = &beaconManager{ + Router: consensusRouter, + beacons: n.bootstrappers, + requiredConns: int64(requiredConns), + onSufficientlyConnected: onSufficientlyConnected, + } + + // Log a warning if we aren't able to connect to a sufficient portion of + // nodes. + go func() { + timer := time.NewTimer(n.Config.BootstrapBeaconConnectionTimeout) + defer timer.Stop() + + select { + case <-timer.C: + if n.shuttingDown.Get() { + return + } n.Log.Warn("failed to connect to bootstrap nodes", zap.Stringer("bootstrappers", n.bootstrappers), zap.Duration("duration", n.Config.BootstrapBeaconConnectionTimeout), ) + case <-onSufficientlyConnected: } - }) - - go timer.Dispatch() - timer.SetTimeoutIn(n.Config.BootstrapBeaconConnectionTimeout) - - consensusRouter = &beaconManager{ - Router: consensusRouter, - timer: timer, - beacons: n.bootstrappers, - requiredConns: int64(requiredConns), - } - } - - // initialize gossip tracker - gossipTracker, err := peer.NewGossipTracker(n.MetricsRegisterer, n.networkNamespace) - if err != nil { - return err + }() } - // keep gossip tracker synchronized with the validator set - n.vdrs.RegisterCallbackListener(constants.PrimaryNetworkID, &peer.GossipTrackerCallback{ - Log: n.Log, - GossipTracker: gossipTracker, - }) - // add node configs to network config n.Config.NetworkConfig.Namespace = n.networkNamespace n.Config.NetworkConfig.MyNodeID = n.ID - n.Config.NetworkConfig.MyIPPort = n.Config.IPPort + n.Config.NetworkConfig.MyIPPort = dynamicIP n.Config.NetworkConfig.NetworkID = n.Config.NetworkID n.Config.NetworkConfig.Validators = n.vdrs n.Config.NetworkConfig.Beacons = n.bootstrappers @@ -531,7 +598,6 @@ func (n *Node) initNetworking() error { n.Config.NetworkConfig.ResourceTracker = n.resourceTracker n.Config.NetworkConfig.CPUTargeter = n.cpuTargeter n.Config.NetworkConfig.DiskTargeter = n.diskTargeter - n.Config.NetworkConfig.GossipTracker = gossipTracker n.Net, err = network.NewNetwork( &n.Config.NetworkConfig, @@ -837,16 +903,76 @@ func (n *Node) initMetrics() { n.MetricsGatherer = metrics.NewMultiGatherer() } +func (n *Node) initNAT() { + n.Log.Info("initializing NAT") + + if n.Config.PublicIP == "" && n.Config.PublicIPResolutionService == "" { + n.router = nat.GetRouter() + if !n.router.SupportsNAT() { + n.Log.Warn("UPnP and NAT-PMP router attach failed, " + + "you may not be listening publicly. " + + "Please confirm the settings in your router") + } + } else { + n.router = nat.NewNoRouter() + } + + n.portMapper = nat.NewPortMapper(n.Log, n.router) +} + // initAPIServer initializes the server that handles HTTP calls func (n *Node) initAPIServer() error { n.Log.Info("initializing API server") + // An empty host is treated as a wildcard to match all addresses, so it is + // considered public. + hostIsPublic := n.Config.HTTPHost == "" + if !hostIsPublic { + ip, err := ips.Lookup(n.Config.HTTPHost) + if err != nil { + n.Log.Fatal("failed to lookup HTTP host", + zap.String("host", n.Config.HTTPHost), + zap.Error(err), + ) + return err + } + hostIsPublic = !ip.IsLoopback() && !ip.IsPrivate() + + n.Log.Debug("finished HTTP host lookup", + zap.String("host", n.Config.HTTPHost), + zap.Stringer("ip", ip), + zap.Bool("isPublic", hostIsPublic), + ) + } + listenAddress := net.JoinHostPort(n.Config.HTTPHost, strconv.FormatUint(uint64(n.Config.HTTPPort), 10)) listener, err := net.Listen("tcp", listenAddress) if err != nil { return err } + addr := listener.Addr().String() + ipPort, err := ips.ToIPPort(addr) + if err != nil { + return err + } + + // Don't open the HTTP port if the HTTP server is private + if hostIsPublic { + n.Log.Warn("HTTP server is binding to a potentially public host. "+ + "You may be vulnerable to a DoS attack if your HTTP port is publicly accessible", + zap.String("host", n.Config.HTTPHost), + ) + + n.portMapper.Map( + ipPort.Port, + ipPort.Port, + httpPortName, + nil, + n.Config.PublicIPResolutionFreq, + ) + } + protocol := "http" if n.Config.HTTPSEnabled { cert, err := tls.X509KeyPair(n.Config.HTTPSCert, n.Config.HTTPSKey) @@ -1046,16 +1172,20 @@ func (n *Node) initVMs() error { vdrs = validators.NewManager() } - vmRegisterer := registry.NewVMRegisterer(registry.VMRegistererConfig{ - APIServer: n.APIServer, - Log: n.Log, - VMFactoryLog: n.VMFactoryLog, - VMManager: n.VMManager, - }) + durangoTime := version.GetDurangoTime(n.Config.NetworkID) + if err := txs.InitCodec(durangoTime); err != nil { + return err + } + if err := block.InitCodec(durangoTime); err != nil { + return err + } + if err := coreth.InitCodec(durangoTime); err != nil { + return err + } // Register the VMs that Avalanche supports err := utils.Err( - vmRegisterer.Register(context.TODO(), constants.PlatformVMID, &platformvm.Factory{ + n.VMManager.RegisterFactory(context.TODO(), constants.PlatformVMID, &platformvm.Factory{ Config: platformconfig.Config{ Chains: n.chainManager, Validators: vdrs, @@ -1084,17 +1214,18 @@ func (n *Node) initVMs() error { ApricotPhase5Time: version.GetApricotPhase5Time(n.Config.NetworkID), BanffTime: version.GetBanffTime(n.Config.NetworkID), CortinaTime: version.GetCortinaTime(n.Config.NetworkID), - DurangoTime: version.GetDurangoTime(n.Config.NetworkID), + DurangoTime: durangoTime, UseCurrentHeight: n.Config.UseCurrentHeight, }, }), - vmRegisterer.Register(context.TODO(), constants.AVMID, &avm.Factory{ + n.VMManager.RegisterFactory(context.TODO(), constants.AVMID, &avm.Factory{ Config: avmconfig.Config{ TxFee: n.Config.TxFee, CreateAssetTxFee: n.Config.CreateAssetTxFee, + DurangoTime: durangoTime, }, }), - vmRegisterer.Register(context.TODO(), constants.EVMID, &coreth.Factory{}), + n.VMManager.RegisterFactory(context.TODO(), constants.EVMID, &coreth.Factory{}), n.VMManager.RegisterFactory(context.TODO(), secp256k1fx.ID, &secp256k1fx.Factory{}), n.VMManager.RegisterFactory(context.TODO(), nftfx.ID, &nftfx.Factory{}), n.VMManager.RegisterFactory(context.TODO(), propertyfx.ID, &propertyfx.Factory{}), @@ -1115,7 +1246,7 @@ func (n *Node) initVMs() error { CPUTracker: n.resourceManager, RuntimeTracker: n.runtimeManager, }), - VMRegisterer: vmRegisterer, + VMManager: n.VMManager, }) // register any vms that need to be installed as plugins from disk @@ -1269,6 +1400,7 @@ func (n *Node) initInfoAPI() error { VMManager: n.VMManager, }, n.Log, + n.vdrs, n.chainManager, n.VMManager, n.Config.NetworkConfig.MyIPPort, @@ -1548,6 +1680,8 @@ func (n *Node) shutdown() { zap.Error(err), ) } + n.portMapper.UnmapAllPorts() + n.ipUpdater.Stop() if err := n.indexer.Close(); err != nil { n.Log.Debug("error closing tx indexer", zap.Error(err), diff --git a/node/overridden_manager.go b/node/overridden_manager.go index 91d8c198a4c3..4dd49b65eab6 100644 --- a/node/overridden_manager.go +++ b/node/overridden_manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package node diff --git a/node/overridden_manager_test.go b/node/overridden_manager_test.go index 79f03579a5d0..8af93ff68071 100644 --- a/node/overridden_manager_test.go +++ b/node/overridden_manager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package node @@ -64,12 +64,12 @@ func TestOverriddenString(t *testing.T) { require.NoError(m.AddStaker(subnetID1, nodeID1, nil, ids.Empty, 1)) om := newOverriddenManager(subnetID0, m) - expected := "Overridden Validator Manager (SubnetID = TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES): Validator Manager: (Size = 2)\n" + - " Subnet[TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES]: Validator Set: (Size = 2, Weight = 9223372036854775807)\n" + - " Validator[0]: NodeID-111111111111111111116DBWJs, 1\n" + - " Validator[1]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 9223372036854775806\n" + - " Subnet[2mcwQKiD8VEspmMJpL1dc7okQQ5dDVAWeCBZ7FWBFAbxpv3t7w]: Validator Set: (Size = 1, Weight = 1)\n" + - " Validator[0]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 1" + expected := `Overridden Validator Manager (SubnetID = TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES): Validator Manager: (Size = 2) + Subnet[TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES]: Validator Set: (Size = 2, Weight = 9223372036854775807) + Validator[0]: NodeID-111111111111111111116DBWJs, 1 + Validator[1]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 9223372036854775806 + Subnet[2mcwQKiD8VEspmMJpL1dc7okQQ5dDVAWeCBZ7FWBFAbxpv3t7w]: Validator Set: (Size = 1, Weight = 1) + Validator[0]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 1` result := om.String() require.Equal(expected, result) } diff --git a/proto/Dockerfile.buf b/proto/Dockerfile.buf index 40d0a5420b4a..3c8864e636b7 100644 --- a/proto/Dockerfile.buf +++ b/proto/Dockerfile.buf @@ -6,7 +6,7 @@ RUN apt-get update && apt -y install bash curl unzip git WORKDIR /opt RUN \ - curl -L https://golang.org/dl/go1.20.8.linux-amd64.tar.gz > golang.tar.gz && \ + curl -L https://golang.org/dl/go1.20.12.linux-amd64.tar.gz > golang.tar.gz && \ mkdir golang && \ tar -zxvf golang.tar.gz -C golang/ diff --git a/proto/p2p/p2p.proto b/proto/p2p/p2p.proto index 9b45148e3344..6b5deacc7e01 100644 --- a/proto/p2p/p2p.proto +++ b/proto/p2p/p2p.proto @@ -8,6 +8,8 @@ option go_package = "github.com/ava-labs/avalanchego/proto/pb/p2p"; // Represents peer-to-peer messages. // Only one type can be non-null. message Message { + reserved 33; // Until after durango activation. + reserved 36; // Next unused field number. // NOTES // Use "oneof" for each message type and set rest to null if not used. // That is because when the compression is enabled, we don't want to include uncompressed fields. @@ -28,7 +30,8 @@ message Message { // Network messages: Ping ping = 11; Pong pong = 12; - Version version = 13; + Handshake handshake = 13; + GetPeerList get_peer_list = 35; PeerList peer_list = 14; // State-sync messages: @@ -56,8 +59,6 @@ message Message { AppRequest app_request = 30; AppResponse app_response = 31; AppGossip app_gossip = 32; - - PeerListAck peer_list_ack = 33; AppError app_error = 34; } } @@ -91,17 +92,17 @@ message Pong { repeated SubnetUptime subnet_uptimes = 2; } -// Version is the first outbound message sent to a peer when a connection is +// Handshake is the first outbound message sent to a peer when a connection is // established to start the p2p handshake. // -// Peers must respond to a Version message with a PeerList message to allow the +// Peers must respond to a Handshake message with a PeerList message to allow the // peer to connect to other peers in the network. // // Peers should drop connections to peers with incompatible versions. -message Version { +message Handshake { // Network the peer is running on (e.g local, testnet, mainnet) uint32 network_id = 1; - // Unix timestamp when this Version message was created + // Unix timestamp when this Handshake message was created uint64 my_time = 2; // IP address of the peer bytes ip_addr = 3; @@ -110,11 +111,31 @@ message Version { // Avalanche client version string my_version = 5; // Timestamp of the IP - uint64 my_version_time = 6; + uint64 ip_signing_time = 6; // Signature of the peer IP port pair at a provided timestamp bytes sig = 7; // Subnets the peer is tracking repeated bytes tracked_subnets = 8; + Client client = 9; + repeated uint32 supported_acps = 10; + repeated uint32 objected_acps = 11; + BloomFilter known_peers = 12; +} + +// Metadata about a peer's P2P client used to determine compatibility +message Client { + // Client name (e.g avalanchego) + string name = 1; + // Client semantic version + uint32 major = 2; + uint32 minor = 3; + uint32 patch = 4; +} + +// BloomFilter with a random salt to prevent consistent hash collisions +message BloomFilter { + bytes filter = 1; + bytes salt = 2; } // ClaimedIpPort contains metadata needed to connect to a peer @@ -133,38 +154,29 @@ message ClaimedIpPort { bytes tx_id = 6; } +// GetPeerList contains a bloom filter of the currently known validator IPs. +// +// GetPeerList must not be responded to until finishing the handshake. After the +// handshake is completed, GetPeerlist messages should be responded to with a +// Peerlist message containing validators that are not present in the bloom +// filter. +message GetPeerList { + BloomFilter known_peers = 1; +} + // PeerList contains network-level metadata for a set of validators. // -// PeerList must be sent in response to an inbound Version message from a +// PeerList must be sent in response to an inbound Handshake message from a // remote peer a peer wants to connect to. Once a PeerList is received after -// a version message, the p2p handshake is complete and the connection is +// a Handshake message, the p2p handshake is complete and the connection is // established. - -// Peers should periodically send PeerList messages to allow peers to -// discover each other. // -// PeerListAck should be sent in response to a PeerList. +// PeerList should be sent in response to a GetPeerlist message if the handshake +// has been completed. message PeerList { repeated ClaimedIpPort claimed_ip_ports = 1; } -// PeerAck acknowledges that a gossiped peer in a PeerList message will be -// tracked by the remote peer. -message PeerAck { - // P-Chain transaction that added the acknowledged peer to the validator - // set - bytes tx_id = 1; - // Timestamp of the signed ip of the peer - uint64 timestamp = 2; -} - -// PeerListAck is sent in response to PeerList to acknowledge the subset of -// peers that the peer will attempt to connect to. -message PeerListAck { - reserved 1; // deprecated; used to be tx_ids - repeated PeerAck peer_acks = 2; -} - // GetStateSummaryFrontier requests a peer's most recently accepted state // summary message GetStateSummaryFrontier { diff --git a/proto/pb/p2p/p2p.pb.go b/proto/pb/p2p/p2p.pb.go index 8b804d1ceb02..0732bf1a13c8 100644 --- a/proto/pb/p2p/p2p.pb.go +++ b/proto/pb/p2p/p2p.pb.go @@ -88,8 +88,9 @@ type Message struct { // *Message_CompressedZstd // *Message_Ping // *Message_Pong - // *Message_Version - // *Message_PeerList + // *Message_Handshake + // *Message_GetPeerList + // *Message_PeerList_ // *Message_GetStateSummaryFrontier // *Message_StateSummaryFrontier_ // *Message_GetAcceptedStateSummary @@ -108,7 +109,6 @@ type Message struct { // *Message_AppRequest // *Message_AppResponse // *Message_AppGossip - // *Message_PeerListAck // *Message_AppError Message isMessage_Message `protobuf_oneof:"message"` } @@ -180,16 +180,23 @@ func (x *Message) GetPong() *Pong { return nil } -func (x *Message) GetVersion() *Version { - if x, ok := x.GetMessage().(*Message_Version); ok { - return x.Version +func (x *Message) GetHandshake() *Handshake { + if x, ok := x.GetMessage().(*Message_Handshake); ok { + return x.Handshake } return nil } -func (x *Message) GetPeerList() *PeerList { - if x, ok := x.GetMessage().(*Message_PeerList); ok { - return x.PeerList +func (x *Message) GetGetPeerList() *GetPeerList { + if x, ok := x.GetMessage().(*Message_GetPeerList); ok { + return x.GetPeerList + } + return nil +} + +func (x *Message) GetPeerList_() *PeerList { + if x, ok := x.GetMessage().(*Message_PeerList_); ok { + return x.PeerList_ } return nil } @@ -320,13 +327,6 @@ func (x *Message) GetAppGossip() *AppGossip { return nil } -func (x *Message) GetPeerListAck() *PeerListAck { - if x, ok := x.GetMessage().(*Message_PeerListAck); ok { - return x.PeerListAck - } - return nil -} - func (x *Message) GetAppError() *AppError { if x, ok := x.GetMessage().(*Message_AppError); ok { return x.AppError @@ -361,12 +361,16 @@ type Message_Pong struct { Pong *Pong `protobuf:"bytes,12,opt,name=pong,proto3,oneof"` } -type Message_Version struct { - Version *Version `protobuf:"bytes,13,opt,name=version,proto3,oneof"` +type Message_Handshake struct { + Handshake *Handshake `protobuf:"bytes,13,opt,name=handshake,proto3,oneof"` +} + +type Message_GetPeerList struct { + GetPeerList *GetPeerList `protobuf:"bytes,35,opt,name=get_peer_list,json=getPeerList,proto3,oneof"` } -type Message_PeerList struct { - PeerList *PeerList `protobuf:"bytes,14,opt,name=peer_list,json=peerList,proto3,oneof"` +type Message_PeerList_ struct { + PeerList_ *PeerList `protobuf:"bytes,14,opt,name=peer_list,json=peerList,proto3,oneof"` } type Message_GetStateSummaryFrontier struct { @@ -445,10 +449,6 @@ type Message_AppGossip struct { AppGossip *AppGossip `protobuf:"bytes,32,opt,name=app_gossip,json=appGossip,proto3,oneof"` } -type Message_PeerListAck struct { - PeerListAck *PeerListAck `protobuf:"bytes,33,opt,name=peer_list_ack,json=peerListAck,proto3,oneof"` -} - type Message_AppError struct { AppError *AppError `protobuf:"bytes,34,opt,name=app_error,json=appError,proto3,oneof"` } @@ -461,9 +461,11 @@ func (*Message_Ping) isMessage_Message() {} func (*Message_Pong) isMessage_Message() {} -func (*Message_Version) isMessage_Message() {} +func (*Message_Handshake) isMessage_Message() {} + +func (*Message_GetPeerList) isMessage_Message() {} -func (*Message_PeerList) isMessage_Message() {} +func (*Message_PeerList_) isMessage_Message() {} func (*Message_GetStateSummaryFrontier) isMessage_Message() {} @@ -501,8 +503,6 @@ func (*Message_AppResponse) isMessage_Message() {} func (*Message_AppGossip) isMessage_Message() {} -func (*Message_PeerListAck) isMessage_Message() {} - func (*Message_AppError) isMessage_Message() {} // Ping reports a peer's perceived uptime percentage. @@ -684,21 +684,21 @@ func (x *Pong) GetSubnetUptimes() []*SubnetUptime { return nil } -// Version is the first outbound message sent to a peer when a connection is +// Handshake is the first outbound message sent to a peer when a connection is // established to start the p2p handshake. // -// Peers must respond to a Version message with a PeerList message to allow the +// Peers must respond to a Handshake message with a PeerList message to allow the // peer to connect to other peers in the network. // // Peers should drop connections to peers with incompatible versions. -type Version struct { +type Handshake struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Network the peer is running on (e.g local, testnet, mainnet) NetworkId uint32 `protobuf:"varint,1,opt,name=network_id,json=networkId,proto3" json:"network_id,omitempty"` - // Unix timestamp when this Version message was created + // Unix timestamp when this Handshake message was created MyTime uint64 `protobuf:"varint,2,opt,name=my_time,json=myTime,proto3" json:"my_time,omitempty"` // IP address of the peer IpAddr []byte `protobuf:"bytes,3,opt,name=ip_addr,json=ipAddr,proto3" json:"ip_addr,omitempty"` @@ -707,15 +707,19 @@ type Version struct { // Avalanche client version MyVersion string `protobuf:"bytes,5,opt,name=my_version,json=myVersion,proto3" json:"my_version,omitempty"` // Timestamp of the IP - MyVersionTime uint64 `protobuf:"varint,6,opt,name=my_version_time,json=myVersionTime,proto3" json:"my_version_time,omitempty"` + IpSigningTime uint64 `protobuf:"varint,6,opt,name=ip_signing_time,json=ipSigningTime,proto3" json:"ip_signing_time,omitempty"` // Signature of the peer IP port pair at a provided timestamp Sig []byte `protobuf:"bytes,7,opt,name=sig,proto3" json:"sig,omitempty"` // Subnets the peer is tracking - TrackedSubnets [][]byte `protobuf:"bytes,8,rep,name=tracked_subnets,json=trackedSubnets,proto3" json:"tracked_subnets,omitempty"` + TrackedSubnets [][]byte `protobuf:"bytes,8,rep,name=tracked_subnets,json=trackedSubnets,proto3" json:"tracked_subnets,omitempty"` + Client *Client `protobuf:"bytes,9,opt,name=client,proto3" json:"client,omitempty"` + SupportedAcps []uint32 `protobuf:"varint,10,rep,packed,name=supported_acps,json=supportedAcps,proto3" json:"supported_acps,omitempty"` + ObjectedAcps []uint32 `protobuf:"varint,11,rep,packed,name=objected_acps,json=objectedAcps,proto3" json:"objected_acps,omitempty"` + KnownPeers *BloomFilter `protobuf:"bytes,12,opt,name=known_peers,json=knownPeers,proto3" json:"known_peers,omitempty"` } -func (x *Version) Reset() { - *x = Version{} +func (x *Handshake) Reset() { + *x = Handshake{} if protoimpl.UnsafeEnabled { mi := &file_p2p_p2p_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -723,13 +727,13 @@ func (x *Version) Reset() { } } -func (x *Version) String() string { +func (x *Handshake) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Version) ProtoMessage() {} +func (*Handshake) ProtoMessage() {} -func (x *Version) ProtoReflect() protoreflect.Message { +func (x *Handshake) ProtoReflect() protoreflect.Message { mi := &file_p2p_p2p_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -741,89 +745,111 @@ func (x *Version) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Version.ProtoReflect.Descriptor instead. -func (*Version) Descriptor() ([]byte, []int) { +// Deprecated: Use Handshake.ProtoReflect.Descriptor instead. +func (*Handshake) Descriptor() ([]byte, []int) { return file_p2p_p2p_proto_rawDescGZIP(), []int{4} } -func (x *Version) GetNetworkId() uint32 { +func (x *Handshake) GetNetworkId() uint32 { if x != nil { return x.NetworkId } return 0 } -func (x *Version) GetMyTime() uint64 { +func (x *Handshake) GetMyTime() uint64 { if x != nil { return x.MyTime } return 0 } -func (x *Version) GetIpAddr() []byte { +func (x *Handshake) GetIpAddr() []byte { if x != nil { return x.IpAddr } return nil } -func (x *Version) GetIpPort() uint32 { +func (x *Handshake) GetIpPort() uint32 { if x != nil { return x.IpPort } return 0 } -func (x *Version) GetMyVersion() string { +func (x *Handshake) GetMyVersion() string { if x != nil { return x.MyVersion } return "" } -func (x *Version) GetMyVersionTime() uint64 { +func (x *Handshake) GetIpSigningTime() uint64 { if x != nil { - return x.MyVersionTime + return x.IpSigningTime } return 0 } -func (x *Version) GetSig() []byte { +func (x *Handshake) GetSig() []byte { if x != nil { return x.Sig } return nil } -func (x *Version) GetTrackedSubnets() [][]byte { +func (x *Handshake) GetTrackedSubnets() [][]byte { if x != nil { return x.TrackedSubnets } return nil } -// ClaimedIpPort contains metadata needed to connect to a peer -type ClaimedIpPort struct { +func (x *Handshake) GetClient() *Client { + if x != nil { + return x.Client + } + return nil +} + +func (x *Handshake) GetSupportedAcps() []uint32 { + if x != nil { + return x.SupportedAcps + } + return nil +} + +func (x *Handshake) GetObjectedAcps() []uint32 { + if x != nil { + return x.ObjectedAcps + } + return nil +} + +func (x *Handshake) GetKnownPeers() *BloomFilter { + if x != nil { + return x.KnownPeers + } + return nil +} + +// Metadata about a peer's P2P client used to determine compatibility +type Client struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // X509 certificate of the peer - X509Certificate []byte `protobuf:"bytes,1,opt,name=x509_certificate,json=x509Certificate,proto3" json:"x509_certificate,omitempty"` - // IP address of the peer - IpAddr []byte `protobuf:"bytes,2,opt,name=ip_addr,json=ipAddr,proto3" json:"ip_addr,omitempty"` - // IP port of the peer - IpPort uint32 `protobuf:"varint,3,opt,name=ip_port,json=ipPort,proto3" json:"ip_port,omitempty"` - // Timestamp of the IP address + port pair - Timestamp uint64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - // Signature of the IP port pair at a provided timestamp - Signature []byte `protobuf:"bytes,5,opt,name=signature,proto3" json:"signature,omitempty"` - // P-Chain transaction that added this peer to the validator set - TxId []byte `protobuf:"bytes,6,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` + // Client name (e.g avalanchego) + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Client semantic version + Major uint32 `protobuf:"varint,2,opt,name=major,proto3" json:"major,omitempty"` + Minor uint32 `protobuf:"varint,3,opt,name=minor,proto3" json:"minor,omitempty"` + Patch uint32 `protobuf:"varint,4,opt,name=patch,proto3" json:"patch,omitempty"` } -func (x *ClaimedIpPort) Reset() { - *x = ClaimedIpPort{} +func (x *Client) Reset() { + *x = Client{} if protoimpl.UnsafeEnabled { mi := &file_p2p_p2p_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -831,13 +857,13 @@ func (x *ClaimedIpPort) Reset() { } } -func (x *ClaimedIpPort) String() string { +func (x *Client) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ClaimedIpPort) ProtoMessage() {} +func (*Client) ProtoMessage() {} -func (x *ClaimedIpPort) ProtoReflect() protoreflect.Message { +func (x *Client) ProtoReflect() protoreflect.Message { mi := &file_p2p_p2p_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -849,67 +875,51 @@ func (x *ClaimedIpPort) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ClaimedIpPort.ProtoReflect.Descriptor instead. -func (*ClaimedIpPort) Descriptor() ([]byte, []int) { +// Deprecated: Use Client.ProtoReflect.Descriptor instead. +func (*Client) Descriptor() ([]byte, []int) { return file_p2p_p2p_proto_rawDescGZIP(), []int{5} } -func (x *ClaimedIpPort) GetX509Certificate() []byte { - if x != nil { - return x.X509Certificate - } - return nil -} - -func (x *ClaimedIpPort) GetIpAddr() []byte { +func (x *Client) GetName() string { if x != nil { - return x.IpAddr + return x.Name } - return nil + return "" } -func (x *ClaimedIpPort) GetIpPort() uint32 { +func (x *Client) GetMajor() uint32 { if x != nil { - return x.IpPort + return x.Major } return 0 } -func (x *ClaimedIpPort) GetTimestamp() uint64 { +func (x *Client) GetMinor() uint32 { if x != nil { - return x.Timestamp + return x.Minor } return 0 } -func (x *ClaimedIpPort) GetSignature() []byte { +func (x *Client) GetPatch() uint32 { if x != nil { - return x.Signature + return x.Patch } - return nil -} - -func (x *ClaimedIpPort) GetTxId() []byte { - if x != nil { - return x.TxId - } - return nil + return 0 } -// Peers should periodically send PeerList messages to allow peers to -// discover each other. -// -// PeerListAck should be sent in response to a PeerList. -type PeerList struct { +// BloomFilter with a random salt to prevent consistent hash collisions +type BloomFilter struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ClaimedIpPorts []*ClaimedIpPort `protobuf:"bytes,1,rep,name=claimed_ip_ports,json=claimedIpPorts,proto3" json:"claimed_ip_ports,omitempty"` + Filter []byte `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"` } -func (x *PeerList) Reset() { - *x = PeerList{} +func (x *BloomFilter) Reset() { + *x = BloomFilter{} if protoimpl.UnsafeEnabled { mi := &file_p2p_p2p_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -917,13 +927,13 @@ func (x *PeerList) Reset() { } } -func (x *PeerList) String() string { +func (x *BloomFilter) String() string { return protoimpl.X.MessageStringOf(x) } -func (*PeerList) ProtoMessage() {} +func (*BloomFilter) ProtoMessage() {} -func (x *PeerList) ProtoReflect() protoreflect.Message { +func (x *BloomFilter) ProtoReflect() protoreflect.Message { mi := &file_p2p_p2p_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -935,34 +945,47 @@ func (x *PeerList) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use PeerList.ProtoReflect.Descriptor instead. -func (*PeerList) Descriptor() ([]byte, []int) { +// Deprecated: Use BloomFilter.ProtoReflect.Descriptor instead. +func (*BloomFilter) Descriptor() ([]byte, []int) { return file_p2p_p2p_proto_rawDescGZIP(), []int{6} } -func (x *PeerList) GetClaimedIpPorts() []*ClaimedIpPort { +func (x *BloomFilter) GetFilter() []byte { if x != nil { - return x.ClaimedIpPorts + return x.Filter } return nil } -// PeerAck acknowledges that a gossiped peer in a PeerList message will be -// tracked by the remote peer. -type PeerAck struct { +func (x *BloomFilter) GetSalt() []byte { + if x != nil { + return x.Salt + } + return nil +} + +// ClaimedIpPort contains metadata needed to connect to a peer +type ClaimedIpPort struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // P-Chain transaction that added the acknowledged peer to the validator - // set - TxId []byte `protobuf:"bytes,1,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` - // Timestamp of the signed ip of the peer - Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // X509 certificate of the peer + X509Certificate []byte `protobuf:"bytes,1,opt,name=x509_certificate,json=x509Certificate,proto3" json:"x509_certificate,omitempty"` + // IP address of the peer + IpAddr []byte `protobuf:"bytes,2,opt,name=ip_addr,json=ipAddr,proto3" json:"ip_addr,omitempty"` + // IP port of the peer + IpPort uint32 `protobuf:"varint,3,opt,name=ip_port,json=ipPort,proto3" json:"ip_port,omitempty"` + // Timestamp of the IP address + port pair + Timestamp uint64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Signature of the IP port pair at a provided timestamp + Signature []byte `protobuf:"bytes,5,opt,name=signature,proto3" json:"signature,omitempty"` + // P-Chain transaction that added this peer to the validator set + TxId []byte `protobuf:"bytes,6,opt,name=tx_id,json=txId,proto3" json:"tx_id,omitempty"` } -func (x *PeerAck) Reset() { - *x = PeerAck{} +func (x *ClaimedIpPort) Reset() { + *x = ClaimedIpPort{} if protoimpl.UnsafeEnabled { mi := &file_p2p_p2p_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -970,13 +993,13 @@ func (x *PeerAck) Reset() { } } -func (x *PeerAck) String() string { +func (x *ClaimedIpPort) String() string { return protoimpl.X.MessageStringOf(x) } -func (*PeerAck) ProtoMessage() {} +func (*ClaimedIpPort) ProtoMessage() {} -func (x *PeerAck) ProtoReflect() protoreflect.Message { +func (x *ClaimedIpPort) ProtoReflect() protoreflect.Message { mi := &file_p2p_p2p_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -988,37 +1011,69 @@ func (x *PeerAck) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use PeerAck.ProtoReflect.Descriptor instead. -func (*PeerAck) Descriptor() ([]byte, []int) { +// Deprecated: Use ClaimedIpPort.ProtoReflect.Descriptor instead. +func (*ClaimedIpPort) Descriptor() ([]byte, []int) { return file_p2p_p2p_proto_rawDescGZIP(), []int{7} } -func (x *PeerAck) GetTxId() []byte { +func (x *ClaimedIpPort) GetX509Certificate() []byte { if x != nil { - return x.TxId + return x.X509Certificate } return nil } -func (x *PeerAck) GetTimestamp() uint64 { +func (x *ClaimedIpPort) GetIpAddr() []byte { + if x != nil { + return x.IpAddr + } + return nil +} + +func (x *ClaimedIpPort) GetIpPort() uint32 { + if x != nil { + return x.IpPort + } + return 0 +} + +func (x *ClaimedIpPort) GetTimestamp() uint64 { if x != nil { return x.Timestamp } return 0 } -// PeerListAck is sent in response to PeerList to acknowledge the subset of -// peers that the peer will attempt to connect to. -type PeerListAck struct { +func (x *ClaimedIpPort) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +func (x *ClaimedIpPort) GetTxId() []byte { + if x != nil { + return x.TxId + } + return nil +} + +// GetPeerList contains a bloom filter of the currently known validator IPs. +// +// GetPeerList must not be responded to until finishing the handshake. After the +// handshake is completed, GetPeerlist messages should be responded to with a +// Peerlist message containing validators that are not present in the bloom +// filter. +type GetPeerList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - PeerAcks []*PeerAck `protobuf:"bytes,2,rep,name=peer_acks,json=peerAcks,proto3" json:"peer_acks,omitempty"` + KnownPeers *BloomFilter `protobuf:"bytes,1,opt,name=known_peers,json=knownPeers,proto3" json:"known_peers,omitempty"` } -func (x *PeerListAck) Reset() { - *x = PeerListAck{} +func (x *GetPeerList) Reset() { + *x = GetPeerList{} if protoimpl.UnsafeEnabled { mi := &file_p2p_p2p_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1026,13 +1081,13 @@ func (x *PeerListAck) Reset() { } } -func (x *PeerListAck) String() string { +func (x *GetPeerList) String() string { return protoimpl.X.MessageStringOf(x) } -func (*PeerListAck) ProtoMessage() {} +func (*GetPeerList) ProtoMessage() {} -func (x *PeerListAck) ProtoReflect() protoreflect.Message { +func (x *GetPeerList) ProtoReflect() protoreflect.Message { mi := &file_p2p_p2p_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1044,14 +1099,70 @@ func (x *PeerListAck) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use PeerListAck.ProtoReflect.Descriptor instead. -func (*PeerListAck) Descriptor() ([]byte, []int) { +// Deprecated: Use GetPeerList.ProtoReflect.Descriptor instead. +func (*GetPeerList) Descriptor() ([]byte, []int) { return file_p2p_p2p_proto_rawDescGZIP(), []int{8} } -func (x *PeerListAck) GetPeerAcks() []*PeerAck { +func (x *GetPeerList) GetKnownPeers() *BloomFilter { if x != nil { - return x.PeerAcks + return x.KnownPeers + } + return nil +} + +// PeerList contains network-level metadata for a set of validators. +// +// PeerList must be sent in response to an inbound Handshake message from a +// remote peer a peer wants to connect to. Once a PeerList is received after +// a Handshake message, the p2p handshake is complete and the connection is +// established. +// +// PeerList should be sent in response to a GetPeerlist message if the handshake +// has been completed. +type PeerList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClaimedIpPorts []*ClaimedIpPort `protobuf:"bytes,1,rep,name=claimed_ip_ports,json=claimedIpPorts,proto3" json:"claimed_ip_ports,omitempty"` +} + +func (x *PeerList) Reset() { + *x = PeerList{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_p2p_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PeerList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerList) ProtoMessage() {} + +func (x *PeerList) ProtoReflect() protoreflect.Message { + mi := &file_p2p_p2p_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PeerList.ProtoReflect.Descriptor instead. +func (*PeerList) Descriptor() ([]byte, []int) { + return file_p2p_p2p_proto_rawDescGZIP(), []int{9} +} + +func (x *PeerList) GetClaimedIpPorts() []*ClaimedIpPort { + if x != nil { + return x.ClaimedIpPorts } return nil } @@ -1074,7 +1185,7 @@ type GetStateSummaryFrontier struct { func (x *GetStateSummaryFrontier) Reset() { *x = GetStateSummaryFrontier{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[9] + mi := &file_p2p_p2p_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1087,7 +1198,7 @@ func (x *GetStateSummaryFrontier) String() string { func (*GetStateSummaryFrontier) ProtoMessage() {} func (x *GetStateSummaryFrontier) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[9] + mi := &file_p2p_p2p_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1100,7 +1211,7 @@ func (x *GetStateSummaryFrontier) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStateSummaryFrontier.ProtoReflect.Descriptor instead. func (*GetStateSummaryFrontier) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{9} + return file_p2p_p2p_proto_rawDescGZIP(), []int{10} } func (x *GetStateSummaryFrontier) GetChainId() []byte { @@ -1141,7 +1252,7 @@ type StateSummaryFrontier struct { func (x *StateSummaryFrontier) Reset() { *x = StateSummaryFrontier{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[10] + mi := &file_p2p_p2p_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1154,7 +1265,7 @@ func (x *StateSummaryFrontier) String() string { func (*StateSummaryFrontier) ProtoMessage() {} func (x *StateSummaryFrontier) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[10] + mi := &file_p2p_p2p_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1167,7 +1278,7 @@ func (x *StateSummaryFrontier) ProtoReflect() protoreflect.Message { // Deprecated: Use StateSummaryFrontier.ProtoReflect.Descriptor instead. func (*StateSummaryFrontier) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{10} + return file_p2p_p2p_proto_rawDescGZIP(), []int{11} } func (x *StateSummaryFrontier) GetChainId() []byte { @@ -1211,7 +1322,7 @@ type GetAcceptedStateSummary struct { func (x *GetAcceptedStateSummary) Reset() { *x = GetAcceptedStateSummary{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[11] + mi := &file_p2p_p2p_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1224,7 +1335,7 @@ func (x *GetAcceptedStateSummary) String() string { func (*GetAcceptedStateSummary) ProtoMessage() {} func (x *GetAcceptedStateSummary) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[11] + mi := &file_p2p_p2p_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1237,7 +1348,7 @@ func (x *GetAcceptedStateSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAcceptedStateSummary.ProtoReflect.Descriptor instead. func (*GetAcceptedStateSummary) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{11} + return file_p2p_p2p_proto_rawDescGZIP(), []int{12} } func (x *GetAcceptedStateSummary) GetChainId() []byte { @@ -1285,7 +1396,7 @@ type AcceptedStateSummary struct { func (x *AcceptedStateSummary) Reset() { *x = AcceptedStateSummary{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[12] + mi := &file_p2p_p2p_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1298,7 +1409,7 @@ func (x *AcceptedStateSummary) String() string { func (*AcceptedStateSummary) ProtoMessage() {} func (x *AcceptedStateSummary) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[12] + mi := &file_p2p_p2p_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1311,7 +1422,7 @@ func (x *AcceptedStateSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptedStateSummary.ProtoReflect.Descriptor instead. func (*AcceptedStateSummary) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{12} + return file_p2p_p2p_proto_rawDescGZIP(), []int{13} } func (x *AcceptedStateSummary) GetChainId() []byte { @@ -1356,7 +1467,7 @@ type GetAcceptedFrontier struct { func (x *GetAcceptedFrontier) Reset() { *x = GetAcceptedFrontier{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[13] + mi := &file_p2p_p2p_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1369,7 +1480,7 @@ func (x *GetAcceptedFrontier) String() string { func (*GetAcceptedFrontier) ProtoMessage() {} func (x *GetAcceptedFrontier) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[13] + mi := &file_p2p_p2p_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1382,7 +1493,7 @@ func (x *GetAcceptedFrontier) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAcceptedFrontier.ProtoReflect.Descriptor instead. func (*GetAcceptedFrontier) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{13} + return file_p2p_p2p_proto_rawDescGZIP(), []int{14} } func (x *GetAcceptedFrontier) GetChainId() []byte { @@ -1432,7 +1543,7 @@ type AcceptedFrontier struct { func (x *AcceptedFrontier) Reset() { *x = AcceptedFrontier{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[14] + mi := &file_p2p_p2p_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1445,7 +1556,7 @@ func (x *AcceptedFrontier) String() string { func (*AcceptedFrontier) ProtoMessage() {} func (x *AcceptedFrontier) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[14] + mi := &file_p2p_p2p_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1458,7 +1569,7 @@ func (x *AcceptedFrontier) ProtoReflect() protoreflect.Message { // Deprecated: Use AcceptedFrontier.ProtoReflect.Descriptor instead. func (*AcceptedFrontier) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{14} + return file_p2p_p2p_proto_rawDescGZIP(), []int{15} } func (x *AcceptedFrontier) GetChainId() []byte { @@ -1506,7 +1617,7 @@ type GetAccepted struct { func (x *GetAccepted) Reset() { *x = GetAccepted{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[15] + mi := &file_p2p_p2p_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1519,7 +1630,7 @@ func (x *GetAccepted) String() string { func (*GetAccepted) ProtoMessage() {} func (x *GetAccepted) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[15] + mi := &file_p2p_p2p_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1532,7 +1643,7 @@ func (x *GetAccepted) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAccepted.ProtoReflect.Descriptor instead. func (*GetAccepted) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{15} + return file_p2p_p2p_proto_rawDescGZIP(), []int{16} } func (x *GetAccepted) GetChainId() []byte { @@ -1590,7 +1701,7 @@ type Accepted struct { func (x *Accepted) Reset() { *x = Accepted{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[16] + mi := &file_p2p_p2p_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1603,7 +1714,7 @@ func (x *Accepted) String() string { func (*Accepted) ProtoMessage() {} func (x *Accepted) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[16] + mi := &file_p2p_p2p_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1616,7 +1727,7 @@ func (x *Accepted) ProtoReflect() protoreflect.Message { // Deprecated: Use Accepted.ProtoReflect.Descriptor instead. func (*Accepted) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{16} + return file_p2p_p2p_proto_rawDescGZIP(), []int{17} } func (x *Accepted) GetChainId() []byte { @@ -1663,7 +1774,7 @@ type GetAncestors struct { func (x *GetAncestors) Reset() { *x = GetAncestors{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[17] + mi := &file_p2p_p2p_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1676,7 +1787,7 @@ func (x *GetAncestors) String() string { func (*GetAncestors) ProtoMessage() {} func (x *GetAncestors) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[17] + mi := &file_p2p_p2p_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1689,7 +1800,7 @@ func (x *GetAncestors) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAncestors.ProtoReflect.Descriptor instead. func (*GetAncestors) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{17} + return file_p2p_p2p_proto_rawDescGZIP(), []int{18} } func (x *GetAncestors) GetChainId() []byte { @@ -1747,7 +1858,7 @@ type Ancestors struct { func (x *Ancestors) Reset() { *x = Ancestors{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[18] + mi := &file_p2p_p2p_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1760,7 +1871,7 @@ func (x *Ancestors) String() string { func (*Ancestors) ProtoMessage() {} func (x *Ancestors) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[18] + mi := &file_p2p_p2p_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1773,7 +1884,7 @@ func (x *Ancestors) ProtoReflect() protoreflect.Message { // Deprecated: Use Ancestors.ProtoReflect.Descriptor instead. func (*Ancestors) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{18} + return file_p2p_p2p_proto_rawDescGZIP(), []int{19} } func (x *Ancestors) GetChainId() []byte { @@ -1820,7 +1931,7 @@ type Get struct { func (x *Get) Reset() { *x = Get{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[19] + mi := &file_p2p_p2p_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1833,7 +1944,7 @@ func (x *Get) String() string { func (*Get) ProtoMessage() {} func (x *Get) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[19] + mi := &file_p2p_p2p_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1846,7 +1957,7 @@ func (x *Get) ProtoReflect() protoreflect.Message { // Deprecated: Use Get.ProtoReflect.Descriptor instead. func (*Get) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{19} + return file_p2p_p2p_proto_rawDescGZIP(), []int{20} } func (x *Get) GetChainId() []byte { @@ -1903,7 +2014,7 @@ type Put struct { func (x *Put) Reset() { *x = Put{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[20] + mi := &file_p2p_p2p_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1916,7 +2027,7 @@ func (x *Put) String() string { func (*Put) ProtoMessage() {} func (x *Put) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[20] + mi := &file_p2p_p2p_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1929,7 +2040,7 @@ func (x *Put) ProtoReflect() protoreflect.Message { // Deprecated: Use Put.ProtoReflect.Descriptor instead. func (*Put) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{20} + return file_p2p_p2p_proto_rawDescGZIP(), []int{21} } func (x *Put) GetChainId() []byte { @@ -1985,7 +2096,7 @@ type PushQuery struct { func (x *PushQuery) Reset() { *x = PushQuery{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[21] + mi := &file_p2p_p2p_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1998,7 +2109,7 @@ func (x *PushQuery) String() string { func (*PushQuery) ProtoMessage() {} func (x *PushQuery) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[21] + mi := &file_p2p_p2p_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2011,7 +2122,7 @@ func (x *PushQuery) ProtoReflect() protoreflect.Message { // Deprecated: Use PushQuery.ProtoReflect.Descriptor instead. func (*PushQuery) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{21} + return file_p2p_p2p_proto_rawDescGZIP(), []int{22} } func (x *PushQuery) GetChainId() []byte { @@ -2081,7 +2192,7 @@ type PullQuery struct { func (x *PullQuery) Reset() { *x = PullQuery{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[22] + mi := &file_p2p_p2p_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2094,7 +2205,7 @@ func (x *PullQuery) String() string { func (*PullQuery) ProtoMessage() {} func (x *PullQuery) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[22] + mi := &file_p2p_p2p_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2107,7 +2218,7 @@ func (x *PullQuery) ProtoReflect() protoreflect.Message { // Deprecated: Use PullQuery.ProtoReflect.Descriptor instead. func (*PullQuery) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{22} + return file_p2p_p2p_proto_rawDescGZIP(), []int{23} } func (x *PullQuery) GetChainId() []byte { @@ -2174,7 +2285,7 @@ type Chits struct { func (x *Chits) Reset() { *x = Chits{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[23] + mi := &file_p2p_p2p_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2187,7 +2298,7 @@ func (x *Chits) String() string { func (*Chits) ProtoMessage() {} func (x *Chits) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[23] + mi := &file_p2p_p2p_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2200,7 +2311,7 @@ func (x *Chits) ProtoReflect() protoreflect.Message { // Deprecated: Use Chits.ProtoReflect.Descriptor instead. func (*Chits) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{23} + return file_p2p_p2p_proto_rawDescGZIP(), []int{24} } func (x *Chits) GetChainId() []byte { @@ -2260,7 +2371,7 @@ type AppRequest struct { func (x *AppRequest) Reset() { *x = AppRequest{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[24] + mi := &file_p2p_p2p_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2273,7 +2384,7 @@ func (x *AppRequest) String() string { func (*AppRequest) ProtoMessage() {} func (x *AppRequest) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[24] + mi := &file_p2p_p2p_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2286,7 +2397,7 @@ func (x *AppRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AppRequest.ProtoReflect.Descriptor instead. func (*AppRequest) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{24} + return file_p2p_p2p_proto_rawDescGZIP(), []int{25} } func (x *AppRequest) GetChainId() []byte { @@ -2334,7 +2445,7 @@ type AppResponse struct { func (x *AppResponse) Reset() { *x = AppResponse{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[25] + mi := &file_p2p_p2p_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2347,7 +2458,7 @@ func (x *AppResponse) String() string { func (*AppResponse) ProtoMessage() {} func (x *AppResponse) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[25] + mi := &file_p2p_p2p_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2360,7 +2471,7 @@ func (x *AppResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AppResponse.ProtoReflect.Descriptor instead. func (*AppResponse) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{25} + return file_p2p_p2p_proto_rawDescGZIP(), []int{26} } func (x *AppResponse) GetChainId() []byte { @@ -2403,7 +2514,7 @@ type AppError struct { func (x *AppError) Reset() { *x = AppError{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[26] + mi := &file_p2p_p2p_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2416,7 +2527,7 @@ func (x *AppError) String() string { func (*AppError) ProtoMessage() {} func (x *AppError) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[26] + mi := &file_p2p_p2p_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2429,7 +2540,7 @@ func (x *AppError) ProtoReflect() protoreflect.Message { // Deprecated: Use AppError.ProtoReflect.Descriptor instead. func (*AppError) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{26} + return file_p2p_p2p_proto_rawDescGZIP(), []int{27} } func (x *AppError) GetChainId() []byte { @@ -2475,7 +2586,7 @@ type AppGossip struct { func (x *AppGossip) Reset() { *x = AppGossip{} if protoimpl.UnsafeEnabled { - mi := &file_p2p_p2p_proto_msgTypes[27] + mi := &file_p2p_p2p_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2488,7 +2599,7 @@ func (x *AppGossip) String() string { func (*AppGossip) ProtoMessage() {} func (x *AppGossip) ProtoReflect() protoreflect.Message { - mi := &file_p2p_p2p_proto_msgTypes[27] + mi := &file_p2p_p2p_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2501,7 +2612,7 @@ func (x *AppGossip) ProtoReflect() protoreflect.Message { // Deprecated: Use AppGossip.ProtoReflect.Descriptor instead. func (*AppGossip) Descriptor() ([]byte, []int) { - return file_p2p_p2p_proto_rawDescGZIP(), []int{27} + return file_p2p_p2p_proto_rawDescGZIP(), []int{28} } func (x *AppGossip) GetChainId() []byte { @@ -2522,7 +2633,7 @@ var File_p2p_p2p_proto protoreflect.FileDescriptor var file_p2p_p2p_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x70, 0x32, 0x70, 0x2f, 0x70, 0x32, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x03, 0x70, 0x32, 0x70, 0x22, 0x8c, 0x0b, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x03, 0x70, 0x32, 0x70, 0x22, 0x9e, 0x0b, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x67, 0x7a, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x47, 0x7a, 0x69, 0x70, 0x12, 0x29, 0x0a, 0x0f, 0x63, @@ -2532,323 +2643,340 @@ var file_p2p_p2p_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x48, 0x00, 0x52, 0x04, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x0a, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x6f, 0x6e, 0x67, - 0x48, 0x00, 0x52, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x12, 0x28, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70, 0x32, 0x70, 0x2e, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x09, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, - 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x65, 0x65, 0x72, - 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x08, 0x70, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, - 0x12, 0x5b, 0x0a, 0x1a, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x18, 0x0f, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x48, 0x00, 0x52, 0x17, 0x67, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x51, 0x0a, - 0x16, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x66, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x70, 0x32, 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x14, 0x73, 0x74, 0x61, 0x74, + 0x48, 0x00, 0x52, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x12, 0x2e, 0x0a, 0x09, 0x68, 0x61, 0x6e, 0x64, + 0x73, 0x68, 0x61, 0x6b, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x32, + 0x70, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x48, 0x00, 0x52, 0x09, 0x68, + 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x36, 0x0a, 0x0d, 0x67, 0x65, 0x74, 0x5f, + 0x70, 0x65, 0x65, 0x72, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, + 0x74, 0x48, 0x00, 0x52, 0x0b, 0x67, 0x65, 0x74, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, + 0x12, 0x2c, 0x0a, 0x09, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, + 0x73, 0x74, 0x48, 0x00, 0x52, 0x08, 0x70, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x5b, + 0x0a, 0x1a, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x12, 0x5b, 0x0a, 0x1a, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, - 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x11, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, - 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x48, 0x00, 0x52, 0x17, 0x67, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, - 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x51, 0x0a, - 0x16, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, - 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x70, 0x32, 0x70, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x14, 0x61, 0x63, 0x63, 0x65, + 0x48, 0x00, 0x52, 0x17, 0x67, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x51, 0x0a, 0x16, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x66, 0x72, 0x6f, + 0x6e, 0x74, 0x69, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x32, + 0x70, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, + 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x14, 0x73, 0x74, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x5b, + 0x0a, 0x1a, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x12, 0x4e, 0x0a, 0x15, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, - 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, - 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x13, 0x67, 0x65, 0x74, + 0x48, 0x00, 0x52, 0x17, 0x67, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x51, 0x0a, 0x16, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x32, + 0x70, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x14, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x4e, + 0x0a, 0x15, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x66, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, + 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x13, 0x67, 0x65, 0x74, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x44, + 0x0a, 0x11, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, - 0x12, 0x44, 0x0a, 0x11, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x32, - 0x70, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, - 0x65, 0x72, 0x48, 0x00, 0x52, 0x10, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, - 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x0c, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, - 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, - 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x00, - 0x52, 0x0b, 0x67, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x2b, 0x0a, - 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0d, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x00, - 0x52, 0x08, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0d, 0x67, 0x65, - 0x74, 0x5f, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x73, 0x48, 0x00, 0x52, 0x0c, 0x67, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x09, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, - 0x73, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x6e, - 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x48, 0x00, 0x52, 0x09, 0x61, 0x6e, 0x63, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x73, 0x12, 0x1c, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x18, 0x19, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x08, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x00, 0x52, 0x03, 0x67, - 0x65, 0x74, 0x12, 0x1c, 0x0a, 0x03, 0x70, 0x75, 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x08, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x75, 0x74, 0x48, 0x00, 0x52, 0x03, 0x70, 0x75, 0x74, - 0x12, 0x2f, 0x0a, 0x0a, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x1b, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x48, 0x00, 0x52, 0x09, 0x70, 0x75, 0x73, 0x68, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x12, 0x2f, 0x0a, 0x0a, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, - 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x75, 0x6c, 0x6c, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x48, 0x00, 0x52, 0x09, 0x70, 0x75, 0x6c, 0x6c, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x12, 0x22, 0x0a, 0x05, 0x63, 0x68, 0x69, 0x74, 0x73, 0x18, 0x1d, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0a, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x43, 0x68, 0x69, 0x74, 0x73, 0x48, 0x00, 0x52, - 0x05, 0x63, 0x68, 0x69, 0x74, 0x73, 0x12, 0x32, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x32, - 0x70, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0a, - 0x61, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x0c, 0x61, 0x70, - 0x70, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x10, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2f, 0x0a, 0x0a, 0x61, 0x70, 0x70, 0x5f, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x18, - 0x20, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x70, 0x70, 0x47, - 0x6f, 0x73, 0x73, 0x69, 0x70, 0x48, 0x00, 0x52, 0x09, 0x61, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, - 0x69, 0x70, 0x12, 0x36, 0x0a, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, - 0x61, 0x63, 0x6b, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, 0x70, 0x2e, - 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x0b, 0x70, - 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x6b, 0x12, 0x2c, 0x0a, 0x09, 0x61, 0x70, - 0x70, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, - 0x70, 0x32, 0x70, 0x2e, 0x41, 0x70, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x08, - 0x61, 0x70, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x58, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x75, - 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x75, 0x70, 0x74, - 0x69, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x0e, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x75, 0x70, - 0x74, 0x69, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x32, - 0x70, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x0d, - 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x22, 0x43, 0x0a, - 0x0c, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, - 0x09, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x08, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, - 0x6d, 0x65, 0x22, 0x58, 0x0a, 0x04, 0x50, 0x6f, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, - 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x0e, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x75, 0x70, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x32, 0x70, - 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x0d, 0x73, - 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x22, 0xf5, 0x01, 0x0a, - 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x79, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x79, 0x54, 0x69, 0x6d, 0x65, - 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x06, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, - 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, 0x70, 0x50, 0x6f, - 0x72, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x79, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x69, 0x67, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x73, 0x69, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x74, - 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x18, 0x08, - 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x75, 0x62, - 0x6e, 0x65, 0x74, 0x73, 0x22, 0xbd, 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, - 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x78, 0x35, 0x30, 0x39, 0x5f, 0x63, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0f, 0x78, 0x35, 0x30, 0x39, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x06, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, - 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, 0x70, 0x50, - 0x6f, 0x72, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, - 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, - 0x74, 0x78, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, - 0x12, 0x3c, 0x0a, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x69, 0x70, 0x5f, 0x70, - 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x32, 0x70, - 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x0e, - 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x22, 0x3c, - 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x6b, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x1c, - 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3e, 0x0a, 0x0b, - 0x50, 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x6b, 0x12, 0x29, 0x0a, 0x09, 0x70, - 0x65, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, - 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x41, 0x63, 0x6b, 0x52, 0x08, 0x70, 0x65, - 0x65, 0x72, 0x41, 0x63, 0x6b, 0x73, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x6f, 0x0a, 0x17, - 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, + 0x48, 0x00, 0x52, 0x10, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x0c, 0x67, 0x65, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x65, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, 0x70, + 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0b, + 0x67, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x2b, 0x0a, 0x08, 0x61, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, + 0x70, 0x32, 0x70, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x08, + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0d, 0x67, 0x65, 0x74, 0x5f, + 0x61, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x11, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, + 0x72, 0x73, 0x48, 0x00, 0x52, 0x0c, 0x67, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, + 0x72, 0x73, 0x12, 0x2e, 0x0a, 0x09, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x18, + 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x6e, 0x63, 0x65, + 0x73, 0x74, 0x6f, 0x72, 0x73, 0x48, 0x00, 0x52, 0x09, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, + 0x72, 0x73, 0x12, 0x1c, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x08, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x00, 0x52, 0x03, 0x67, 0x65, 0x74, + 0x12, 0x1c, 0x0a, 0x03, 0x70, 0x75, 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, + 0x70, 0x32, 0x70, 0x2e, 0x50, 0x75, 0x74, 0x48, 0x00, 0x52, 0x03, 0x70, 0x75, 0x74, 0x12, 0x2f, + 0x0a, 0x0a, 0x70, 0x75, 0x73, 0x68, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x1b, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x75, 0x73, 0x68, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x48, 0x00, 0x52, 0x09, 0x70, 0x75, 0x73, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, + 0x2f, 0x0a, 0x0a, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x1c, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x48, 0x00, 0x52, 0x09, 0x70, 0x75, 0x6c, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x12, 0x22, 0x0a, 0x05, 0x63, 0x68, 0x69, 0x74, 0x73, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0a, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x43, 0x68, 0x69, 0x74, 0x73, 0x48, 0x00, 0x52, 0x05, 0x63, + 0x68, 0x69, 0x74, 0x73, 0x12, 0x32, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, + 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x70, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x0c, 0x61, 0x70, 0x70, 0x5f, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x48, 0x00, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2f, 0x0a, 0x0a, 0x61, 0x70, 0x70, 0x5f, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x18, 0x20, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x70, 0x70, 0x47, 0x6f, 0x73, + 0x73, 0x69, 0x70, 0x48, 0x00, 0x52, 0x09, 0x61, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, + 0x12, 0x2c, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x22, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x41, 0x70, 0x70, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x48, 0x00, 0x52, 0x08, 0x61, 0x70, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, 0x09, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4a, 0x04, 0x08, 0x21, 0x10, 0x22, 0x4a, + 0x04, 0x08, 0x24, 0x10, 0x25, 0x22, 0x58, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, + 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x75, + 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x0e, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, + 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x70, 0x32, 0x70, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, + 0x52, 0x0d, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x22, + 0x43, 0x0a, 0x0c, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x08, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x75, 0x70, + 0x74, 0x69, 0x6d, 0x65, 0x22, 0x58, 0x0a, 0x04, 0x50, 0x6f, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, + 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x75, 0x70, + 0x74, 0x69, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x0e, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x75, + 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, + 0x32, 0x70, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x52, + 0x0d, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x22, 0x9b, + 0x03, 0x0a, 0x09, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x09, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6d, + 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x79, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, + 0x07, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, + 0x69, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x79, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x79, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x70, 0x5f, 0x73, 0x69, 0x67, 0x6e, + 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, + 0x69, 0x70, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x73, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x73, 0x69, 0x67, 0x12, + 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, + 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, + 0x64, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x43, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, + 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x70, 0x73, 0x18, + 0x0a, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, + 0x41, 0x63, 0x70, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x63, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x63, 0x70, 0x73, 0x12, 0x31, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, + 0x77, 0x6e, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0x5e, 0x0a, 0x06, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, + 0x6a, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, + 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x22, 0x39, 0x0a, 0x0b, + 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x22, 0xbd, 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x61, 0x69, + 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x78, 0x35, 0x30, + 0x39, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x78, 0x35, 0x30, 0x39, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, + 0x07, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, + 0x69, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x22, 0x40, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x50, 0x65, + 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, + 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, + 0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x6b, + 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x65, 0x65, + 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, + 0x5f, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, + 0x6f, 0x72, 0x74, 0x52, 0x0e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, + 0x72, 0x74, 0x73, 0x22, 0x6f, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x6a, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x22, 0x89, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, + 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, + 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x04, 0x52, 0x07, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x22, 0x71, 0x0a, 0x14, + 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1f, + 0x0a, 0x0b, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x49, 0x64, 0x73, 0x22, + 0x9d, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x6a, 0x0a, - 0x14, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, - 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x89, 0x01, 0x0a, 0x17, 0x47, 0x65, - 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, + 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x30, 0x0a, + 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, + 0x75, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, + 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, + 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xba, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x23, 0x0a, 0x0d, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, + 0x73, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, + 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x22, 0x6f, 0x0a, 0x08, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x4a, 0x04, + 0x08, 0x04, 0x10, 0x05, 0x22, 0xb9, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, + 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x68, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x73, 0x22, 0x71, 0x0a, 0x14, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, - 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, + 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, + 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x22, 0x6b, 0x0a, 0x09, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x49, 0x64, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, - 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xb0, 0x01, + 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, + 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x22, 0x8f, 0x01, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, + 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x22, 0xdc, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, - 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, - 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x75, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, - 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, - 0xba, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, + 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, + 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, + 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x22, 0xe1, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x6c, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, - 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, - 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x6f, 0x0a, 0x08, - 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xb9, 0x01, - 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, - 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, - 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, - 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, - 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, - 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x6b, 0x0a, 0x09, 0x41, 0x6e, 0x63, - 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, - 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xb0, 0x01, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x19, - 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, - 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, - 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, - 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, - 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0x8f, 0x01, 0x0a, 0x03, 0x50, 0x75, - 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, - 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, - 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0xdc, 0x01, 0x0a, 0x09, - 0x50, 0x75, 0x73, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, - 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x30, 0x0a, - 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xe1, 0x01, 0x0a, 0x09, 0x50, - 0x75, 0x6c, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, - 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xba, - 0x01, 0x0a, 0x05, 0x43, 0x68, 0x69, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x72, 0x65, 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, - 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, - 0x70, 0x74, 0x65, 0x64, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, - 0x64, 0x49, 0x64, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x7f, 0x0a, 0x0a, 0x41, - 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, - 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x0b, - 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, - 0x65, 0x73, 0x22, 0x88, 0x01, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, + 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, + 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, + 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, + 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x05, 0x43, 0x68, 0x69, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x11, 0x52, 0x09, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x43, 0x0a, - 0x09, 0x41, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, - 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, - 0x65, 0x73, 0x2a, 0x5d, 0x0a, 0x0a, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, - 0x15, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x56, 0x41, - 0x4c, 0x41, 0x4e, 0x43, 0x48, 0x45, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x45, 0x4e, 0x47, 0x49, - 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x4e, 0x4f, 0x57, 0x4d, 0x41, 0x4e, 0x10, - 0x02, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x61, 0x76, 0x61, 0x2d, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x61, 0x76, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x68, 0x65, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x70, 0x32, - 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x49, 0x64, 0x12, 0x33, 0x0a, + 0x16, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x5f, 0x61, 0x74, + 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x70, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x49, 0x64, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x22, 0x7f, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, + 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, + 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, + 0x74, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x88, 0x01, 0x0a, 0x08, 0x41, 0x70, + 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x11, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, + 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x22, 0x43, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, 0x69, + 0x70, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x2a, 0x5d, 0x0a, 0x0a, 0x45, 0x6e, 0x67, + 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x4e, 0x47, 0x49, 0x4e, + 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, + 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x41, 0x56, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x48, 0x45, 0x10, 0x01, 0x12, + 0x17, 0x0a, 0x13, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, + 0x4e, 0x4f, 0x57, 0x4d, 0x41, 0x4e, 0x10, 0x02, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, 0x6c, 0x61, 0x62, 0x73, 0x2f, + 0x61, 0x76, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x70, 0x32, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2864,79 +2992,82 @@ func file_p2p_p2p_proto_rawDescGZIP() []byte { } var file_p2p_p2p_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_p2p_p2p_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_p2p_p2p_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_p2p_p2p_proto_goTypes = []interface{}{ (EngineType)(0), // 0: p2p.EngineType (*Message)(nil), // 1: p2p.Message (*Ping)(nil), // 2: p2p.Ping (*SubnetUptime)(nil), // 3: p2p.SubnetUptime (*Pong)(nil), // 4: p2p.Pong - (*Version)(nil), // 5: p2p.Version - (*ClaimedIpPort)(nil), // 6: p2p.ClaimedIpPort - (*PeerList)(nil), // 7: p2p.PeerList - (*PeerAck)(nil), // 8: p2p.PeerAck - (*PeerListAck)(nil), // 9: p2p.PeerListAck - (*GetStateSummaryFrontier)(nil), // 10: p2p.GetStateSummaryFrontier - (*StateSummaryFrontier)(nil), // 11: p2p.StateSummaryFrontier - (*GetAcceptedStateSummary)(nil), // 12: p2p.GetAcceptedStateSummary - (*AcceptedStateSummary)(nil), // 13: p2p.AcceptedStateSummary - (*GetAcceptedFrontier)(nil), // 14: p2p.GetAcceptedFrontier - (*AcceptedFrontier)(nil), // 15: p2p.AcceptedFrontier - (*GetAccepted)(nil), // 16: p2p.GetAccepted - (*Accepted)(nil), // 17: p2p.Accepted - (*GetAncestors)(nil), // 18: p2p.GetAncestors - (*Ancestors)(nil), // 19: p2p.Ancestors - (*Get)(nil), // 20: p2p.Get - (*Put)(nil), // 21: p2p.Put - (*PushQuery)(nil), // 22: p2p.PushQuery - (*PullQuery)(nil), // 23: p2p.PullQuery - (*Chits)(nil), // 24: p2p.Chits - (*AppRequest)(nil), // 25: p2p.AppRequest - (*AppResponse)(nil), // 26: p2p.AppResponse - (*AppError)(nil), // 27: p2p.AppError - (*AppGossip)(nil), // 28: p2p.AppGossip + (*Handshake)(nil), // 5: p2p.Handshake + (*Client)(nil), // 6: p2p.Client + (*BloomFilter)(nil), // 7: p2p.BloomFilter + (*ClaimedIpPort)(nil), // 8: p2p.ClaimedIpPort + (*GetPeerList)(nil), // 9: p2p.GetPeerList + (*PeerList)(nil), // 10: p2p.PeerList + (*GetStateSummaryFrontier)(nil), // 11: p2p.GetStateSummaryFrontier + (*StateSummaryFrontier)(nil), // 12: p2p.StateSummaryFrontier + (*GetAcceptedStateSummary)(nil), // 13: p2p.GetAcceptedStateSummary + (*AcceptedStateSummary)(nil), // 14: p2p.AcceptedStateSummary + (*GetAcceptedFrontier)(nil), // 15: p2p.GetAcceptedFrontier + (*AcceptedFrontier)(nil), // 16: p2p.AcceptedFrontier + (*GetAccepted)(nil), // 17: p2p.GetAccepted + (*Accepted)(nil), // 18: p2p.Accepted + (*GetAncestors)(nil), // 19: p2p.GetAncestors + (*Ancestors)(nil), // 20: p2p.Ancestors + (*Get)(nil), // 21: p2p.Get + (*Put)(nil), // 22: p2p.Put + (*PushQuery)(nil), // 23: p2p.PushQuery + (*PullQuery)(nil), // 24: p2p.PullQuery + (*Chits)(nil), // 25: p2p.Chits + (*AppRequest)(nil), // 26: p2p.AppRequest + (*AppResponse)(nil), // 27: p2p.AppResponse + (*AppError)(nil), // 28: p2p.AppError + (*AppGossip)(nil), // 29: p2p.AppGossip } var file_p2p_p2p_proto_depIdxs = []int32{ 2, // 0: p2p.Message.ping:type_name -> p2p.Ping 4, // 1: p2p.Message.pong:type_name -> p2p.Pong - 5, // 2: p2p.Message.version:type_name -> p2p.Version - 7, // 3: p2p.Message.peer_list:type_name -> p2p.PeerList - 10, // 4: p2p.Message.get_state_summary_frontier:type_name -> p2p.GetStateSummaryFrontier - 11, // 5: p2p.Message.state_summary_frontier:type_name -> p2p.StateSummaryFrontier - 12, // 6: p2p.Message.get_accepted_state_summary:type_name -> p2p.GetAcceptedStateSummary - 13, // 7: p2p.Message.accepted_state_summary:type_name -> p2p.AcceptedStateSummary - 14, // 8: p2p.Message.get_accepted_frontier:type_name -> p2p.GetAcceptedFrontier - 15, // 9: p2p.Message.accepted_frontier:type_name -> p2p.AcceptedFrontier - 16, // 10: p2p.Message.get_accepted:type_name -> p2p.GetAccepted - 17, // 11: p2p.Message.accepted:type_name -> p2p.Accepted - 18, // 12: p2p.Message.get_ancestors:type_name -> p2p.GetAncestors - 19, // 13: p2p.Message.ancestors:type_name -> p2p.Ancestors - 20, // 14: p2p.Message.get:type_name -> p2p.Get - 21, // 15: p2p.Message.put:type_name -> p2p.Put - 22, // 16: p2p.Message.push_query:type_name -> p2p.PushQuery - 23, // 17: p2p.Message.pull_query:type_name -> p2p.PullQuery - 24, // 18: p2p.Message.chits:type_name -> p2p.Chits - 25, // 19: p2p.Message.app_request:type_name -> p2p.AppRequest - 26, // 20: p2p.Message.app_response:type_name -> p2p.AppResponse - 28, // 21: p2p.Message.app_gossip:type_name -> p2p.AppGossip - 9, // 22: p2p.Message.peer_list_ack:type_name -> p2p.PeerListAck - 27, // 23: p2p.Message.app_error:type_name -> p2p.AppError + 5, // 2: p2p.Message.handshake:type_name -> p2p.Handshake + 9, // 3: p2p.Message.get_peer_list:type_name -> p2p.GetPeerList + 10, // 4: p2p.Message.peer_list:type_name -> p2p.PeerList + 11, // 5: p2p.Message.get_state_summary_frontier:type_name -> p2p.GetStateSummaryFrontier + 12, // 6: p2p.Message.state_summary_frontier:type_name -> p2p.StateSummaryFrontier + 13, // 7: p2p.Message.get_accepted_state_summary:type_name -> p2p.GetAcceptedStateSummary + 14, // 8: p2p.Message.accepted_state_summary:type_name -> p2p.AcceptedStateSummary + 15, // 9: p2p.Message.get_accepted_frontier:type_name -> p2p.GetAcceptedFrontier + 16, // 10: p2p.Message.accepted_frontier:type_name -> p2p.AcceptedFrontier + 17, // 11: p2p.Message.get_accepted:type_name -> p2p.GetAccepted + 18, // 12: p2p.Message.accepted:type_name -> p2p.Accepted + 19, // 13: p2p.Message.get_ancestors:type_name -> p2p.GetAncestors + 20, // 14: p2p.Message.ancestors:type_name -> p2p.Ancestors + 21, // 15: p2p.Message.get:type_name -> p2p.Get + 22, // 16: p2p.Message.put:type_name -> p2p.Put + 23, // 17: p2p.Message.push_query:type_name -> p2p.PushQuery + 24, // 18: p2p.Message.pull_query:type_name -> p2p.PullQuery + 25, // 19: p2p.Message.chits:type_name -> p2p.Chits + 26, // 20: p2p.Message.app_request:type_name -> p2p.AppRequest + 27, // 21: p2p.Message.app_response:type_name -> p2p.AppResponse + 29, // 22: p2p.Message.app_gossip:type_name -> p2p.AppGossip + 28, // 23: p2p.Message.app_error:type_name -> p2p.AppError 3, // 24: p2p.Ping.subnet_uptimes:type_name -> p2p.SubnetUptime 3, // 25: p2p.Pong.subnet_uptimes:type_name -> p2p.SubnetUptime - 6, // 26: p2p.PeerList.claimed_ip_ports:type_name -> p2p.ClaimedIpPort - 8, // 27: p2p.PeerListAck.peer_acks:type_name -> p2p.PeerAck - 0, // 28: p2p.GetAcceptedFrontier.engine_type:type_name -> p2p.EngineType - 0, // 29: p2p.GetAccepted.engine_type:type_name -> p2p.EngineType - 0, // 30: p2p.GetAncestors.engine_type:type_name -> p2p.EngineType - 0, // 31: p2p.Get.engine_type:type_name -> p2p.EngineType - 0, // 32: p2p.Put.engine_type:type_name -> p2p.EngineType - 0, // 33: p2p.PushQuery.engine_type:type_name -> p2p.EngineType - 0, // 34: p2p.PullQuery.engine_type:type_name -> p2p.EngineType - 35, // [35:35] is the sub-list for method output_type - 35, // [35:35] is the sub-list for method input_type - 35, // [35:35] is the sub-list for extension type_name - 35, // [35:35] is the sub-list for extension extendee - 0, // [0:35] is the sub-list for field type_name + 6, // 26: p2p.Handshake.client:type_name -> p2p.Client + 7, // 27: p2p.Handshake.known_peers:type_name -> p2p.BloomFilter + 7, // 28: p2p.GetPeerList.known_peers:type_name -> p2p.BloomFilter + 8, // 29: p2p.PeerList.claimed_ip_ports:type_name -> p2p.ClaimedIpPort + 0, // 30: p2p.GetAcceptedFrontier.engine_type:type_name -> p2p.EngineType + 0, // 31: p2p.GetAccepted.engine_type:type_name -> p2p.EngineType + 0, // 32: p2p.GetAncestors.engine_type:type_name -> p2p.EngineType + 0, // 33: p2p.Get.engine_type:type_name -> p2p.EngineType + 0, // 34: p2p.Put.engine_type:type_name -> p2p.EngineType + 0, // 35: p2p.PushQuery.engine_type:type_name -> p2p.EngineType + 0, // 36: p2p.PullQuery.engine_type:type_name -> p2p.EngineType + 37, // [37:37] is the sub-list for method output_type + 37, // [37:37] is the sub-list for method input_type + 37, // [37:37] is the sub-list for extension type_name + 37, // [37:37] is the sub-list for extension extendee + 0, // [0:37] is the sub-list for field type_name } func init() { file_p2p_p2p_proto_init() } @@ -2994,7 +3125,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Version); i { + switch v := v.(*Handshake); i { case 0: return &v.state case 1: @@ -3006,7 +3137,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClaimedIpPort); i { + switch v := v.(*Client); i { case 0: return &v.state case 1: @@ -3018,7 +3149,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerList); i { + switch v := v.(*BloomFilter); i { case 0: return &v.state case 1: @@ -3030,7 +3161,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerAck); i { + switch v := v.(*ClaimedIpPort); i { case 0: return &v.state case 1: @@ -3042,7 +3173,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeerListAck); i { + switch v := v.(*GetPeerList); i { case 0: return &v.state case 1: @@ -3054,7 +3185,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetStateSummaryFrontier); i { + switch v := v.(*PeerList); i { case 0: return &v.state case 1: @@ -3066,7 +3197,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StateSummaryFrontier); i { + switch v := v.(*GetStateSummaryFrontier); i { case 0: return &v.state case 1: @@ -3078,7 +3209,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAcceptedStateSummary); i { + switch v := v.(*StateSummaryFrontier); i { case 0: return &v.state case 1: @@ -3090,7 +3221,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AcceptedStateSummary); i { + switch v := v.(*GetAcceptedStateSummary); i { case 0: return &v.state case 1: @@ -3102,7 +3233,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAcceptedFrontier); i { + switch v := v.(*AcceptedStateSummary); i { case 0: return &v.state case 1: @@ -3114,7 +3245,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AcceptedFrontier); i { + switch v := v.(*GetAcceptedFrontier); i { case 0: return &v.state case 1: @@ -3126,7 +3257,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAccepted); i { + switch v := v.(*AcceptedFrontier); i { case 0: return &v.state case 1: @@ -3138,7 +3269,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Accepted); i { + switch v := v.(*GetAccepted); i { case 0: return &v.state case 1: @@ -3150,7 +3281,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetAncestors); i { + switch v := v.(*Accepted); i { case 0: return &v.state case 1: @@ -3162,7 +3293,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Ancestors); i { + switch v := v.(*GetAncestors); i { case 0: return &v.state case 1: @@ -3174,7 +3305,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Get); i { + switch v := v.(*Ancestors); i { case 0: return &v.state case 1: @@ -3186,7 +3317,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Put); i { + switch v := v.(*Get); i { case 0: return &v.state case 1: @@ -3198,7 +3329,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PushQuery); i { + switch v := v.(*Put); i { case 0: return &v.state case 1: @@ -3210,7 +3341,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PullQuery); i { + switch v := v.(*PushQuery); i { case 0: return &v.state case 1: @@ -3222,7 +3353,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Chits); i { + switch v := v.(*PullQuery); i { case 0: return &v.state case 1: @@ -3234,7 +3365,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AppRequest); i { + switch v := v.(*Chits); i { case 0: return &v.state case 1: @@ -3246,7 +3377,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AppResponse); i { + switch v := v.(*AppRequest); i { case 0: return &v.state case 1: @@ -3258,7 +3389,7 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AppError); i { + switch v := v.(*AppResponse); i { case 0: return &v.state case 1: @@ -3270,6 +3401,18 @@ func file_p2p_p2p_proto_init() { } } file_p2p_p2p_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AppError); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_p2p_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppGossip); i { case 0: return &v.state @@ -3287,8 +3430,9 @@ func file_p2p_p2p_proto_init() { (*Message_CompressedZstd)(nil), (*Message_Ping)(nil), (*Message_Pong)(nil), - (*Message_Version)(nil), - (*Message_PeerList)(nil), + (*Message_Handshake)(nil), + (*Message_GetPeerList)(nil), + (*Message_PeerList_)(nil), (*Message_GetStateSummaryFrontier)(nil), (*Message_StateSummaryFrontier_)(nil), (*Message_GetAcceptedStateSummary)(nil), @@ -3307,7 +3451,6 @@ func file_p2p_p2p_proto_init() { (*Message_AppRequest)(nil), (*Message_AppResponse)(nil), (*Message_AppGossip)(nil), - (*Message_PeerListAck)(nil), (*Message_AppError)(nil), } type x struct{} @@ -3316,7 +3459,7 @@ func file_p2p_p2p_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_p2p_p2p_proto_rawDesc, NumEnums: 1, - NumMessages: 28, + NumMessages: 29, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/pb/sdk/sdk.pb.go b/proto/pb/sdk/sdk.pb.go index 120974ee5976..b90c23450270 100644 --- a/proto/pb/sdk/sdk.pb.go +++ b/proto/pb/sdk/sdk.pb.go @@ -25,8 +25,8 @@ type PullGossipRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Filter []byte `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` Salt []byte `protobuf:"bytes,2,opt,name=salt,proto3" json:"salt,omitempty"` + Filter []byte `protobuf:"bytes,3,opt,name=filter,proto3" json:"filter,omitempty"` } func (x *PullGossipRequest) Reset() { @@ -61,16 +61,16 @@ func (*PullGossipRequest) Descriptor() ([]byte, []int) { return file_sdk_sdk_proto_rawDescGZIP(), []int{0} } -func (x *PullGossipRequest) GetFilter() []byte { +func (x *PullGossipRequest) GetSalt() []byte { if x != nil { - return x.Filter + return x.Salt } return nil } -func (x *PullGossipRequest) GetSalt() []byte { +func (x *PullGossipRequest) GetFilter() []byte { if x != nil { - return x.Salt + return x.Filter } return nil } @@ -122,21 +122,71 @@ func (x *PullGossipResponse) GetGossip() [][]byte { return nil } +type PushGossip struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Gossip [][]byte `protobuf:"bytes,1,rep,name=gossip,proto3" json:"gossip,omitempty"` +} + +func (x *PushGossip) Reset() { + *x = PushGossip{} + if protoimpl.UnsafeEnabled { + mi := &file_sdk_sdk_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PushGossip) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PushGossip) ProtoMessage() {} + +func (x *PushGossip) ProtoReflect() protoreflect.Message { + mi := &file_sdk_sdk_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PushGossip.ProtoReflect.Descriptor instead. +func (*PushGossip) Descriptor() ([]byte, []int) { + return file_sdk_sdk_proto_rawDescGZIP(), []int{2} +} + +func (x *PushGossip) GetGossip() [][]byte { + if x != nil { + return x.Gossip + } + return nil +} + var File_sdk_sdk_proto protoreflect.FileDescriptor var file_sdk_sdk_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x73, 0x64, 0x6b, 0x2f, 0x73, 0x64, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, - 0x03, 0x73, 0x64, 0x6b, 0x22, 0x3f, 0x0a, 0x11, 0x50, 0x75, 0x6c, 0x6c, 0x47, 0x6f, 0x73, 0x73, - 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x04, 0x73, 0x61, 0x6c, 0x74, 0x22, 0x2c, 0x0a, 0x12, 0x50, 0x75, 0x6c, 0x6c, 0x47, 0x6f, 0x73, - 0x73, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, - 0x6f, 0x73, 0x73, 0x69, 0x70, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x67, 0x6f, 0x73, - 0x73, 0x69, 0x70, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x61, 0x76, 0x61, 0x6c, 0x61, - 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x2f, - 0x73, 0x64, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x03, 0x73, 0x64, 0x6b, 0x22, 0x45, 0x0a, 0x11, 0x50, 0x75, 0x6c, 0x6c, 0x47, 0x6f, 0x73, 0x73, + 0x69, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x2c, 0x0a, 0x12, 0x50, + 0x75, 0x6c, 0x6c, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x06, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x22, 0x24, 0x0a, 0x0a, 0x50, 0x75, 0x73, + 0x68, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x6f, 0x73, 0x73, 0x69, + 0x70, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x67, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x42, + 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x76, + 0x61, 0x2d, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x61, 0x76, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x68, 0x65, + 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x64, 0x6b, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -151,10 +201,11 @@ func file_sdk_sdk_proto_rawDescGZIP() []byte { return file_sdk_sdk_proto_rawDescData } -var file_sdk_sdk_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_sdk_sdk_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_sdk_sdk_proto_goTypes = []interface{}{ (*PullGossipRequest)(nil), // 0: sdk.PullGossipRequest (*PullGossipResponse)(nil), // 1: sdk.PullGossipResponse + (*PushGossip)(nil), // 2: sdk.PushGossip } var file_sdk_sdk_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -194,6 +245,18 @@ func file_sdk_sdk_proto_init() { return nil } } + file_sdk_sdk_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PushGossip); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -201,7 +264,7 @@ func file_sdk_sdk_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_sdk_sdk_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/pb/vm/vm.pb.go b/proto/pb/vm/vm.pb.go index ebc64f5c3a48..7f38e5bbf4a7 100644 --- a/proto/pb/vm/vm.pb.go +++ b/proto/pb/vm/vm.pb.go @@ -232,7 +232,7 @@ func (x StateSummaryAcceptResponse_Mode) Number() protoreflect.EnumNumber { // Deprecated: Use StateSummaryAcceptResponse_Mode.Descriptor instead. func (StateSummaryAcceptResponse_Mode) EnumDescriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{45, 0} + return file_vm_vm_proto_rawDescGZIP(), []int{44, 0} } type InitializeRequest struct { @@ -643,53 +643,6 @@ func (x *CreateHandlersResponse) GetHandlers() []*Handler { return nil } -type CreateStaticHandlersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Handlers []*Handler `protobuf:"bytes,1,rep,name=handlers,proto3" json:"handlers,omitempty"` -} - -func (x *CreateStaticHandlersResponse) Reset() { - *x = CreateStaticHandlersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CreateStaticHandlersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateStaticHandlersResponse) ProtoMessage() {} - -func (x *CreateStaticHandlersResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateStaticHandlersResponse.ProtoReflect.Descriptor instead. -func (*CreateStaticHandlersResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{5} -} - -func (x *CreateStaticHandlersResponse) GetHandlers() []*Handler { - if x != nil { - return x.Handlers - } - return nil -} - type Handler struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -704,7 +657,7 @@ type Handler struct { func (x *Handler) Reset() { *x = Handler{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[6] + mi := &file_vm_vm_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -717,7 +670,7 @@ func (x *Handler) String() string { func (*Handler) ProtoMessage() {} func (x *Handler) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[6] + mi := &file_vm_vm_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -730,7 +683,7 @@ func (x *Handler) ProtoReflect() protoreflect.Message { // Deprecated: Use Handler.ProtoReflect.Descriptor instead. func (*Handler) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{6} + return file_vm_vm_proto_rawDescGZIP(), []int{5} } func (x *Handler) GetPrefix() string { @@ -758,7 +711,7 @@ type BuildBlockRequest struct { func (x *BuildBlockRequest) Reset() { *x = BuildBlockRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[7] + mi := &file_vm_vm_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -771,7 +724,7 @@ func (x *BuildBlockRequest) String() string { func (*BuildBlockRequest) ProtoMessage() {} func (x *BuildBlockRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[7] + mi := &file_vm_vm_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -784,7 +737,7 @@ func (x *BuildBlockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildBlockRequest.ProtoReflect.Descriptor instead. func (*BuildBlockRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{7} + return file_vm_vm_proto_rawDescGZIP(), []int{6} } func (x *BuildBlockRequest) GetPChainHeight() uint64 { @@ -811,7 +764,7 @@ type BuildBlockResponse struct { func (x *BuildBlockResponse) Reset() { *x = BuildBlockResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[8] + mi := &file_vm_vm_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -824,7 +777,7 @@ func (x *BuildBlockResponse) String() string { func (*BuildBlockResponse) ProtoMessage() {} func (x *BuildBlockResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[8] + mi := &file_vm_vm_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -837,7 +790,7 @@ func (x *BuildBlockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildBlockResponse.ProtoReflect.Descriptor instead. func (*BuildBlockResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{8} + return file_vm_vm_proto_rawDescGZIP(), []int{7} } func (x *BuildBlockResponse) GetId() []byte { @@ -893,7 +846,7 @@ type ParseBlockRequest struct { func (x *ParseBlockRequest) Reset() { *x = ParseBlockRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[9] + mi := &file_vm_vm_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -906,7 +859,7 @@ func (x *ParseBlockRequest) String() string { func (*ParseBlockRequest) ProtoMessage() {} func (x *ParseBlockRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[9] + mi := &file_vm_vm_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -919,7 +872,7 @@ func (x *ParseBlockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ParseBlockRequest.ProtoReflect.Descriptor instead. func (*ParseBlockRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{9} + return file_vm_vm_proto_rawDescGZIP(), []int{8} } func (x *ParseBlockRequest) GetBytes() []byte { @@ -945,7 +898,7 @@ type ParseBlockResponse struct { func (x *ParseBlockResponse) Reset() { *x = ParseBlockResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[10] + mi := &file_vm_vm_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -958,7 +911,7 @@ func (x *ParseBlockResponse) String() string { func (*ParseBlockResponse) ProtoMessage() {} func (x *ParseBlockResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[10] + mi := &file_vm_vm_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -971,7 +924,7 @@ func (x *ParseBlockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ParseBlockResponse.ProtoReflect.Descriptor instead. func (*ParseBlockResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{10} + return file_vm_vm_proto_rawDescGZIP(), []int{9} } func (x *ParseBlockResponse) GetId() []byte { @@ -1027,7 +980,7 @@ type GetBlockRequest struct { func (x *GetBlockRequest) Reset() { *x = GetBlockRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[11] + mi := &file_vm_vm_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1040,7 +993,7 @@ func (x *GetBlockRequest) String() string { func (*GetBlockRequest) ProtoMessage() {} func (x *GetBlockRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[11] + mi := &file_vm_vm_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1053,7 +1006,7 @@ func (x *GetBlockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlockRequest.ProtoReflect.Descriptor instead. func (*GetBlockRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{11} + return file_vm_vm_proto_rawDescGZIP(), []int{10} } func (x *GetBlockRequest) GetId() []byte { @@ -1081,7 +1034,7 @@ type GetBlockResponse struct { func (x *GetBlockResponse) Reset() { *x = GetBlockResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[12] + mi := &file_vm_vm_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1094,7 +1047,7 @@ func (x *GetBlockResponse) String() string { func (*GetBlockResponse) ProtoMessage() {} func (x *GetBlockResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[12] + mi := &file_vm_vm_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1107,7 +1060,7 @@ func (x *GetBlockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlockResponse.ProtoReflect.Descriptor instead. func (*GetBlockResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{12} + return file_vm_vm_proto_rawDescGZIP(), []int{11} } func (x *GetBlockResponse) GetParentId() []byte { @@ -1170,7 +1123,7 @@ type SetPreferenceRequest struct { func (x *SetPreferenceRequest) Reset() { *x = SetPreferenceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[13] + mi := &file_vm_vm_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1183,7 +1136,7 @@ func (x *SetPreferenceRequest) String() string { func (*SetPreferenceRequest) ProtoMessage() {} func (x *SetPreferenceRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[13] + mi := &file_vm_vm_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1196,7 +1149,7 @@ func (x *SetPreferenceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetPreferenceRequest.ProtoReflect.Descriptor instead. func (*SetPreferenceRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{13} + return file_vm_vm_proto_rawDescGZIP(), []int{12} } func (x *SetPreferenceRequest) GetId() []byte { @@ -1220,7 +1173,7 @@ type BlockVerifyRequest struct { func (x *BlockVerifyRequest) Reset() { *x = BlockVerifyRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[14] + mi := &file_vm_vm_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1233,7 +1186,7 @@ func (x *BlockVerifyRequest) String() string { func (*BlockVerifyRequest) ProtoMessage() {} func (x *BlockVerifyRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[14] + mi := &file_vm_vm_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1246,7 +1199,7 @@ func (x *BlockVerifyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockVerifyRequest.ProtoReflect.Descriptor instead. func (*BlockVerifyRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{14} + return file_vm_vm_proto_rawDescGZIP(), []int{13} } func (x *BlockVerifyRequest) GetBytes() []byte { @@ -1274,7 +1227,7 @@ type BlockVerifyResponse struct { func (x *BlockVerifyResponse) Reset() { *x = BlockVerifyResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[15] + mi := &file_vm_vm_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1287,7 +1240,7 @@ func (x *BlockVerifyResponse) String() string { func (*BlockVerifyResponse) ProtoMessage() {} func (x *BlockVerifyResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[15] + mi := &file_vm_vm_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1300,7 +1253,7 @@ func (x *BlockVerifyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockVerifyResponse.ProtoReflect.Descriptor instead. func (*BlockVerifyResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{15} + return file_vm_vm_proto_rawDescGZIP(), []int{14} } func (x *BlockVerifyResponse) GetTimestamp() *timestamppb.Timestamp { @@ -1321,7 +1274,7 @@ type BlockAcceptRequest struct { func (x *BlockAcceptRequest) Reset() { *x = BlockAcceptRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[16] + mi := &file_vm_vm_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1334,7 +1287,7 @@ func (x *BlockAcceptRequest) String() string { func (*BlockAcceptRequest) ProtoMessage() {} func (x *BlockAcceptRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[16] + mi := &file_vm_vm_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1347,7 +1300,7 @@ func (x *BlockAcceptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockAcceptRequest.ProtoReflect.Descriptor instead. func (*BlockAcceptRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{16} + return file_vm_vm_proto_rawDescGZIP(), []int{15} } func (x *BlockAcceptRequest) GetId() []byte { @@ -1368,7 +1321,7 @@ type BlockRejectRequest struct { func (x *BlockRejectRequest) Reset() { *x = BlockRejectRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[17] + mi := &file_vm_vm_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1381,7 +1334,7 @@ func (x *BlockRejectRequest) String() string { func (*BlockRejectRequest) ProtoMessage() {} func (x *BlockRejectRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[17] + mi := &file_vm_vm_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1394,7 +1347,7 @@ func (x *BlockRejectRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockRejectRequest.ProtoReflect.Descriptor instead. func (*BlockRejectRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{17} + return file_vm_vm_proto_rawDescGZIP(), []int{16} } func (x *BlockRejectRequest) GetId() []byte { @@ -1415,7 +1368,7 @@ type HealthResponse struct { func (x *HealthResponse) Reset() { *x = HealthResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[18] + mi := &file_vm_vm_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1428,7 +1381,7 @@ func (x *HealthResponse) String() string { func (*HealthResponse) ProtoMessage() {} func (x *HealthResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[18] + mi := &file_vm_vm_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1441,7 +1394,7 @@ func (x *HealthResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HealthResponse.ProtoReflect.Descriptor instead. func (*HealthResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{18} + return file_vm_vm_proto_rawDescGZIP(), []int{17} } func (x *HealthResponse) GetDetails() []byte { @@ -1462,7 +1415,7 @@ type VersionResponse struct { func (x *VersionResponse) Reset() { *x = VersionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[19] + mi := &file_vm_vm_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1475,7 +1428,7 @@ func (x *VersionResponse) String() string { func (*VersionResponse) ProtoMessage() {} func (x *VersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[19] + mi := &file_vm_vm_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1488,7 +1441,7 @@ func (x *VersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead. func (*VersionResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{19} + return file_vm_vm_proto_rawDescGZIP(), []int{18} } func (x *VersionResponse) GetVersion() string { @@ -1516,7 +1469,7 @@ type AppRequestMsg struct { func (x *AppRequestMsg) Reset() { *x = AppRequestMsg{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[20] + mi := &file_vm_vm_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1529,7 +1482,7 @@ func (x *AppRequestMsg) String() string { func (*AppRequestMsg) ProtoMessage() {} func (x *AppRequestMsg) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[20] + mi := &file_vm_vm_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1542,7 +1495,7 @@ func (x *AppRequestMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use AppRequestMsg.ProtoReflect.Descriptor instead. func (*AppRequestMsg) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{20} + return file_vm_vm_proto_rawDescGZIP(), []int{19} } func (x *AppRequestMsg) GetNodeId() []byte { @@ -1582,12 +1535,16 @@ type AppRequestFailedMsg struct { NodeId []byte `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` // The ID of the request we sent and didn't get a response to RequestId uint32 `protobuf:"varint,2,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Application-defined error code + ErrorCode int32 `protobuf:"zigzag32,3,opt,name=error_code,json=errorCode,proto3" json:"error_code,omitempty"` + // Application-defined error message + ErrorMessage string `protobuf:"bytes,4,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` } func (x *AppRequestFailedMsg) Reset() { *x = AppRequestFailedMsg{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[21] + mi := &file_vm_vm_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1600,7 +1557,7 @@ func (x *AppRequestFailedMsg) String() string { func (*AppRequestFailedMsg) ProtoMessage() {} func (x *AppRequestFailedMsg) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[21] + mi := &file_vm_vm_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1613,7 +1570,7 @@ func (x *AppRequestFailedMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use AppRequestFailedMsg.ProtoReflect.Descriptor instead. func (*AppRequestFailedMsg) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{21} + return file_vm_vm_proto_rawDescGZIP(), []int{20} } func (x *AppRequestFailedMsg) GetNodeId() []byte { @@ -1630,6 +1587,20 @@ func (x *AppRequestFailedMsg) GetRequestId() uint32 { return 0 } +func (x *AppRequestFailedMsg) GetErrorCode() int32 { + if x != nil { + return x.ErrorCode + } + return 0 +} + +func (x *AppRequestFailedMsg) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + type AppResponseMsg struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1646,7 +1617,7 @@ type AppResponseMsg struct { func (x *AppResponseMsg) Reset() { *x = AppResponseMsg{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[22] + mi := &file_vm_vm_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1659,7 +1630,7 @@ func (x *AppResponseMsg) String() string { func (*AppResponseMsg) ProtoMessage() {} func (x *AppResponseMsg) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[22] + mi := &file_vm_vm_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1672,7 +1643,7 @@ func (x *AppResponseMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use AppResponseMsg.ProtoReflect.Descriptor instead. func (*AppResponseMsg) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{22} + return file_vm_vm_proto_rawDescGZIP(), []int{21} } func (x *AppResponseMsg) GetNodeId() []byte { @@ -1710,7 +1681,7 @@ type AppGossipMsg struct { func (x *AppGossipMsg) Reset() { *x = AppGossipMsg{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[23] + mi := &file_vm_vm_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1723,7 +1694,7 @@ func (x *AppGossipMsg) String() string { func (*AppGossipMsg) ProtoMessage() {} func (x *AppGossipMsg) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[23] + mi := &file_vm_vm_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1736,7 +1707,7 @@ func (x *AppGossipMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use AppGossipMsg.ProtoReflect.Descriptor instead. func (*AppGossipMsg) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{23} + return file_vm_vm_proto_rawDescGZIP(), []int{22} } func (x *AppGossipMsg) GetNodeId() []byte { @@ -1771,7 +1742,7 @@ type CrossChainAppRequestMsg struct { func (x *CrossChainAppRequestMsg) Reset() { *x = CrossChainAppRequestMsg{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[24] + mi := &file_vm_vm_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1784,7 +1755,7 @@ func (x *CrossChainAppRequestMsg) String() string { func (*CrossChainAppRequestMsg) ProtoMessage() {} func (x *CrossChainAppRequestMsg) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[24] + mi := &file_vm_vm_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1797,7 +1768,7 @@ func (x *CrossChainAppRequestMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use CrossChainAppRequestMsg.ProtoReflect.Descriptor instead. func (*CrossChainAppRequestMsg) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{24} + return file_vm_vm_proto_rawDescGZIP(), []int{23} } func (x *CrossChainAppRequestMsg) GetChainId() []byte { @@ -1837,12 +1808,16 @@ type CrossChainAppRequestFailedMsg struct { ChainId []byte `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` // The ID of the request we sent and didn't get a response to RequestId uint32 `protobuf:"varint,2,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Application-defined error code + ErrorCode int32 `protobuf:"zigzag32,3,opt,name=error_code,json=errorCode,proto3" json:"error_code,omitempty"` + // Application-defined error message + ErrorMessage string `protobuf:"bytes,4,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` } func (x *CrossChainAppRequestFailedMsg) Reset() { *x = CrossChainAppRequestFailedMsg{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[25] + mi := &file_vm_vm_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1855,7 +1830,7 @@ func (x *CrossChainAppRequestFailedMsg) String() string { func (*CrossChainAppRequestFailedMsg) ProtoMessage() {} func (x *CrossChainAppRequestFailedMsg) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[25] + mi := &file_vm_vm_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1868,7 +1843,7 @@ func (x *CrossChainAppRequestFailedMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use CrossChainAppRequestFailedMsg.ProtoReflect.Descriptor instead. func (*CrossChainAppRequestFailedMsg) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{25} + return file_vm_vm_proto_rawDescGZIP(), []int{24} } func (x *CrossChainAppRequestFailedMsg) GetChainId() []byte { @@ -1885,6 +1860,20 @@ func (x *CrossChainAppRequestFailedMsg) GetRequestId() uint32 { return 0 } +func (x *CrossChainAppRequestFailedMsg) GetErrorCode() int32 { + if x != nil { + return x.ErrorCode + } + return 0 +} + +func (x *CrossChainAppRequestFailedMsg) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + type CrossChainAppResponseMsg struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1901,7 +1890,7 @@ type CrossChainAppResponseMsg struct { func (x *CrossChainAppResponseMsg) Reset() { *x = CrossChainAppResponseMsg{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[26] + mi := &file_vm_vm_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1914,7 +1903,7 @@ func (x *CrossChainAppResponseMsg) String() string { func (*CrossChainAppResponseMsg) ProtoMessage() {} func (x *CrossChainAppResponseMsg) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[26] + mi := &file_vm_vm_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1927,7 +1916,7 @@ func (x *CrossChainAppResponseMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use CrossChainAppResponseMsg.ProtoReflect.Descriptor instead. func (*CrossChainAppResponseMsg) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{26} + return file_vm_vm_proto_rawDescGZIP(), []int{25} } func (x *CrossChainAppResponseMsg) GetChainId() []byte { @@ -1956,14 +1945,19 @@ type ConnectedRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - NodeId []byte `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` - Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + NodeId []byte `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + // Client name (e.g avalanchego) + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + // Client semantic version + Major uint32 `protobuf:"varint,3,opt,name=major,proto3" json:"major,omitempty"` + Minor uint32 `protobuf:"varint,4,opt,name=minor,proto3" json:"minor,omitempty"` + Patch uint32 `protobuf:"varint,5,opt,name=patch,proto3" json:"patch,omitempty"` } func (x *ConnectedRequest) Reset() { *x = ConnectedRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[27] + mi := &file_vm_vm_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1976,7 +1970,7 @@ func (x *ConnectedRequest) String() string { func (*ConnectedRequest) ProtoMessage() {} func (x *ConnectedRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[27] + mi := &file_vm_vm_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1989,7 +1983,7 @@ func (x *ConnectedRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectedRequest.ProtoReflect.Descriptor instead. func (*ConnectedRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{27} + return file_vm_vm_proto_rawDescGZIP(), []int{26} } func (x *ConnectedRequest) GetNodeId() []byte { @@ -1999,13 +1993,34 @@ func (x *ConnectedRequest) GetNodeId() []byte { return nil } -func (x *ConnectedRequest) GetVersion() string { +func (x *ConnectedRequest) GetName() string { if x != nil { - return x.Version + return x.Name } return "" } +func (x *ConnectedRequest) GetMajor() uint32 { + if x != nil { + return x.Major + } + return 0 +} + +func (x *ConnectedRequest) GetMinor() uint32 { + if x != nil { + return x.Minor + } + return 0 +} + +func (x *ConnectedRequest) GetPatch() uint32 { + if x != nil { + return x.Patch + } + return 0 +} + type DisconnectedRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2017,7 +2032,7 @@ type DisconnectedRequest struct { func (x *DisconnectedRequest) Reset() { *x = DisconnectedRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[28] + mi := &file_vm_vm_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2030,7 +2045,7 @@ func (x *DisconnectedRequest) String() string { func (*DisconnectedRequest) ProtoMessage() {} func (x *DisconnectedRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[28] + mi := &file_vm_vm_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2043,7 +2058,7 @@ func (x *DisconnectedRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DisconnectedRequest.ProtoReflect.Descriptor instead. func (*DisconnectedRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{28} + return file_vm_vm_proto_rawDescGZIP(), []int{27} } func (x *DisconnectedRequest) GetNodeId() []byte { @@ -2067,7 +2082,7 @@ type GetAncestorsRequest struct { func (x *GetAncestorsRequest) Reset() { *x = GetAncestorsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[29] + mi := &file_vm_vm_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2080,7 +2095,7 @@ func (x *GetAncestorsRequest) String() string { func (*GetAncestorsRequest) ProtoMessage() {} func (x *GetAncestorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[29] + mi := &file_vm_vm_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2093,7 +2108,7 @@ func (x *GetAncestorsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAncestorsRequest.ProtoReflect.Descriptor instead. func (*GetAncestorsRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{29} + return file_vm_vm_proto_rawDescGZIP(), []int{28} } func (x *GetAncestorsRequest) GetBlkId() []byte { @@ -2135,7 +2150,7 @@ type GetAncestorsResponse struct { func (x *GetAncestorsResponse) Reset() { *x = GetAncestorsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[30] + mi := &file_vm_vm_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2148,7 +2163,7 @@ func (x *GetAncestorsResponse) String() string { func (*GetAncestorsResponse) ProtoMessage() {} func (x *GetAncestorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[30] + mi := &file_vm_vm_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2161,7 +2176,7 @@ func (x *GetAncestorsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAncestorsResponse.ProtoReflect.Descriptor instead. func (*GetAncestorsResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{30} + return file_vm_vm_proto_rawDescGZIP(), []int{29} } func (x *GetAncestorsResponse) GetBlksBytes() [][]byte { @@ -2182,7 +2197,7 @@ type BatchedParseBlockRequest struct { func (x *BatchedParseBlockRequest) Reset() { *x = BatchedParseBlockRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[31] + mi := &file_vm_vm_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2195,7 +2210,7 @@ func (x *BatchedParseBlockRequest) String() string { func (*BatchedParseBlockRequest) ProtoMessage() {} func (x *BatchedParseBlockRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[31] + mi := &file_vm_vm_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2208,7 +2223,7 @@ func (x *BatchedParseBlockRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchedParseBlockRequest.ProtoReflect.Descriptor instead. func (*BatchedParseBlockRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{31} + return file_vm_vm_proto_rawDescGZIP(), []int{30} } func (x *BatchedParseBlockRequest) GetRequest() [][]byte { @@ -2229,7 +2244,7 @@ type BatchedParseBlockResponse struct { func (x *BatchedParseBlockResponse) Reset() { *x = BatchedParseBlockResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[32] + mi := &file_vm_vm_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2242,7 +2257,7 @@ func (x *BatchedParseBlockResponse) String() string { func (*BatchedParseBlockResponse) ProtoMessage() {} func (x *BatchedParseBlockResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[32] + mi := &file_vm_vm_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2255,7 +2270,7 @@ func (x *BatchedParseBlockResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BatchedParseBlockResponse.ProtoReflect.Descriptor instead. func (*BatchedParseBlockResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{32} + return file_vm_vm_proto_rawDescGZIP(), []int{31} } func (x *BatchedParseBlockResponse) GetResponse() []*ParseBlockResponse { @@ -2276,7 +2291,7 @@ type VerifyHeightIndexResponse struct { func (x *VerifyHeightIndexResponse) Reset() { *x = VerifyHeightIndexResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[33] + mi := &file_vm_vm_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2289,7 +2304,7 @@ func (x *VerifyHeightIndexResponse) String() string { func (*VerifyHeightIndexResponse) ProtoMessage() {} func (x *VerifyHeightIndexResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[33] + mi := &file_vm_vm_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2302,7 +2317,7 @@ func (x *VerifyHeightIndexResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyHeightIndexResponse.ProtoReflect.Descriptor instead. func (*VerifyHeightIndexResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{33} + return file_vm_vm_proto_rawDescGZIP(), []int{32} } func (x *VerifyHeightIndexResponse) GetErr() Error { @@ -2323,7 +2338,7 @@ type GetBlockIDAtHeightRequest struct { func (x *GetBlockIDAtHeightRequest) Reset() { *x = GetBlockIDAtHeightRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[34] + mi := &file_vm_vm_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2336,7 +2351,7 @@ func (x *GetBlockIDAtHeightRequest) String() string { func (*GetBlockIDAtHeightRequest) ProtoMessage() {} func (x *GetBlockIDAtHeightRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[34] + mi := &file_vm_vm_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2349,7 +2364,7 @@ func (x *GetBlockIDAtHeightRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlockIDAtHeightRequest.ProtoReflect.Descriptor instead. func (*GetBlockIDAtHeightRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{34} + return file_vm_vm_proto_rawDescGZIP(), []int{33} } func (x *GetBlockIDAtHeightRequest) GetHeight() uint64 { @@ -2371,7 +2386,7 @@ type GetBlockIDAtHeightResponse struct { func (x *GetBlockIDAtHeightResponse) Reset() { *x = GetBlockIDAtHeightResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[35] + mi := &file_vm_vm_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2384,7 +2399,7 @@ func (x *GetBlockIDAtHeightResponse) String() string { func (*GetBlockIDAtHeightResponse) ProtoMessage() {} func (x *GetBlockIDAtHeightResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[35] + mi := &file_vm_vm_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2397,7 +2412,7 @@ func (x *GetBlockIDAtHeightResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlockIDAtHeightResponse.ProtoReflect.Descriptor instead. func (*GetBlockIDAtHeightResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{35} + return file_vm_vm_proto_rawDescGZIP(), []int{34} } func (x *GetBlockIDAtHeightResponse) GetBlkId() []byte { @@ -2425,7 +2440,7 @@ type GatherResponse struct { func (x *GatherResponse) Reset() { *x = GatherResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[36] + mi := &file_vm_vm_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2438,7 +2453,7 @@ func (x *GatherResponse) String() string { func (*GatherResponse) ProtoMessage() {} func (x *GatherResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[36] + mi := &file_vm_vm_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2451,7 +2466,7 @@ func (x *GatherResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GatherResponse.ProtoReflect.Descriptor instead. func (*GatherResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{36} + return file_vm_vm_proto_rawDescGZIP(), []int{35} } func (x *GatherResponse) GetMetricFamilies() []*_go.MetricFamily { @@ -2473,7 +2488,7 @@ type StateSyncEnabledResponse struct { func (x *StateSyncEnabledResponse) Reset() { *x = StateSyncEnabledResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[37] + mi := &file_vm_vm_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2486,7 +2501,7 @@ func (x *StateSyncEnabledResponse) String() string { func (*StateSyncEnabledResponse) ProtoMessage() {} func (x *StateSyncEnabledResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[37] + mi := &file_vm_vm_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2499,7 +2514,7 @@ func (x *StateSyncEnabledResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StateSyncEnabledResponse.ProtoReflect.Descriptor instead. func (*StateSyncEnabledResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{37} + return file_vm_vm_proto_rawDescGZIP(), []int{36} } func (x *StateSyncEnabledResponse) GetEnabled() bool { @@ -2530,7 +2545,7 @@ type GetOngoingSyncStateSummaryResponse struct { func (x *GetOngoingSyncStateSummaryResponse) Reset() { *x = GetOngoingSyncStateSummaryResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[38] + mi := &file_vm_vm_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2543,7 +2558,7 @@ func (x *GetOngoingSyncStateSummaryResponse) String() string { func (*GetOngoingSyncStateSummaryResponse) ProtoMessage() {} func (x *GetOngoingSyncStateSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[38] + mi := &file_vm_vm_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2556,7 +2571,7 @@ func (x *GetOngoingSyncStateSummaryResponse) ProtoReflect() protoreflect.Message // Deprecated: Use GetOngoingSyncStateSummaryResponse.ProtoReflect.Descriptor instead. func (*GetOngoingSyncStateSummaryResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{38} + return file_vm_vm_proto_rawDescGZIP(), []int{37} } func (x *GetOngoingSyncStateSummaryResponse) GetId() []byte { @@ -2601,7 +2616,7 @@ type GetLastStateSummaryResponse struct { func (x *GetLastStateSummaryResponse) Reset() { *x = GetLastStateSummaryResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[39] + mi := &file_vm_vm_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2614,7 +2629,7 @@ func (x *GetLastStateSummaryResponse) String() string { func (*GetLastStateSummaryResponse) ProtoMessage() {} func (x *GetLastStateSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[39] + mi := &file_vm_vm_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2627,7 +2642,7 @@ func (x *GetLastStateSummaryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLastStateSummaryResponse.ProtoReflect.Descriptor instead. func (*GetLastStateSummaryResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{39} + return file_vm_vm_proto_rawDescGZIP(), []int{38} } func (x *GetLastStateSummaryResponse) GetId() []byte { @@ -2669,7 +2684,7 @@ type ParseStateSummaryRequest struct { func (x *ParseStateSummaryRequest) Reset() { *x = ParseStateSummaryRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[40] + mi := &file_vm_vm_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2682,7 +2697,7 @@ func (x *ParseStateSummaryRequest) String() string { func (*ParseStateSummaryRequest) ProtoMessage() {} func (x *ParseStateSummaryRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[40] + mi := &file_vm_vm_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2695,7 +2710,7 @@ func (x *ParseStateSummaryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ParseStateSummaryRequest.ProtoReflect.Descriptor instead. func (*ParseStateSummaryRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{40} + return file_vm_vm_proto_rawDescGZIP(), []int{39} } func (x *ParseStateSummaryRequest) GetBytes() []byte { @@ -2718,7 +2733,7 @@ type ParseStateSummaryResponse struct { func (x *ParseStateSummaryResponse) Reset() { *x = ParseStateSummaryResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[41] + mi := &file_vm_vm_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2731,7 +2746,7 @@ func (x *ParseStateSummaryResponse) String() string { func (*ParseStateSummaryResponse) ProtoMessage() {} func (x *ParseStateSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[41] + mi := &file_vm_vm_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2744,7 +2759,7 @@ func (x *ParseStateSummaryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ParseStateSummaryResponse.ProtoReflect.Descriptor instead. func (*ParseStateSummaryResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{41} + return file_vm_vm_proto_rawDescGZIP(), []int{40} } func (x *ParseStateSummaryResponse) GetId() []byte { @@ -2779,7 +2794,7 @@ type GetStateSummaryRequest struct { func (x *GetStateSummaryRequest) Reset() { *x = GetStateSummaryRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[42] + mi := &file_vm_vm_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2792,7 +2807,7 @@ func (x *GetStateSummaryRequest) String() string { func (*GetStateSummaryRequest) ProtoMessage() {} func (x *GetStateSummaryRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[42] + mi := &file_vm_vm_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2805,7 +2820,7 @@ func (x *GetStateSummaryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStateSummaryRequest.ProtoReflect.Descriptor instead. func (*GetStateSummaryRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{42} + return file_vm_vm_proto_rawDescGZIP(), []int{41} } func (x *GetStateSummaryRequest) GetHeight() uint64 { @@ -2828,7 +2843,7 @@ type GetStateSummaryResponse struct { func (x *GetStateSummaryResponse) Reset() { *x = GetStateSummaryResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[43] + mi := &file_vm_vm_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2841,7 +2856,7 @@ func (x *GetStateSummaryResponse) String() string { func (*GetStateSummaryResponse) ProtoMessage() {} func (x *GetStateSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[43] + mi := &file_vm_vm_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2854,7 +2869,7 @@ func (x *GetStateSummaryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStateSummaryResponse.ProtoReflect.Descriptor instead. func (*GetStateSummaryResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{43} + return file_vm_vm_proto_rawDescGZIP(), []int{42} } func (x *GetStateSummaryResponse) GetId() []byte { @@ -2889,7 +2904,7 @@ type StateSummaryAcceptRequest struct { func (x *StateSummaryAcceptRequest) Reset() { *x = StateSummaryAcceptRequest{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[44] + mi := &file_vm_vm_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2902,7 +2917,7 @@ func (x *StateSummaryAcceptRequest) String() string { func (*StateSummaryAcceptRequest) ProtoMessage() {} func (x *StateSummaryAcceptRequest) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[44] + mi := &file_vm_vm_proto_msgTypes[43] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2915,7 +2930,7 @@ func (x *StateSummaryAcceptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StateSummaryAcceptRequest.ProtoReflect.Descriptor instead. func (*StateSummaryAcceptRequest) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{44} + return file_vm_vm_proto_rawDescGZIP(), []int{43} } func (x *StateSummaryAcceptRequest) GetBytes() []byte { @@ -2937,7 +2952,7 @@ type StateSummaryAcceptResponse struct { func (x *StateSummaryAcceptResponse) Reset() { *x = StateSummaryAcceptResponse{} if protoimpl.UnsafeEnabled { - mi := &file_vm_vm_proto_msgTypes[45] + mi := &file_vm_vm_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2950,7 +2965,7 @@ func (x *StateSummaryAcceptResponse) String() string { func (*StateSummaryAcceptResponse) ProtoMessage() {} func (x *StateSummaryAcceptResponse) ProtoReflect() protoreflect.Message { - mi := &file_vm_vm_proto_msgTypes[45] + mi := &file_vm_vm_proto_msgTypes[44] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2963,7 +2978,7 @@ func (x *StateSummaryAcceptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StateSummaryAcceptResponse.ProtoReflect.Descriptor instead. func (*StateSummaryAcceptResponse) Descriptor() ([]byte, []int) { - return file_vm_vm_proto_rawDescGZIP(), []int{45} + return file_vm_vm_proto_rawDescGZIP(), []int{44} } func (x *StateSummaryAcceptResponse) GetMode() StateSummaryAcceptResponse_Mode { @@ -3055,418 +3070,421 @@ var file_vm_vm_proto_rawDesc = []byte{ 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x08, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x76, 0x6d, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x52, 0x08, 0x68, - 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x22, 0x47, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x08, 0x68, 0x61, 0x6e, 0x64, 0x6c, - 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x76, 0x6d, 0x2e, 0x48, - 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x52, 0x08, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, - 0x22, 0x42, 0x0a, 0x07, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, - 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, - 0x66, 0x69, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x64, - 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x41, 0x64, 0x64, 0x72, 0x22, 0x51, 0x0a, 0x11, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x0e, 0x70, 0x5f, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x70, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xd9, 0x01, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, - 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, - 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, - 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x12, 0x2e, 0x0a, 0x13, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x77, 0x69, - 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x57, 0x69, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x74, - 0x65, 0x78, 0x74, 0x22, 0x29, 0x0a, 0x11, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, 0xe7, - 0x01, 0x0a, 0x12, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, + 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x22, 0x42, 0x0a, 0x07, 0x48, 0x61, 0x6e, 0x64, 0x6c, + 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x22, 0x51, 0x0a, 0x11, 0x42, + 0x75, 0x69, 0x6c, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x29, 0x0a, 0x0e, 0x70, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x43, 0x68, 0x61, + 0x69, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, + 0x70, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xd9, + 0x01, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, - 0x49, 0x64, 0x12, 0x22, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x0a, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, - 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2e, 0x0a, 0x13, 0x76, 0x65, 0x72, 0x69, - 0x66, 0x79, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x57, 0x69, 0x74, - 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x21, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, - 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0x88, 0x02, 0x0a, 0x10, - 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, - 0x74, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x0a, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, - 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, - 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x12, 0x2e, 0x0a, 0x13, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, - 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x57, 0x69, 0x74, 0x68, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x26, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x50, 0x72, 0x65, - 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0x68, - 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x0e, 0x70, 0x5f, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x04, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x48, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x70, 0x5f, 0x63, 0x68, 0x61, 0x69, - 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x4f, 0x0a, 0x13, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, - 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x24, 0x0a, 0x12, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, - 0x24, 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, + 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2e, 0x0a, 0x13, 0x76, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x57, + 0x69, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x29, 0x0a, 0x11, 0x50, 0x61, + 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, 0xe7, 0x01, 0x0a, 0x12, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0a, 0x2e, 0x76, 0x6d, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, + 0x2e, 0x0a, 0x13, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x76, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x57, 0x69, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, + 0x21, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x88, 0x02, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0a, 0x2e, 0x76, 0x6d, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, + 0x76, 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x12, 0x2e, 0x0a, + 0x13, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x78, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x76, 0x65, 0x72, 0x69, + 0x66, 0x79, 0x57, 0x69, 0x74, 0x68, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x26, 0x0a, + 0x14, 0x53, 0x65, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2a, 0x0a, 0x0e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, - 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, - 0x73, 0x22, 0x2b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x99, - 0x01, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x73, 0x67, - 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4d, 0x0a, 0x13, 0x41, 0x70, - 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4d, 0x73, - 0x67, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, 0x64, 0x0a, 0x0e, 0x41, 0x70, 0x70, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x6e, + 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0x68, 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x12, 0x29, 0x0a, 0x0e, 0x70, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x43, 0x68, + 0x61, 0x69, 0x6e, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x42, 0x11, 0x0a, 0x0f, + 0x5f, 0x70, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, + 0x4f, 0x0a, 0x13, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x22, 0x24, 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0x24, 0x0a, 0x12, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, + 0x65, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2a, 0x0a, 0x0e, + 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x2b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x99, 0x01, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x4d, 0x73, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x36, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0x91, 0x01, 0x0a, 0x13, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x11, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x64, 0x0a, 0x0e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, + 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x0a, 0x0c, 0x41, + 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x4d, 0x73, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, - 0x64, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x39, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x4d, 0x73, 0x67, 0x12, - 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x22, 0xa5, 0x01, 0x0a, 0x17, 0x43, - 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x73, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x36, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, - 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x59, 0x0a, 0x1d, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x4d, 0x73, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x22, 0x70, 0x0a, - 0x18, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, - 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x45, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2e, 0x0a, 0x13, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, - 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, - 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x22, 0xb3, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x6e, - 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, - 0x0a, 0x06, 0x62, 0x6c, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x62, 0x6c, 0x6b, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x73, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, - 0x61, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x4e, 0x75, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x6d, - 0x61, 0x78, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x53, - 0x69, 0x7a, 0x65, 0x12, 0x37, 0x0a, 0x18, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x73, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x69, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6d, 0x61, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x52, 0x65, 0x74, 0x72, 0x69, 0x76, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x35, 0x0a, 0x14, - 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6b, 0x73, 0x5f, 0x62, 0x79, 0x74, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6b, 0x73, 0x42, 0x79, - 0x74, 0x65, 0x73, 0x22, 0x34, 0x0a, 0x18, 0x42, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x50, 0x61, - 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, - 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4f, 0x0a, 0x19, 0x42, 0x61, 0x74, - 0x63, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x50, 0x61, - 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x0a, 0x19, 0x56, 0x65, - 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, - 0x03, 0x65, 0x72, 0x72, 0x22, 0x33, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x49, 0x44, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x50, 0x0a, 0x1a, 0x47, 0x65, 0x74, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x44, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x6c, 0x6b, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x6c, 0x6b, 0x49, 0x64, 0x12, 0x1b, + 0x64, 0x65, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x22, 0xa5, 0x01, 0x0a, 0x17, 0x43, 0x72, 0x6f, 0x73, 0x73, + 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, + 0x73, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x08, + 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, + 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9d, + 0x01, 0x0a, 0x1d, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4d, 0x73, 0x67, + 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x11, 0x52, 0x09, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x70, + 0x0a, 0x18, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x81, 0x01, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x6f, + 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x14, + 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, + 0x61, 0x74, 0x63, 0x68, 0x22, 0x2e, 0x0a, 0x13, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6e, + 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6e, 0x6f, + 0x64, 0x65, 0x49, 0x64, 0x22, 0xb3, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, + 0x73, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, + 0x62, 0x6c, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x6c, + 0x6b, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x73, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x61, 0x78, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x4e, 0x75, 0x6d, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, + 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x53, 0x69, 0x7a, + 0x65, 0x12, 0x37, 0x0a, 0x18, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, + 0x72, 0x65, 0x74, 0x72, 0x69, 0x76, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x15, 0x6d, 0x61, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, + 0x74, 0x72, 0x69, 0x76, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x35, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6b, 0x73, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x6c, 0x6b, 0x73, 0x42, 0x79, 0x74, 0x65, + 0x73, 0x22, 0x34, 0x0a, 0x18, 0x42, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, + 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x07, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4f, 0x0a, 0x19, 0x42, 0x61, 0x74, 0x63, 0x68, + 0x65, 0x64, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x0a, 0x19, 0x56, 0x65, 0x72, 0x69, + 0x66, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, + 0x72, 0x72, 0x22, 0x33, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x44, + 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x50, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x49, 0x44, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x6c, 0x6b, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x6c, 0x6b, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x03, + 0x65, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x5d, 0x0a, 0x0e, 0x47, 0x61, 0x74, + 0x68, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x6d, + 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, + 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x52, 0x0e, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x22, 0x51, 0x0a, 0x18, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x53, 0x79, 0x6e, 0x63, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, - 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x5d, 0x0a, 0x0e, 0x47, - 0x61, 0x74, 0x68, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, - 0x0f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, - 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x52, 0x0e, 0x6d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x69, 0x65, 0x73, 0x22, 0x51, 0x0a, 0x18, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, - 0x76, 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x7f, 0x0a, - 0x22, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6e, 0x63, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, - 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, - 0x2e, 0x76, 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x78, - 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x03, 0x65, - 0x72, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x30, 0x0a, 0x18, 0x50, 0x61, 0x72, 0x73, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, 0x60, 0x0a, 0x19, 0x50, 0x61, - 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, - 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, - 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x30, 0x0a, 0x16, + 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x7f, 0x0a, 0x22, 0x47, + 0x65, 0x74, 0x4f, 0x6e, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, + 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, + 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x78, 0x0a, 0x1b, + 0x47, 0x65, 0x74, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x30, 0x0a, 0x18, 0x50, 0x61, 0x72, 0x73, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, 0x60, 0x0a, 0x19, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1b, 0x0a, + 0x03, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x30, 0x0a, 0x16, 0x47, 0x65, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x5c, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x5c, - 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, - 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, - 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x31, 0x0a, 0x19, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x0a, + 0x03, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, 0x72, 0x72, 0x22, 0x31, 0x0a, 0x19, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, 0xc5, 0x01, + 0x0a, 0x1a, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x04, + 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x41, 0x63, 0x63, 0x65, - 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, - 0xc5, 0x01, 0x0a, 0x1a, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, - 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x76, - 0x6d, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x41, 0x63, - 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x6f, 0x64, - 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, - 0x03, 0x65, 0x72, 0x72, 0x22, 0x51, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x10, - 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x4b, 0x49, 0x50, 0x50, - 0x45, 0x44, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x54, 0x41, - 0x54, 0x49, 0x43, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x44, 0x59, - 0x4e, 0x41, 0x4d, 0x49, 0x43, 0x10, 0x03, 0x2a, 0x65, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x54, 0x41, 0x54, 0x45, - 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x49, 0x4e, 0x47, 0x10, 0x01, - 0x12, 0x17, 0x0a, 0x13, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x42, 0x4f, 0x4f, 0x54, 0x53, 0x54, - 0x52, 0x41, 0x50, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x5f, 0x4f, 0x50, 0x10, 0x03, 0x2a, 0x61, - 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, - 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, - 0x53, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x55, - 0x53, 0x5f, 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, - 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, - 0x03, 0x2a, 0x8e, 0x01, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4c, 0x4f, 0x53, - 0x45, 0x44, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4e, 0x4f, - 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x5f, 0x48, 0x45, 0x49, 0x47, 0x48, 0x54, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, - 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, - 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x45, 0x44, - 0x10, 0x04, 0x32, 0xa4, 0x12, 0x0a, 0x02, 0x56, 0x4d, 0x12, 0x3b, 0x0a, 0x0a, 0x49, 0x6e, 0x69, - 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x15, 0x2e, 0x76, 0x6d, 0x2e, 0x49, 0x6e, 0x69, - 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x76, 0x6d, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x13, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x65, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, - 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x44, 0x0a, 0x0e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x76, 0x6d, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, - 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x50, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, - 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x52, + 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x76, 0x6d, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x03, 0x65, + 0x72, 0x72, 0x22, 0x51, 0x0a, 0x04, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x4f, + 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x4b, 0x49, 0x50, 0x50, 0x45, 0x44, + 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x49, + 0x43, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x44, 0x59, 0x4e, 0x41, + 0x4d, 0x49, 0x43, 0x10, 0x03, 0x2a, 0x65, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x15, + 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x17, + 0x0a, 0x13, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x42, 0x4f, 0x4f, 0x54, 0x53, 0x54, 0x52, 0x41, + 0x50, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x45, + 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x5f, 0x4f, 0x50, 0x10, 0x03, 0x2a, 0x61, 0x0a, 0x06, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, + 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, + 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, + 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, + 0x52, 0x45, 0x4a, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x03, 0x2a, + 0x8e, 0x01, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, + 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, + 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x5f, 0x48, 0x45, 0x49, 0x47, 0x48, 0x54, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x49, 0x4e, + 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x5f, 0x4e, + 0x4f, 0x54, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x45, 0x44, 0x10, 0x04, + 0x32, 0xd2, 0x11, 0x0a, 0x02, 0x56, 0x4d, 0x12, 0x3b, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x15, 0x2e, 0x76, 0x6d, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x76, + 0x6d, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x13, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x08, 0x53, + 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x20, 0x2e, 0x76, 0x6d, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x69, - 0x63, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x39, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x14, - 0x2e, 0x76, 0x6d, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0c, - 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x17, 0x2e, 0x76, - 0x6d, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3b, 0x0a, - 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x15, 0x2e, 0x76, 0x6d, - 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0a, 0x50, 0x61, - 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x15, 0x2e, 0x76, 0x6d, 0x2e, 0x50, 0x61, - 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x42, 0x6c, - 0x6f, 0x63, 0x6b, 0x12, 0x13, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, - 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, - 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, - 0x18, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x44, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x12, 0x34, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x76, 0x6d, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x76, 0x6d, 0x2e, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x37, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x11, 0x2e, - 0x76, 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x73, 0x67, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x17, 0x2e, 0x76, - 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, - 0x65, 0x64, 0x4d, 0x73, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x39, 0x0a, - 0x0b, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x2e, 0x76, - 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x73, 0x67, + 0x79, 0x1a, 0x1a, 0x2e, 0x76, 0x6d, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x61, 0x6e, + 0x64, 0x6c, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, + 0x09, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x14, 0x2e, 0x76, 0x6d, 0x2e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x35, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x47, - 0x6f, 0x73, 0x73, 0x69, 0x70, 0x12, 0x10, 0x2e, 0x76, 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x47, 0x6f, - 0x73, 0x73, 0x69, 0x70, 0x4d, 0x73, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, - 0x34, 0x0a, 0x06, 0x47, 0x61, 0x74, 0x68, 0x65, 0x72, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x12, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x61, 0x74, 0x68, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x14, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, - 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x2e, - 0x76, 0x6d, 0x2e, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x73, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x57, 0x0a, 0x1a, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x12, 0x21, 0x2e, 0x76, 0x6d, 0x2e, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, - 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x17, 0x2e, 0x76, 0x6d, 0x2e, 0x44, 0x69, + 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0a, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x15, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x75, 0x69, + 0x6c, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0a, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x15, 0x2e, 0x76, 0x6d, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x76, 0x6d, + 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, + 0x13, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0d, 0x53, 0x65, + 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x18, 0x2e, 0x76, 0x6d, + 0x2e, 0x53, 0x65, 0x74, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x34, 0x0a, + 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, + 0x12, 0x2e, 0x76, 0x6d, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x76, 0x6d, 0x2e, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x41, + 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x76, 0x6d, 0x2e, 0x41, + 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x73, 0x67, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x17, 0x2e, 0x76, 0x6d, 0x2e, 0x41, 0x70, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4d, 0x73, + 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x39, 0x0a, 0x0b, 0x41, 0x70, 0x70, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x2e, 0x76, 0x6d, 0x2e, 0x41, 0x70, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x35, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, 0x69, + 0x70, 0x12, 0x10, 0x2e, 0x76, 0x6d, 0x2e, 0x41, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, 0x69, 0x70, 0x4d, 0x73, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, 0x43, - 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x2e, 0x76, 0x6d, 0x2e, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, - 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, - 0x73, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x41, 0x0a, 0x0c, 0x47, 0x65, - 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x17, 0x2e, 0x76, 0x6d, 0x2e, - 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, - 0x73, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, - 0x11, 0x42, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, - 0x63, 0x6b, 0x12, 0x1c, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x50, - 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, - 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4a, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x76, - 0x6d, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x12, 0x47, - 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x44, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x12, 0x1d, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, - 0x44, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x44, - 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x48, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x45, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x76, - 0x6d, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x45, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x1a, 0x47, 0x65, - 0x74, 0x4f, 0x6e, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x26, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x67, 0x6f, 0x69, 0x6e, 0x67, - 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4c, - 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x06, 0x47, + 0x61, 0x74, 0x68, 0x65, 0x72, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, + 0x76, 0x6d, 0x2e, 0x47, 0x61, 0x74, 0x68, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x4b, 0x0a, 0x14, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, + 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x2e, 0x76, 0x6d, 0x2e, 0x43, + 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x73, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x57, + 0x0a, 0x1a, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x21, 0x2e, 0x76, + 0x6d, 0x2e, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x4d, 0x73, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, - 0x4c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x11, 0x50, 0x61, 0x72, 0x73, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1c, 0x2e, - 0x76, 0x6d, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x76, 0x6d, - 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0f, 0x47, 0x65, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1a, 0x2e, - 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x76, 0x6d, 0x2e, 0x47, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, 0x43, 0x72, 0x6f, 0x73, 0x73, + 0x43, 0x68, 0x61, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1c, 0x2e, 0x76, 0x6d, 0x2e, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x41, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, + 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x17, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x41, + 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x11, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1c, + 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x73, 0x65, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x76, + 0x6d, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x50, 0x61, 0x72, 0x73, 0x65, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x76, 0x6d, 0x2e, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x49, 0x44, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1d, 0x2e, + 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x44, 0x41, 0x74, 0x48, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x76, + 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x44, 0x41, 0x74, 0x48, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x10, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x67, + 0x6f, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x26, 0x2e, 0x76, + 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x6e, 0x63, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x73, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x73, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x11, 0x50, 0x61, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1c, 0x2e, 0x76, 0x6d, 0x2e, 0x50, + 0x61, 0x72, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x76, 0x6d, 0x2e, 0x50, 0x61, 0x72, + 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1a, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, - 0x76, 0x6d, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, - 0x65, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x12, 0x53, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x1d, 0x2e, 0x76, 0x6d, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x41, 0x63, 0x63, 0x65, - 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x76, 0x6d, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x41, 0x63, 0x63, 0x65, 0x70, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, 0x6c, 0x61, 0x62, 0x73, - 0x2f, 0x61, 0x76, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x76, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x76, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x12, 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, 0x65, 0x72, 0x69, + 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x76, 0x6d, 0x2e, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x41, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x12, 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x41, 0x63, 0x63, 0x65, + 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, + 0x12, 0x16, 0x2e, 0x76, 0x6d, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x6a, 0x65, 0x63, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x53, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x1d, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x76, 0x6d, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x61, 0x76, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, + 0x62, 0x2f, 0x76, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3482,7 +3500,7 @@ func file_vm_vm_proto_rawDescGZIP() []byte { } var file_vm_vm_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_vm_vm_proto_msgTypes = make([]protoimpl.MessageInfo, 46) +var file_vm_vm_proto_msgTypes = make([]protoimpl.MessageInfo, 45) var file_vm_vm_proto_goTypes = []interface{}{ (State)(0), // 0: vm.State (Status)(0), // 1: vm.Status @@ -3493,150 +3511,146 @@ var file_vm_vm_proto_goTypes = []interface{}{ (*SetStateRequest)(nil), // 6: vm.SetStateRequest (*SetStateResponse)(nil), // 7: vm.SetStateResponse (*CreateHandlersResponse)(nil), // 8: vm.CreateHandlersResponse - (*CreateStaticHandlersResponse)(nil), // 9: vm.CreateStaticHandlersResponse - (*Handler)(nil), // 10: vm.Handler - (*BuildBlockRequest)(nil), // 11: vm.BuildBlockRequest - (*BuildBlockResponse)(nil), // 12: vm.BuildBlockResponse - (*ParseBlockRequest)(nil), // 13: vm.ParseBlockRequest - (*ParseBlockResponse)(nil), // 14: vm.ParseBlockResponse - (*GetBlockRequest)(nil), // 15: vm.GetBlockRequest - (*GetBlockResponse)(nil), // 16: vm.GetBlockResponse - (*SetPreferenceRequest)(nil), // 17: vm.SetPreferenceRequest - (*BlockVerifyRequest)(nil), // 18: vm.BlockVerifyRequest - (*BlockVerifyResponse)(nil), // 19: vm.BlockVerifyResponse - (*BlockAcceptRequest)(nil), // 20: vm.BlockAcceptRequest - (*BlockRejectRequest)(nil), // 21: vm.BlockRejectRequest - (*HealthResponse)(nil), // 22: vm.HealthResponse - (*VersionResponse)(nil), // 23: vm.VersionResponse - (*AppRequestMsg)(nil), // 24: vm.AppRequestMsg - (*AppRequestFailedMsg)(nil), // 25: vm.AppRequestFailedMsg - (*AppResponseMsg)(nil), // 26: vm.AppResponseMsg - (*AppGossipMsg)(nil), // 27: vm.AppGossipMsg - (*CrossChainAppRequestMsg)(nil), // 28: vm.CrossChainAppRequestMsg - (*CrossChainAppRequestFailedMsg)(nil), // 29: vm.CrossChainAppRequestFailedMsg - (*CrossChainAppResponseMsg)(nil), // 30: vm.CrossChainAppResponseMsg - (*ConnectedRequest)(nil), // 31: vm.ConnectedRequest - (*DisconnectedRequest)(nil), // 32: vm.DisconnectedRequest - (*GetAncestorsRequest)(nil), // 33: vm.GetAncestorsRequest - (*GetAncestorsResponse)(nil), // 34: vm.GetAncestorsResponse - (*BatchedParseBlockRequest)(nil), // 35: vm.BatchedParseBlockRequest - (*BatchedParseBlockResponse)(nil), // 36: vm.BatchedParseBlockResponse - (*VerifyHeightIndexResponse)(nil), // 37: vm.VerifyHeightIndexResponse - (*GetBlockIDAtHeightRequest)(nil), // 38: vm.GetBlockIDAtHeightRequest - (*GetBlockIDAtHeightResponse)(nil), // 39: vm.GetBlockIDAtHeightResponse - (*GatherResponse)(nil), // 40: vm.GatherResponse - (*StateSyncEnabledResponse)(nil), // 41: vm.StateSyncEnabledResponse - (*GetOngoingSyncStateSummaryResponse)(nil), // 42: vm.GetOngoingSyncStateSummaryResponse - (*GetLastStateSummaryResponse)(nil), // 43: vm.GetLastStateSummaryResponse - (*ParseStateSummaryRequest)(nil), // 44: vm.ParseStateSummaryRequest - (*ParseStateSummaryResponse)(nil), // 45: vm.ParseStateSummaryResponse - (*GetStateSummaryRequest)(nil), // 46: vm.GetStateSummaryRequest - (*GetStateSummaryResponse)(nil), // 47: vm.GetStateSummaryResponse - (*StateSummaryAcceptRequest)(nil), // 48: vm.StateSummaryAcceptRequest - (*StateSummaryAcceptResponse)(nil), // 49: vm.StateSummaryAcceptResponse - (*timestamppb.Timestamp)(nil), // 50: google.protobuf.Timestamp - (*_go.MetricFamily)(nil), // 51: io.prometheus.client.MetricFamily - (*emptypb.Empty)(nil), // 52: google.protobuf.Empty + (*Handler)(nil), // 9: vm.Handler + (*BuildBlockRequest)(nil), // 10: vm.BuildBlockRequest + (*BuildBlockResponse)(nil), // 11: vm.BuildBlockResponse + (*ParseBlockRequest)(nil), // 12: vm.ParseBlockRequest + (*ParseBlockResponse)(nil), // 13: vm.ParseBlockResponse + (*GetBlockRequest)(nil), // 14: vm.GetBlockRequest + (*GetBlockResponse)(nil), // 15: vm.GetBlockResponse + (*SetPreferenceRequest)(nil), // 16: vm.SetPreferenceRequest + (*BlockVerifyRequest)(nil), // 17: vm.BlockVerifyRequest + (*BlockVerifyResponse)(nil), // 18: vm.BlockVerifyResponse + (*BlockAcceptRequest)(nil), // 19: vm.BlockAcceptRequest + (*BlockRejectRequest)(nil), // 20: vm.BlockRejectRequest + (*HealthResponse)(nil), // 21: vm.HealthResponse + (*VersionResponse)(nil), // 22: vm.VersionResponse + (*AppRequestMsg)(nil), // 23: vm.AppRequestMsg + (*AppRequestFailedMsg)(nil), // 24: vm.AppRequestFailedMsg + (*AppResponseMsg)(nil), // 25: vm.AppResponseMsg + (*AppGossipMsg)(nil), // 26: vm.AppGossipMsg + (*CrossChainAppRequestMsg)(nil), // 27: vm.CrossChainAppRequestMsg + (*CrossChainAppRequestFailedMsg)(nil), // 28: vm.CrossChainAppRequestFailedMsg + (*CrossChainAppResponseMsg)(nil), // 29: vm.CrossChainAppResponseMsg + (*ConnectedRequest)(nil), // 30: vm.ConnectedRequest + (*DisconnectedRequest)(nil), // 31: vm.DisconnectedRequest + (*GetAncestorsRequest)(nil), // 32: vm.GetAncestorsRequest + (*GetAncestorsResponse)(nil), // 33: vm.GetAncestorsResponse + (*BatchedParseBlockRequest)(nil), // 34: vm.BatchedParseBlockRequest + (*BatchedParseBlockResponse)(nil), // 35: vm.BatchedParseBlockResponse + (*VerifyHeightIndexResponse)(nil), // 36: vm.VerifyHeightIndexResponse + (*GetBlockIDAtHeightRequest)(nil), // 37: vm.GetBlockIDAtHeightRequest + (*GetBlockIDAtHeightResponse)(nil), // 38: vm.GetBlockIDAtHeightResponse + (*GatherResponse)(nil), // 39: vm.GatherResponse + (*StateSyncEnabledResponse)(nil), // 40: vm.StateSyncEnabledResponse + (*GetOngoingSyncStateSummaryResponse)(nil), // 41: vm.GetOngoingSyncStateSummaryResponse + (*GetLastStateSummaryResponse)(nil), // 42: vm.GetLastStateSummaryResponse + (*ParseStateSummaryRequest)(nil), // 43: vm.ParseStateSummaryRequest + (*ParseStateSummaryResponse)(nil), // 44: vm.ParseStateSummaryResponse + (*GetStateSummaryRequest)(nil), // 45: vm.GetStateSummaryRequest + (*GetStateSummaryResponse)(nil), // 46: vm.GetStateSummaryResponse + (*StateSummaryAcceptRequest)(nil), // 47: vm.StateSummaryAcceptRequest + (*StateSummaryAcceptResponse)(nil), // 48: vm.StateSummaryAcceptResponse + (*timestamppb.Timestamp)(nil), // 49: google.protobuf.Timestamp + (*_go.MetricFamily)(nil), // 50: io.prometheus.client.MetricFamily + (*emptypb.Empty)(nil), // 51: google.protobuf.Empty } var file_vm_vm_proto_depIdxs = []int32{ - 50, // 0: vm.InitializeResponse.timestamp:type_name -> google.protobuf.Timestamp + 49, // 0: vm.InitializeResponse.timestamp:type_name -> google.protobuf.Timestamp 0, // 1: vm.SetStateRequest.state:type_name -> vm.State - 50, // 2: vm.SetStateResponse.timestamp:type_name -> google.protobuf.Timestamp - 10, // 3: vm.CreateHandlersResponse.handlers:type_name -> vm.Handler - 10, // 4: vm.CreateStaticHandlersResponse.handlers:type_name -> vm.Handler - 50, // 5: vm.BuildBlockResponse.timestamp:type_name -> google.protobuf.Timestamp - 1, // 6: vm.ParseBlockResponse.status:type_name -> vm.Status - 50, // 7: vm.ParseBlockResponse.timestamp:type_name -> google.protobuf.Timestamp - 1, // 8: vm.GetBlockResponse.status:type_name -> vm.Status - 50, // 9: vm.GetBlockResponse.timestamp:type_name -> google.protobuf.Timestamp - 2, // 10: vm.GetBlockResponse.err:type_name -> vm.Error - 50, // 11: vm.BlockVerifyResponse.timestamp:type_name -> google.protobuf.Timestamp - 50, // 12: vm.AppRequestMsg.deadline:type_name -> google.protobuf.Timestamp - 50, // 13: vm.CrossChainAppRequestMsg.deadline:type_name -> google.protobuf.Timestamp - 14, // 14: vm.BatchedParseBlockResponse.response:type_name -> vm.ParseBlockResponse - 2, // 15: vm.VerifyHeightIndexResponse.err:type_name -> vm.Error - 2, // 16: vm.GetBlockIDAtHeightResponse.err:type_name -> vm.Error - 51, // 17: vm.GatherResponse.metric_families:type_name -> io.prometheus.client.MetricFamily - 2, // 18: vm.StateSyncEnabledResponse.err:type_name -> vm.Error - 2, // 19: vm.GetOngoingSyncStateSummaryResponse.err:type_name -> vm.Error - 2, // 20: vm.GetLastStateSummaryResponse.err:type_name -> vm.Error - 2, // 21: vm.ParseStateSummaryResponse.err:type_name -> vm.Error - 2, // 22: vm.GetStateSummaryResponse.err:type_name -> vm.Error - 3, // 23: vm.StateSummaryAcceptResponse.mode:type_name -> vm.StateSummaryAcceptResponse.Mode - 2, // 24: vm.StateSummaryAcceptResponse.err:type_name -> vm.Error - 4, // 25: vm.VM.Initialize:input_type -> vm.InitializeRequest - 6, // 26: vm.VM.SetState:input_type -> vm.SetStateRequest - 52, // 27: vm.VM.Shutdown:input_type -> google.protobuf.Empty - 52, // 28: vm.VM.CreateHandlers:input_type -> google.protobuf.Empty - 52, // 29: vm.VM.CreateStaticHandlers:input_type -> google.protobuf.Empty - 31, // 30: vm.VM.Connected:input_type -> vm.ConnectedRequest - 32, // 31: vm.VM.Disconnected:input_type -> vm.DisconnectedRequest - 11, // 32: vm.VM.BuildBlock:input_type -> vm.BuildBlockRequest - 13, // 33: vm.VM.ParseBlock:input_type -> vm.ParseBlockRequest - 15, // 34: vm.VM.GetBlock:input_type -> vm.GetBlockRequest - 17, // 35: vm.VM.SetPreference:input_type -> vm.SetPreferenceRequest - 52, // 36: vm.VM.Health:input_type -> google.protobuf.Empty - 52, // 37: vm.VM.Version:input_type -> google.protobuf.Empty - 24, // 38: vm.VM.AppRequest:input_type -> vm.AppRequestMsg - 25, // 39: vm.VM.AppRequestFailed:input_type -> vm.AppRequestFailedMsg - 26, // 40: vm.VM.AppResponse:input_type -> vm.AppResponseMsg - 27, // 41: vm.VM.AppGossip:input_type -> vm.AppGossipMsg - 52, // 42: vm.VM.Gather:input_type -> google.protobuf.Empty - 28, // 43: vm.VM.CrossChainAppRequest:input_type -> vm.CrossChainAppRequestMsg - 29, // 44: vm.VM.CrossChainAppRequestFailed:input_type -> vm.CrossChainAppRequestFailedMsg - 30, // 45: vm.VM.CrossChainAppResponse:input_type -> vm.CrossChainAppResponseMsg - 33, // 46: vm.VM.GetAncestors:input_type -> vm.GetAncestorsRequest - 35, // 47: vm.VM.BatchedParseBlock:input_type -> vm.BatchedParseBlockRequest - 52, // 48: vm.VM.VerifyHeightIndex:input_type -> google.protobuf.Empty - 38, // 49: vm.VM.GetBlockIDAtHeight:input_type -> vm.GetBlockIDAtHeightRequest - 52, // 50: vm.VM.StateSyncEnabled:input_type -> google.protobuf.Empty - 52, // 51: vm.VM.GetOngoingSyncStateSummary:input_type -> google.protobuf.Empty - 52, // 52: vm.VM.GetLastStateSummary:input_type -> google.protobuf.Empty - 44, // 53: vm.VM.ParseStateSummary:input_type -> vm.ParseStateSummaryRequest - 46, // 54: vm.VM.GetStateSummary:input_type -> vm.GetStateSummaryRequest - 18, // 55: vm.VM.BlockVerify:input_type -> vm.BlockVerifyRequest - 20, // 56: vm.VM.BlockAccept:input_type -> vm.BlockAcceptRequest - 21, // 57: vm.VM.BlockReject:input_type -> vm.BlockRejectRequest - 48, // 58: vm.VM.StateSummaryAccept:input_type -> vm.StateSummaryAcceptRequest - 5, // 59: vm.VM.Initialize:output_type -> vm.InitializeResponse - 7, // 60: vm.VM.SetState:output_type -> vm.SetStateResponse - 52, // 61: vm.VM.Shutdown:output_type -> google.protobuf.Empty - 8, // 62: vm.VM.CreateHandlers:output_type -> vm.CreateHandlersResponse - 9, // 63: vm.VM.CreateStaticHandlers:output_type -> vm.CreateStaticHandlersResponse - 52, // 64: vm.VM.Connected:output_type -> google.protobuf.Empty - 52, // 65: vm.VM.Disconnected:output_type -> google.protobuf.Empty - 12, // 66: vm.VM.BuildBlock:output_type -> vm.BuildBlockResponse - 14, // 67: vm.VM.ParseBlock:output_type -> vm.ParseBlockResponse - 16, // 68: vm.VM.GetBlock:output_type -> vm.GetBlockResponse - 52, // 69: vm.VM.SetPreference:output_type -> google.protobuf.Empty - 22, // 70: vm.VM.Health:output_type -> vm.HealthResponse - 23, // 71: vm.VM.Version:output_type -> vm.VersionResponse - 52, // 72: vm.VM.AppRequest:output_type -> google.protobuf.Empty - 52, // 73: vm.VM.AppRequestFailed:output_type -> google.protobuf.Empty - 52, // 74: vm.VM.AppResponse:output_type -> google.protobuf.Empty - 52, // 75: vm.VM.AppGossip:output_type -> google.protobuf.Empty - 40, // 76: vm.VM.Gather:output_type -> vm.GatherResponse - 52, // 77: vm.VM.CrossChainAppRequest:output_type -> google.protobuf.Empty - 52, // 78: vm.VM.CrossChainAppRequestFailed:output_type -> google.protobuf.Empty - 52, // 79: vm.VM.CrossChainAppResponse:output_type -> google.protobuf.Empty - 34, // 80: vm.VM.GetAncestors:output_type -> vm.GetAncestorsResponse - 36, // 81: vm.VM.BatchedParseBlock:output_type -> vm.BatchedParseBlockResponse - 37, // 82: vm.VM.VerifyHeightIndex:output_type -> vm.VerifyHeightIndexResponse - 39, // 83: vm.VM.GetBlockIDAtHeight:output_type -> vm.GetBlockIDAtHeightResponse - 41, // 84: vm.VM.StateSyncEnabled:output_type -> vm.StateSyncEnabledResponse - 42, // 85: vm.VM.GetOngoingSyncStateSummary:output_type -> vm.GetOngoingSyncStateSummaryResponse - 43, // 86: vm.VM.GetLastStateSummary:output_type -> vm.GetLastStateSummaryResponse - 45, // 87: vm.VM.ParseStateSummary:output_type -> vm.ParseStateSummaryResponse - 47, // 88: vm.VM.GetStateSummary:output_type -> vm.GetStateSummaryResponse - 19, // 89: vm.VM.BlockVerify:output_type -> vm.BlockVerifyResponse - 52, // 90: vm.VM.BlockAccept:output_type -> google.protobuf.Empty - 52, // 91: vm.VM.BlockReject:output_type -> google.protobuf.Empty - 49, // 92: vm.VM.StateSummaryAccept:output_type -> vm.StateSummaryAcceptResponse - 59, // [59:93] is the sub-list for method output_type - 25, // [25:59] is the sub-list for method input_type - 25, // [25:25] is the sub-list for extension type_name - 25, // [25:25] is the sub-list for extension extendee - 0, // [0:25] is the sub-list for field type_name + 49, // 2: vm.SetStateResponse.timestamp:type_name -> google.protobuf.Timestamp + 9, // 3: vm.CreateHandlersResponse.handlers:type_name -> vm.Handler + 49, // 4: vm.BuildBlockResponse.timestamp:type_name -> google.protobuf.Timestamp + 1, // 5: vm.ParseBlockResponse.status:type_name -> vm.Status + 49, // 6: vm.ParseBlockResponse.timestamp:type_name -> google.protobuf.Timestamp + 1, // 7: vm.GetBlockResponse.status:type_name -> vm.Status + 49, // 8: vm.GetBlockResponse.timestamp:type_name -> google.protobuf.Timestamp + 2, // 9: vm.GetBlockResponse.err:type_name -> vm.Error + 49, // 10: vm.BlockVerifyResponse.timestamp:type_name -> google.protobuf.Timestamp + 49, // 11: vm.AppRequestMsg.deadline:type_name -> google.protobuf.Timestamp + 49, // 12: vm.CrossChainAppRequestMsg.deadline:type_name -> google.protobuf.Timestamp + 13, // 13: vm.BatchedParseBlockResponse.response:type_name -> vm.ParseBlockResponse + 2, // 14: vm.VerifyHeightIndexResponse.err:type_name -> vm.Error + 2, // 15: vm.GetBlockIDAtHeightResponse.err:type_name -> vm.Error + 50, // 16: vm.GatherResponse.metric_families:type_name -> io.prometheus.client.MetricFamily + 2, // 17: vm.StateSyncEnabledResponse.err:type_name -> vm.Error + 2, // 18: vm.GetOngoingSyncStateSummaryResponse.err:type_name -> vm.Error + 2, // 19: vm.GetLastStateSummaryResponse.err:type_name -> vm.Error + 2, // 20: vm.ParseStateSummaryResponse.err:type_name -> vm.Error + 2, // 21: vm.GetStateSummaryResponse.err:type_name -> vm.Error + 3, // 22: vm.StateSummaryAcceptResponse.mode:type_name -> vm.StateSummaryAcceptResponse.Mode + 2, // 23: vm.StateSummaryAcceptResponse.err:type_name -> vm.Error + 4, // 24: vm.VM.Initialize:input_type -> vm.InitializeRequest + 6, // 25: vm.VM.SetState:input_type -> vm.SetStateRequest + 51, // 26: vm.VM.Shutdown:input_type -> google.protobuf.Empty + 51, // 27: vm.VM.CreateHandlers:input_type -> google.protobuf.Empty + 30, // 28: vm.VM.Connected:input_type -> vm.ConnectedRequest + 31, // 29: vm.VM.Disconnected:input_type -> vm.DisconnectedRequest + 10, // 30: vm.VM.BuildBlock:input_type -> vm.BuildBlockRequest + 12, // 31: vm.VM.ParseBlock:input_type -> vm.ParseBlockRequest + 14, // 32: vm.VM.GetBlock:input_type -> vm.GetBlockRequest + 16, // 33: vm.VM.SetPreference:input_type -> vm.SetPreferenceRequest + 51, // 34: vm.VM.Health:input_type -> google.protobuf.Empty + 51, // 35: vm.VM.Version:input_type -> google.protobuf.Empty + 23, // 36: vm.VM.AppRequest:input_type -> vm.AppRequestMsg + 24, // 37: vm.VM.AppRequestFailed:input_type -> vm.AppRequestFailedMsg + 25, // 38: vm.VM.AppResponse:input_type -> vm.AppResponseMsg + 26, // 39: vm.VM.AppGossip:input_type -> vm.AppGossipMsg + 51, // 40: vm.VM.Gather:input_type -> google.protobuf.Empty + 27, // 41: vm.VM.CrossChainAppRequest:input_type -> vm.CrossChainAppRequestMsg + 28, // 42: vm.VM.CrossChainAppRequestFailed:input_type -> vm.CrossChainAppRequestFailedMsg + 29, // 43: vm.VM.CrossChainAppResponse:input_type -> vm.CrossChainAppResponseMsg + 32, // 44: vm.VM.GetAncestors:input_type -> vm.GetAncestorsRequest + 34, // 45: vm.VM.BatchedParseBlock:input_type -> vm.BatchedParseBlockRequest + 51, // 46: vm.VM.VerifyHeightIndex:input_type -> google.protobuf.Empty + 37, // 47: vm.VM.GetBlockIDAtHeight:input_type -> vm.GetBlockIDAtHeightRequest + 51, // 48: vm.VM.StateSyncEnabled:input_type -> google.protobuf.Empty + 51, // 49: vm.VM.GetOngoingSyncStateSummary:input_type -> google.protobuf.Empty + 51, // 50: vm.VM.GetLastStateSummary:input_type -> google.protobuf.Empty + 43, // 51: vm.VM.ParseStateSummary:input_type -> vm.ParseStateSummaryRequest + 45, // 52: vm.VM.GetStateSummary:input_type -> vm.GetStateSummaryRequest + 17, // 53: vm.VM.BlockVerify:input_type -> vm.BlockVerifyRequest + 19, // 54: vm.VM.BlockAccept:input_type -> vm.BlockAcceptRequest + 20, // 55: vm.VM.BlockReject:input_type -> vm.BlockRejectRequest + 47, // 56: vm.VM.StateSummaryAccept:input_type -> vm.StateSummaryAcceptRequest + 5, // 57: vm.VM.Initialize:output_type -> vm.InitializeResponse + 7, // 58: vm.VM.SetState:output_type -> vm.SetStateResponse + 51, // 59: vm.VM.Shutdown:output_type -> google.protobuf.Empty + 8, // 60: vm.VM.CreateHandlers:output_type -> vm.CreateHandlersResponse + 51, // 61: vm.VM.Connected:output_type -> google.protobuf.Empty + 51, // 62: vm.VM.Disconnected:output_type -> google.protobuf.Empty + 11, // 63: vm.VM.BuildBlock:output_type -> vm.BuildBlockResponse + 13, // 64: vm.VM.ParseBlock:output_type -> vm.ParseBlockResponse + 15, // 65: vm.VM.GetBlock:output_type -> vm.GetBlockResponse + 51, // 66: vm.VM.SetPreference:output_type -> google.protobuf.Empty + 21, // 67: vm.VM.Health:output_type -> vm.HealthResponse + 22, // 68: vm.VM.Version:output_type -> vm.VersionResponse + 51, // 69: vm.VM.AppRequest:output_type -> google.protobuf.Empty + 51, // 70: vm.VM.AppRequestFailed:output_type -> google.protobuf.Empty + 51, // 71: vm.VM.AppResponse:output_type -> google.protobuf.Empty + 51, // 72: vm.VM.AppGossip:output_type -> google.protobuf.Empty + 39, // 73: vm.VM.Gather:output_type -> vm.GatherResponse + 51, // 74: vm.VM.CrossChainAppRequest:output_type -> google.protobuf.Empty + 51, // 75: vm.VM.CrossChainAppRequestFailed:output_type -> google.protobuf.Empty + 51, // 76: vm.VM.CrossChainAppResponse:output_type -> google.protobuf.Empty + 33, // 77: vm.VM.GetAncestors:output_type -> vm.GetAncestorsResponse + 35, // 78: vm.VM.BatchedParseBlock:output_type -> vm.BatchedParseBlockResponse + 36, // 79: vm.VM.VerifyHeightIndex:output_type -> vm.VerifyHeightIndexResponse + 38, // 80: vm.VM.GetBlockIDAtHeight:output_type -> vm.GetBlockIDAtHeightResponse + 40, // 81: vm.VM.StateSyncEnabled:output_type -> vm.StateSyncEnabledResponse + 41, // 82: vm.VM.GetOngoingSyncStateSummary:output_type -> vm.GetOngoingSyncStateSummaryResponse + 42, // 83: vm.VM.GetLastStateSummary:output_type -> vm.GetLastStateSummaryResponse + 44, // 84: vm.VM.ParseStateSummary:output_type -> vm.ParseStateSummaryResponse + 46, // 85: vm.VM.GetStateSummary:output_type -> vm.GetStateSummaryResponse + 18, // 86: vm.VM.BlockVerify:output_type -> vm.BlockVerifyResponse + 51, // 87: vm.VM.BlockAccept:output_type -> google.protobuf.Empty + 51, // 88: vm.VM.BlockReject:output_type -> google.protobuf.Empty + 48, // 89: vm.VM.StateSummaryAccept:output_type -> vm.StateSummaryAcceptResponse + 57, // [57:90] is the sub-list for method output_type + 24, // [24:57] is the sub-list for method input_type + 24, // [24:24] is the sub-list for extension type_name + 24, // [24:24] is the sub-list for extension extendee + 0, // [0:24] is the sub-list for field type_name } func init() { file_vm_vm_proto_init() } @@ -3706,18 +3720,6 @@ func file_vm_vm_proto_init() { } } file_vm_vm_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateStaticHandlersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_vm_vm_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Handler); i { case 0: return &v.state @@ -3729,7 +3731,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BuildBlockRequest); i { case 0: return &v.state @@ -3741,7 +3743,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BuildBlockResponse); i { case 0: return &v.state @@ -3753,7 +3755,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ParseBlockRequest); i { case 0: return &v.state @@ -3765,7 +3767,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ParseBlockResponse); i { case 0: return &v.state @@ -3777,7 +3779,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetBlockRequest); i { case 0: return &v.state @@ -3789,7 +3791,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetBlockResponse); i { case 0: return &v.state @@ -3801,7 +3803,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SetPreferenceRequest); i { case 0: return &v.state @@ -3813,7 +3815,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BlockVerifyRequest); i { case 0: return &v.state @@ -3825,7 +3827,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BlockVerifyResponse); i { case 0: return &v.state @@ -3837,7 +3839,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BlockAcceptRequest); i { case 0: return &v.state @@ -3849,7 +3851,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BlockRejectRequest); i { case 0: return &v.state @@ -3861,7 +3863,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HealthResponse); i { case 0: return &v.state @@ -3873,7 +3875,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*VersionResponse); i { case 0: return &v.state @@ -3885,7 +3887,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppRequestMsg); i { case 0: return &v.state @@ -3897,7 +3899,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppRequestFailedMsg); i { case 0: return &v.state @@ -3909,7 +3911,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppResponseMsg); i { case 0: return &v.state @@ -3921,7 +3923,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AppGossipMsg); i { case 0: return &v.state @@ -3933,7 +3935,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CrossChainAppRequestMsg); i { case 0: return &v.state @@ -3945,7 +3947,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CrossChainAppRequestFailedMsg); i { case 0: return &v.state @@ -3957,7 +3959,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CrossChainAppResponseMsg); i { case 0: return &v.state @@ -3969,7 +3971,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ConnectedRequest); i { case 0: return &v.state @@ -3981,7 +3983,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DisconnectedRequest); i { case 0: return &v.state @@ -3993,7 +3995,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetAncestorsRequest); i { case 0: return &v.state @@ -4005,7 +4007,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetAncestorsResponse); i { case 0: return &v.state @@ -4017,7 +4019,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BatchedParseBlockRequest); i { case 0: return &v.state @@ -4029,7 +4031,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BatchedParseBlockResponse); i { case 0: return &v.state @@ -4041,7 +4043,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*VerifyHeightIndexResponse); i { case 0: return &v.state @@ -4053,7 +4055,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetBlockIDAtHeightRequest); i { case 0: return &v.state @@ -4065,7 +4067,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetBlockIDAtHeightResponse); i { case 0: return &v.state @@ -4077,7 +4079,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GatherResponse); i { case 0: return &v.state @@ -4089,7 +4091,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StateSyncEnabledResponse); i { case 0: return &v.state @@ -4101,7 +4103,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetOngoingSyncStateSummaryResponse); i { case 0: return &v.state @@ -4113,7 +4115,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetLastStateSummaryResponse); i { case 0: return &v.state @@ -4125,7 +4127,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ParseStateSummaryRequest); i { case 0: return &v.state @@ -4137,7 +4139,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ParseStateSummaryResponse); i { case 0: return &v.state @@ -4149,7 +4151,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetStateSummaryRequest); i { case 0: return &v.state @@ -4161,7 +4163,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetStateSummaryResponse); i { case 0: return &v.state @@ -4173,7 +4175,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StateSummaryAcceptRequest); i { case 0: return &v.state @@ -4185,7 +4187,7 @@ func file_vm_vm_proto_init() { return nil } } - file_vm_vm_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { + file_vm_vm_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StateSummaryAcceptResponse); i { case 0: return &v.state @@ -4198,15 +4200,15 @@ func file_vm_vm_proto_init() { } } } - file_vm_vm_proto_msgTypes[7].OneofWrappers = []interface{}{} - file_vm_vm_proto_msgTypes[14].OneofWrappers = []interface{}{} + file_vm_vm_proto_msgTypes[6].OneofWrappers = []interface{}{} + file_vm_vm_proto_msgTypes[13].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_vm_vm_proto_rawDesc, NumEnums: 4, - NumMessages: 46, + NumMessages: 45, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/pb/vm/vm_grpc.pb.go b/proto/pb/vm/vm_grpc.pb.go index 5250af11f86f..6d7bb17f6c33 100644 --- a/proto/pb/vm/vm_grpc.pb.go +++ b/proto/pb/vm/vm_grpc.pb.go @@ -24,7 +24,6 @@ const ( VM_SetState_FullMethodName = "/vm.VM/SetState" VM_Shutdown_FullMethodName = "/vm.VM/Shutdown" VM_CreateHandlers_FullMethodName = "/vm.VM/CreateHandlers" - VM_CreateStaticHandlers_FullMethodName = "/vm.VM/CreateStaticHandlers" VM_Connected_FullMethodName = "/vm.VM/Connected" VM_Disconnected_FullMethodName = "/vm.VM/Disconnected" VM_BuildBlock_FullMethodName = "/vm.VM/BuildBlock" @@ -70,13 +69,6 @@ type VMClient interface { Shutdown(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) // Creates the HTTP handlers for custom chain network calls. CreateHandlers(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CreateHandlersResponse, error) - // Creates the HTTP handlers for custom VM network calls. - // - // Note: RPC Chain VM Factory will start a new instance of the VM in a - // seperate process which will populate the static handlers. After this - // process is created other processes will be created to populate blockchains, - // but they will not have the static handlers be called again. - CreateStaticHandlers(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CreateStaticHandlersResponse, error) Connected(ctx context.Context, in *ConnectedRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) Disconnected(ctx context.Context, in *DisconnectedRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) // Attempt to create a new block from data contained in the VM. @@ -177,15 +169,6 @@ func (c *vMClient) CreateHandlers(ctx context.Context, in *emptypb.Empty, opts . return out, nil } -func (c *vMClient) CreateStaticHandlers(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CreateStaticHandlersResponse, error) { - out := new(CreateStaticHandlersResponse) - err := c.cc.Invoke(ctx, VM_CreateStaticHandlers_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *vMClient) Connected(ctx context.Context, in *ConnectedRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) err := c.cc.Invoke(ctx, VM_Connected_FullMethodName, in, out, opts...) @@ -461,13 +444,6 @@ type VMServer interface { Shutdown(context.Context, *emptypb.Empty) (*emptypb.Empty, error) // Creates the HTTP handlers for custom chain network calls. CreateHandlers(context.Context, *emptypb.Empty) (*CreateHandlersResponse, error) - // Creates the HTTP handlers for custom VM network calls. - // - // Note: RPC Chain VM Factory will start a new instance of the VM in a - // seperate process which will populate the static handlers. After this - // process is created other processes will be created to populate blockchains, - // but they will not have the static handlers be called again. - CreateStaticHandlers(context.Context, *emptypb.Empty) (*CreateStaticHandlersResponse, error) Connected(context.Context, *ConnectedRequest) (*emptypb.Empty, error) Disconnected(context.Context, *DisconnectedRequest) (*emptypb.Empty, error) // Attempt to create a new block from data contained in the VM. @@ -541,9 +517,6 @@ func (UnimplementedVMServer) Shutdown(context.Context, *emptypb.Empty) (*emptypb func (UnimplementedVMServer) CreateHandlers(context.Context, *emptypb.Empty) (*CreateHandlersResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateHandlers not implemented") } -func (UnimplementedVMServer) CreateStaticHandlers(context.Context, *emptypb.Empty) (*CreateStaticHandlersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateStaticHandlers not implemented") -} func (UnimplementedVMServer) Connected(context.Context, *ConnectedRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method Connected not implemented") } @@ -716,24 +689,6 @@ func _VM_CreateHandlers_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } -func _VM_CreateStaticHandlers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(emptypb.Empty) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(VMServer).CreateStaticHandlers(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: VM_CreateStaticHandlers_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(VMServer).CreateStaticHandlers(ctx, req.(*emptypb.Empty)) - } - return interceptor(ctx, in, info, handler) -} - func _VM_Connected_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ConnectedRequest) if err := dec(in); err != nil { @@ -1279,10 +1234,6 @@ var VM_ServiceDesc = grpc.ServiceDesc{ MethodName: "CreateHandlers", Handler: _VM_CreateHandlers_Handler, }, - { - MethodName: "CreateStaticHandlers", - Handler: _VM_CreateStaticHandlers_Handler, - }, { MethodName: "Connected", Handler: _VM_Connected_Handler, diff --git a/proto/sdk/sdk.proto b/proto/sdk/sdk.proto index 20bfca081856..f42912391fe7 100644 --- a/proto/sdk/sdk.proto +++ b/proto/sdk/sdk.proto @@ -5,10 +5,16 @@ package sdk; option go_package = "github.com/ava-labs/avalanchego/proto/pb/sdk"; message PullGossipRequest { - bytes filter = 1; + // TODO: Remove reservation after v1.11.x activates. + reserved 1; bytes salt = 2; + bytes filter = 3; } message PullGossipResponse { repeated bytes gossip = 1; } + +message PushGossip { + repeated bytes gossip = 1; +} diff --git a/proto/vm/vm.proto b/proto/vm/vm.proto index 0eca74b46041..4a0557ba4e67 100644 --- a/proto/vm/vm.proto +++ b/proto/vm/vm.proto @@ -21,13 +21,6 @@ service VM { rpc Shutdown(google.protobuf.Empty) returns (google.protobuf.Empty); // Creates the HTTP handlers for custom chain network calls. rpc CreateHandlers(google.protobuf.Empty) returns (CreateHandlersResponse); - // Creates the HTTP handlers for custom VM network calls. - // - // Note: RPC Chain VM Factory will start a new instance of the VM in a - // seperate process which will populate the static handlers. After this - // process is created other processes will be created to populate blockchains, - // but they will not have the static handlers be called again. - rpc CreateStaticHandlers(google.protobuf.Empty) returns (CreateStaticHandlersResponse); rpc Connected(ConnectedRequest) returns (google.protobuf.Empty); rpc Disconnected(DisconnectedRequest) returns (google.protobuf.Empty); // Attempt to create a new block from data contained in the VM. @@ -158,10 +151,6 @@ message CreateHandlersResponse { repeated Handler handlers = 1; } -message CreateStaticHandlersResponse { - repeated Handler handlers = 1; -} - message Handler { string prefix = 1; // server_addr is the address of the gRPC server which serves the @@ -259,6 +248,10 @@ message AppRequestFailedMsg { bytes node_id = 1; // The ID of the request we sent and didn't get a response to uint32 request_id = 2; + // Application-defined error code + sint32 error_code = 3; + // Application-defined error message + string error_message = 4; } message AppResponseMsg { @@ -293,6 +286,10 @@ message CrossChainAppRequestFailedMsg { bytes chain_id = 1; // The ID of the request we sent and didn't get a response to uint32 request_id = 2; + // Application-defined error code + sint32 error_code = 3; + // Application-defined error message + string error_message = 4; } message CrossChainAppResponseMsg { @@ -306,7 +303,12 @@ message CrossChainAppResponseMsg { message ConnectedRequest { bytes node_id = 1; - string version = 2; + // Client name (e.g avalanchego) + string name = 2; + // Client semantic version + uint32 major = 3; + uint32 minor = 4; + uint32 patch = 5; } message DisconnectedRequest { diff --git a/pubsub/bloom/filter.go b/pubsub/bloom/filter.go new file mode 100644 index 000000000000..b0d023b51f19 --- /dev/null +++ b/pubsub/bloom/filter.go @@ -0,0 +1,51 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bloom + +import ( + "errors" + + "github.com/ava-labs/avalanchego/utils/bloom" +) + +const bytesPerHash = 8 + +var ( + _ Filter = (*filter)(nil) + + errMaxBytes = errors.New("too large") +) + +type Filter interface { + // Add adds to filter, assumed thread safe + Add(...[]byte) + + // Check checks filter, assumed thread safe + Check([]byte) bool +} + +func New(maxN int, p float64, maxBytes int) (Filter, error) { + numHashes, numEntries := bloom.OptimalParameters(maxN, p) + if neededBytes := 1 + numHashes*bytesPerHash + numEntries; neededBytes > maxBytes { + return nil, errMaxBytes + } + f, err := bloom.New(numHashes, numEntries) + return &filter{ + filter: f, + }, err +} + +type filter struct { + filter *bloom.Filter +} + +func (f *filter) Add(bl ...[]byte) { + for _, b := range bl { + bloom.Add(f.filter, b, nil) + } +} + +func (f *filter) Check(b []byte) bool { + return bloom.Contains(f.filter, b, nil) +} diff --git a/utils/bloom/bloom_filter_test.go b/pubsub/bloom/filter_test.go similarity index 72% rename from utils/bloom/bloom_filter_test.go rename to pubsub/bloom/filter_test.go index 7e810add0f3e..3b2c4b71a59d 100644 --- a/utils/bloom/bloom_filter_test.go +++ b/pubsub/bloom/filter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bloom @@ -13,10 +13,10 @@ import ( func TestNew(t *testing.T) { var ( - require = require.New(t) - maxN uint64 = 10000 - p = 0.1 - maxBytes uint64 = 1 * units.MiB // 1 MiB + require = require.New(t) + maxN = 10000 + p = 0.1 + maxBytes = 1 * units.MiB // 1 MiB ) f, err := New(maxN, p, maxBytes) require.NoError(err) diff --git a/utils/bloom/map_filter.go b/pubsub/bloom/map_filter.go similarity index 88% rename from utils/bloom/map_filter.go rename to pubsub/bloom/map_filter.go index 19046bea4c11..d0edcbe88fd0 100644 --- a/utils/bloom/map_filter.go +++ b/pubsub/bloom/map_filter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bloom diff --git a/pubsub/connection.go b/pubsub/connection.go index 2dae38acd1e6..901a33a25da3 100644 --- a/pubsub/connection.go +++ b/pubsub/connection.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pubsub @@ -14,7 +14,7 @@ import ( "go.uber.org/zap" - "github.com/ava-labs/avalanchego/utils/bloom" + "github.com/ava-labs/avalanchego/pubsub/bloom" ) var ( @@ -190,7 +190,7 @@ func (c *connection) handleNewBloom(cmd *NewBloom) error { if !cmd.IsParamsValid() { return ErrInvalidFilterParam } - filter, err := bloom.New(uint64(cmd.MaxElements), float64(cmd.CollisionProb), MaxBytes) + filter, err := bloom.New(int(cmd.MaxElements), float64(cmd.CollisionProb), MaxBytes) if err != nil { return fmt.Errorf("bloom filter creation failed %w", err) } diff --git a/pubsub/connections.go b/pubsub/connections.go index 417e1aa8f365..25d35ac8cd82 100644 --- a/pubsub/connections.go +++ b/pubsub/connections.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pubsub diff --git a/pubsub/filter_param.go b/pubsub/filter_param.go index e7e2453c3e95..5fd80a2ad706 100644 --- a/pubsub/filter_param.go +++ b/pubsub/filter_param.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pubsub @@ -6,7 +6,7 @@ package pubsub import ( "sync" - "github.com/ava-labs/avalanchego/utils/bloom" + "github.com/ava-labs/avalanchego/pubsub/bloom" "github.com/ava-labs/avalanchego/utils/set" ) diff --git a/pubsub/filter_test.go b/pubsub/filter_test.go index edc88794fa34..3b47a38e0237 100644 --- a/pubsub/filter_test.go +++ b/pubsub/filter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pubsub @@ -10,7 +10,7 @@ import ( "github.com/ava-labs/avalanchego/api" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/bloom" + "github.com/ava-labs/avalanchego/pubsub/bloom" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/formatting/address" ) diff --git a/pubsub/filterer.go b/pubsub/filterer.go index 389448ea7af2..3ec2910a9c4c 100644 --- a/pubsub/filterer.go +++ b/pubsub/filterer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pubsub diff --git a/pubsub/messages.go b/pubsub/messages.go index 525ae035f15a..ec41af813cdb 100644 --- a/pubsub/messages.go +++ b/pubsub/messages.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pubsub diff --git a/pubsub/server.go b/pubsub/server.go index b7e4eaf74377..6cc8b649296c 100644 --- a/pubsub/server.go +++ b/pubsub/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package pubsub diff --git a/scripts/build_avalanche.sh b/scripts/build_avalanche.sh index 1db63eb946f4..dcfaed4c420d 100755 --- a/scripts/build_avalanche.sh +++ b/scripts/build_avalanche.sh @@ -27,7 +27,7 @@ done # Dockerfile # README.md # go.mod -go_version_minimum="1.20.10" +go_version_minimum="1.20.12" go_version() { go version | sed -nE -e 's/[^0-9.]+([0-9.]+).+/\1/p' diff --git a/scripts/build_fuzz.sh b/scripts/build_fuzz.sh index 0f7ad7de8ede..54ed02c27e21 100755 --- a/scripts/build_fuzz.sh +++ b/scripts/build_fuzz.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +# First argument is the time, in seconds, to run each fuzz test for. +# If not provided, defaults to 1 second. +# +# Second argument is the directory to run fuzz tests in. +# If not provided, defaults to the current directory. + set -euo pipefail # Mostly taken from https://github.com/golang/go/issues/46312#issuecomment-1153345129 @@ -10,7 +16,9 @@ AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) source "$AVALANCHE_PATH"/scripts/constants.sh fuzzTime=${1:-1} -files=$(grep -r --include='**_test.go' --files-with-matches 'func Fuzz' .) +fuzzDir=${2:-.} + +files=$(grep -r --include='**_test.go' --files-with-matches 'func Fuzz' $fuzzDir) failed=false for file in ${files} do diff --git a/scripts/constants.sh b/scripts/constants.sh index 433e424f7acc..c175bffd57cf 100755 --- a/scripts/constants.sh +++ b/scripts/constants.sh @@ -36,3 +36,6 @@ export CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" # While CGO_ENABLED doesn't need to be explicitly set, it produces a much more # clear error due to the default value change in go1.20. export CGO_ENABLED=1 + +# Disable version control fallbacks +export GOPROXY="https://proxy.golang.org" diff --git a/scripts/mock.gen.sh b/scripts/mock.gen.sh index 855dbc94b304..16b00a42a569 100755 --- a/scripts/mock.gen.sh +++ b/scripts/mock.gen.sh @@ -7,22 +7,13 @@ if ! [[ "$0" =~ scripts/mock.gen.sh ]]; then exit 255 fi -if ! command -v mockgen &> /dev/null -then - echo "mockgen not found, installing..." - # https://github.com/uber-go/mock - go install -v go.uber.org/mock/mockgen@v0.2.0 -fi - -if ! command -v go-license &> /dev/null -then - echo "go-license not found, installing..." - # https://github.com/palantir/go-license - go install -v github.com/palantir/go-license@v1.25.0 -fi +# https://github.com/uber-go/mock +go install -v go.uber.org/mock/mockgen@v0.4.0 source ./scripts/constants.sh +outputted_files=() + # tuples of (source interface import path, comma-separated interface names, output file path) input="scripts/mocks.mockgen.txt" while IFS= read -r line @@ -30,11 +21,42 @@ do IFS='=' read src_import_path interface_name output_path <<< "${line}" package_name=$(basename $(dirname $output_path)) echo "Generating ${output_path}..." + outputted_files+=(${output_path}) mockgen -package=${package_name} -destination=${output_path} ${src_import_path} ${interface_name} - go-license \ - --config=./header.yml \ - "${output_path}" done < "$input" +# tuples of (source import path, comma-separated interface names to exclude, output file path) +input="scripts/mocks.mockgen.source.txt" +while IFS= read -r line +do + IFS='=' read source_path exclude_interfaces output_path <<< "${line}" + package_name=$(basename $(dirname $output_path)) + outputted_files+=(${output_path}) + echo "Generating ${output_path}..." + + mockgen \ + -source=${source_path} \ + -destination=${output_path} \ + -package=${package_name} \ + -exclude_interfaces=${exclude_interfaces} + +done < "$input" + +all_generated_files=( $(grep -Rl 'Code generated by MockGen. DO NOT EDIT.') ) + +# Exclude certain files +outputted_files+=('scripts/mock.gen.sh') # This file +outputted_files+=('vms/components/avax/mock_transferable_out.go') # Embedded verify.IsState +outputted_files+=('vms/platformvm/fx/mock_fx.go') # Embedded verify.IsNotState + +diff_files=(`echo ${all_generated_files[@]} ${outputted_files[@]} | tr ' ' '\n' | sort | uniq -u`) + +if (( ${#diff_files[@]} )); then + printf "\nFAILURE\n" + echo "Detected MockGen generated files that are not in scripts/mocks.mockgen.source.txt or scripts/mocks.mockgen.txt:" + printf "%s\n" "${diff_files[@]}" + exit 255 +fi + echo "SUCCESS" diff --git a/scripts/mocks.mockgen.source.txt b/scripts/mocks.mockgen.source.txt new file mode 100644 index 000000000000..02782a7b7d9c --- /dev/null +++ b/scripts/mocks.mockgen.source.txt @@ -0,0 +1,10 @@ +snow/engine/common/sender.go=StateSummarySender,AcceptedStateSummarySender,FrontierSender,AcceptedSender,FetchSender,AppSender,QuerySender,CrossChainAppSender,NetworkAppSender,Gossiper=snow/engine/common/mock_sender.go +snow/networking/router/router.go=InternalHandler=snow/networking/router/mock_router.go +snow/networking/sender/external_sender.go==snow/networking/sender/mock_external_sender.go +snow/validators/manager.go=SetCallbackListener=snow/validators/mock_manager.go +vms/avm/block/executor/manager.go==vms/avm/block/executor/mock_manager.go +vms/avm/txs/tx.go==vms/avm/txs/mock_unsigned_tx.go +vms/platformvm/block/executor/manager.go==vms/platformvm/block/executor/mock_manager.go +vms/platformvm/txs/staker_tx.go=ValidatorTx,DelegatorTx,StakerTx,PermissionlessStaker=vms/platformvm/txs/mock_staker_tx.go +vms/platformvm/txs/unsigned_tx.go==vms/platformvm/txs/mock_unsigned_tx.go +x/merkledb/db.go=ChangeProofer,RangeProofer,Clearer,Prefetcher=x/merkledb/mock_db.go diff --git a/scripts/mocks.mockgen.txt b/scripts/mocks.mockgen.txt index 76add90f3f7f..ba2be886b0b6 100644 --- a/scripts/mocks.mockgen.txt +++ b/scripts/mocks.mockgen.txt @@ -5,14 +5,12 @@ github.com/ava-labs/avalanchego/database=Batch=database/mock_batch.go github.com/ava-labs/avalanchego/database=Iterator=database/mock_iterator.go github.com/ava-labs/avalanchego/message=OutboundMessage=message/mock_message.go github.com/ava-labs/avalanchego/message=OutboundMsgBuilder=message/mock_outbound_message_builder.go -github.com/ava-labs/avalanchego/network/peer=GossipTracker=network/peer/mock_gossip_tracker.go -github.com/ava-labs/avalanchego/network/p2p=Handler=network/p2p/mocks/mock_handler.go github.com/ava-labs/avalanchego/snow/consensus/snowman=Block=snow/consensus/snowman/mock_block.go github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex=LinearizableVM=snow/engine/avalanche/vertex/mock_vm.go -github.com/ava-labs/avalanchego/snow/engine/snowman/block=BuildBlockWithContextChainVM=snow/engine/snowman/block/mocks/build_block_with_context_vm.go -github.com/ava-labs/avalanchego/snow/engine/snowman/block=ChainVM=snow/engine/snowman/block/mocks/chain_vm.go -github.com/ava-labs/avalanchego/snow/engine/snowman/block=StateSyncableVM=snow/engine/snowman/block/mocks/state_syncable_vm.go -github.com/ava-labs/avalanchego/snow/engine/snowman/block=WithVerifyContext=snow/engine/snowman/block/mocks/with_verify_context.go +github.com/ava-labs/avalanchego/snow/engine/snowman/block=BuildBlockWithContextChainVM=snow/engine/snowman/block/mock_build_block_with_context_vm.go +github.com/ava-labs/avalanchego/snow/engine/snowman/block=ChainVM=snow/engine/snowman/block/mock_chain_vm.go +github.com/ava-labs/avalanchego/snow/engine/snowman/block=StateSyncableVM=snow/engine/snowman/block/mock_state_syncable_vm.go +github.com/ava-labs/avalanchego/snow/engine/snowman/block=WithVerifyContext=snow/engine/snowman/block/mock_with_verify_context.go github.com/ava-labs/avalanchego/snow/networking/handler=Handler=snow/networking/handler/mock_handler.go github.com/ava-labs/avalanchego/snow/networking/timeout=Manager=snow/networking/timeout/mock_manager.go github.com/ava-labs/avalanchego/snow/networking/tracker=Targeter=snow/networking/tracker/mock_targeter.go @@ -23,7 +21,6 @@ github.com/ava-labs/avalanchego/snow/validators=SubnetConnector=snow/validators/ github.com/ava-labs/avalanchego/utils/crypto/keychain=Ledger=utils/crypto/keychain/mock_ledger.go github.com/ava-labs/avalanchego/utils/filesystem=Reader=utils/filesystem/mock_io.go github.com/ava-labs/avalanchego/utils/hashing=Hasher=utils/hashing/mock_hasher.go -github.com/ava-labs/avalanchego/utils/logging=Logger=utils/logging/mock_logger.go github.com/ava-labs/avalanchego/utils/resource=User=utils/resource/mock_user.go github.com/ava-labs/avalanchego/vms/avm/block=Block=vms/avm/block/mock_block.go github.com/ava-labs/avalanchego/vms/avm/metrics=Metrics=vms/avm/metrics/mock_metrics.go @@ -31,19 +28,17 @@ github.com/ava-labs/avalanchego/vms/avm/state=Chain,State,Diff=vms/avm/state/moc github.com/ava-labs/avalanchego/vms/avm/txs/mempool=Mempool=vms/avm/txs/mempool/mock_mempool.go github.com/ava-labs/avalanchego/vms/components/avax=TransferableIn=vms/components/avax/mock_transferable_in.go github.com/ava-labs/avalanchego/vms/components/verify=Verifiable=vms/components/verify/mock_verifiable.go -github.com/ava-labs/avalanchego/vms/platformvm/block/executor=Manager=vms/platformvm/block/executor/mock_manager.go github.com/ava-labs/avalanchego/vms/platformvm/block=Block=vms/platformvm/block/mock_block.go github.com/ava-labs/avalanchego/vms/platformvm/state=Chain,Diff,State,Versions=vms/platformvm/state/mock_state.go github.com/ava-labs/avalanchego/vms/platformvm/state=StakerIterator=vms/platformvm/state/mock_staker_iterator.go -github.com/ava-labs/avalanchego/vms/platformvm/txs/builder=Builder=vms/platformvm/txs/builder/mock_builder.go github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool=Mempool=vms/platformvm/txs/mempool/mock_mempool.go github.com/ava-labs/avalanchego/vms/platformvm/utxo=Verifier=vms/platformvm/utxo/mock_verifier.go github.com/ava-labs/avalanchego/vms/proposervm/proposer=Windower=vms/proposervm/proposer/mock_windower.go +github.com/ava-labs/avalanchego/vms/proposervm/scheduler=Scheduler=vms/proposervm/scheduler/mock_scheduler.go github.com/ava-labs/avalanchego/vms/proposervm/state=State=vms/proposervm/state/mock_state.go github.com/ava-labs/avalanchego/vms/proposervm=PostForkBlock=vms/proposervm/mock_post_fork_block.go github.com/ava-labs/avalanchego/vms/registry=VMGetter=vms/registry/mock_vm_getter.go -github.com/ava-labs/avalanchego/vms/registry=VMRegisterer=vms/registry/mock_vm_registerer.go github.com/ava-labs/avalanchego/vms/registry=VMRegistry=vms/registry/mock_vm_registry.go github.com/ava-labs/avalanchego/vms=Factory,Manager=vms/mock_manager.go -github.com/ava-labs/avalanchego/x/merkledb=MerkleDB=x/merkledb/mock_db.go github.com/ava-labs/avalanchego/x/sync=Client=x/sync/mock_client.go +github.com/ava-labs/avalanchego/x/sync=NetworkClient=x/sync/mock_network_client.go diff --git a/scripts/tests.e2e.sh b/scripts/tests.e2e.sh index 638700c9cc47..63718428b8fd 100755 --- a/scripts/tests.e2e.sh +++ b/scripts/tests.e2e.sh @@ -23,7 +23,7 @@ source ./scripts/constants.sh ################################# echo "building e2e.test" # to install the ginkgo binary (required for test build and run) -go install -v github.com/onsi/ginkgo/v2/ginkgo@v2.1.4 +go install -v github.com/onsi/ginkgo/v2/ginkgo@v2.13.1 ACK_GINKGO_RC=true ginkgo build ./tests/e2e ./tests/e2e/e2e.test --help diff --git a/scripts/tests.upgrade.sh b/scripts/tests.upgrade.sh index 8da20b2d65c0..49c402da29a1 100755 --- a/scripts/tests.upgrade.sh +++ b/scripts/tests.upgrade.sh @@ -3,14 +3,24 @@ set -euo pipefail # e.g., -# ./scripts/tests.upgrade.sh 1.7.16 -# AVALANCHEGO_PATH=./path/to/avalanchego ./scripts/tests.upgrade.sh 1.7.16 # Customization of avalanchego path +# ./scripts/tests.upgrade.sh # Use default version +# ./scripts/tests.upgrade.sh 1.10.18 # Specify a version +# AVALANCHEGO_PATH=./path/to/avalanchego ./scripts/tests.upgrade.sh 1.10.18 # Customization of avalanchego path if ! [[ "$0" =~ scripts/tests.upgrade.sh ]]; then echo "must be run from repository root" exit 255 fi -VERSION="${1:-}" +# The AvalancheGo local network does not support long-lived +# backwards-compatible networks. When a breaking change is made to the +# local network, this flag must be updated to the last compatible +# version with the latest code. +# +# v1.10.17 includes the AWM activation on the C-Chain local network +# and the inclusion of BLS Public Keys in the network genesis. +DEFAULT_VERSION="1.10.17" + +VERSION="${1:-${DEFAULT_VERSION}}" if [[ -z "${VERSION}" ]]; then echo "Missing version argument!" echo "Usage: ${0} [VERSION]" >>/dev/stderr @@ -56,7 +66,7 @@ source ./scripts/constants.sh ################################# echo "building upgrade.test" # to install the ginkgo binary (required for test build and run) -go install -v github.com/onsi/ginkgo/v2/ginkgo@v2.1.4 +go install -v github.com/onsi/ginkgo/v2/ginkgo@v2.13.1 ACK_GINKGO_RC=true ginkgo build ./tests/upgrade ./tests/upgrade/upgrade.test --help diff --git a/snow/acceptor.go b/snow/acceptor.go index f1a92e2f0303..83575e5cf3e2 100644 --- a/snow/acceptor.go +++ b/snow/acceptor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snow @@ -14,8 +14,6 @@ import ( ) var ( - _ Acceptor = noOpAcceptor{} - _ Acceptor = (*AcceptorTracker)(nil) _ Acceptor = acceptorWrapper{} _ AcceptorGroup = (*acceptorGroup)(nil) @@ -32,39 +30,6 @@ type Acceptor interface { Accept(ctx *ConsensusContext, containerID ids.ID, container []byte) error } -type noOpAcceptor struct{} - -func (noOpAcceptor) Accept(*ConsensusContext, ids.ID, []byte) error { - return nil -} - -// AcceptorTracker tracks the dispatched accept events by its ID and counts. -// Useful for testing. -type AcceptorTracker struct { - lock sync.RWMutex - accepted map[ids.ID]int -} - -func NewAcceptorTracker() *AcceptorTracker { - return &AcceptorTracker{ - accepted: make(map[ids.ID]int), - } -} - -func (a *AcceptorTracker) Accept(_ *ConsensusContext, containerID ids.ID, _ []byte) error { - a.lock.Lock() - a.accepted[containerID]++ - a.lock.Unlock() - return nil -} - -func (a *AcceptorTracker) IsAccepted(containerID ids.ID) (int, bool) { - a.lock.RLock() - count, ok := a.accepted[containerID] - a.lock.RUnlock() - return count, ok -} - type acceptorWrapper struct { Acceptor diff --git a/snow/choices/decidable.go b/snow/choices/decidable.go index b49cd75d3a1d..4c9ba886b105 100644 --- a/snow/choices/decidable.go +++ b/snow/choices/decidable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package choices diff --git a/snow/choices/status.go b/snow/choices/status.go index 255356b73960..ff530e9b7547 100644 --- a/snow/choices/status.go +++ b/snow/choices/status.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package choices @@ -29,22 +29,19 @@ func (s Status) MarshalJSON() ([]byte, error) { if err := s.Valid(); err != nil { return nil, err } - return []byte("\"" + s.String() + "\""), nil + return []byte(`"` + s.String() + `"`), nil } func (s *Status) UnmarshalJSON(b []byte) error { - str := string(b) - if str == "null" { - return nil - } - switch str { - case "\"Unknown\"": + switch string(b) { + case "null": + case `"Unknown"`: *s = Unknown - case "\"Processing\"": + case `"Processing"`: *s = Processing - case "\"Rejected\"": + case `"Rejected"`: *s = Rejected - case "\"Accepted\"": + case `"Accepted"`: *s = Accepted default: return errUnknownStatus diff --git a/snow/choices/status_test.go b/snow/choices/status_test.go index 59d2c4071fc5..5134ca2b752f 100644 --- a/snow/choices/status_test.go +++ b/snow/choices/status_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package choices diff --git a/snow/choices/test_decidable.go b/snow/choices/test_decidable.go index 055a54050d32..39e8ed67b7c1 100644 --- a/snow/choices/test_decidable.go +++ b/snow/choices/test_decidable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package choices diff --git a/snow/consensus/avalanche/test_vertex.go b/snow/consensus/avalanche/test_vertex.go index 60bfdc10c1b0..a3bc2fb06723 100644 --- a/snow/consensus/avalanche/test_vertex.go +++ b/snow/consensus/avalanche/test_vertex.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avalanche diff --git a/snow/consensus/avalanche/vertex.go b/snow/consensus/avalanche/vertex.go index 9f8af73264fe..0356dc1902da 100644 --- a/snow/consensus/avalanche/vertex.go +++ b/snow/consensus/avalanche/vertex.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avalanche diff --git a/snow/consensus/snowball/binary_slush.go b/snow/consensus/snowball/binary_slush.go index b4e1bc2ace08..a440fce0e1ec 100644 --- a/snow/consensus/snowball/binary_slush.go +++ b/snow/consensus/snowball/binary_slush.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/binary_snowball.go b/snow/consensus/snowball/binary_snowball.go index aa1dc37bbe34..f1b213ad98fc 100644 --- a/snow/consensus/snowball/binary_snowball.go +++ b/snow/consensus/snowball/binary_snowball.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/binary_snowball_test.go b/snow/consensus/snowball/binary_snowball_test.go index 42b6b404caa0..2c2a8421e043 100644 --- a/snow/consensus/snowball/binary_snowball_test.go +++ b/snow/consensus/snowball/binary_snowball_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/binary_snowflake.go b/snow/consensus/snowball/binary_snowflake.go index d95ef9709ec6..139bd40361f7 100644 --- a/snow/consensus/snowball/binary_snowflake.go +++ b/snow/consensus/snowball/binary_snowflake.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/binary_snowflake_test.go b/snow/consensus/snowball/binary_snowflake_test.go index 2f14396da959..085b94c5f450 100644 --- a/snow/consensus/snowball/binary_snowflake_test.go +++ b/snow/consensus/snowball/binary_snowflake_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/consensus.go b/snow/consensus/snowball/consensus.go index 3f3c508af053..e28f4cad4d36 100644 --- a/snow/consensus/snowball/consensus.go +++ b/snow/consensus/snowball/consensus.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/consensus_performance_test.go b/snow/consensus/snowball/consensus_performance_test.go index a84e1e60a9c7..9ebb3362720d 100644 --- a/snow/consensus/snowball/consensus_performance_test.go +++ b/snow/consensus/snowball/consensus_performance_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/ava-labs/avalanchego/utils/sampler" + "gonum.org/v1/gonum/mathext/prng" ) // Test that a network running the lower AlphaPreference converges faster than a @@ -16,37 +16,38 @@ import ( func TestDualAlphaOptimization(t *testing.T) { require := require.New(t) - numColors := 10 - numNodes := 100 - params := Parameters{ - K: 20, - AlphaPreference: 15, - AlphaConfidence: 15, - BetaVirtuous: 15, - BetaRogue: 20, - } - seed := int64(0) - - singleAlphaNetwork := Network{} - singleAlphaNetwork.Initialize(params, numColors) + var ( + numColors = 10 + numNodes = 100 + params = Parameters{ + K: 20, + AlphaPreference: 15, + AlphaConfidence: 15, + BetaVirtuous: 15, + BetaRogue: 20, + } + seed uint64 = 0 + source = prng.NewMT19937() + ) + + singleAlphaNetwork := NewNetwork(params, numColors, source) params.AlphaPreference = params.K/2 + 1 - dualAlphaNetwork := Network{} - dualAlphaNetwork.Initialize(params, numColors) + dualAlphaNetwork := NewNetwork(params, numColors, source) - sampler.Seed(seed) + source.Seed(seed) for i := 0; i < numNodes; i++ { dualAlphaNetwork.AddNode(NewTree) } - sampler.Seed(seed) + source.Seed(seed) for i := 0; i < numNodes; i++ { singleAlphaNetwork.AddNode(NewTree) } // Although this can theoretically fail with a correct implementation, it // shouldn't in practice - runNetworksInLockstep(require, seed, &dualAlphaNetwork, &singleAlphaNetwork) + runNetworksInLockstep(require, seed, source, dualAlphaNetwork, singleAlphaNetwork) } // Test that a network running the snowball tree converges faster than a network @@ -54,38 +55,39 @@ func TestDualAlphaOptimization(t *testing.T) { func TestTreeConvergenceOptimization(t *testing.T) { require := require.New(t) - numColors := 10 - numNodes := 100 - params := DefaultParameters - seed := int64(0) - - treeNetwork := Network{} - treeNetwork.Initialize(params, numColors) + var ( + numColors = 10 + numNodes = 100 + params = DefaultParameters + seed uint64 = 0 + source = prng.NewMT19937() + ) - flatNetwork := treeNetwork + treeNetwork := NewNetwork(params, numColors, source) + flatNetwork := NewNetwork(params, numColors, source) - sampler.Seed(seed) + source.Seed(seed) for i := 0; i < numNodes; i++ { treeNetwork.AddNode(NewTree) } - sampler.Seed(seed) + source.Seed(seed) for i := 0; i < numNodes; i++ { flatNetwork.AddNode(NewFlat) } // Although this can theoretically fail with a correct implementation, it // shouldn't in practice - runNetworksInLockstep(require, seed, &treeNetwork, &flatNetwork) + runNetworksInLockstep(require, seed, source, treeNetwork, flatNetwork) } -func runNetworksInLockstep(require *require.Assertions, seed int64, fast *Network, slow *Network) { +func runNetworksInLockstep(require *require.Assertions, seed uint64, source *prng.MT19937, fast *Network, slow *Network) { numRounds := 0 for !fast.Finalized() && !fast.Disagreement() && !slow.Finalized() && !slow.Disagreement() { - sampler.Seed(int64(numRounds) + seed) + source.Seed(uint64(numRounds) + seed) fast.Round() - sampler.Seed(int64(numRounds) + seed) + source.Seed(uint64(numRounds) + seed) slow.Round() numRounds++ } diff --git a/snow/consensus/snowball/consensus_reversibility_test.go b/snow/consensus/snowball/consensus_reversibility_test.go index fd03a0411581..d0f3065a9db9 100644 --- a/snow/consensus/snowball/consensus_reversibility_test.go +++ b/snow/consensus/snowball/consensus_reversibility_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball @@ -8,23 +8,25 @@ import ( "github.com/stretchr/testify/require" - "github.com/ava-labs/avalanchego/utils/sampler" + "gonum.org/v1/gonum/mathext/prng" ) func TestSnowballGovernance(t *testing.T) { require := require.New(t) - numColors := 2 - numNodes := 100 - numByzantine := 10 - numRed := 55 - params := DefaultParameters - seed := int64(0) + var ( + numColors = 2 + numNodes = 100 + numByzantine = 10 + numRed = 55 + params = DefaultParameters + seed uint64 = 0 + source = prng.NewMT19937() + ) - nBitwise := Network{} - nBitwise.Initialize(params, numColors) + nBitwise := NewNetwork(params, numColors, source) - sampler.Seed(seed) + source.Seed(seed) for i := 0; i < numRed; i++ { nBitwise.AddNodeSpecificColor(NewTree, 0, []int{1}) } diff --git a/snow/consensus/snowball/consensus_test.go b/snow/consensus/snowball/consensus_test.go index 484c5c9fbc87..264edaa733d9 100644 --- a/snow/consensus/snowball/consensus_test.go +++ b/snow/consensus/snowball/consensus_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/flat.go b/snow/consensus/snowball/flat.go index 7f633efb8006..97c549816be0 100644 --- a/snow/consensus/snowball/flat.go +++ b/snow/consensus/snowball/flat.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/flat_test.go b/snow/consensus/snowball/flat_test.go index dac78fc0cede..38ca57d83b0e 100644 --- a/snow/consensus/snowball/flat_test.go +++ b/snow/consensus/snowball/flat_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/network_test.go b/snow/consensus/snowball/network_test.go index 711bbf89010e..56e7d0ca7dbc 100644 --- a/snow/consensus/snowball/network_test.go +++ b/snow/consensus/snowball/network_test.go @@ -1,13 +1,12 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball import ( - "math/rand" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/bag" + "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/sampler" ) @@ -16,20 +15,24 @@ type newConsensusFunc func(params Parameters, choice ids.ID) Consensus type Network struct { params Parameters colors []ids.ID + rngSource sampler.Source nodes, running []Consensus } -// Initialize sets the parameters for the network and adds [numColors] different -// possible colors to the network configuration. -func (n *Network) Initialize(params Parameters, numColors int) { - n.params = params +// Create a new network with [numColors] different possible colors to finalize. +func NewNetwork(params Parameters, numColors int, rngSource sampler.Source) *Network { + n := &Network{ + params: params, + rngSource: rngSource, + } for i := 0; i < numColors; i++ { n.colors = append(n.colors, ids.Empty.Prefix(uint64(i))) } + return n } func (n *Network) AddNode(newConsensusFunc newConsensusFunc) Consensus { - s := sampler.NewUniform() + s := sampler.NewDeterministicUniform(n.rngSource) s.Initialize(uint64(len(n.colors))) indices, _ := s.Sample(len(n.colors)) @@ -78,15 +81,14 @@ func (n *Network) Finalized() bool { // performing an unbiased poll of the nodes in the network for that node. func (n *Network) Round() { if len(n.running) > 0 { - runningInd := rand.Intn(len(n.running)) // #nosec G404 + s := sampler.NewDeterministicUniform(n.rngSource) + + s.Initialize(uint64(len(n.running))) + runningInd, _ := s.Next() running := n.running[runningInd] - s := sampler.NewUniform() s.Initialize(uint64(len(n.nodes))) - count := len(n.nodes) - if count > n.params.K { - count = n.params.K - } + count := math.Min(n.params.K, len(n.nodes)) indices, _ := s.Sample(count) sampledColors := bag.Bag[ids.ID]{} for _, index := range indices { diff --git a/snow/consensus/snowball/nnary_slush.go b/snow/consensus/snowball/nnary_slush.go index 2987861f7943..dad85252906f 100644 --- a/snow/consensus/snowball/nnary_slush.go +++ b/snow/consensus/snowball/nnary_slush.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/nnary_snowball.go b/snow/consensus/snowball/nnary_snowball.go index 0fe8c25f8617..2a968c0ba91c 100644 --- a/snow/consensus/snowball/nnary_snowball.go +++ b/snow/consensus/snowball/nnary_snowball.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/nnary_snowball_test.go b/snow/consensus/snowball/nnary_snowball_test.go index 10b63ce647cb..18bea5eef65e 100644 --- a/snow/consensus/snowball/nnary_snowball_test.go +++ b/snow/consensus/snowball/nnary_snowball_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/nnary_snowflake.go b/snow/consensus/snowball/nnary_snowflake.go index 503fcd614c7b..de898f155f38 100644 --- a/snow/consensus/snowball/nnary_snowflake.go +++ b/snow/consensus/snowball/nnary_snowflake.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/nnary_snowflake_test.go b/snow/consensus/snowball/nnary_snowflake_test.go index 07601a6065eb..5df8c2966335 100644 --- a/snow/consensus/snowball/nnary_snowflake_test.go +++ b/snow/consensus/snowball/nnary_snowflake_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/parameters.go b/snow/consensus/snowball/parameters.go index 29bb0ba9e215..bf458fbf9f40 100644 --- a/snow/consensus/snowball/parameters.go +++ b/snow/consensus/snowball/parameters.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball @@ -17,21 +17,21 @@ const ( // 1 means MinPercentConnected = 1 (fully connected). MinPercentConnectedBuffer = .2 - errMsg = "" + - `__________ .___` + "\n" + - `\______ \____________ __| _/__.__.` + "\n" + - ` | | _/\_ __ \__ \ / __ < | |` + "\n" + - ` | | \ | | \// __ \_/ /_/ |\___ |` + "\n" + - ` |______ / |__| (____ /\____ |/ ____|` + "\n" + - ` \/ \/ \/\/` + "\n" + - "\n" + - ` 🏆 🏆 🏆 🏆 🏆 🏆 🏆` + "\n" + - ` ________ ________ ________________` + "\n" + - ` / _____/ \_____ \ / _ \__ ___/` + "\n" + - `/ \ ___ / | \ / /_\ \| |` + "\n" + - `\ \_\ \/ | \/ | \ |` + "\n" + - ` \______ /\_______ /\____|__ /____|` + "\n" + - ` \/ \/ \/` + "\n" + errMsg = `__________ .___ +\______ \____________ __| _/__.__. + | | _/\_ __ \__ \ / __ < | | + | | \ | | \// __ \_/ /_/ |\___ | + |______ / |__| (____ /\____ |/ ____| + \/ \/ \/\/ + + 🏆 🏆 🏆 🏆 🏆 🏆 🏆 + ________ ________ ________________ + / _____/ \_____ \ / _ \__ ___/ +/ \ ___ / | \ / /_\ \| | +\ \_\ \/ | \/ | \ | + \______ /\_______ /\____|__ /____| + \/ \/ \/ +` ) var ( @@ -39,7 +39,7 @@ var ( K: 20, AlphaPreference: 15, AlphaConfidence: 15, - BetaVirtuous: 15, + BetaVirtuous: 20, BetaRogue: 20, ConcurrentRepolls: 4, OptimalProcessing: 10, diff --git a/snow/consensus/snowball/parameters_test.go b/snow/consensus/snowball/parameters_test.go index 26ffab8763c7..525001fd535f 100644 --- a/snow/consensus/snowball/parameters_test.go +++ b/snow/consensus/snowball/parameters_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/tree.go b/snow/consensus/snowball/tree.go index 052e75ec8d35..2278975f8843 100644 --- a/snow/consensus/snowball/tree.go +++ b/snow/consensus/snowball/tree.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/tree_test.go b/snow/consensus/snowball/tree_test.go index 8b0f6159df72..99bf25769f98 100644 --- a/snow/consensus/snowball/tree_test.go +++ b/snow/consensus/snowball/tree_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //nolint:goconst @@ -10,9 +10,10 @@ import ( "github.com/stretchr/testify/require" + "gonum.org/v1/gonum/mathext/prng" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/bag" - "github.com/ava-labs/avalanchego/utils/sampler" ) const initialUnaryDescription = "SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [0, 256)" @@ -147,8 +148,8 @@ func TestSnowballLastBinary(t *testing.T) { // Should do nothing tree.Add(one) - expected := "SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [0, 255)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 255" + expected := `SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [0, 255) + SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 255` require.Equal(expected, tree.String()) require.Equal(zero, tree.Preference()) require.False(tree.Finalized()) @@ -158,8 +159,8 @@ func TestSnowballLastBinary(t *testing.T) { require.Equal(one, tree.Preference()) require.False(tree.Finalized()) - expected = "SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [0, 255)\n" + - " SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 255" + expected = `SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [0, 255) + SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 255` require.Equal(expected, tree.String()) require.True(tree.RecordPoll(oneBag)) @@ -190,12 +191,12 @@ func TestSnowballAddPreviouslyRejected(t *testing.T) { tree.Add(four) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 2)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 2\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 2) + SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 2 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(zero, tree.Preference()) require.False(tree.Finalized()) @@ -205,11 +206,11 @@ func TestSnowballAddPreviouslyRejected(t *testing.T) { require.True(tree.RecordPoll(zeroBag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(zero, tree.Preference()) require.False(tree.Finalized()) @@ -218,11 +219,11 @@ func TestSnowballAddPreviouslyRejected(t *testing.T) { tree.Add(two) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(zero, tree.Preference()) require.False(tree.Finalized()) @@ -246,9 +247,9 @@ func TestSnowballNewUnary(t *testing.T) { tree.Add(one) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(zero, tree.Preference()) require.False(tree.Finalized()) @@ -258,9 +259,9 @@ func TestSnowballNewUnary(t *testing.T) { require.True(tree.RecordPoll(oneBag)) { - expected := "SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 0\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 0 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256) + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(one, tree.Preference()) require.False(tree.Finalized()) @@ -269,9 +270,9 @@ func TestSnowballNewUnary(t *testing.T) { require.True(tree.RecordPoll(oneBag)) { - expected := "SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 2, SF(Confidence = 2, Finalized = false, SL(Preference = 1))) Bit = 0\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)\n" + - " SB(PreferenceStrength = 2, SF(Confidence = 2, Finalized = true)) Bits = [1, 256)" + expected := `SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 2, SF(Confidence = 2, Finalized = false, SL(Preference = 1))) Bit = 0 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256) + SB(PreferenceStrength = 2, SF(Confidence = 2, Finalized = true)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(one, tree.Preference()) require.False(tree.Finalized()) @@ -297,13 +298,13 @@ func TestSnowballTransitiveReset(t *testing.T) { tree.Add(eight) { - expected := "SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [0, 1)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 1\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 3)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 3\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)" + expected := `SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [0, 1) + SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 1 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 3) + SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 3 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)` require.Equal(expected, tree.String()) require.Equal(zero, tree.Preference()) require.False(tree.Finalized()) @@ -313,13 +314,13 @@ func TestSnowballTransitiveReset(t *testing.T) { require.True(tree.RecordPoll(zeroBag)) { - expected := "SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [0, 1)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [2, 3)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 3\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [4, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)" + expected := `SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [0, 1) + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [2, 3) + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 3 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [4, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)` require.Equal(expected, tree.String()) require.Equal(zero, tree.Preference()) require.False(tree.Finalized()) @@ -329,13 +330,13 @@ func TestSnowballTransitiveReset(t *testing.T) { require.False(tree.RecordPoll(emptyBag)) { - expected := "SB(PreferenceStrength = 1, SF(Confidence = 0, Finalized = false)) Bits = [0, 1)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [2, 3)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 3\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [4, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)" + expected := `SB(PreferenceStrength = 1, SF(Confidence = 0, Finalized = false)) Bits = [0, 1) + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [2, 3) + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 3 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = false)) Bits = [4, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)` require.Equal(expected, tree.String()) require.Equal(zero, tree.Preference()) require.False(tree.Finalized()) @@ -344,13 +345,13 @@ func TestSnowballTransitiveReset(t *testing.T) { require.True(tree.RecordPoll(zeroBag)) { - expected := "SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = false)) Bits = [0, 1)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1\n" + - " SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = false)) Bits = [2, 3)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 3\n" + - " SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = false)) Bits = [4, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)" + expected := `SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = false)) Bits = [0, 1) + SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1 + SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = false)) Bits = [2, 3) + SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 3 + SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = false)) Bits = [4, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [4, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)` require.Equal(expected, tree.String()) require.Equal(zero, tree.Preference()) require.False(tree.Finalized()) @@ -490,11 +491,11 @@ func TestSnowballAddRejected(t *testing.T) { require.True(tree.RecordPoll(c0010Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 2\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 2 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0010, tree.Preference()) require.False(tree.Finalized()) @@ -503,11 +504,11 @@ func TestSnowballAddRejected(t *testing.T) { tree.Add(c0101) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 2\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 2 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0010, tree.Preference()) require.False(tree.Finalized()) @@ -539,11 +540,11 @@ func TestSnowballResetChild(t *testing.T) { require.True(tree.RecordPoll(c0000Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -553,11 +554,11 @@ func TestSnowballResetChild(t *testing.T) { require.False(tree.RecordPoll(emptyBag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -566,11 +567,11 @@ func TestSnowballResetChild(t *testing.T) { require.True(tree.RecordPoll(c0000Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1\n" + - " SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = true)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 1 + SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = true)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -602,11 +603,11 @@ func TestSnowballResetSibling(t *testing.T) { require.True(tree.RecordPoll(c0100Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 1\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 1 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256) + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0100, tree.Preference()) require.False(tree.Finalized()) @@ -616,11 +617,11 @@ func TestSnowballResetSibling(t *testing.T) { require.True(tree.RecordPoll(c1000Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 0\n" + - " SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 1\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 0 + SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 1 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256) + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [2, 256) + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0100, tree.Preference()) require.False(tree.Finalized()) @@ -629,11 +630,11 @@ func TestSnowballResetSibling(t *testing.T) { require.True(tree.RecordPoll(c0100Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 2, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 1\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = true)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 2, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 2, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 1 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256) + SB(PreferenceStrength = 2, SF(Confidence = 1, Finalized = true)) Bits = [2, 256) + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0100, tree.Preference()) require.False(tree.Finalized()) @@ -700,9 +701,9 @@ func TestSnowballFineGrained(t *testing.T) { tree.Add(c1100) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -711,11 +712,11 @@ func TestSnowballFineGrained(t *testing.T) { tree.Add(c1000) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)\n" + - " SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 1))) Bit = 1\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256) + SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 1))) Bit = 1 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -724,14 +725,14 @@ func TestSnowballFineGrained(t *testing.T) { tree.Add(c0010) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 2)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 2\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 1))) Bit = 1\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 2) + SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 2 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 1))) Bit = 1 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -741,13 +742,13 @@ func TestSnowballFineGrained(t *testing.T) { require.True(tree.RecordPoll(c0000Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 1))) Bit = 1\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(Preference = 1, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 1))) Bit = 1 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [2, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -757,9 +758,9 @@ func TestSnowballFineGrained(t *testing.T) { require.True(tree.RecordPoll(c0010Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 2\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 1, SF(Confidence = 1, Finalized = false, SL(Preference = 1))) Bit = 2 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256) + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -796,22 +797,23 @@ func TestSnowballDoubleAdd(t *testing.T) { func TestSnowballConsistent(t *testing.T) { require := require.New(t) - numColors := 50 - numNodes := 100 - params := Parameters{ - K: 20, - AlphaPreference: 15, - AlphaConfidence: 15, - BetaVirtuous: 20, - BetaRogue: 30, - } - seed := int64(0) - - sampler.Seed(seed) - - n := Network{} - n.Initialize(params, numColors) - + var ( + numColors = 50 + numNodes = 100 + params = Parameters{ + K: 20, + AlphaPreference: 15, + AlphaConfidence: 15, + BetaVirtuous: 20, + BetaRogue: 30, + } + seed uint64 = 0 + source = prng.NewMT19937() + ) + + n := NewNetwork(params, numColors, source) + + source.Seed(seed) for i := 0; i < numNodes; i++ { n.AddNode(NewTree) } @@ -847,9 +849,9 @@ func TestSnowballFilterBinaryChildren(t *testing.T) { tree.Add(c1000) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -858,12 +860,12 @@ func TestSnowballFilterBinaryChildren(t *testing.T) { tree.Add(c0010) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 2)\n" + - " SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 2\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 2) + SB(Preference = 0, PreferenceStrength[0] = 0, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 2 + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -873,11 +875,11 @@ func TestSnowballFilterBinaryChildren(t *testing.T) { require.True(tree.RecordPoll(c0000Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -886,11 +888,11 @@ func TestSnowballFilterBinaryChildren(t *testing.T) { tree.Add(c0100) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0\n" + - " SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 0 + SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 1, Finalized = false, SL(Preference = 0))) Bit = 2 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [1, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) @@ -900,9 +902,9 @@ func TestSnowballFilterBinaryChildren(t *testing.T) { require.True(tree.RecordPoll(c0100Bag)) { - expected := "SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 2\n" + - " SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256)\n" + - " SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)" + expected := `SB(Preference = 0, PreferenceStrength[0] = 1, PreferenceStrength[1] = 0, SF(Confidence = 0, Finalized = false, SL(Preference = 0))) Bit = 2 + SB(PreferenceStrength = 1, SF(Confidence = 1, Finalized = true)) Bits = [3, 256) + SB(PreferenceStrength = 0, SF(Confidence = 0, Finalized = false)) Bits = [3, 256)` require.Equal(expected, tree.String()) require.Equal(c0000, tree.Preference()) require.False(tree.Finalized()) diff --git a/snow/consensus/snowball/unary_snowball.go b/snow/consensus/snowball/unary_snowball.go index 6223d6f3a0cc..3e4477b4b82a 100644 --- a/snow/consensus/snowball/unary_snowball.go +++ b/snow/consensus/snowball/unary_snowball.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/unary_snowball_test.go b/snow/consensus/snowball/unary_snowball_test.go index 6fd6cd9b40c7..d94d2b61d63d 100644 --- a/snow/consensus/snowball/unary_snowball_test.go +++ b/snow/consensus/snowball/unary_snowball_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/unary_snowflake.go b/snow/consensus/snowball/unary_snowflake.go index 68def8663724..6bcfebe23fe8 100644 --- a/snow/consensus/snowball/unary_snowflake.go +++ b/snow/consensus/snowball/unary_snowflake.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowball/unary_snowflake_test.go b/snow/consensus/snowball/unary_snowflake_test.go index 162f4a56e200..0791b688065e 100644 --- a/snow/consensus/snowball/unary_snowflake_test.go +++ b/snow/consensus/snowball/unary_snowflake_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowball diff --git a/snow/consensus/snowman/block.go b/snow/consensus/snowman/block.go index b5d79983ef6a..c950ac3c29ee 100644 --- a/snow/consensus/snowman/block.go +++ b/snow/consensus/snowman/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/consensus/snowman/bootstrapper/majority.go b/snow/consensus/snowman/bootstrapper/majority.go index 1decb837ef40..7fe028288656 100644 --- a/snow/consensus/snowman/bootstrapper/majority.go +++ b/snow/consensus/snowman/bootstrapper/majority.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/majority_test.go b/snow/consensus/snowman/bootstrapper/majority_test.go index d276566fb910..819840f28311 100644 --- a/snow/consensus/snowman/bootstrapper/majority_test.go +++ b/snow/consensus/snowman/bootstrapper/majority_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/minority.go b/snow/consensus/snowman/bootstrapper/minority.go index 52b45c4407ba..4674921aaf6b 100644 --- a/snow/consensus/snowman/bootstrapper/minority.go +++ b/snow/consensus/snowman/bootstrapper/minority.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/minority_test.go b/snow/consensus/snowman/bootstrapper/minority_test.go index f720ee18025a..c44b314f3443 100644 --- a/snow/consensus/snowman/bootstrapper/minority_test.go +++ b/snow/consensus/snowman/bootstrapper/minority_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/noop.go b/snow/consensus/snowman/bootstrapper/noop.go index 1cd3bffd58b7..6d97eed069a8 100644 --- a/snow/consensus/snowman/bootstrapper/noop.go +++ b/snow/consensus/snowman/bootstrapper/noop.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/noop_test.go b/snow/consensus/snowman/bootstrapper/noop_test.go index 0a485a8fae76..e0bccb8aad7f 100644 --- a/snow/consensus/snowman/bootstrapper/noop_test.go +++ b/snow/consensus/snowman/bootstrapper/noop_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/poll.go b/snow/consensus/snowman/bootstrapper/poll.go index 450341d9d64d..0d3eb7143167 100644 --- a/snow/consensus/snowman/bootstrapper/poll.go +++ b/snow/consensus/snowman/bootstrapper/poll.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/poll_test.go b/snow/consensus/snowman/bootstrapper/poll_test.go index 134867ae1822..bbdcc0db51a4 100644 --- a/snow/consensus/snowman/bootstrapper/poll_test.go +++ b/snow/consensus/snowman/bootstrapper/poll_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/requests.go b/snow/consensus/snowman/bootstrapper/requests.go index 28fc25ce1643..ebeaf57ac70f 100644 --- a/snow/consensus/snowman/bootstrapper/requests.go +++ b/snow/consensus/snowman/bootstrapper/requests.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/sampler.go b/snow/consensus/snowman/bootstrapper/sampler.go index 9511a1e4243f..e23253864669 100644 --- a/snow/consensus/snowman/bootstrapper/sampler.go +++ b/snow/consensus/snowman/bootstrapper/sampler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/bootstrapper/sampler_test.go b/snow/consensus/snowman/bootstrapper/sampler_test.go index 1b9e366decc7..b438a5fb2629 100644 --- a/snow/consensus/snowman/bootstrapper/sampler_test.go +++ b/snow/consensus/snowman/bootstrapper/sampler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrapper diff --git a/snow/consensus/snowman/consensus.go b/snow/consensus/snowman/consensus.go index 25b2c7242ec1..3f1006416366 100644 --- a/snow/consensus/snowman/consensus.go +++ b/snow/consensus/snowman/consensus.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/consensus/snowman/consensus_test.go b/snow/consensus/snowman/consensus_test.go index 401435738b18..15e56709dd28 100644 --- a/snow/consensus/snowman/consensus_test.go +++ b/snow/consensus/snowman/consensus_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman @@ -17,12 +17,13 @@ import ( "github.com/stretchr/testify/require" + "gonum.org/v1/gonum/mathext/prng" + "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowball" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/bag" - "github.com/ava-labs/avalanchego/utils/sampler" ) type testFunc func(*testing.T, Factory) @@ -93,7 +94,8 @@ func InitializeTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -118,7 +120,8 @@ func NumProcessingTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -160,7 +163,8 @@ func AddToTailTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -199,7 +203,8 @@ func AddToNonTailTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -247,7 +252,8 @@ func AddToUnknownTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -287,7 +293,8 @@ func StatusOrProcessingPreviouslyAcceptedTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -316,7 +323,8 @@ func StatusOrProcessingPreviouslyRejectedTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -353,7 +361,8 @@ func StatusOrProcessingUnissuedTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -390,7 +399,8 @@ func StatusOrProcessingIssuedTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -429,7 +439,8 @@ func RecordPollAcceptSingleBlockTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -471,7 +482,8 @@ func RecordPollAcceptAndRejectTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -524,7 +536,8 @@ func RecordPollSplitVoteNoChangeTest(t *testing.T, factory Factory) { require := require.New(t) sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) registerer := prometheus.NewRegistry() ctx.Registerer = registerer @@ -587,7 +600,8 @@ func RecordPollWhenFinalizedTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -612,7 +626,8 @@ func RecordPollRejectTransitivelyTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -682,7 +697,8 @@ func RecordPollTransitivelyResetConfidenceTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -774,7 +790,8 @@ func RecordPollInvalidVoteTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -815,7 +832,8 @@ func RecordPollTransitiveVotingTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 3, AlphaPreference: 3, @@ -925,7 +943,8 @@ func RecordPollDivergedVotingTest(t *testing.T, factory Factory) { sm := factory.New() require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1028,7 +1047,8 @@ func RecordPollDivergedVotingWithNoConflictingBitTest(t *testing.T, factory Fact sm := factory.New() require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1132,7 +1152,8 @@ func RecordPollChangePreferredChainTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1239,7 +1260,8 @@ func LastAcceptedTest(t *testing.T, factory Factory) { sm := factory.New() require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1323,7 +1345,8 @@ func MetricsProcessingErrorTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1352,7 +1375,8 @@ func MetricsAcceptedErrorTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1381,7 +1405,8 @@ func MetricsRejectedErrorTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1410,7 +1435,8 @@ func ErrorOnInitialRejectionTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1449,7 +1475,8 @@ func ErrorOnAcceptTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1486,7 +1513,8 @@ func ErrorOnRejectSiblingTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1532,7 +1560,8 @@ func ErrorOnTransitiveRejectionTest(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1585,28 +1614,30 @@ func ErrorOnTransitiveRejectionTest(t *testing.T, factory Factory) { func RandomizedConsistencyTest(t *testing.T, factory Factory) { require := require.New(t) - numColors := 50 - numNodes := 100 - params := snowball.Parameters{ - K: 20, - AlphaPreference: 15, - AlphaConfidence: 15, - BetaVirtuous: 20, - BetaRogue: 30, - ConcurrentRepolls: 1, - OptimalProcessing: 1, - MaxOutstandingItems: 1, - MaxItemProcessingTime: 1, - } - seed := int64(0) + var ( + numColors = 50 + numNodes = 100 + params = snowball.Parameters{ + K: 20, + AlphaPreference: 15, + AlphaConfidence: 15, + BetaVirtuous: 20, + BetaRogue: 30, + ConcurrentRepolls: 1, + OptimalProcessing: 1, + MaxOutstandingItems: 1, + MaxItemProcessingTime: 1, + } + seed uint64 = 0 + source = prng.NewMT19937() + ) - sampler.Seed(seed) + source.Seed(seed) - n := Network{} - n.Initialize(params, numColors) + n := NewNetwork(params, numColors, source) for i := 0; i < numNodes; i++ { - require.NoError(n.AddNode(factory.New())) + require.NoError(n.AddNode(t, factory.New())) } for !n.Finalized() { @@ -1620,7 +1651,8 @@ func ErrorOnAddDecidedBlockTest(t *testing.T, factory Factory) { sm := factory.New() require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1650,7 +1682,8 @@ func ErrorOnAddDuplicateBlockIDTest(t *testing.T, factory Factory) { sm := factory.New() require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.Parameters{ K: 1, AlphaPreference: 1, @@ -1714,7 +1747,8 @@ func RecordPollWithDefaultParameters(t *testing.T, factory Factory) { sm := factory.New() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) params := snowball.DefaultParameters require.NoError(sm.Initialize(ctx, params, GenesisID, GenesisHeight, GenesisTimestamp)) diff --git a/snow/consensus/snowman/factory.go b/snow/consensus/snowman/factory.go index 06341981aef4..c2fc76e83ef9 100644 --- a/snow/consensus/snowman/factory.go +++ b/snow/consensus/snowman/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/consensus/snowman/metrics.go b/snow/consensus/snowman/metrics.go index 6e5159d6c1a5..a052db5144d4 100644 --- a/snow/consensus/snowman/metrics.go +++ b/snow/consensus/snowman/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/consensus/snowman/mock_block.go b/snow/consensus/snowman/mock_block.go index f5b7422190e5..45393bfe7bdb 100644 --- a/snow/consensus/snowman/mock_block.go +++ b/snow/consensus/snowman/mock_block.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/consensus/snowman (interfaces: Block) +// +// Generated by this command: +// +// mockgen -package=snowman -destination=snow/consensus/snowman/mock_block.go github.com/ava-labs/avalanchego/snow/consensus/snowman Block +// // Package snowman is a generated GoMock package. package snowman @@ -49,7 +51,7 @@ func (m *MockBlock) Accept(arg0 context.Context) error { } // Accept indicates an expected call of Accept. -func (mr *MockBlockMockRecorder) Accept(arg0 interface{}) *gomock.Call { +func (mr *MockBlockMockRecorder) Accept(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockBlock)(nil).Accept), arg0) } @@ -119,7 +121,7 @@ func (m *MockBlock) Reject(arg0 context.Context) error { } // Reject indicates an expected call of Reject. -func (mr *MockBlockMockRecorder) Reject(arg0 interface{}) *gomock.Call { +func (mr *MockBlockMockRecorder) Reject(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reject", reflect.TypeOf((*MockBlock)(nil).Reject), arg0) } @@ -161,7 +163,7 @@ func (m *MockBlock) Verify(arg0 context.Context) error { } // Verify indicates an expected call of Verify. -func (mr *MockBlockMockRecorder) Verify(arg0 interface{}) *gomock.Call { +func (mr *MockBlockMockRecorder) Verify(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockBlock)(nil).Verify), arg0) } diff --git a/snow/consensus/snowman/network_test.go b/snow/consensus/snowman/network_test.go index ae855ab84ac6..aead346fb5e4 100644 --- a/snow/consensus/snowman/network_test.go +++ b/snow/consensus/snowman/network_test.go @@ -1,16 +1,16 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman import ( "context" - "math/rand" + "testing" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowball" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/bag" "github.com/ava-labs/avalanchego/utils/sampler" @@ -19,49 +19,57 @@ import ( type Network struct { params snowball.Parameters colors []*TestBlock + rngSource sampler.Source nodes, running []Consensus } -func (n *Network) shuffleColors() { - s := sampler.NewUniform() - s.Initialize(uint64(len(n.colors))) - indices, _ := s.Sample(len(n.colors)) - colors := []*TestBlock(nil) - for _, index := range indices { - colors = append(colors, n.colors[int(index)]) +func NewNetwork(params snowball.Parameters, numColors int, rngSource sampler.Source) *Network { + n := &Network{ + params: params, + colors: []*TestBlock{{ + TestDecidable: choices.TestDecidable{ + IDV: ids.Empty.Prefix(rngSource.Uint64()), + StatusV: choices.Processing, + }, + ParentV: Genesis.IDV, + HeightV: 1, + }}, + rngSource: rngSource, } - n.colors = colors - utils.Sort(n.colors) -} - -func (n *Network) Initialize(params snowball.Parameters, numColors int) { - n.params = params - // #nosec G404 - n.colors = append(n.colors, &TestBlock{ - TestDecidable: choices.TestDecidable{ - IDV: ids.Empty.Prefix(uint64(rand.Int63())), - StatusV: choices.Processing, - }, - ParentV: Genesis.IDV, - HeightV: 1, - }) + s := sampler.NewDeterministicUniform(n.rngSource) for i := 1; i < numColors; i++ { - dependency := n.colors[rand.Intn(len(n.colors))] // #nosec G404 - // #nosec G404 + s.Initialize(uint64(len(n.colors))) + dependencyInd, _ := s.Next() + dependency := n.colors[dependencyInd] n.colors = append(n.colors, &TestBlock{ TestDecidable: choices.TestDecidable{ - IDV: ids.Empty.Prefix(uint64(rand.Int63())), + IDV: ids.Empty.Prefix(rngSource.Uint64()), StatusV: choices.Processing, }, ParentV: dependency.IDV, HeightV: dependency.HeightV + 1, }) } + return n } -func (n *Network) AddNode(sm Consensus) error { - if err := sm.Initialize(snow.DefaultConsensusContextTest(), n.params, Genesis.ID(), Genesis.Height(), Genesis.Timestamp()); err != nil { +func (n *Network) shuffleColors() { + s := sampler.NewDeterministicUniform(n.rngSource) + s.Initialize(uint64(len(n.colors))) + indices, _ := s.Sample(len(n.colors)) + colors := []*TestBlock(nil) + for _, index := range indices { + colors = append(colors, n.colors[int(index)]) + } + n.colors = colors + utils.Sort(n.colors) +} + +func (n *Network) AddNode(t testing.TB, sm Consensus) error { + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) + if err := sm.Initialize(ctx, n.params, Genesis.ID(), Genesis.Height(), Genesis.Timestamp()); err != nil { return err } @@ -101,10 +109,12 @@ func (n *Network) Round() error { return nil } - runningInd := rand.Intn(len(n.running)) // #nosec G404 + s := sampler.NewDeterministicUniform(n.rngSource) + s.Initialize(uint64(len(n.running))) + + runningInd, _ := s.Next() running := n.running[runningInd] - s := sampler.NewUniform() s.Initialize(uint64(len(n.nodes))) indices, _ := s.Sample(n.params.K) sampledColors := bag.Bag[ids.ID]{} diff --git a/snow/consensus/snowman/oracle_block.go b/snow/consensus/snowman/oracle_block.go index 0ead79d03d22..4688927e566e 100644 --- a/snow/consensus/snowman/oracle_block.go +++ b/snow/consensus/snowman/oracle_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/consensus/snowman/poll/early_term_no_traversal.go b/snow/consensus/snowman/poll/early_term_no_traversal.go index fcad5b71932b..460805ab7820 100644 --- a/snow/consensus/snowman/poll/early_term_no_traversal.go +++ b/snow/consensus/snowman/poll/early_term_no_traversal.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package poll diff --git a/snow/consensus/snowman/poll/early_term_no_traversal_test.go b/snow/consensus/snowman/poll/early_term_no_traversal_test.go index 8255818abdbb..9d215c246eec 100644 --- a/snow/consensus/snowman/poll/early_term_no_traversal_test.go +++ b/snow/consensus/snowman/poll/early_term_no_traversal_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package poll diff --git a/snow/consensus/snowman/poll/interfaces.go b/snow/consensus/snowman/poll/interfaces.go index cab31cfc54ce..c1a776b4dc5f 100644 --- a/snow/consensus/snowman/poll/interfaces.go +++ b/snow/consensus/snowman/poll/interfaces.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package poll diff --git a/snow/consensus/snowman/poll/set.go b/snow/consensus/snowman/poll/set.go index e58059f20c3d..4c085b7aa4bc 100644 --- a/snow/consensus/snowman/poll/set.go +++ b/snow/consensus/snowman/poll/set.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package poll diff --git a/snow/consensus/snowman/poll/set_test.go b/snow/consensus/snowman/poll/set_test.go index 84ed8f7a5c8c..cdcf0e7d8903 100644 --- a/snow/consensus/snowman/poll/set_test.go +++ b/snow/consensus/snowman/poll/set_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package poll diff --git a/snow/consensus/snowman/snowman_block.go b/snow/consensus/snowman/snowman_block.go index 782c77d8e415..a25099b4519f 100644 --- a/snow/consensus/snowman/snowman_block.go +++ b/snow/consensus/snowman/snowman_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/consensus/snowman/test_block.go b/snow/consensus/snowman/test_block.go index a02bf31787c1..b59eb2ed5f89 100644 --- a/snow/consensus/snowman/test_block.go +++ b/snow/consensus/snowman/test_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman @@ -48,6 +48,6 @@ func (b *TestBlock) Bytes() []byte { return b.BytesV } -func (b *TestBlock) Less(other *TestBlock) bool { - return b.HeightV < other.HeightV +func (b *TestBlock) Compare(other *TestBlock) int { + return utils.Compare(b.HeightV, other.HeightV) } diff --git a/snow/consensus/snowman/topological.go b/snow/consensus/snowman/topological.go index f3fc838a8f8b..5b12ce3f2e6b 100644 --- a/snow/consensus/snowman/topological.go +++ b/snow/consensus/snowman/topological.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/consensus/snowman/topological_test.go b/snow/consensus/snowman/topological_test.go index 53a1b4416d9a..540b5a8f2eb1 100644 --- a/snow/consensus/snowman/topological_test.go +++ b/snow/consensus/snowman/topological_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/consensus/snowman/traced_consensus.go b/snow/consensus/snowman/traced_consensus.go index 67a8797b294a..363fa15334b0 100644 --- a/snow/consensus/snowman/traced_consensus.go +++ b/snow/consensus/snowman/traced_consensus.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/consensus/snowstorm/test_tx.go b/snow/consensus/snowstorm/test_tx.go index 8c5aa9aa3544..a8b514c8164d 100644 --- a/snow/consensus/snowstorm/test_tx.go +++ b/snow/consensus/snowstorm/test_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowstorm diff --git a/snow/consensus/snowstorm/tx.go b/snow/consensus/snowstorm/tx.go index 54a56a42f5b4..cc1cf649e9a8 100644 --- a/snow/consensus/snowstorm/tx.go +++ b/snow/consensus/snowstorm/tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowstorm diff --git a/snow/context.go b/snow/context.go index c89c2dd07a11..2cbbedb38b47 100644 --- a/snow/context.go +++ b/snow/context.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snow @@ -96,33 +96,3 @@ type ConsensusContext struct { // True iff this chain is currently state-syncing StateSyncing utils.Atomic[bool] } - -func DefaultContextTest() *Context { - sk, err := bls.NewSecretKey() - if err != nil { - panic(err) - } - pk := bls.PublicFromSecretKey(sk) - return &Context{ - NetworkID: 0, - SubnetID: ids.Empty, - ChainID: ids.Empty, - NodeID: ids.EmptyNodeID, - PublicKey: pk, - Log: logging.NoLog{}, - BCLookup: ids.NewAliaser(), - Metrics: metrics.NewOptionalGatherer(), - ChainDataDir: "", - } -} - -func DefaultConsensusContextTest() *ConsensusContext { - return &ConsensusContext{ - Context: DefaultContextTest(), - Registerer: prometheus.NewRegistry(), - AvalancheRegisterer: prometheus.NewRegistry(), - BlockAcceptor: noOpAcceptor{}, - TxAcceptor: noOpAcceptor{}, - VertexAcceptor: noOpAcceptor{}, - } -} diff --git a/snow/engine/avalanche/bootstrap/bootstrapper.go b/snow/engine/avalanche/bootstrap/bootstrapper.go index 162937dc7860..cd530d1cb1f8 100644 --- a/snow/engine/avalanche/bootstrap/bootstrapper.go +++ b/snow/engine/avalanche/bootstrap/bootstrapper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap diff --git a/snow/engine/avalanche/bootstrap/bootstrapper_test.go b/snow/engine/avalanche/bootstrap/bootstrapper_test.go index 4e61e9b137ee..7da0b99a8736 100644 --- a/snow/engine/avalanche/bootstrap/bootstrapper_test.go +++ b/snow/engine/avalanche/bootstrap/bootstrapper_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap @@ -27,6 +27,7 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/common/queue" "github.com/ava-labs/avalanchego/snow/engine/common/tracker" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/set" @@ -55,7 +56,8 @@ func (t *testTx) Accept(ctx context.Context) error { func newConfig(t *testing.T) (Config, ids.NodeID, *common.SenderTest, *vertex.TestManager, *vertex.TestVM) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() db := memdb.New() diff --git a/snow/engine/avalanche/bootstrap/config.go b/snow/engine/avalanche/bootstrap/config.go index 54fe7f2e45fa..a674c2758460 100644 --- a/snow/engine/avalanche/bootstrap/config.go +++ b/snow/engine/avalanche/bootstrap/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap diff --git a/snow/engine/avalanche/bootstrap/metrics.go b/snow/engine/avalanche/bootstrap/metrics.go index b9d5824ec95a..cc357f25901f 100644 --- a/snow/engine/avalanche/bootstrap/metrics.go +++ b/snow/engine/avalanche/bootstrap/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap diff --git a/snow/engine/avalanche/bootstrap/tx_job.go b/snow/engine/avalanche/bootstrap/tx_job.go index 615108c992ba..9bb939d3d186 100644 --- a/snow/engine/avalanche/bootstrap/tx_job.go +++ b/snow/engine/avalanche/bootstrap/tx_job.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap diff --git a/snow/engine/avalanche/bootstrap/vertex_job.go b/snow/engine/avalanche/bootstrap/vertex_job.go index 3001ce89904c..30d33c5dc9fb 100644 --- a/snow/engine/avalanche/bootstrap/vertex_job.go +++ b/snow/engine/avalanche/bootstrap/vertex_job.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap diff --git a/snow/engine/avalanche/engine.go b/snow/engine/avalanche/engine.go index 375666c99598..530a319e0fc6 100644 --- a/snow/engine/avalanche/engine.go +++ b/snow/engine/avalanche/engine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avalanche diff --git a/snow/engine/avalanche/getter/getter.go b/snow/engine/avalanche/getter/getter.go index a93d2f1d069e..8903eec1b3ba 100644 --- a/snow/engine/avalanche/getter/getter.go +++ b/snow/engine/avalanche/getter/getter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package getter diff --git a/snow/engine/avalanche/getter/getter_test.go b/snow/engine/avalanche/getter/getter_test.go index 4d25e29f296b..4977d53fa7ee 100644 --- a/snow/engine/avalanche/getter/getter_test.go +++ b/snow/engine/avalanche/getter/getter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package getter diff --git a/snow/engine/avalanche/state/prefixed_state.go b/snow/engine/avalanche/state/prefixed_state.go index 5fac890b9ed1..1ff634d5c61e 100644 --- a/snow/engine/avalanche/state/prefixed_state.go +++ b/snow/engine/avalanche/state/prefixed_state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/snow/engine/avalanche/state/serializer.go b/snow/engine/avalanche/state/serializer.go index 88dc7afefae4..b305100b9ce2 100644 --- a/snow/engine/avalanche/state/serializer.go +++ b/snow/engine/avalanche/state/serializer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Package state manages the meta-data required by consensus for an avalanche diff --git a/snow/engine/avalanche/state/state.go b/snow/engine/avalanche/state/state.go index f7f94c5e6923..021a4c7e1d68 100644 --- a/snow/engine/avalanche/state/state.go +++ b/snow/engine/avalanche/state/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/snow/engine/avalanche/state/unique_vertex.go b/snow/engine/avalanche/state/unique_vertex.go index 73c1ef94ccdc..bc245d1b5496 100644 --- a/snow/engine/avalanche/state/unique_vertex.go +++ b/snow/engine/avalanche/state/unique_vertex.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/snow/engine/avalanche/state/unique_vertex_test.go b/snow/engine/avalanche/state/unique_vertex_test.go index 4d6dcc55e385..6f644680d290 100644 --- a/snow/engine/avalanche/state/unique_vertex_test.go +++ b/snow/engine/avalanche/state/unique_vertex_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -13,11 +13,11 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowstorm" "github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex" "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/utils/logging" ) var errUnknownTx = errors.New("unknown tx") @@ -29,13 +29,12 @@ func newTestSerializer(t *testing.T, parse func(context.Context, []byte) (snowst vm.ParseTxF = parse baseDB := memdb.New() - ctx := snow.DefaultContextTest() s := NewSerializer( SerializerConfig{ - ChainID: ctx.ChainID, + ChainID: ids.Empty, VM: &vm, DB: baseDB, - Log: ctx.Log, + Log: logging.NoLog{}, }, ) @@ -260,9 +259,9 @@ func TestParseVertexWithIncorrectChainID(t *testing.T) { func TestParseVertexWithInvalidTxs(t *testing.T) { require := require.New(t) - ctx := snow.DefaultContextTest() + chainID := ids.Empty statelessVertex, err := vertex.Build( // regular, non-stop vertex - ctx.ChainID, + chainID, 0, nil, [][]byte{{1}}, @@ -290,7 +289,7 @@ func TestParseVertexWithInvalidTxs(t *testing.T) { require.ErrorIs(err, errUnknownVertex) childStatelessVertex, err := vertex.Build( // regular, non-stop vertex - ctx.ChainID, + chainID, 1, []ids.ID{id}, [][]byte{{2}}, diff --git a/snow/engine/avalanche/vertex/builder.go b/snow/engine/avalanche/vertex/builder.go index 34ee26763849..cf3e88ee6a35 100644 --- a/snow/engine/avalanche/vertex/builder.go +++ b/snow/engine/avalanche/vertex/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex @@ -62,10 +62,10 @@ func buildVtx( utils.Sort(parentIDs) utils.SortByHash(txs) - codecVer := codecVersion + codecVer := CodecVersion if stopVertex { // use new codec version for the "StopVertex" - codecVer = codecVersionWithStopVtx + codecVer = CodecVersionWithStopVtx } innerVtx := innerStatelessVertex{ @@ -80,7 +80,7 @@ func buildVtx( return nil, err } - vtxBytes, err := c.Marshal(innerVtx.Version, innerVtx) + vtxBytes, err := Codec.Marshal(innerVtx.Version, innerVtx) vtx := statelessVertex{ innerStatelessVertex: innerVtx, id: hashing.ComputeHash256Array(vtxBytes), diff --git a/snow/engine/avalanche/vertex/builder_test.go b/snow/engine/avalanche/vertex/builder_test.go index 132ccbc33f73..a70b14ba8ff1 100644 --- a/snow/engine/avalanche/vertex/builder_test.go +++ b/snow/engine/avalanche/vertex/builder_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/codec.go b/snow/engine/avalanche/vertex/codec.go index 564d699a25e9..12f387d0d25d 100644 --- a/snow/engine/avalanche/vertex/codec.go +++ b/snow/engine/avalanche/vertex/codec.go @@ -1,35 +1,38 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex import ( + "time" + "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/codec/reflectcodec" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/units" ) const ( + CodecVersion uint16 = 0 + CodecVersionWithStopVtx uint16 = 1 + // maxSize is the maximum allowed vertex size. It is necessary to deter DoS maxSize = units.MiB - - codecVersion uint16 = 0 - codecVersionWithStopVtx uint16 = 1 ) -var c codec.Manager +var Codec codec.Manager func init() { - lc := linearcodec.New([]string{reflectcodec.DefaultTagName + "V0"}, maxSize) - lc2 := linearcodec.New([]string{reflectcodec.DefaultTagName + "V1"}, maxSize) + lc0 := linearcodec.New(time.Time{}, []string{reflectcodec.DefaultTagName + "V0"}, maxSize) + lc1 := linearcodec.New(time.Time{}, []string{reflectcodec.DefaultTagName + "V1"}, maxSize) - c = codec.NewManager(maxSize) - // for backward compatibility, still register the initial codec version - if err := c.RegisterCodec(codecVersion, lc); err != nil { - panic(err) - } - if err := c.RegisterCodec(codecVersionWithStopVtx, lc2); err != nil { + Codec = codec.NewManager(maxSize) + err := utils.Err( + Codec.RegisterCodec(CodecVersion, lc0), + Codec.RegisterCodec(CodecVersionWithStopVtx, lc1), + ) + if err != nil { panic(err) } } diff --git a/snow/engine/avalanche/vertex/manager.go b/snow/engine/avalanche/vertex/manager.go index cf206742b629..a300affdb0e9 100644 --- a/snow/engine/avalanche/vertex/manager.go +++ b/snow/engine/avalanche/vertex/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/mock_vm.go b/snow/engine/avalanche/vertex/mock_vm.go index b8d2637c7311..7ad293f6313f 100644 --- a/snow/engine/avalanche/vertex/mock_vm.go +++ b/snow/engine/avalanche/vertex/mock_vm.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex (interfaces: LinearizableVM) +// +// Generated by this command: +// +// mockgen -package=vertex -destination=snow/engine/avalanche/vertex/mock_vm.go github.com/ava-labs/avalanchego/snow/engine/avalanche/vertex LinearizableVM +// // Package vertex is a generated GoMock package. package vertex @@ -55,7 +57,7 @@ func (m *MockLinearizableVM) AppGossip(arg0 context.Context, arg1 ids.NodeID, ar } // AppGossip indicates an expected call of AppGossip. -func (mr *MockLinearizableVMMockRecorder) AppGossip(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) AppGossip(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppGossip", reflect.TypeOf((*MockLinearizableVM)(nil).AppGossip), arg0, arg1, arg2) } @@ -69,23 +71,23 @@ func (m *MockLinearizableVM) AppRequest(arg0 context.Context, arg1 ids.NodeID, a } // AppRequest indicates an expected call of AppRequest. -func (mr *MockLinearizableVMMockRecorder) AppRequest(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) AppRequest(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppRequest", reflect.TypeOf((*MockLinearizableVM)(nil).AppRequest), arg0, arg1, arg2, arg3, arg4) } // AppRequestFailed mocks base method. -func (m *MockLinearizableVM) AppRequestFailed(arg0 context.Context, arg1 ids.NodeID, arg2 uint32) error { +func (m *MockLinearizableVM) AppRequestFailed(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 *common.AppError) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AppRequestFailed", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "AppRequestFailed", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // AppRequestFailed indicates an expected call of AppRequestFailed. -func (mr *MockLinearizableVMMockRecorder) AppRequestFailed(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) AppRequestFailed(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppRequestFailed", reflect.TypeOf((*MockLinearizableVM)(nil).AppRequestFailed), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppRequestFailed", reflect.TypeOf((*MockLinearizableVM)(nil).AppRequestFailed), arg0, arg1, arg2, arg3) } // AppResponse mocks base method. @@ -97,7 +99,7 @@ func (m *MockLinearizableVM) AppResponse(arg0 context.Context, arg1 ids.NodeID, } // AppResponse indicates an expected call of AppResponse. -func (mr *MockLinearizableVMMockRecorder) AppResponse(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) AppResponse(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppResponse", reflect.TypeOf((*MockLinearizableVM)(nil).AppResponse), arg0, arg1, arg2, arg3) } @@ -112,7 +114,7 @@ func (m *MockLinearizableVM) BuildBlock(arg0 context.Context) (snowman.Block, er } // BuildBlock indicates an expected call of BuildBlock. -func (mr *MockLinearizableVMMockRecorder) BuildBlock(arg0 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) BuildBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildBlock", reflect.TypeOf((*MockLinearizableVM)(nil).BuildBlock), arg0) } @@ -126,7 +128,7 @@ func (m *MockLinearizableVM) Connected(arg0 context.Context, arg1 ids.NodeID, ar } // Connected indicates an expected call of Connected. -func (mr *MockLinearizableVMMockRecorder) Connected(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) Connected(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connected", reflect.TypeOf((*MockLinearizableVM)(nil).Connected), arg0, arg1, arg2) } @@ -141,26 +143,11 @@ func (m *MockLinearizableVM) CreateHandlers(arg0 context.Context) (map[string]ht } // CreateHandlers indicates an expected call of CreateHandlers. -func (mr *MockLinearizableVMMockRecorder) CreateHandlers(arg0 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) CreateHandlers(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHandlers", reflect.TypeOf((*MockLinearizableVM)(nil).CreateHandlers), arg0) } -// CreateStaticHandlers mocks base method. -func (m *MockLinearizableVM) CreateStaticHandlers(arg0 context.Context) (map[string]http.Handler, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateStaticHandlers", arg0) - ret0, _ := ret[0].(map[string]http.Handler) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateStaticHandlers indicates an expected call of CreateStaticHandlers. -func (mr *MockLinearizableVMMockRecorder) CreateStaticHandlers(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStaticHandlers", reflect.TypeOf((*MockLinearizableVM)(nil).CreateStaticHandlers), arg0) -} - // CrossChainAppRequest mocks base method. func (m *MockLinearizableVM) CrossChainAppRequest(arg0 context.Context, arg1 ids.ID, arg2 uint32, arg3 time.Time, arg4 []byte) error { m.ctrl.T.Helper() @@ -170,23 +157,23 @@ func (m *MockLinearizableVM) CrossChainAppRequest(arg0 context.Context, arg1 ids } // CrossChainAppRequest indicates an expected call of CrossChainAppRequest. -func (mr *MockLinearizableVMMockRecorder) CrossChainAppRequest(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) CrossChainAppRequest(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CrossChainAppRequest", reflect.TypeOf((*MockLinearizableVM)(nil).CrossChainAppRequest), arg0, arg1, arg2, arg3, arg4) } // CrossChainAppRequestFailed mocks base method. -func (m *MockLinearizableVM) CrossChainAppRequestFailed(arg0 context.Context, arg1 ids.ID, arg2 uint32) error { +func (m *MockLinearizableVM) CrossChainAppRequestFailed(arg0 context.Context, arg1 ids.ID, arg2 uint32, arg3 *common.AppError) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CrossChainAppRequestFailed", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "CrossChainAppRequestFailed", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // CrossChainAppRequestFailed indicates an expected call of CrossChainAppRequestFailed. -func (mr *MockLinearizableVMMockRecorder) CrossChainAppRequestFailed(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) CrossChainAppRequestFailed(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CrossChainAppRequestFailed", reflect.TypeOf((*MockLinearizableVM)(nil).CrossChainAppRequestFailed), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CrossChainAppRequestFailed", reflect.TypeOf((*MockLinearizableVM)(nil).CrossChainAppRequestFailed), arg0, arg1, arg2, arg3) } // CrossChainAppResponse mocks base method. @@ -198,7 +185,7 @@ func (m *MockLinearizableVM) CrossChainAppResponse(arg0 context.Context, arg1 id } // CrossChainAppResponse indicates an expected call of CrossChainAppResponse. -func (mr *MockLinearizableVMMockRecorder) CrossChainAppResponse(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) CrossChainAppResponse(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CrossChainAppResponse", reflect.TypeOf((*MockLinearizableVM)(nil).CrossChainAppResponse), arg0, arg1, arg2, arg3) } @@ -212,7 +199,7 @@ func (m *MockLinearizableVM) Disconnected(arg0 context.Context, arg1 ids.NodeID) } // Disconnected indicates an expected call of Disconnected. -func (mr *MockLinearizableVMMockRecorder) Disconnected(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) Disconnected(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnected", reflect.TypeOf((*MockLinearizableVM)(nil).Disconnected), arg0, arg1) } @@ -227,7 +214,7 @@ func (m *MockLinearizableVM) GetBlock(arg0 context.Context, arg1 ids.ID) (snowma } // GetBlock indicates an expected call of GetBlock. -func (mr *MockLinearizableVMMockRecorder) GetBlock(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) GetBlock(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockLinearizableVM)(nil).GetBlock), arg0, arg1) } @@ -242,22 +229,22 @@ func (m *MockLinearizableVM) GetBlockIDAtHeight(arg0 context.Context, arg1 uint6 } // GetBlockIDAtHeight indicates an expected call of GetBlockIDAtHeight. -func (mr *MockLinearizableVMMockRecorder) GetBlockIDAtHeight(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) GetBlockIDAtHeight(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockIDAtHeight", reflect.TypeOf((*MockLinearizableVM)(nil).GetBlockIDAtHeight), arg0, arg1) } // HealthCheck mocks base method. -func (m *MockLinearizableVM) HealthCheck(arg0 context.Context) (interface{}, error) { +func (m *MockLinearizableVM) HealthCheck(arg0 context.Context) (any, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HealthCheck", arg0) - ret0, _ := ret[0].(interface{}) + ret0, _ := ret[0].(any) ret1, _ := ret[1].(error) return ret0, ret1 } // HealthCheck indicates an expected call of HealthCheck. -func (mr *MockLinearizableVMMockRecorder) HealthCheck(arg0 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) HealthCheck(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockLinearizableVM)(nil).HealthCheck), arg0) } @@ -271,7 +258,7 @@ func (m *MockLinearizableVM) Initialize(arg0 context.Context, arg1 *snow.Context } // Initialize indicates an expected call of Initialize. -func (mr *MockLinearizableVMMockRecorder) Initialize(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) Initialize(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockLinearizableVM)(nil).Initialize), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) } @@ -286,7 +273,7 @@ func (m *MockLinearizableVM) LastAccepted(arg0 context.Context) (ids.ID, error) } // LastAccepted indicates an expected call of LastAccepted. -func (mr *MockLinearizableVMMockRecorder) LastAccepted(arg0 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) LastAccepted(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastAccepted", reflect.TypeOf((*MockLinearizableVM)(nil).LastAccepted), arg0) } @@ -300,7 +287,7 @@ func (m *MockLinearizableVM) Linearize(arg0 context.Context, arg1 ids.ID) error } // Linearize indicates an expected call of Linearize. -func (mr *MockLinearizableVMMockRecorder) Linearize(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) Linearize(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Linearize", reflect.TypeOf((*MockLinearizableVM)(nil).Linearize), arg0, arg1) } @@ -315,7 +302,7 @@ func (m *MockLinearizableVM) ParseBlock(arg0 context.Context, arg1 []byte) (snow } // ParseBlock indicates an expected call of ParseBlock. -func (mr *MockLinearizableVMMockRecorder) ParseBlock(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) ParseBlock(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParseBlock", reflect.TypeOf((*MockLinearizableVM)(nil).ParseBlock), arg0, arg1) } @@ -330,7 +317,7 @@ func (m *MockLinearizableVM) ParseTx(arg0 context.Context, arg1 []byte) (snowsto } // ParseTx indicates an expected call of ParseTx. -func (mr *MockLinearizableVMMockRecorder) ParseTx(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) ParseTx(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParseTx", reflect.TypeOf((*MockLinearizableVM)(nil).ParseTx), arg0, arg1) } @@ -344,7 +331,7 @@ func (m *MockLinearizableVM) SetPreference(arg0 context.Context, arg1 ids.ID) er } // SetPreference indicates an expected call of SetPreference. -func (mr *MockLinearizableVMMockRecorder) SetPreference(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) SetPreference(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPreference", reflect.TypeOf((*MockLinearizableVM)(nil).SetPreference), arg0, arg1) } @@ -358,7 +345,7 @@ func (m *MockLinearizableVM) SetState(arg0 context.Context, arg1 snow.State) err } // SetState indicates an expected call of SetState. -func (mr *MockLinearizableVMMockRecorder) SetState(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) SetState(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetState", reflect.TypeOf((*MockLinearizableVM)(nil).SetState), arg0, arg1) } @@ -372,7 +359,7 @@ func (m *MockLinearizableVM) Shutdown(arg0 context.Context) error { } // Shutdown indicates an expected call of Shutdown. -func (mr *MockLinearizableVMMockRecorder) Shutdown(arg0 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) Shutdown(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockLinearizableVM)(nil).Shutdown), arg0) } @@ -386,7 +373,7 @@ func (m *MockLinearizableVM) VerifyHeightIndex(arg0 context.Context) error { } // VerifyHeightIndex indicates an expected call of VerifyHeightIndex. -func (mr *MockLinearizableVMMockRecorder) VerifyHeightIndex(arg0 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) VerifyHeightIndex(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyHeightIndex", reflect.TypeOf((*MockLinearizableVM)(nil).VerifyHeightIndex), arg0) } @@ -401,7 +388,7 @@ func (m *MockLinearizableVM) Version(arg0 context.Context) (string, error) { } // Version indicates an expected call of Version. -func (mr *MockLinearizableVMMockRecorder) Version(arg0 interface{}) *gomock.Call { +func (mr *MockLinearizableVMMockRecorder) Version(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*MockLinearizableVM)(nil).Version), arg0) } diff --git a/snow/engine/avalanche/vertex/parser.go b/snow/engine/avalanche/vertex/parser.go index cd409c7edc65..41f848e781c6 100644 --- a/snow/engine/avalanche/vertex/parser.go +++ b/snow/engine/avalanche/vertex/parser.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex @@ -19,7 +19,7 @@ type Parser interface { // Parse parses the provided vertex bytes into a stateless vertex func Parse(bytes []byte) (StatelessVertex, error) { vtx := innerStatelessVertex{} - version, err := c.Unmarshal(bytes, &vtx) + version, err := Codec.Unmarshal(bytes, &vtx) if err != nil { return nil, err } diff --git a/snow/engine/avalanche/vertex/parser_test.go b/snow/engine/avalanche/vertex/parser_test.go index 5d765d8384dd..f3016895848d 100644 --- a/snow/engine/avalanche/vertex/parser_test.go +++ b/snow/engine/avalanche/vertex/parser_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/stateless_vertex.go b/snow/engine/avalanche/vertex/stateless_vertex.go index cef298c9b90f..88884d9e90bb 100644 --- a/snow/engine/avalanche/vertex/stateless_vertex.go +++ b/snow/engine/avalanche/vertex/stateless_vertex.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex @@ -73,7 +73,7 @@ func (v statelessVertex) ChainID() ids.ID { } func (v statelessVertex) StopVertex() bool { - return v.innerStatelessVertex.Version == codecVersionWithStopVtx + return v.innerStatelessVertex.Version == CodecVersionWithStopVtx } func (v statelessVertex) Height() uint64 { @@ -94,15 +94,15 @@ func (v statelessVertex) Txs() [][]byte { type innerStatelessVertex struct { Version uint16 `json:"version"` - ChainID ids.ID `json:"chainID" serializeV0:"true" serializeV1:"true"` - Height uint64 `json:"height" serializeV0:"true" serializeV1:"true"` - Epoch uint32 `json:"epoch" serializeV0:"true"` - ParentIDs []ids.ID `json:"parentIDs" len:"128" serializeV0:"true" serializeV1:"true"` - Txs [][]byte `json:"txs" len:"128" serializeV0:"true"` + ChainID ids.ID `json:"chainID" serializeV0:"true" serializeV1:"true"` + Height uint64 `json:"height" serializeV0:"true" serializeV1:"true"` + Epoch uint32 `json:"epoch" serializeV0:"true"` + ParentIDs []ids.ID `json:"parentIDs" serializeV0:"true" serializeV1:"true"` + Txs [][]byte `json:"txs" serializeV0:"true"` } func (v innerStatelessVertex) Verify() error { - if v.Version == codecVersionWithStopVtx { + if v.Version == CodecVersionWithStopVtx { return v.verifyStopVertex() } return v.verify() @@ -110,7 +110,7 @@ func (v innerStatelessVertex) Verify() error { func (v innerStatelessVertex) verify() error { switch { - case v.Version != codecVersion: + case v.Version != CodecVersion: return errBadVersion case v.Epoch != 0: return errBadEpoch @@ -131,7 +131,7 @@ func (v innerStatelessVertex) verify() error { func (v innerStatelessVertex) verifyStopVertex() error { switch { - case v.Version != codecVersionWithStopVtx: + case v.Version != CodecVersionWithStopVtx: return errBadVersion case v.Epoch != 0: return errBadEpoch diff --git a/snow/engine/avalanche/vertex/stateless_vertex_test.go b/snow/engine/avalanche/vertex/stateless_vertex_test.go index a18a045a95d4..35ece98c51da 100644 --- a/snow/engine/avalanche/vertex/stateless_vertex_test.go +++ b/snow/engine/avalanche/vertex/stateless_vertex_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/storage.go b/snow/engine/avalanche/vertex/storage.go index 40ec863d2cba..cac766c6b103 100644 --- a/snow/engine/avalanche/vertex/storage.go +++ b/snow/engine/avalanche/vertex/storage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/test_builder.go b/snow/engine/avalanche/vertex/test_builder.go index 0bd63b26bcb1..534629372249 100644 --- a/snow/engine/avalanche/vertex/test_builder.go +++ b/snow/engine/avalanche/vertex/test_builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/test_manager.go b/snow/engine/avalanche/vertex/test_manager.go index a2f55ee7f8b8..6954161cdd46 100644 --- a/snow/engine/avalanche/vertex/test_manager.go +++ b/snow/engine/avalanche/vertex/test_manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/test_parser.go b/snow/engine/avalanche/vertex/test_parser.go index 3ca17b3440f1..2ee10add6090 100644 --- a/snow/engine/avalanche/vertex/test_parser.go +++ b/snow/engine/avalanche/vertex/test_parser.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/test_storage.go b/snow/engine/avalanche/vertex/test_storage.go index b5250ee1fca4..8e0b8bc1e84d 100644 --- a/snow/engine/avalanche/vertex/test_storage.go +++ b/snow/engine/avalanche/vertex/test_storage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/test_vm.go b/snow/engine/avalanche/vertex/test_vm.go index d20e57000da9..ee17c8b13ae0 100644 --- a/snow/engine/avalanche/vertex/test_vm.go +++ b/snow/engine/avalanche/vertex/test_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/avalanche/vertex/vm.go b/snow/engine/avalanche/vertex/vm.go index 67f3fc586b26..9987fe164d35 100644 --- a/snow/engine/avalanche/vertex/vm.go +++ b/snow/engine/avalanche/vertex/vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vertex diff --git a/snow/engine/common/appsender/appsender_client.go b/snow/engine/common/appsender/appsender_client.go index c74616d71006..acde7109f751 100644 --- a/snow/engine/common/appsender/appsender_client.go +++ b/snow/engine/common/appsender/appsender_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package appsender diff --git a/snow/engine/common/appsender/appsender_server.go b/snow/engine/common/appsender/appsender_server.go index 3583940db108..84763e17bf11 100644 --- a/snow/engine/common/appsender/appsender_server.go +++ b/snow/engine/common/appsender/appsender_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package appsender diff --git a/snow/engine/common/bootstrap_tracker.go b/snow/engine/common/bootstrap_tracker.go index 04b90a122f98..bd2ef43cf1f3 100644 --- a/snow/engine/common/bootstrap_tracker.go +++ b/snow/engine/common/bootstrap_tracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/bootstrapable.go b/snow/engine/common/bootstrapable.go index 256acb6d468e..517eba2aa154 100644 --- a/snow/engine/common/bootstrapable.go +++ b/snow/engine/common/bootstrapable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/engine.go b/snow/engine/common/engine.go index e50d94327a30..cbd9c37dc10c 100644 --- a/snow/engine/common/engine.go +++ b/snow/engine/common/engine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common @@ -396,6 +396,7 @@ type AppResponseHandler interface { ctx context.Context, nodeID ids.NodeID, requestID uint32, + appErr *AppError, ) error } @@ -465,6 +466,7 @@ type CrossChainAppResponseHandler interface { ctx context.Context, chainID ids.ID, requestID uint32, + appErr *AppError, ) error } diff --git a/snow/engine/common/error.go b/snow/engine/common/error.go new file mode 100644 index 000000000000..261fedaa260c --- /dev/null +++ b/snow/engine/common/error.go @@ -0,0 +1,43 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package common + +import "fmt" + +var ( + _ error = (*AppError)(nil) + + // ErrUndefined indicates an undefined error + ErrUndefined = &AppError{ + Code: 0, + Message: "undefined", + } + + // ErrTimeout is used to signal a response timeout + ErrTimeout = &AppError{ + Code: -1, + Message: "timed out", + } +) + +// AppError is an application-defined error +type AppError struct { + // Code is application-defined and should be used for error matching + Code int32 + // Message is a human-readable error message + Message string +} + +func (a *AppError) Error() string { + return fmt.Sprintf("%d: %s", a.Code, a.Message) +} + +func (a *AppError) Is(target error) bool { + appErr, ok := target.(*AppError) + if !ok { + return false + } + + return a.Code == appErr.Code +} diff --git a/snow/engine/common/error_test.go b/snow/engine/common/error_test.go new file mode 100644 index 000000000000..0204e010045b --- /dev/null +++ b/snow/engine/common/error_test.go @@ -0,0 +1,92 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package common + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +// Tests the invariant that AppErrors are matched against their error codes +func TestAppErrorEqual(t *testing.T) { + tests := []struct { + name string + err1 *AppError + err2 error + expected bool + }{ + { + name: "is - equal", + err1: &AppError{ + Code: 1, + }, + err2: &AppError{ + Code: 1, + }, + expected: true, + }, + { + name: "is - same error code different messages", + err1: &AppError{ + Code: 1, + Message: "foo", + }, + err2: &AppError{ + Code: 1, + Message: "bar", + }, + expected: true, + }, + { + name: "not is - different error code", + err1: &AppError{ + Code: 1, + }, + err2: &AppError{ + Code: 2, + }, + }, + { + name: "not is - different type", + err1: &AppError{ + Code: 1, + }, + err2: errors.New("foobar"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.expected, errors.Is(tt.err1, tt.err2)) + }) + } +} + +// Tests reserved error types +func TestErrorCode(t *testing.T) { + tests := []struct { + name string + code int32 + expected *AppError + }{ + { + name: "undefined", + code: 0, + expected: ErrUndefined, + }, + { + name: "undefined", + code: -1, + expected: ErrTimeout, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.ErrorIs(t, tt.expected, &AppError{Code: tt.code}) + }) + } +} diff --git a/snow/engine/common/fx.go b/snow/engine/common/fx.go index 861986462bb2..000c22ed6baf 100644 --- a/snow/engine/common/fx.go +++ b/snow/engine/common/fx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/halter.go b/snow/engine/common/halter.go index bdfe3c9d489e..1fcea981d2e4 100644 --- a/snow/engine/common/halter.go +++ b/snow/engine/common/halter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/message.go b/snow/engine/common/message.go index 6ce1c4501a1c..1bc05991973e 100644 --- a/snow/engine/common/message.go +++ b/snow/engine/common/message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/mock_sender.go b/snow/engine/common/mock_sender.go index 3850c198a236..6ebeeb636675 100644 --- a/snow/engine/common/mock_sender.go +++ b/snow/engine/common/mock_sender.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/snow/engine/common (interfaces: Sender) +// Source: snow/engine/common/sender.go +// +// Generated by this command: +// +// mockgen -source=snow/engine/common/sender.go -destination=snow/engine/common/mock_sender.go -package=common -exclude_interfaces=StateSummarySender,AcceptedStateSummarySender,FrontierSender,AcceptedSender,FetchSender,AppSender,QuerySender,CrossChainAppSender,NetworkAppSender,Gossiper +// // Package common is a generated GoMock package. package common @@ -41,291 +43,291 @@ func (m *MockSender) EXPECT() *MockSenderMockRecorder { } // Accept mocks base method. -func (m *MockSender) Accept(arg0 *snow.ConsensusContext, arg1 ids.ID, arg2 []byte) error { +func (m *MockSender) Accept(ctx *snow.ConsensusContext, containerID ids.ID, container []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Accept", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "Accept", ctx, containerID, container) ret0, _ := ret[0].(error) return ret0 } // Accept indicates an expected call of Accept. -func (mr *MockSenderMockRecorder) Accept(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) Accept(ctx, containerID, container any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockSender)(nil).Accept), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockSender)(nil).Accept), ctx, containerID, container) } // SendAccepted mocks base method. -func (m *MockSender) SendAccepted(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 []ids.ID) { +func (m *MockSender) SendAccepted(ctx context.Context, nodeID ids.NodeID, requestID uint32, containerIDs []ids.ID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendAccepted", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendAccepted", ctx, nodeID, requestID, containerIDs) } // SendAccepted indicates an expected call of SendAccepted. -func (mr *MockSenderMockRecorder) SendAccepted(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendAccepted(ctx, nodeID, requestID, containerIDs any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAccepted", reflect.TypeOf((*MockSender)(nil).SendAccepted), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAccepted", reflect.TypeOf((*MockSender)(nil).SendAccepted), ctx, nodeID, requestID, containerIDs) } // SendAcceptedFrontier mocks base method. -func (m *MockSender) SendAcceptedFrontier(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 []ids.ID) { +func (m *MockSender) SendAcceptedFrontier(ctx context.Context, nodeID ids.NodeID, requestID uint32, containerID ids.ID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendAcceptedFrontier", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendAcceptedFrontier", ctx, nodeID, requestID, containerID) } // SendAcceptedFrontier indicates an expected call of SendAcceptedFrontier. -func (mr *MockSenderMockRecorder) SendAcceptedFrontier(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendAcceptedFrontier(ctx, nodeID, requestID, containerID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAcceptedFrontier", reflect.TypeOf((*MockSender)(nil).SendAcceptedFrontier), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAcceptedFrontier", reflect.TypeOf((*MockSender)(nil).SendAcceptedFrontier), ctx, nodeID, requestID, containerID) } // SendAcceptedStateSummary mocks base method. -func (m *MockSender) SendAcceptedStateSummary(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 []ids.ID) { +func (m *MockSender) SendAcceptedStateSummary(ctx context.Context, nodeID ids.NodeID, requestID uint32, summaryIDs []ids.ID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendAcceptedStateSummary", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendAcceptedStateSummary", ctx, nodeID, requestID, summaryIDs) } // SendAcceptedStateSummary indicates an expected call of SendAcceptedStateSummary. -func (mr *MockSenderMockRecorder) SendAcceptedStateSummary(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendAcceptedStateSummary(ctx, nodeID, requestID, summaryIDs any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAcceptedStateSummary", reflect.TypeOf((*MockSender)(nil).SendAcceptedStateSummary), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAcceptedStateSummary", reflect.TypeOf((*MockSender)(nil).SendAcceptedStateSummary), ctx, nodeID, requestID, summaryIDs) } // SendAncestors mocks base method. -func (m *MockSender) SendAncestors(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 [][]byte) { +func (m *MockSender) SendAncestors(ctx context.Context, nodeID ids.NodeID, requestID uint32, containers [][]byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendAncestors", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendAncestors", ctx, nodeID, requestID, containers) } // SendAncestors indicates an expected call of SendAncestors. -func (mr *MockSenderMockRecorder) SendAncestors(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendAncestors(ctx, nodeID, requestID, containers any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAncestors", reflect.TypeOf((*MockSender)(nil).SendAncestors), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAncestors", reflect.TypeOf((*MockSender)(nil).SendAncestors), ctx, nodeID, requestID, containers) } // SendAppGossip mocks base method. -func (m *MockSender) SendAppGossip(arg0 context.Context, arg1 []byte) error { +func (m *MockSender) SendAppGossip(ctx context.Context, appGossipBytes []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendAppGossip", arg0, arg1) + ret := m.ctrl.Call(m, "SendAppGossip", ctx, appGossipBytes) ret0, _ := ret[0].(error) return ret0 } // SendAppGossip indicates an expected call of SendAppGossip. -func (mr *MockSenderMockRecorder) SendAppGossip(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendAppGossip(ctx, appGossipBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAppGossip", reflect.TypeOf((*MockSender)(nil).SendAppGossip), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAppGossip", reflect.TypeOf((*MockSender)(nil).SendAppGossip), ctx, appGossipBytes) } // SendAppGossipSpecific mocks base method. -func (m *MockSender) SendAppGossipSpecific(arg0 context.Context, arg1 set.Set[ids.NodeID], arg2 []byte) error { +func (m *MockSender) SendAppGossipSpecific(ctx context.Context, nodeIDs set.Set[ids.NodeID], appGossipBytes []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendAppGossipSpecific", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "SendAppGossipSpecific", ctx, nodeIDs, appGossipBytes) ret0, _ := ret[0].(error) return ret0 } // SendAppGossipSpecific indicates an expected call of SendAppGossipSpecific. -func (mr *MockSenderMockRecorder) SendAppGossipSpecific(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendAppGossipSpecific(ctx, nodeIDs, appGossipBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAppGossipSpecific", reflect.TypeOf((*MockSender)(nil).SendAppGossipSpecific), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAppGossipSpecific", reflect.TypeOf((*MockSender)(nil).SendAppGossipSpecific), ctx, nodeIDs, appGossipBytes) } // SendAppRequest mocks base method. -func (m *MockSender) SendAppRequest(arg0 context.Context, arg1 set.Set[ids.NodeID], arg2 uint32, arg3 []byte) error { +func (m *MockSender) SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, appRequestBytes []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendAppRequest", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "SendAppRequest", ctx, nodeIDs, requestID, appRequestBytes) ret0, _ := ret[0].(error) return ret0 } // SendAppRequest indicates an expected call of SendAppRequest. -func (mr *MockSenderMockRecorder) SendAppRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendAppRequest(ctx, nodeIDs, requestID, appRequestBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAppRequest", reflect.TypeOf((*MockSender)(nil).SendAppRequest), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAppRequest", reflect.TypeOf((*MockSender)(nil).SendAppRequest), ctx, nodeIDs, requestID, appRequestBytes) } // SendAppResponse mocks base method. -func (m *MockSender) SendAppResponse(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 []byte) error { +func (m *MockSender) SendAppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, appResponseBytes []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendAppResponse", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "SendAppResponse", ctx, nodeID, requestID, appResponseBytes) ret0, _ := ret[0].(error) return ret0 } // SendAppResponse indicates an expected call of SendAppResponse. -func (mr *MockSenderMockRecorder) SendAppResponse(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendAppResponse(ctx, nodeID, requestID, appResponseBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAppResponse", reflect.TypeOf((*MockSender)(nil).SendAppResponse), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAppResponse", reflect.TypeOf((*MockSender)(nil).SendAppResponse), ctx, nodeID, requestID, appResponseBytes) } // SendChits mocks base method. -func (m *MockSender) SendChits(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3, arg4 ids.ID) { +func (m *MockSender) SendChits(ctx context.Context, nodeID ids.NodeID, requestID uint32, preferredID, preferredIDAtHeight, acceptedID ids.ID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendChits", arg0, arg1, arg2, arg3, arg4) + m.ctrl.Call(m, "SendChits", ctx, nodeID, requestID, preferredID, preferredIDAtHeight, acceptedID) } // SendChits indicates an expected call of SendChits. -func (mr *MockSenderMockRecorder) SendChits(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendChits(ctx, nodeID, requestID, preferredID, preferredIDAtHeight, acceptedID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendChits", reflect.TypeOf((*MockSender)(nil).SendChits), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendChits", reflect.TypeOf((*MockSender)(nil).SendChits), ctx, nodeID, requestID, preferredID, preferredIDAtHeight, acceptedID) } // SendCrossChainAppRequest mocks base method. -func (m *MockSender) SendCrossChainAppRequest(arg0 context.Context, arg1 ids.ID, arg2 uint32, arg3 []byte) error { +func (m *MockSender) SendCrossChainAppRequest(ctx context.Context, chainID ids.ID, requestID uint32, appRequestBytes []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendCrossChainAppRequest", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "SendCrossChainAppRequest", ctx, chainID, requestID, appRequestBytes) ret0, _ := ret[0].(error) return ret0 } // SendCrossChainAppRequest indicates an expected call of SendCrossChainAppRequest. -func (mr *MockSenderMockRecorder) SendCrossChainAppRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendCrossChainAppRequest(ctx, chainID, requestID, appRequestBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCrossChainAppRequest", reflect.TypeOf((*MockSender)(nil).SendCrossChainAppRequest), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCrossChainAppRequest", reflect.TypeOf((*MockSender)(nil).SendCrossChainAppRequest), ctx, chainID, requestID, appRequestBytes) } // SendCrossChainAppResponse mocks base method. -func (m *MockSender) SendCrossChainAppResponse(arg0 context.Context, arg1 ids.ID, arg2 uint32, arg3 []byte) error { +func (m *MockSender) SendCrossChainAppResponse(ctx context.Context, chainID ids.ID, requestID uint32, appResponseBytes []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendCrossChainAppResponse", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "SendCrossChainAppResponse", ctx, chainID, requestID, appResponseBytes) ret0, _ := ret[0].(error) return ret0 } // SendCrossChainAppResponse indicates an expected call of SendCrossChainAppResponse. -func (mr *MockSenderMockRecorder) SendCrossChainAppResponse(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendCrossChainAppResponse(ctx, chainID, requestID, appResponseBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCrossChainAppResponse", reflect.TypeOf((*MockSender)(nil).SendCrossChainAppResponse), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCrossChainAppResponse", reflect.TypeOf((*MockSender)(nil).SendCrossChainAppResponse), ctx, chainID, requestID, appResponseBytes) } // SendGet mocks base method. -func (m *MockSender) SendGet(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 ids.ID) { +func (m *MockSender) SendGet(ctx context.Context, nodeID ids.NodeID, requestID uint32, containerID ids.ID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendGet", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendGet", ctx, nodeID, requestID, containerID) } // SendGet indicates an expected call of SendGet. -func (mr *MockSenderMockRecorder) SendGet(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendGet(ctx, nodeID, requestID, containerID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGet", reflect.TypeOf((*MockSender)(nil).SendGet), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGet", reflect.TypeOf((*MockSender)(nil).SendGet), ctx, nodeID, requestID, containerID) } // SendGetAccepted mocks base method. -func (m *MockSender) SendGetAccepted(arg0 context.Context, arg1 set.Set[ids.NodeID], arg2 uint32, arg3 []ids.ID) { +func (m *MockSender) SendGetAccepted(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, containerIDs []ids.ID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendGetAccepted", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendGetAccepted", ctx, nodeIDs, requestID, containerIDs) } // SendGetAccepted indicates an expected call of SendGetAccepted. -func (mr *MockSenderMockRecorder) SendGetAccepted(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendGetAccepted(ctx, nodeIDs, requestID, containerIDs any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetAccepted", reflect.TypeOf((*MockSender)(nil).SendGetAccepted), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetAccepted", reflect.TypeOf((*MockSender)(nil).SendGetAccepted), ctx, nodeIDs, requestID, containerIDs) } // SendGetAcceptedFrontier mocks base method. -func (m *MockSender) SendGetAcceptedFrontier(arg0 context.Context, arg1 set.Set[ids.NodeID], arg2 uint32) { +func (m *MockSender) SendGetAcceptedFrontier(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendGetAcceptedFrontier", arg0, arg1, arg2) + m.ctrl.Call(m, "SendGetAcceptedFrontier", ctx, nodeIDs, requestID) } // SendGetAcceptedFrontier indicates an expected call of SendGetAcceptedFrontier. -func (mr *MockSenderMockRecorder) SendGetAcceptedFrontier(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendGetAcceptedFrontier(ctx, nodeIDs, requestID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetAcceptedFrontier", reflect.TypeOf((*MockSender)(nil).SendGetAcceptedFrontier), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetAcceptedFrontier", reflect.TypeOf((*MockSender)(nil).SendGetAcceptedFrontier), ctx, nodeIDs, requestID) } // SendGetAcceptedStateSummary mocks base method. -func (m *MockSender) SendGetAcceptedStateSummary(arg0 context.Context, arg1 set.Set[ids.NodeID], arg2 uint32, arg3 []uint64) { +func (m *MockSender) SendGetAcceptedStateSummary(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, heights []uint64) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendGetAcceptedStateSummary", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendGetAcceptedStateSummary", ctx, nodeIDs, requestID, heights) } // SendGetAcceptedStateSummary indicates an expected call of SendGetAcceptedStateSummary. -func (mr *MockSenderMockRecorder) SendGetAcceptedStateSummary(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendGetAcceptedStateSummary(ctx, nodeIDs, requestID, heights any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetAcceptedStateSummary", reflect.TypeOf((*MockSender)(nil).SendGetAcceptedStateSummary), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetAcceptedStateSummary", reflect.TypeOf((*MockSender)(nil).SendGetAcceptedStateSummary), ctx, nodeIDs, requestID, heights) } // SendGetAncestors mocks base method. -func (m *MockSender) SendGetAncestors(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 ids.ID) { +func (m *MockSender) SendGetAncestors(ctx context.Context, nodeID ids.NodeID, requestID uint32, containerID ids.ID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendGetAncestors", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendGetAncestors", ctx, nodeID, requestID, containerID) } // SendGetAncestors indicates an expected call of SendGetAncestors. -func (mr *MockSenderMockRecorder) SendGetAncestors(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendGetAncestors(ctx, nodeID, requestID, containerID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetAncestors", reflect.TypeOf((*MockSender)(nil).SendGetAncestors), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetAncestors", reflect.TypeOf((*MockSender)(nil).SendGetAncestors), ctx, nodeID, requestID, containerID) } // SendGetStateSummaryFrontier mocks base method. -func (m *MockSender) SendGetStateSummaryFrontier(arg0 context.Context, arg1 set.Set[ids.NodeID], arg2 uint32) { +func (m *MockSender) SendGetStateSummaryFrontier(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendGetStateSummaryFrontier", arg0, arg1, arg2) + m.ctrl.Call(m, "SendGetStateSummaryFrontier", ctx, nodeIDs, requestID) } // SendGetStateSummaryFrontier indicates an expected call of SendGetStateSummaryFrontier. -func (mr *MockSenderMockRecorder) SendGetStateSummaryFrontier(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendGetStateSummaryFrontier(ctx, nodeIDs, requestID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetStateSummaryFrontier", reflect.TypeOf((*MockSender)(nil).SendGetStateSummaryFrontier), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGetStateSummaryFrontier", reflect.TypeOf((*MockSender)(nil).SendGetStateSummaryFrontier), ctx, nodeIDs, requestID) } // SendGossip mocks base method. -func (m *MockSender) SendGossip(arg0 context.Context, arg1 []byte) { +func (m *MockSender) SendGossip(ctx context.Context, container []byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendGossip", arg0, arg1) + m.ctrl.Call(m, "SendGossip", ctx, container) } // SendGossip indicates an expected call of SendGossip. -func (mr *MockSenderMockRecorder) SendGossip(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendGossip(ctx, container any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGossip", reflect.TypeOf((*MockSender)(nil).SendGossip), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendGossip", reflect.TypeOf((*MockSender)(nil).SendGossip), ctx, container) } // SendPullQuery mocks base method. -func (m *MockSender) SendPullQuery(arg0 context.Context, arg1 set.Set[ids.NodeID], arg2 uint32, arg3 ids.ID) { +func (m *MockSender) SendPullQuery(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, containerID ids.ID, requestedHeight uint64) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendPullQuery", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendPullQuery", ctx, nodeIDs, requestID, containerID, requestedHeight) } // SendPullQuery indicates an expected call of SendPullQuery. -func (mr *MockSenderMockRecorder) SendPullQuery(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendPullQuery(ctx, nodeIDs, requestID, containerID, requestedHeight any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendPullQuery", reflect.TypeOf((*MockSender)(nil).SendPullQuery), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendPullQuery", reflect.TypeOf((*MockSender)(nil).SendPullQuery), ctx, nodeIDs, requestID, containerID, requestedHeight) } // SendPushQuery mocks base method. -func (m *MockSender) SendPushQuery(arg0 context.Context, arg1 set.Set[ids.NodeID], arg2 uint32, arg3 []byte) { +func (m *MockSender) SendPushQuery(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, container []byte, requestedHeight uint64) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendPushQuery", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendPushQuery", ctx, nodeIDs, requestID, container, requestedHeight) } // SendPushQuery indicates an expected call of SendPushQuery. -func (mr *MockSenderMockRecorder) SendPushQuery(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendPushQuery(ctx, nodeIDs, requestID, container, requestedHeight any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendPushQuery", reflect.TypeOf((*MockSender)(nil).SendPushQuery), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendPushQuery", reflect.TypeOf((*MockSender)(nil).SendPushQuery), ctx, nodeIDs, requestID, container, requestedHeight) } // SendPut mocks base method. -func (m *MockSender) SendPut(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 []byte) { +func (m *MockSender) SendPut(ctx context.Context, nodeID ids.NodeID, requestID uint32, container []byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendPut", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendPut", ctx, nodeID, requestID, container) } // SendPut indicates an expected call of SendPut. -func (mr *MockSenderMockRecorder) SendPut(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendPut(ctx, nodeID, requestID, container any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendPut", reflect.TypeOf((*MockSender)(nil).SendPut), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendPut", reflect.TypeOf((*MockSender)(nil).SendPut), ctx, nodeID, requestID, container) } // SendStateSummaryFrontier mocks base method. -func (m *MockSender) SendStateSummaryFrontier(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 []byte) { +func (m *MockSender) SendStateSummaryFrontier(ctx context.Context, nodeID ids.NodeID, requestID uint32, summary []byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SendStateSummaryFrontier", arg0, arg1, arg2, arg3) + m.ctrl.Call(m, "SendStateSummaryFrontier", ctx, nodeID, requestID, summary) } // SendStateSummaryFrontier indicates an expected call of SendStateSummaryFrontier. -func (mr *MockSenderMockRecorder) SendStateSummaryFrontier(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockSenderMockRecorder) SendStateSummaryFrontier(ctx, nodeID, requestID, summary any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendStateSummaryFrontier", reflect.TypeOf((*MockSender)(nil).SendStateSummaryFrontier), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendStateSummaryFrontier", reflect.TypeOf((*MockSender)(nil).SendStateSummaryFrontier), ctx, nodeID, requestID, summary) } diff --git a/snow/engine/common/no_ops_handlers.go b/snow/engine/common/no_ops_handlers.go index 716d1d5111b3..870c6694a7a7 100644 --- a/snow/engine/common/no_ops_handlers.go +++ b/snow/engine/common/no_ops_handlers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common @@ -288,12 +288,13 @@ func (nop *noOpAppHandler) CrossChainAppRequest(_ context.Context, chainID ids.I return nil } -func (nop *noOpAppHandler) CrossChainAppRequestFailed(_ context.Context, chainID ids.ID, requestID uint32) error { +func (nop *noOpAppHandler) CrossChainAppRequestFailed(_ context.Context, chainID ids.ID, requestID uint32, appErr *AppError) error { nop.log.Debug("dropping request", zap.String("reason", "unhandled by this gear"), - zap.Stringer("messageOp", message.CrossChainAppRequestFailedOp), + zap.Stringer("messageOp", message.CrossChainAppErrorOp), zap.Stringer("chainID", chainID), zap.Uint32("requestID", requestID), + zap.Error(appErr), ) return nil } @@ -318,12 +319,13 @@ func (nop *noOpAppHandler) AppRequest(_ context.Context, nodeID ids.NodeID, requ return nil } -func (nop *noOpAppHandler) AppRequestFailed(_ context.Context, nodeID ids.NodeID, requestID uint32) error { +func (nop *noOpAppHandler) AppRequestFailed(_ context.Context, nodeID ids.NodeID, requestID uint32, appErr *AppError) error { nop.log.Debug("dropping request", zap.String("reason", "unhandled by this gear"), - zap.Stringer("messageOp", message.AppRequestFailedOp), + zap.Stringer("messageOp", message.AppErrorOp), zap.Stringer("nodeID", nodeID), zap.Uint32("requestID", requestID), + zap.Error(appErr), ) return nil } diff --git a/snow/engine/common/queue/job.go b/snow/engine/common/queue/job.go index 4ac5a60fb835..3b36893f1d96 100644 --- a/snow/engine/common/queue/job.go +++ b/snow/engine/common/queue/job.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package queue diff --git a/snow/engine/common/queue/jobs.go b/snow/engine/common/queue/jobs.go index 5592ad822439..0577602a3729 100644 --- a/snow/engine/common/queue/jobs.go +++ b/snow/engine/common/queue/jobs.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package queue diff --git a/snow/engine/common/queue/jobs_test.go b/snow/engine/common/queue/jobs_test.go index 57b63e19750d..a098cb6e6d6d 100644 --- a/snow/engine/common/queue/jobs_test.go +++ b/snow/engine/common/queue/jobs_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package queue @@ -16,8 +16,8 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/set" ) @@ -116,7 +116,8 @@ func TestPushAndExecute(t *testing.T) { return job, nil } - count, err := jobs.ExecuteAll(context.Background(), snow.DefaultConsensusContextTest(), &common.Halter{}, false) + snowCtx := snowtest.Context(t, snowtest.CChainID) + count, err := jobs.ExecuteAll(context.Background(), snowtest.ConsensusContext(snowCtx), &common.Halter{}, false) require.NoError(err) require.Equal(1, count) @@ -182,7 +183,8 @@ func TestRemoveDependency(t *testing.T) { } } - count, err := jobs.ExecuteAll(context.Background(), snow.DefaultConsensusContextTest(), &common.Halter{}, false) + snowCtx := snowtest.Context(t, snowtest.CChainID) + count, err := jobs.ExecuteAll(context.Background(), snowtest.ConsensusContext(snowCtx), &common.Halter{}, false) require.NoError(err) require.Equal(2, count) require.True(executed0) @@ -355,7 +357,8 @@ func TestHandleJobWithMissingDependencyOnRunnableStack(t *testing.T) { } } - _, err = jobs.ExecuteAll(context.Background(), snow.DefaultConsensusContextTest(), &common.Halter{}, false) + snowCtx := snowtest.Context(t, snowtest.CChainID) + _, err = jobs.ExecuteAll(context.Background(), snowtest.ConsensusContext(snowCtx), &common.Halter{}, false) // Assert that the database closed error on job1 causes ExecuteAll // to fail in the middle of execution. require.ErrorIs(err, database.ErrClosed) @@ -387,7 +390,7 @@ func TestHandleJobWithMissingDependencyOnRunnableStack(t *testing.T) { require.NoError(err) require.True(hasNext) - count, err := jobs.ExecuteAll(context.Background(), snow.DefaultConsensusContextTest(), &common.Halter{}, false) + count, err := jobs.ExecuteAll(context.Background(), snowtest.ConsensusContext(snowCtx), &common.Halter{}, false) require.NoError(err) require.Equal(2, count) require.True(executed1) diff --git a/snow/engine/common/queue/parser.go b/snow/engine/common/queue/parser.go index ee8f39807f35..07e9df50887d 100644 --- a/snow/engine/common/queue/parser.go +++ b/snow/engine/common/queue/parser.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package queue diff --git a/snow/engine/common/queue/state.go b/snow/engine/common/queue/state.go index cae43f8c2101..68ba67ce38c1 100644 --- a/snow/engine/common/queue/state.go +++ b/snow/engine/common/queue/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package queue diff --git a/snow/engine/common/queue/test_job.go b/snow/engine/common/queue/test_job.go index 98ea33614b50..fd9af544fb62 100644 --- a/snow/engine/common/queue/test_job.go +++ b/snow/engine/common/queue/test_job.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package queue diff --git a/snow/engine/common/queue/test_parser.go b/snow/engine/common/queue/test_parser.go index 85a079cc1435..1cc1cfd2973f 100644 --- a/snow/engine/common/queue/test_parser.go +++ b/snow/engine/common/queue/test_parser.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package queue diff --git a/snow/engine/common/request.go b/snow/engine/common/request.go index d677a485c8f4..f92e347cc73b 100644 --- a/snow/engine/common/request.go +++ b/snow/engine/common/request.go @@ -1,11 +1,19 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common -import "github.com/ava-labs/avalanchego/ids" +import ( + "fmt" + + "github.com/ava-labs/avalanchego/ids" +) type Request struct { NodeID ids.NodeID RequestID uint32 } + +func (r Request) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("%s:%d", r.NodeID, r.RequestID)), nil +} diff --git a/snow/engine/common/request_test.go b/snow/engine/common/request_test.go new file mode 100644 index 000000000000..0da4c8c438f7 --- /dev/null +++ b/snow/engine/common/request_test.go @@ -0,0 +1,24 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package common + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/ids" +) + +func TestRequestJSONMarshal(t *testing.T) { + requestMap := map[Request]ids.ID{ + { + NodeID: ids.GenerateTestNodeID(), + RequestID: 12345, + }: ids.GenerateTestID(), + } + _, err := json.Marshal(requestMap) + require.NoError(t, err) +} diff --git a/snow/engine/common/sender.go b/snow/engine/common/sender.go index d596a35bcc2e..b40084fc714f 100644 --- a/snow/engine/common/sender.go +++ b/snow/engine/common/sender.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common @@ -175,15 +175,12 @@ type NetworkAppSender interface { // * An AppResponse from nodeID with ID [requestID] // * An AppRequestFailed from nodeID with ID [requestID] // Exactly one of the above messages will eventually be received per nodeID. - // A non-nil error should be considered fatal. SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID], requestID uint32, appRequestBytes []byte) error // Send an application-level response to a request. // This response must be in response to an AppRequest that the VM corresponding // to this AppSender received from [nodeID] with ID [requestID]. - // A non-nil error should be considered fatal. SendAppResponse(ctx context.Context, nodeID ids.NodeID, requestID uint32, appResponseBytes []byte) error // Gossip an application-level message. - // A non-nil error should be considered fatal. SendAppGossip(ctx context.Context, appGossipBytes []byte) error SendAppGossipSpecific(ctx context.Context, nodeIDs set.Set[ids.NodeID], appGossipBytes []byte) error } @@ -199,7 +196,6 @@ type CrossChainAppSender interface { // * A CrossChainAppRequestFailed from [chainID] with ID [requestID] // Exactly one of the above messages will eventually be received from // [chainID]. - // A non-nil error should be considered fatal. SendCrossChainAppRequest(ctx context.Context, chainID ids.ID, requestID uint32, appRequestBytes []byte) error // SendCrossChainAppResponse sends an application-level response to a // specific chain @@ -207,7 +203,6 @@ type CrossChainAppSender interface { // This response must be in response to a CrossChainAppRequest that the VM // corresponding to this CrossChainAppSender received from [chainID] with ID // [requestID]. - // A non-nil error should be considered fatal. SendCrossChainAppResponse(ctx context.Context, chainID ids.ID, requestID uint32, appResponseBytes []byte) error } diff --git a/snow/engine/common/state_syncer.go b/snow/engine/common/state_syncer.go index e23ad126407c..a6d159bb6949 100644 --- a/snow/engine/common/state_syncer.go +++ b/snow/engine/common/state_syncer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/test_bootstrap_tracker.go b/snow/engine/common/test_bootstrap_tracker.go index ba377b39dce3..2e940f1a43b1 100644 --- a/snow/engine/common/test_bootstrap_tracker.go +++ b/snow/engine/common/test_bootstrap_tracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/test_bootstrapper.go b/snow/engine/common/test_bootstrapper.go index 6ee9e223dc18..259fcb07fb3e 100644 --- a/snow/engine/common/test_bootstrapper.go +++ b/snow/engine/common/test_bootstrapper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/test_engine.go b/snow/engine/common/test_engine.go index 0081d63b5753..e07352d43713 100644 --- a/snow/engine/common/test_engine.go +++ b/snow/engine/common/test_engine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common @@ -125,7 +125,7 @@ type EngineTest struct { GetStateSummaryFrontierF, GetStateSummaryFrontierFailedF, GetAcceptedStateSummaryFailedF, GetAcceptedFrontierF, GetFailedF, GetAncestorsFailedF, QueryFailedF, GetAcceptedFrontierFailedF, GetAcceptedFailedF func(ctx context.Context, nodeID ids.NodeID, requestID uint32) error - AppRequestFailedF func(ctx context.Context, nodeID ids.NodeID, requestID uint32) error + AppRequestFailedF func(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *AppError) error StateSummaryFrontierF func(ctx context.Context, nodeID ids.NodeID, requestID uint32, summary []byte) error GetAcceptedStateSummaryF func(ctx context.Context, nodeID ids.NodeID, requestID uint32, keys set.Set[uint64]) error AcceptedStateSummaryF func(ctx context.Context, nodeID ids.NodeID, requestID uint32, summaryIDs set.Set[ids.ID]) error @@ -138,7 +138,7 @@ type EngineTest struct { AppGossipF func(ctx context.Context, nodeID ids.NodeID, msg []byte) error CrossChainAppRequestF func(ctx context.Context, chainID ids.ID, requestID uint32, deadline time.Time, msg []byte) error CrossChainAppResponseF func(ctx context.Context, chainID ids.ID, requestID uint32, msg []byte) error - CrossChainAppRequestFailedF func(ctx context.Context, chainID ids.ID, requestID uint32) error + CrossChainAppRequestFailedF func(ctx context.Context, chainID ids.ID, requestID uint32, appErr *AppError) error } func (e *EngineTest) Default(cant bool) { @@ -562,9 +562,9 @@ func (e *EngineTest) CrossChainAppRequest(ctx context.Context, chainID ids.ID, r return errCrossChainAppRequest } -func (e *EngineTest) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32) error { +func (e *EngineTest) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *AppError) error { if e.CrossChainAppRequestFailedF != nil { - return e.CrossChainAppRequestFailedF(ctx, chainID, requestID) + return e.CrossChainAppRequestFailedF(ctx, chainID, requestID, appErr) } if !e.CantCrossChainAppRequestFailed { return nil @@ -614,9 +614,9 @@ func (e *EngineTest) AppResponse(ctx context.Context, nodeID ids.NodeID, request return errAppResponse } -func (e *EngineTest) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error { +func (e *EngineTest) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *AppError) error { if e.AppRequestFailedF != nil { - return e.AppRequestFailedF(ctx, nodeID, requestID) + return e.AppRequestFailedF(ctx, nodeID, requestID, appErr) } if !e.CantAppRequestFailed { return nil diff --git a/snow/engine/common/test_sender.go b/snow/engine/common/test_sender.go index 5b76f3b6a2f4..32af682838fa 100644 --- a/snow/engine/common/test_sender.go +++ b/snow/engine/common/test_sender.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common @@ -15,7 +15,8 @@ import ( ) var ( - _ Sender = (*SenderTest)(nil) + _ Sender = (*SenderTest)(nil) + _ AppSender = (*FakeSender)(nil) errAccept = errors.New("unexpectedly called Accept") errSendAppRequest = errors.New("unexpectedly called SendAppRequest") @@ -358,3 +359,64 @@ func (s *SenderTest) SendAppGossipSpecific(ctx context.Context, nodeIDs set.Set[ } return errSendAppGossipSpecific } + +// FakeSender is used for testing +type FakeSender struct { + SentAppRequest, SentAppResponse, + SentAppGossip, SentAppGossipSpecific, + SentCrossChainAppRequest, SentCrossChainAppResponse chan []byte +} + +func (f FakeSender) SendAppRequest(_ context.Context, _ set.Set[ids.NodeID], _ uint32, bytes []byte) error { + if f.SentAppRequest == nil { + return nil + } + + f.SentAppRequest <- bytes + return nil +} + +func (f FakeSender) SendAppResponse(_ context.Context, _ ids.NodeID, _ uint32, bytes []byte) error { + if f.SentAppResponse == nil { + return nil + } + + f.SentAppResponse <- bytes + return nil +} + +func (f FakeSender) SendAppGossip(_ context.Context, bytes []byte) error { + if f.SentAppGossip == nil { + return nil + } + + f.SentAppGossip <- bytes + return nil +} + +func (f FakeSender) SendAppGossipSpecific(_ context.Context, _ set.Set[ids.NodeID], bytes []byte) error { + if f.SentAppGossipSpecific == nil { + return nil + } + + f.SentAppGossipSpecific <- bytes + return nil +} + +func (f FakeSender) SendCrossChainAppRequest(_ context.Context, _ ids.ID, _ uint32, bytes []byte) error { + if f.SentCrossChainAppRequest == nil { + return nil + } + + f.SentCrossChainAppRequest <- bytes + return nil +} + +func (f FakeSender) SendCrossChainAppResponse(_ context.Context, _ ids.ID, _ uint32, bytes []byte) error { + if f.SentCrossChainAppResponse == nil { + return nil + } + + f.SentCrossChainAppResponse <- bytes + return nil +} diff --git a/snow/engine/common/test_timer.go b/snow/engine/common/test_timer.go index e5e2b232d390..6da0d9251712 100644 --- a/snow/engine/common/test_timer.go +++ b/snow/engine/common/test_timer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/test_vm.go b/snow/engine/common/test_vm.go index 9d1a77ef2a9f..828b49f5e1fe 100644 --- a/snow/engine/common/test_vm.go +++ b/snow/engine/common/test_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common @@ -23,7 +23,6 @@ var ( errSetState = errors.New("unexpectedly called SetState") errShutdown = errors.New("unexpectedly called Shutdown") errCreateHandlers = errors.New("unexpectedly called CreateHandlers") - errCreateStaticHandlers = errors.New("unexpectedly called CreateStaticHandlers") errHealthCheck = errors.New("unexpectedly called HealthCheck") errConnected = errors.New("unexpectedly called Connected") errDisconnected = errors.New("unexpectedly called Disconnected") @@ -44,7 +43,7 @@ type TestVM struct { T *testing.T CantInitialize, CantSetState, - CantShutdown, CantCreateHandlers, CantCreateStaticHandlers, + CantShutdown, CantCreateHandlers, CantHealthCheck, CantConnected, CantDisconnected, CantVersion, CantAppRequest, CantAppResponse, CantAppGossip, CantAppRequestFailed, CantCrossChainAppRequest, CantCrossChainAppResponse, CantCrossChainAppRequestFailed bool @@ -53,18 +52,17 @@ type TestVM struct { SetStateF func(ctx context.Context, state snow.State) error ShutdownF func(context.Context) error CreateHandlersF func(context.Context) (map[string]http.Handler, error) - CreateStaticHandlersF func(context.Context) (map[string]http.Handler, error) ConnectedF func(ctx context.Context, nodeID ids.NodeID, nodeVersion *version.Application) error DisconnectedF func(ctx context.Context, nodeID ids.NodeID) error HealthCheckF func(context.Context) (interface{}, error) AppRequestF func(ctx context.Context, nodeID ids.NodeID, requestID uint32, deadline time.Time, msg []byte) error AppResponseF func(ctx context.Context, nodeID ids.NodeID, requestID uint32, msg []byte) error AppGossipF func(ctx context.Context, nodeID ids.NodeID, msg []byte) error - AppRequestFailedF func(ctx context.Context, nodeID ids.NodeID, requestID uint32) error + AppRequestFailedF func(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *AppError) error VersionF func(context.Context) (string, error) CrossChainAppRequestF func(ctx context.Context, chainID ids.ID, requestID uint32, deadline time.Time, msg []byte) error CrossChainAppResponseF func(ctx context.Context, chainID ids.ID, requestID uint32, msg []byte) error - CrossChainAppRequestFailedF func(ctx context.Context, chainID ids.ID, requestID uint32) error + CrossChainAppRequestFailedF func(ctx context.Context, chainID ids.ID, requestID uint32, appErr *AppError) error } func (vm *TestVM) Default(cant bool) { @@ -72,7 +70,6 @@ func (vm *TestVM) Default(cant bool) { vm.CantSetState = cant vm.CantShutdown = cant vm.CantCreateHandlers = cant - vm.CantCreateStaticHandlers = cant vm.CantHealthCheck = cant vm.CantAppRequest = cant vm.CantAppRequestFailed = cant @@ -152,16 +149,6 @@ func (vm *TestVM) CreateHandlers(ctx context.Context) (map[string]http.Handler, return nil, nil } -func (vm *TestVM) CreateStaticHandlers(ctx context.Context) (map[string]http.Handler, error) { - if vm.CreateStaticHandlersF != nil { - return vm.CreateStaticHandlersF(ctx) - } - if vm.CantCreateStaticHandlers && vm.T != nil { - require.FailNow(vm.T, errCreateStaticHandlers.Error()) - } - return nil, nil -} - func (vm *TestVM) HealthCheck(ctx context.Context) (interface{}, error) { if vm.HealthCheckF != nil { return vm.HealthCheckF(ctx) @@ -185,9 +172,9 @@ func (vm *TestVM) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID u return errAppRequest } -func (vm *TestVM) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error { +func (vm *TestVM) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *AppError) error { if vm.AppRequestFailedF != nil { - return vm.AppRequestFailedF(ctx, nodeID, requestID) + return vm.AppRequestFailedF(ctx, nodeID, requestID, appErr) } if !vm.CantAppRequestFailed { return nil @@ -237,9 +224,9 @@ func (vm *TestVM) CrossChainAppRequest(ctx context.Context, chainID ids.ID, requ return errCrossChainAppRequest } -func (vm *TestVM) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32) error { +func (vm *TestVM) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *AppError) error { if vm.CrossChainAppRequestFailedF != nil { - return vm.CrossChainAppRequestFailedF(ctx, chainID, requestID) + return vm.CrossChainAppRequestFailedF(ctx, chainID, requestID, appErr) } if !vm.CantCrossChainAppRequestFailed { return nil diff --git a/snow/engine/common/timer.go b/snow/engine/common/timer.go index 56312d08a4fa..432bb9170ccb 100644 --- a/snow/engine/common/timer.go +++ b/snow/engine/common/timer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/traced_bootstrapable_engine.go b/snow/engine/common/traced_bootstrapable_engine.go index d387df04ac34..4c64206ae081 100644 --- a/snow/engine/common/traced_bootstrapable_engine.go +++ b/snow/engine/common/traced_bootstrapable_engine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/traced_engine.go b/snow/engine/common/traced_engine.go index 28be03d6df7c..5ffad7c543d7 100644 --- a/snow/engine/common/traced_engine.go +++ b/snow/engine/common/traced_engine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common @@ -291,14 +291,14 @@ func (e *tracedEngine) AppResponse(ctx context.Context, nodeID ids.NodeID, reque return e.engine.AppResponse(ctx, nodeID, requestID, response) } -func (e *tracedEngine) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error { +func (e *tracedEngine) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *AppError) error { ctx, span := e.tracer.Start(ctx, "tracedEngine.AppRequestFailed", oteltrace.WithAttributes( attribute.Stringer("nodeID", nodeID), attribute.Int64("requestID", int64(requestID)), )) defer span.End() - return e.engine.AppRequestFailed(ctx, nodeID, requestID) + return e.engine.AppRequestFailed(ctx, nodeID, requestID, appErr) } func (e *tracedEngine) AppGossip(ctx context.Context, nodeID ids.NodeID, msg []byte) error { @@ -333,14 +333,14 @@ func (e *tracedEngine) CrossChainAppResponse(ctx context.Context, chainID ids.ID return e.engine.CrossChainAppResponse(ctx, chainID, requestID, response) } -func (e *tracedEngine) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32) error { +func (e *tracedEngine) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *AppError) error { ctx, span := e.tracer.Start(ctx, "tracedEngine.CrossChainAppRequestFailed", oteltrace.WithAttributes( attribute.Stringer("chainID", chainID), attribute.Int64("requestID", int64(requestID)), )) defer span.End() - return e.engine.CrossChainAppRequestFailed(ctx, chainID, requestID) + return e.engine.CrossChainAppRequestFailed(ctx, chainID, requestID, appErr) } func (e *tracedEngine) Connected(ctx context.Context, nodeID ids.NodeID, nodeVersion *version.Application) error { diff --git a/snow/engine/common/traced_state_syncer.go b/snow/engine/common/traced_state_syncer.go index db2569eef995..e598b6094076 100644 --- a/snow/engine/common/traced_state_syncer.go +++ b/snow/engine/common/traced_state_syncer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/snow/engine/common/tracker/accepted.go b/snow/engine/common/tracker/accepted.go index 0be64bc9035e..f6c63e3ff28d 100644 --- a/snow/engine/common/tracker/accepted.go +++ b/snow/engine/common/tracker/accepted.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracker diff --git a/snow/engine/common/tracker/accepted_test.go b/snow/engine/common/tracker/accepted_test.go index 7bb617d789f9..8ff489f51aea 100644 --- a/snow/engine/common/tracker/accepted_test.go +++ b/snow/engine/common/tracker/accepted_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracker diff --git a/snow/engine/common/tracker/peers.go b/snow/engine/common/tracker/peers.go index 94d653a53b1f..fdf070613d83 100644 --- a/snow/engine/common/tracker/peers.go +++ b/snow/engine/common/tracker/peers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracker diff --git a/snow/engine/common/tracker/peers_test.go b/snow/engine/common/tracker/peers_test.go index 4af065113385..b627b79a16ed 100644 --- a/snow/engine/common/tracker/peers_test.go +++ b/snow/engine/common/tracker/peers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracker diff --git a/snow/engine/common/tracker/startup.go b/snow/engine/common/tracker/startup.go index 282d88ce832d..c5e75613fcaa 100644 --- a/snow/engine/common/tracker/startup.go +++ b/snow/engine/common/tracker/startup.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracker diff --git a/snow/engine/common/vm.go b/snow/engine/common/vm.go index e77bdd552bbf..65cbfb158656 100644 --- a/snow/engine/common/vm.go +++ b/snow/engine/common/vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common @@ -65,23 +65,6 @@ type VM interface { // Version returns the version of the VM. Version(context.Context) (string, error) - // Creates the HTTP handlers for custom VM network calls. - // - // This exposes handlers that the outside world can use to communicate with - // a static reference to the VM. Each handler has the path: - // [Address of node]/ext/VM/[VM ID]/[extension] - // - // Returns a mapping from [extension]s to HTTP handlers. - // - // For example, it might make sense to have an extension for creating - // genesis bytes this VM can interpret. - // - // Note: If this method is called, no other method will be called on this VM. - // Each registered VM will have a single instance created to handle static - // APIs. This instance will be handled separately from instances created to - // service an instance of a chain. - CreateStaticHandlers(context.Context) (map[string]http.Handler, error) - // Creates the HTTP handlers for custom chain network calls. // // This exposes handlers that the outside world can use to communicate with diff --git a/snow/engine/snowman/ancestor/tree.go b/snow/engine/snowman/ancestor/tree.go index 3d7fda833cbc..9e0eb4e4f02f 100644 --- a/snow/engine/snowman/ancestor/tree.go +++ b/snow/engine/snowman/ancestor/tree.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ancestor diff --git a/snow/engine/snowman/ancestor/tree_test.go b/snow/engine/snowman/ancestor/tree_test.go index 605f2a08572d..d17d38ceb0ca 100644 --- a/snow/engine/snowman/ancestor/tree_test.go +++ b/snow/engine/snowman/ancestor/tree_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ancestor diff --git a/snow/engine/snowman/block/batched_vm.go b/snow/engine/snowman/block/batched_vm.go index 3d5b869b98c1..ad52e3592ae6 100644 --- a/snow/engine/snowman/block/batched_vm.go +++ b/snow/engine/snowman/block/batched_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/batched_vm_test.go b/snow/engine/snowman/block/batched_vm_test.go index 92426c1b474d..b4d251c284ba 100644 --- a/snow/engine/snowman/block/batched_vm_test.go +++ b/snow/engine/snowman/block/batched_vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/block_context_vm.go b/snow/engine/snowman/block/block_context_vm.go index 4a259571a006..6b8b78235431 100644 --- a/snow/engine/snowman/block/block_context_vm.go +++ b/snow/engine/snowman/block/block_context_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/mocks/build_block_with_context_vm.go b/snow/engine/snowman/block/mock_build_block_with_context_vm.go similarity index 82% rename from snow/engine/snowman/block/mocks/build_block_with_context_vm.go rename to snow/engine/snowman/block/mock_build_block_with_context_vm.go index 2a72ada73e2c..016007b0dee7 100644 --- a/snow/engine/snowman/block/mocks/build_block_with_context_vm.go +++ b/snow/engine/snowman/block/mock_build_block_with_context_vm.go @@ -1,18 +1,19 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/engine/snowman/block (interfaces: BuildBlockWithContextChainVM) +// +// Generated by this command: +// +// mockgen -package=block -destination=snow/engine/snowman/block/mock_build_block_with_context_vm.go github.com/ava-labs/avalanchego/snow/engine/snowman/block BuildBlockWithContextChainVM +// -// Package mocks is a generated GoMock package. -package mocks +// Package block is a generated GoMock package. +package block import ( context "context" reflect "reflect" snowman "github.com/ava-labs/avalanchego/snow/consensus/snowman" - block "github.com/ava-labs/avalanchego/snow/engine/snowman/block" gomock "go.uber.org/mock/gomock" ) @@ -40,7 +41,7 @@ func (m *MockBuildBlockWithContextChainVM) EXPECT() *MockBuildBlockWithContextCh } // BuildBlockWithContext mocks base method. -func (m *MockBuildBlockWithContextChainVM) BuildBlockWithContext(arg0 context.Context, arg1 *block.Context) (snowman.Block, error) { +func (m *MockBuildBlockWithContextChainVM) BuildBlockWithContext(arg0 context.Context, arg1 *Context) (snowman.Block, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "BuildBlockWithContext", arg0, arg1) ret0, _ := ret[0].(snowman.Block) @@ -49,7 +50,7 @@ func (m *MockBuildBlockWithContextChainVM) BuildBlockWithContext(arg0 context.Co } // BuildBlockWithContext indicates an expected call of BuildBlockWithContext. -func (mr *MockBuildBlockWithContextChainVMMockRecorder) BuildBlockWithContext(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockBuildBlockWithContextChainVMMockRecorder) BuildBlockWithContext(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildBlockWithContext", reflect.TypeOf((*MockBuildBlockWithContextChainVM)(nil).BuildBlockWithContext), arg0, arg1) } diff --git a/snow/engine/snowman/block/mocks/chain_vm.go b/snow/engine/snowman/block/mock_chain_vm.go similarity index 82% rename from snow/engine/snowman/block/mocks/chain_vm.go rename to snow/engine/snowman/block/mock_chain_vm.go index 2a2446a94ee5..ad99e3f716d0 100644 --- a/snow/engine/snowman/block/mocks/chain_vm.go +++ b/snow/engine/snowman/block/mock_chain_vm.go @@ -1,11 +1,13 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/engine/snowman/block (interfaces: ChainVM) +// +// Generated by this command: +// +// mockgen -package=block -destination=snow/engine/snowman/block/mock_chain_vm.go github.com/ava-labs/avalanchego/snow/engine/snowman/block ChainVM +// -// Package mocks is a generated GoMock package. -package mocks +// Package block is a generated GoMock package. +package block import ( context "context" @@ -54,7 +56,7 @@ func (m *MockChainVM) AppGossip(arg0 context.Context, arg1 ids.NodeID, arg2 []by } // AppGossip indicates an expected call of AppGossip. -func (mr *MockChainVMMockRecorder) AppGossip(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) AppGossip(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppGossip", reflect.TypeOf((*MockChainVM)(nil).AppGossip), arg0, arg1, arg2) } @@ -68,23 +70,23 @@ func (m *MockChainVM) AppRequest(arg0 context.Context, arg1 ids.NodeID, arg2 uin } // AppRequest indicates an expected call of AppRequest. -func (mr *MockChainVMMockRecorder) AppRequest(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) AppRequest(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppRequest", reflect.TypeOf((*MockChainVM)(nil).AppRequest), arg0, arg1, arg2, arg3, arg4) } // AppRequestFailed mocks base method. -func (m *MockChainVM) AppRequestFailed(arg0 context.Context, arg1 ids.NodeID, arg2 uint32) error { +func (m *MockChainVM) AppRequestFailed(arg0 context.Context, arg1 ids.NodeID, arg2 uint32, arg3 *common.AppError) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AppRequestFailed", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "AppRequestFailed", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // AppRequestFailed indicates an expected call of AppRequestFailed. -func (mr *MockChainVMMockRecorder) AppRequestFailed(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) AppRequestFailed(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppRequestFailed", reflect.TypeOf((*MockChainVM)(nil).AppRequestFailed), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppRequestFailed", reflect.TypeOf((*MockChainVM)(nil).AppRequestFailed), arg0, arg1, arg2, arg3) } // AppResponse mocks base method. @@ -96,7 +98,7 @@ func (m *MockChainVM) AppResponse(arg0 context.Context, arg1 ids.NodeID, arg2 ui } // AppResponse indicates an expected call of AppResponse. -func (mr *MockChainVMMockRecorder) AppResponse(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) AppResponse(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppResponse", reflect.TypeOf((*MockChainVM)(nil).AppResponse), arg0, arg1, arg2, arg3) } @@ -111,7 +113,7 @@ func (m *MockChainVM) BuildBlock(arg0 context.Context) (snowman.Block, error) { } // BuildBlock indicates an expected call of BuildBlock. -func (mr *MockChainVMMockRecorder) BuildBlock(arg0 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) BuildBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildBlock", reflect.TypeOf((*MockChainVM)(nil).BuildBlock), arg0) } @@ -125,7 +127,7 @@ func (m *MockChainVM) Connected(arg0 context.Context, arg1 ids.NodeID, arg2 *ver } // Connected indicates an expected call of Connected. -func (mr *MockChainVMMockRecorder) Connected(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) Connected(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connected", reflect.TypeOf((*MockChainVM)(nil).Connected), arg0, arg1, arg2) } @@ -140,26 +142,11 @@ func (m *MockChainVM) CreateHandlers(arg0 context.Context) (map[string]http.Hand } // CreateHandlers indicates an expected call of CreateHandlers. -func (mr *MockChainVMMockRecorder) CreateHandlers(arg0 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) CreateHandlers(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHandlers", reflect.TypeOf((*MockChainVM)(nil).CreateHandlers), arg0) } -// CreateStaticHandlers mocks base method. -func (m *MockChainVM) CreateStaticHandlers(arg0 context.Context) (map[string]http.Handler, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateStaticHandlers", arg0) - ret0, _ := ret[0].(map[string]http.Handler) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateStaticHandlers indicates an expected call of CreateStaticHandlers. -func (mr *MockChainVMMockRecorder) CreateStaticHandlers(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateStaticHandlers", reflect.TypeOf((*MockChainVM)(nil).CreateStaticHandlers), arg0) -} - // CrossChainAppRequest mocks base method. func (m *MockChainVM) CrossChainAppRequest(arg0 context.Context, arg1 ids.ID, arg2 uint32, arg3 time.Time, arg4 []byte) error { m.ctrl.T.Helper() @@ -169,23 +156,23 @@ func (m *MockChainVM) CrossChainAppRequest(arg0 context.Context, arg1 ids.ID, ar } // CrossChainAppRequest indicates an expected call of CrossChainAppRequest. -func (mr *MockChainVMMockRecorder) CrossChainAppRequest(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) CrossChainAppRequest(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CrossChainAppRequest", reflect.TypeOf((*MockChainVM)(nil).CrossChainAppRequest), arg0, arg1, arg2, arg3, arg4) } // CrossChainAppRequestFailed mocks base method. -func (m *MockChainVM) CrossChainAppRequestFailed(arg0 context.Context, arg1 ids.ID, arg2 uint32) error { +func (m *MockChainVM) CrossChainAppRequestFailed(arg0 context.Context, arg1 ids.ID, arg2 uint32, arg3 *common.AppError) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CrossChainAppRequestFailed", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "CrossChainAppRequestFailed", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // CrossChainAppRequestFailed indicates an expected call of CrossChainAppRequestFailed. -func (mr *MockChainVMMockRecorder) CrossChainAppRequestFailed(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) CrossChainAppRequestFailed(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CrossChainAppRequestFailed", reflect.TypeOf((*MockChainVM)(nil).CrossChainAppRequestFailed), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CrossChainAppRequestFailed", reflect.TypeOf((*MockChainVM)(nil).CrossChainAppRequestFailed), arg0, arg1, arg2, arg3) } // CrossChainAppResponse mocks base method. @@ -197,7 +184,7 @@ func (m *MockChainVM) CrossChainAppResponse(arg0 context.Context, arg1 ids.ID, a } // CrossChainAppResponse indicates an expected call of CrossChainAppResponse. -func (mr *MockChainVMMockRecorder) CrossChainAppResponse(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) CrossChainAppResponse(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CrossChainAppResponse", reflect.TypeOf((*MockChainVM)(nil).CrossChainAppResponse), arg0, arg1, arg2, arg3) } @@ -211,7 +198,7 @@ func (m *MockChainVM) Disconnected(arg0 context.Context, arg1 ids.NodeID) error } // Disconnected indicates an expected call of Disconnected. -func (mr *MockChainVMMockRecorder) Disconnected(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) Disconnected(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnected", reflect.TypeOf((*MockChainVM)(nil).Disconnected), arg0, arg1) } @@ -226,7 +213,7 @@ func (m *MockChainVM) GetBlock(arg0 context.Context, arg1 ids.ID) (snowman.Block } // GetBlock indicates an expected call of GetBlock. -func (mr *MockChainVMMockRecorder) GetBlock(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) GetBlock(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockChainVM)(nil).GetBlock), arg0, arg1) } @@ -241,22 +228,22 @@ func (m *MockChainVM) GetBlockIDAtHeight(arg0 context.Context, arg1 uint64) (ids } // GetBlockIDAtHeight indicates an expected call of GetBlockIDAtHeight. -func (mr *MockChainVMMockRecorder) GetBlockIDAtHeight(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) GetBlockIDAtHeight(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockIDAtHeight", reflect.TypeOf((*MockChainVM)(nil).GetBlockIDAtHeight), arg0, arg1) } // HealthCheck mocks base method. -func (m *MockChainVM) HealthCheck(arg0 context.Context) (interface{}, error) { +func (m *MockChainVM) HealthCheck(arg0 context.Context) (any, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HealthCheck", arg0) - ret0, _ := ret[0].(interface{}) + ret0, _ := ret[0].(any) ret1, _ := ret[1].(error) return ret0, ret1 } // HealthCheck indicates an expected call of HealthCheck. -func (mr *MockChainVMMockRecorder) HealthCheck(arg0 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) HealthCheck(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockChainVM)(nil).HealthCheck), arg0) } @@ -270,7 +257,7 @@ func (m *MockChainVM) Initialize(arg0 context.Context, arg1 *snow.Context, arg2 } // Initialize indicates an expected call of Initialize. -func (mr *MockChainVMMockRecorder) Initialize(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) Initialize(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockChainVM)(nil).Initialize), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) } @@ -285,7 +272,7 @@ func (m *MockChainVM) LastAccepted(arg0 context.Context) (ids.ID, error) { } // LastAccepted indicates an expected call of LastAccepted. -func (mr *MockChainVMMockRecorder) LastAccepted(arg0 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) LastAccepted(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastAccepted", reflect.TypeOf((*MockChainVM)(nil).LastAccepted), arg0) } @@ -300,7 +287,7 @@ func (m *MockChainVM) ParseBlock(arg0 context.Context, arg1 []byte) (snowman.Blo } // ParseBlock indicates an expected call of ParseBlock. -func (mr *MockChainVMMockRecorder) ParseBlock(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) ParseBlock(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParseBlock", reflect.TypeOf((*MockChainVM)(nil).ParseBlock), arg0, arg1) } @@ -314,7 +301,7 @@ func (m *MockChainVM) SetPreference(arg0 context.Context, arg1 ids.ID) error { } // SetPreference indicates an expected call of SetPreference. -func (mr *MockChainVMMockRecorder) SetPreference(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) SetPreference(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPreference", reflect.TypeOf((*MockChainVM)(nil).SetPreference), arg0, arg1) } @@ -328,7 +315,7 @@ func (m *MockChainVM) SetState(arg0 context.Context, arg1 snow.State) error { } // SetState indicates an expected call of SetState. -func (mr *MockChainVMMockRecorder) SetState(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) SetState(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetState", reflect.TypeOf((*MockChainVM)(nil).SetState), arg0, arg1) } @@ -342,7 +329,7 @@ func (m *MockChainVM) Shutdown(arg0 context.Context) error { } // Shutdown indicates an expected call of Shutdown. -func (mr *MockChainVMMockRecorder) Shutdown(arg0 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) Shutdown(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockChainVM)(nil).Shutdown), arg0) } @@ -356,7 +343,7 @@ func (m *MockChainVM) VerifyHeightIndex(arg0 context.Context) error { } // VerifyHeightIndex indicates an expected call of VerifyHeightIndex. -func (mr *MockChainVMMockRecorder) VerifyHeightIndex(arg0 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) VerifyHeightIndex(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyHeightIndex", reflect.TypeOf((*MockChainVM)(nil).VerifyHeightIndex), arg0) } @@ -371,7 +358,7 @@ func (m *MockChainVM) Version(arg0 context.Context) (string, error) { } // Version indicates an expected call of Version. -func (mr *MockChainVMMockRecorder) Version(arg0 interface{}) *gomock.Call { +func (mr *MockChainVMMockRecorder) Version(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*MockChainVM)(nil).Version), arg0) } diff --git a/snow/engine/snowman/block/mocks/state_syncable_vm.go b/snow/engine/snowman/block/mock_state_syncable_vm.go similarity index 83% rename from snow/engine/snowman/block/mocks/state_syncable_vm.go rename to snow/engine/snowman/block/mock_state_syncable_vm.go index 50a1fe92e117..8d8abca53a0c 100644 --- a/snow/engine/snowman/block/mocks/state_syncable_vm.go +++ b/snow/engine/snowman/block/mock_state_syncable_vm.go @@ -1,17 +1,18 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/engine/snowman/block (interfaces: StateSyncableVM) +// +// Generated by this command: +// +// mockgen -package=block -destination=snow/engine/snowman/block/mock_state_syncable_vm.go github.com/ava-labs/avalanchego/snow/engine/snowman/block StateSyncableVM +// -// Package mocks is a generated GoMock package. -package mocks +// Package block is a generated GoMock package. +package block import ( context "context" reflect "reflect" - block "github.com/ava-labs/avalanchego/snow/engine/snowman/block" gomock "go.uber.org/mock/gomock" ) @@ -39,61 +40,61 @@ func (m *MockStateSyncableVM) EXPECT() *MockStateSyncableVMMockRecorder { } // GetLastStateSummary mocks base method. -func (m *MockStateSyncableVM) GetLastStateSummary(arg0 context.Context) (block.StateSummary, error) { +func (m *MockStateSyncableVM) GetLastStateSummary(arg0 context.Context) (StateSummary, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetLastStateSummary", arg0) - ret0, _ := ret[0].(block.StateSummary) + ret0, _ := ret[0].(StateSummary) ret1, _ := ret[1].(error) return ret0, ret1 } // GetLastStateSummary indicates an expected call of GetLastStateSummary. -func (mr *MockStateSyncableVMMockRecorder) GetLastStateSummary(arg0 interface{}) *gomock.Call { +func (mr *MockStateSyncableVMMockRecorder) GetLastStateSummary(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastStateSummary", reflect.TypeOf((*MockStateSyncableVM)(nil).GetLastStateSummary), arg0) } // GetOngoingSyncStateSummary mocks base method. -func (m *MockStateSyncableVM) GetOngoingSyncStateSummary(arg0 context.Context) (block.StateSummary, error) { +func (m *MockStateSyncableVM) GetOngoingSyncStateSummary(arg0 context.Context) (StateSummary, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetOngoingSyncStateSummary", arg0) - ret0, _ := ret[0].(block.StateSummary) + ret0, _ := ret[0].(StateSummary) ret1, _ := ret[1].(error) return ret0, ret1 } // GetOngoingSyncStateSummary indicates an expected call of GetOngoingSyncStateSummary. -func (mr *MockStateSyncableVMMockRecorder) GetOngoingSyncStateSummary(arg0 interface{}) *gomock.Call { +func (mr *MockStateSyncableVMMockRecorder) GetOngoingSyncStateSummary(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOngoingSyncStateSummary", reflect.TypeOf((*MockStateSyncableVM)(nil).GetOngoingSyncStateSummary), arg0) } // GetStateSummary mocks base method. -func (m *MockStateSyncableVM) GetStateSummary(arg0 context.Context, arg1 uint64) (block.StateSummary, error) { +func (m *MockStateSyncableVM) GetStateSummary(arg0 context.Context, arg1 uint64) (StateSummary, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetStateSummary", arg0, arg1) - ret0, _ := ret[0].(block.StateSummary) + ret0, _ := ret[0].(StateSummary) ret1, _ := ret[1].(error) return ret0, ret1 } // GetStateSummary indicates an expected call of GetStateSummary. -func (mr *MockStateSyncableVMMockRecorder) GetStateSummary(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateSyncableVMMockRecorder) GetStateSummary(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStateSummary", reflect.TypeOf((*MockStateSyncableVM)(nil).GetStateSummary), arg0, arg1) } // ParseStateSummary mocks base method. -func (m *MockStateSyncableVM) ParseStateSummary(arg0 context.Context, arg1 []byte) (block.StateSummary, error) { +func (m *MockStateSyncableVM) ParseStateSummary(arg0 context.Context, arg1 []byte) (StateSummary, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParseStateSummary", arg0, arg1) - ret0, _ := ret[0].(block.StateSummary) + ret0, _ := ret[0].(StateSummary) ret1, _ := ret[1].(error) return ret0, ret1 } // ParseStateSummary indicates an expected call of ParseStateSummary. -func (mr *MockStateSyncableVMMockRecorder) ParseStateSummary(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateSyncableVMMockRecorder) ParseStateSummary(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParseStateSummary", reflect.TypeOf((*MockStateSyncableVM)(nil).ParseStateSummary), arg0, arg1) } @@ -108,7 +109,7 @@ func (m *MockStateSyncableVM) StateSyncEnabled(arg0 context.Context) (bool, erro } // StateSyncEnabled indicates an expected call of StateSyncEnabled. -func (mr *MockStateSyncableVMMockRecorder) StateSyncEnabled(arg0 interface{}) *gomock.Call { +func (mr *MockStateSyncableVMMockRecorder) StateSyncEnabled(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSyncEnabled", reflect.TypeOf((*MockStateSyncableVM)(nil).StateSyncEnabled), arg0) } diff --git a/snow/engine/snowman/block/mocks/with_verify_context.go b/snow/engine/snowman/block/mock_with_verify_context.go similarity index 85% rename from snow/engine/snowman/block/mocks/with_verify_context.go rename to snow/engine/snowman/block/mock_with_verify_context.go index ea509980f130..1c18e3e9f6cb 100644 --- a/snow/engine/snowman/block/mocks/with_verify_context.go +++ b/snow/engine/snowman/block/mock_with_verify_context.go @@ -1,17 +1,18 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/engine/snowman/block (interfaces: WithVerifyContext) +// +// Generated by this command: +// +// mockgen -package=block -destination=snow/engine/snowman/block/mock_with_verify_context.go github.com/ava-labs/avalanchego/snow/engine/snowman/block WithVerifyContext +// -// Package mocks is a generated GoMock package. -package mocks +// Package block is a generated GoMock package. +package block import ( context "context" reflect "reflect" - block "github.com/ava-labs/avalanchego/snow/engine/snowman/block" gomock "go.uber.org/mock/gomock" ) @@ -48,13 +49,13 @@ func (m *MockWithVerifyContext) ShouldVerifyWithContext(arg0 context.Context) (b } // ShouldVerifyWithContext indicates an expected call of ShouldVerifyWithContext. -func (mr *MockWithVerifyContextMockRecorder) ShouldVerifyWithContext(arg0 interface{}) *gomock.Call { +func (mr *MockWithVerifyContextMockRecorder) ShouldVerifyWithContext(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldVerifyWithContext", reflect.TypeOf((*MockWithVerifyContext)(nil).ShouldVerifyWithContext), arg0) } // VerifyWithContext mocks base method. -func (m *MockWithVerifyContext) VerifyWithContext(arg0 context.Context, arg1 *block.Context) error { +func (m *MockWithVerifyContext) VerifyWithContext(arg0 context.Context, arg1 *Context) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "VerifyWithContext", arg0, arg1) ret0, _ := ret[0].(error) @@ -62,7 +63,7 @@ func (m *MockWithVerifyContext) VerifyWithContext(arg0 context.Context, arg1 *bl } // VerifyWithContext indicates an expected call of VerifyWithContext. -func (mr *MockWithVerifyContextMockRecorder) VerifyWithContext(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockWithVerifyContextMockRecorder) VerifyWithContext(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyWithContext", reflect.TypeOf((*MockWithVerifyContext)(nil).VerifyWithContext), arg0, arg1) } diff --git a/snow/engine/snowman/block/state_summary.go b/snow/engine/snowman/block/state_summary.go index 337a27d9f1d8..d89d77a22396 100644 --- a/snow/engine/snowman/block/state_summary.go +++ b/snow/engine/snowman/block/state_summary.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/state_sync_mode.go b/snow/engine/snowman/block/state_sync_mode.go index 79f5c2e8043e..35da3ab4eda9 100644 --- a/snow/engine/snowman/block/state_sync_mode.go +++ b/snow/engine/snowman/block/state_sync_mode.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/state_syncable_vm.go b/snow/engine/snowman/block/state_syncable_vm.go index 5c25f37a7ad7..0457505183e5 100644 --- a/snow/engine/snowman/block/state_syncable_vm.go +++ b/snow/engine/snowman/block/state_syncable_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/test_batched_vm.go b/snow/engine/snowman/block/test_batched_vm.go index ef7991156070..e5d654ec4a87 100644 --- a/snow/engine/snowman/block/test_batched_vm.go +++ b/snow/engine/snowman/block/test_batched_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/test_state_summary.go b/snow/engine/snowman/block/test_state_summary.go index 089e6dcfd364..7287cff10120 100644 --- a/snow/engine/snowman/block/test_state_summary.go +++ b/snow/engine/snowman/block/test_state_summary.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/test_state_syncable_vm.go b/snow/engine/snowman/block/test_state_syncable_vm.go index b05dd8118683..f1eeb9606642 100644 --- a/snow/engine/snowman/block/test_state_syncable_vm.go +++ b/snow/engine/snowman/block/test_state_syncable_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/test_vm.go b/snow/engine/snowman/block/test_vm.go index 4f3a2835eda5..376dd27066f7 100644 --- a/snow/engine/snowman/block/test_vm.go +++ b/snow/engine/snowman/block/test_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/block/vm.go b/snow/engine/snowman/block/vm.go index 13d4fa75ed02..4153632a7616 100644 --- a/snow/engine/snowman/block/vm.go +++ b/snow/engine/snowman/block/vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/snow/engine/snowman/bootstrap/block_job.go b/snow/engine/snowman/bootstrap/block_job.go index 06ae8fbcb84f..696cbddb58a9 100644 --- a/snow/engine/snowman/bootstrap/block_job.go +++ b/snow/engine/snowman/bootstrap/block_job.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap diff --git a/snow/engine/snowman/bootstrap/bootstrapper.go b/snow/engine/snowman/bootstrap/bootstrapper.go index 0e2c7e0dab16..29754a24d734 100644 --- a/snow/engine/snowman/bootstrap/bootstrapper.go +++ b/snow/engine/snowman/bootstrap/bootstrapper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap @@ -781,6 +781,7 @@ func (b *Bootstrapper) Timeout(ctx context.Context) error { func (b *Bootstrapper) restartBootstrapping(ctx context.Context) error { b.Ctx.Log.Debug("Checking for new frontiers") b.restarted = true + b.outstandingRequests = bimap.New[common.Request, ids.ID]() return b.startBootstrapping(ctx) } diff --git a/snow/engine/snowman/bootstrap/bootstrapper_test.go b/snow/engine/snowman/bootstrap/bootstrapper_test.go index 83cbca730ba5..08f63f163b80 100644 --- a/snow/engine/snowman/bootstrap/bootstrapper_test.go +++ b/snow/engine/snowman/bootstrap/bootstrapper_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap @@ -26,6 +26,7 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/common/tracker" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/engine/snowman/getter" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/logging" @@ -38,7 +39,8 @@ var errUnknownBlock = errors.New("unknown block") func newConfig(t *testing.T) (Config, ids.NodeID, *common.SenderTest, *block.TestVM) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() @@ -104,7 +106,8 @@ func TestBootstrapperStartsOnlyIfEnoughStakeIsConnected(t *testing.T) { sender.Default(true) vm.Default(true) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) // create boostrapper configuration peers := validators.NewManager() sampleK := 2 @@ -1314,7 +1317,8 @@ func TestBootstrapContinueAfterHalt(t *testing.T) { func TestBootstrapNoParseOnNew(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) peers := validators.NewManager() sender := &common.SenderTest{} @@ -1423,3 +1427,124 @@ func TestBootstrapNoParseOnNew(t *testing.T) { ) require.NoError(err) } + +func TestBootstrapperReceiveStaleAncestorsMessage(t *testing.T) { + require := require.New(t) + + config, peerID, sender, vm := newConfig(t) + + var ( + blkID0 = ids.GenerateTestID() + blkBytes0 = utils.RandomBytes(1024) + blk0 = &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: blkID0, + StatusV: choices.Accepted, + }, + HeightV: 0, + BytesV: blkBytes0, + } + + blkID1 = ids.GenerateTestID() + blkBytes1 = utils.RandomBytes(1024) + blk1 = &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: blkID1, + StatusV: choices.Processing, + }, + ParentV: blk0.IDV, + HeightV: blk0.HeightV + 1, + BytesV: blkBytes1, + } + + blkID2 = ids.GenerateTestID() + blkBytes2 = utils.RandomBytes(1024) + blk2 = &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: blkID2, + StatusV: choices.Processing, + }, + ParentV: blk1.IDV, + HeightV: blk1.HeightV + 1, + BytesV: blkBytes2, + } + ) + + vm.LastAcceptedF = func(context.Context) (ids.ID, error) { + return blk0.ID(), nil + } + vm.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { + require.Equal(blkID0, blkID) + return blk0, nil + } + bs, err := New( + config, + func(context.Context, uint32) error { + config.Ctx.State.Set(snow.EngineState{ + Type: p2p.EngineType_ENGINE_TYPE_SNOWMAN, + State: snow.NormalOp, + }) + return nil + }, + ) + require.NoError(err) + + vm.CantSetState = false + require.NoError(bs.Start(context.Background(), 0)) + + vm.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { + switch blkID { + case blkID0: + return blk0, nil + case blkID1: + if blk1.StatusV == choices.Accepted { + return blk1, nil + } + return nil, database.ErrNotFound + case blkID2: + if blk2.StatusV == choices.Accepted { + return blk2, nil + } + return nil, database.ErrNotFound + default: + require.FailNow(database.ErrNotFound.Error()) + return nil, database.ErrNotFound + } + } + vm.ParseBlockF = func(_ context.Context, blkBytes []byte) (snowman.Block, error) { + switch { + case bytes.Equal(blkBytes, blkBytes0): + return blk0, nil + case bytes.Equal(blkBytes, blkBytes1): + return blk1, nil + case bytes.Equal(blkBytes, blkBytes2): + return blk2, nil + default: + require.FailNow(errUnknownBlock.Error()) + return nil, errUnknownBlock + } + } + + requestIDs := map[ids.ID]uint32{} + sender.SendGetAncestorsF = func(_ context.Context, vdr ids.NodeID, reqID uint32, blkID ids.ID) { + require.Equal(peerID, vdr) + requestIDs[blkID] = reqID + } + + require.NoError(bs.startSyncing(context.Background(), []ids.ID{blkID1, blkID2})) // should request blk2 and blk1 + + reqIDBlk1, ok := requestIDs[blkID1] + require.True(ok) + reqIDBlk2, ok := requestIDs[blkID2] + require.True(ok) + + require.NoError(bs.Ancestors(context.Background(), peerID, reqIDBlk2, [][]byte{blkBytes2, blkBytes1})) + + require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) + require.Equal(choices.Accepted, blk0.Status()) + require.Equal(choices.Accepted, blk1.Status()) + require.Equal(choices.Accepted, blk2.Status()) + + require.NoError(bs.Ancestors(context.Background(), peerID, reqIDBlk1, [][]byte{blkBytes1})) + require.Equal(snow.Bootstrapping, config.Ctx.State.Get().State) +} diff --git a/snow/engine/snowman/bootstrap/config.go b/snow/engine/snowman/bootstrap/config.go index 785654cb72dc..6fb8894db96f 100644 --- a/snow/engine/snowman/bootstrap/config.go +++ b/snow/engine/snowman/bootstrap/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap diff --git a/snow/engine/snowman/bootstrap/metrics.go b/snow/engine/snowman/bootstrap/metrics.go index 9359ecfadb19..f6ad90d16419 100644 --- a/snow/engine/snowman/bootstrap/metrics.go +++ b/snow/engine/snowman/bootstrap/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bootstrap diff --git a/snow/engine/snowman/config.go b/snow/engine/snowman/config.go index 65a24a2ea816..3162471a2476 100644 --- a/snow/engine/snowman/config.go +++ b/snow/engine/snowman/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/engine/snowman/config_test.go b/snow/engine/snowman/config_test.go index 9611990d9d95..fe66256c68db 100644 --- a/snow/engine/snowman/config_test.go +++ b/snow/engine/snowman/config_test.go @@ -1,21 +1,25 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman import ( - "github.com/ava-labs/avalanchego/snow" + "testing" + "github.com/ava-labs/avalanchego/snow/consensus/snowball" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/common/tracker" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" ) -func DefaultConfig() Config { +func DefaultConfig(t testing.TB) Config { + ctx := snowtest.Context(t, snowtest.PChainID) + return Config{ - Ctx: snow.DefaultConsensusContextTest(), + Ctx: snowtest.ConsensusContext(ctx), VM: &block.TestVM{}, Sender: &common.SenderTest{}, Validators: validators.NewManager(), diff --git a/snow/engine/snowman/engine.go b/snow/engine/snowman/engine.go index 37985f5b48fa..b5e3fb1020e3 100644 --- a/snow/engine/snowman/engine.go +++ b/snow/engine/snowman/engine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/engine/snowman/getter/getter.go b/snow/engine/snowman/getter/getter.go index 0f9dc40b0a19..ff8fe13f8fe9 100644 --- a/snow/engine/snowman/getter/getter.go +++ b/snow/engine/snowman/getter/getter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package getter diff --git a/snow/engine/snowman/getter/getter_test.go b/snow/engine/snowman/getter/getter_test.go index 12ecd1abdd80..4fc03d4795d6 100644 --- a/snow/engine/snowman/getter/getter_test.go +++ b/snow/engine/snowman/getter/getter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package getter @@ -20,7 +20,6 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" ) @@ -29,7 +28,7 @@ var errUnknownBlock = errors.New("unknown block") type StateSyncEnabledMock struct { *block.TestVM - *mocks.MockStateSyncableVM + *block.MockStateSyncableVM } func newTest(t *testing.T) (common.AllGetsServer, StateSyncEnabledMock, *common.SenderTest) { @@ -37,7 +36,7 @@ func newTest(t *testing.T) (common.AllGetsServer, StateSyncEnabledMock, *common. vm := StateSyncEnabledMock{ TestVM: &block.TestVM{}, - MockStateSyncableVM: mocks.NewMockStateSyncableVM(ctrl), + MockStateSyncableVM: block.NewMockStateSyncableVM(ctrl), } sender := &common.SenderTest{ diff --git a/snow/engine/snowman/issuer.go b/snow/engine/snowman/issuer.go index 3558d47360dc..d952dfe2cc6b 100644 --- a/snow/engine/snowman/issuer.go +++ b/snow/engine/snowman/issuer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/engine/snowman/memory_block.go b/snow/engine/snowman/memory_block.go index c3b476b9f496..d91118afa5b3 100644 --- a/snow/engine/snowman/memory_block.go +++ b/snow/engine/snowman/memory_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/engine/snowman/metrics.go b/snow/engine/snowman/metrics.go index dfdc92c636db..5dd65d8afa14 100644 --- a/snow/engine/snowman/metrics.go +++ b/snow/engine/snowman/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/engine/snowman/syncer/config.go b/snow/engine/snowman/syncer/config.go index 9f7acf7a4910..b5fae133a376 100644 --- a/snow/engine/snowman/syncer/config.go +++ b/snow/engine/snowman/syncer/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package syncer diff --git a/snow/engine/snowman/syncer/state_syncer.go b/snow/engine/snowman/syncer/state_syncer.go index 42db0b264cd1..bc549a0ce93a 100644 --- a/snow/engine/snowman/syncer/state_syncer.go +++ b/snow/engine/snowman/syncer/state_syncer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package syncer diff --git a/snow/engine/snowman/syncer/state_syncer_test.go b/snow/engine/snowman/syncer/state_syncer_test.go index c3dd9b2f16a6..11faeae69f67 100644 --- a/snow/engine/snowman/syncer/state_syncer_test.go +++ b/snow/engine/snowman/syncer/state_syncer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package syncer @@ -17,11 +17,11 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/common/tracker" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/engine/snowman/getter" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/version" @@ -39,7 +39,8 @@ func TestStateSyncerIsEnabledIfVMSupportsStateSyncing(t *testing.T) { require := require.New(t) // Build state syncer - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) sender := &common.SenderTest{T: t} // Non state syncableVM case @@ -109,7 +110,8 @@ func TestStateSyncerIsEnabledIfVMSupportsStateSyncing(t *testing.T) { func TestStateSyncingStartsOnlyIfEnoughStakeIsConnected(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) alpha, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -151,7 +153,8 @@ func TestStateSyncingStartsOnlyIfEnoughStakeIsConnected(t *testing.T) { func TestStateSyncLocalSummaryIsIncludedAmongFrontiersIfAvailable(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -188,7 +191,8 @@ func TestStateSyncLocalSummaryIsIncludedAmongFrontiersIfAvailable(t *testing.T) func TestStateSyncNotFoundOngoingSummaryIsNotIncludedAmongFrontiers(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -218,7 +222,8 @@ func TestStateSyncNotFoundOngoingSummaryIsNotIncludedAmongFrontiers(t *testing.T func TestBeaconsAreReachedForFrontiersUponStartup(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -256,7 +261,8 @@ func TestBeaconsAreReachedForFrontiersUponStartup(t *testing.T) { func TestUnRequestedStateSummaryFrontiersAreDropped(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -345,7 +351,8 @@ func TestUnRequestedStateSummaryFrontiersAreDropped(t *testing.T) { func TestMalformedStateSummaryFrontiersAreDropped(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -413,7 +420,8 @@ func TestMalformedStateSummaryFrontiersAreDropped(t *testing.T) { func TestLateResponsesFromUnresponsiveFrontiersAreNotRecorded(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -495,7 +503,8 @@ func TestLateResponsesFromUnresponsiveFrontiersAreNotRecorded(t *testing.T) { func TestStateSyncIsRestartedIfTooManyFrontierSeedersTimeout(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -583,7 +592,8 @@ func TestStateSyncIsRestartedIfTooManyFrontierSeedersTimeout(t *testing.T) { func TestVoteRequestsAreSentAsAllFrontierBeaconsResponded(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -653,7 +663,8 @@ func TestVoteRequestsAreSentAsAllFrontierBeaconsResponded(t *testing.T) { func TestUnRequestedVotesAreDropped(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -769,7 +780,8 @@ func TestUnRequestedVotesAreDropped(t *testing.T) { func TestVotesForUnknownSummariesAreDropped(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -871,7 +883,8 @@ func TestVotesForUnknownSummariesAreDropped(t *testing.T) { func TestStateSummaryIsPassedToVMAsMajorityOfVotesIsCastedForIt(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -1015,7 +1028,8 @@ func TestStateSummaryIsPassedToVMAsMajorityOfVotesIsCastedForIt(t *testing.T) { func TestVotingIsRestartedIfMajorityIsNotReachedDueToTimeouts(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -1120,7 +1134,8 @@ func TestVotingIsRestartedIfMajorityIsNotReachedDueToTimeouts(t *testing.T) { func TestStateSyncIsStoppedIfEnoughVotesAreCastedWithNoClearMajority(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) @@ -1265,7 +1280,8 @@ func TestStateSyncIsStoppedIfEnoughVotesAreCastedWithNoClearMajority(t *testing. func TestStateSyncIsDoneOnceVMNotifies(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) beacons := buildTestPeers(t, ctx.SubnetID) totalWeight, err := beacons.TotalWeight(ctx.SubnetID) require.NoError(err) diff --git a/snow/engine/snowman/syncer/utils_test.go b/snow/engine/snowman/syncer/utils_test.go index 149d1fe0e681..a5217a4bf0dd 100644 --- a/snow/engine/snowman/syncer/utils_test.go +++ b/snow/engine/snowman/syncer/utils_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package syncer diff --git a/snow/engine/snowman/test_engine.go b/snow/engine/snowman/test_engine.go index ed6e1b1743c5..eada8463a041 100644 --- a/snow/engine/snowman/test_engine.go +++ b/snow/engine/snowman/test_engine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/engine/snowman/traced_engine.go b/snow/engine/snowman/traced_engine.go index 56b46de45d4e..f736dff48fbf 100644 --- a/snow/engine/snowman/traced_engine.go +++ b/snow/engine/snowman/traced_engine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/engine/snowman/transitive.go b/snow/engine/snowman/transitive.go index 4b43dcda0acb..bf76970b5295 100644 --- a/snow/engine/snowman/transitive.go +++ b/snow/engine/snowman/transitive.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman @@ -556,6 +556,15 @@ func (t *Transitive) HealthCheck(ctx context.Context) (interface{}, error) { t.Ctx.Lock.Lock() defer t.Ctx.Lock.Unlock() + t.Ctx.Log.Verbo("running health check", + zap.Uint32("requestID", t.requestID), + zap.Int("gossipCounter", t.gossipCounter), + zap.Stringer("polls", t.polls), + zap.Reflect("outstandingBlockRequests", t.blkReqs), + zap.Stringer("blockedJobs", &t.blocked), + zap.Int("pendingBuildBlocks", t.pendingBuildBlocks), + ) + consensusIntf, consensusErr := t.Consensus.HealthCheck(ctx) vmIntf, vmErr := t.VM.HealthCheck(ctx) intf := map[string]interface{}{ diff --git a/snow/engine/snowman/transitive_test.go b/snow/engine/snowman/transitive_test.go index 738f20440c58..a6b96ced21bd 100644 --- a/snow/engine/snowman/transitive_test.go +++ b/snow/engine/snowman/transitive_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman @@ -98,7 +98,7 @@ func setup(t *testing.T, engCfg Config) (ids.NodeID, validators.Manager, *common } func setupDefaultConfig(t *testing.T) (ids.NodeID, validators.Manager, *common.SenderTest, *block.TestVM, *Transitive, snowman.Block) { - engCfg := DefaultConfig() + engCfg := DefaultConfig(t) return setup(t, engCfg) } @@ -328,7 +328,7 @@ func TestEngineQuery(t *testing.T) { func TestEngineMultipleQuery(t *testing.T) { require := require.New(t) - engCfg := DefaultConfig() + engCfg := DefaultConfig(t) engCfg.Params = snowball.Parameters{ K: 3, AlphaPreference: 2, @@ -747,7 +747,7 @@ func TestEngineRepoll(t *testing.T) { func TestVoteCanceling(t *testing.T) { require := require.New(t) - engCfg := DefaultConfig() + engCfg := DefaultConfig(t) engCfg.Params = snowball.Parameters{ K: 3, AlphaPreference: 2, @@ -851,7 +851,7 @@ func TestVoteCanceling(t *testing.T) { func TestEngineNoQuery(t *testing.T) { require := require.New(t) - engCfg := DefaultConfig() + engCfg := DefaultConfig(t) sender := &common.SenderTest{T: t} engCfg.Sender = sender @@ -904,7 +904,7 @@ func TestEngineNoQuery(t *testing.T) { func TestEngineNoRepollQuery(t *testing.T) { require := require.New(t) - engCfg := DefaultConfig() + engCfg := DefaultConfig(t) sender := &common.SenderTest{T: t} engCfg.Sender = sender @@ -1596,7 +1596,7 @@ func TestEnginePushQueryRequestIDConflict(t *testing.T) { func TestEngineAggressivePolling(t *testing.T) { require := require.New(t) - engCfg := DefaultConfig() + engCfg := DefaultConfig(t) engCfg.Params.ConcurrentRepolls = 2 vals := validators.NewManager() @@ -1684,7 +1684,7 @@ func TestEngineAggressivePolling(t *testing.T) { func TestEngineDoubleChit(t *testing.T) { require := require.New(t) - engCfg := DefaultConfig() + engCfg := DefaultConfig(t) engCfg.Params = snowball.Parameters{ K: 2, AlphaPreference: 2, @@ -1794,7 +1794,7 @@ func TestEngineDoubleChit(t *testing.T) { func TestEngineBuildBlockLimit(t *testing.T) { require := require.New(t) - engCfg := DefaultConfig() + engCfg := DefaultConfig(t) engCfg.Params.K = 1 engCfg.Params.AlphaPreference = 1 engCfg.Params.AlphaConfidence = 1 @@ -2816,7 +2816,7 @@ func TestEngineBuildBlockWithCachedNonVerifiedParent(t *testing.T) { func TestEngineApplyAcceptedFrontierInQueryFailed(t *testing.T) { require := require.New(t) - engCfg := DefaultConfig() + engCfg := DefaultConfig(t) engCfg.Params = snowball.Parameters{ K: 1, AlphaPreference: 1, diff --git a/snow/engine/snowman/voter.go b/snow/engine/snowman/voter.go index 7d267b2efbcf..0a029e870ec2 100644 --- a/snow/engine/snowman/voter.go +++ b/snow/engine/snowman/voter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snowman diff --git a/snow/event/blockable.go b/snow/event/blockable.go index 05521dc2fe16..404e95c2aee3 100644 --- a/snow/event/blockable.go +++ b/snow/event/blockable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package event diff --git a/snow/event/blocker.go b/snow/event/blocker.go index 6f8e76b2d476..9c15ffb50604 100644 --- a/snow/event/blocker.go +++ b/snow/event/blocker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package event diff --git a/snow/event/blocker_test.go b/snow/event/blocker_test.go index 838a4f69d24b..d7620bfebe1a 100644 --- a/snow/event/blocker_test.go +++ b/snow/event/blocker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package event diff --git a/snow/networking/benchlist/benchable.go b/snow/networking/benchlist/benchable.go index e7ed46c678f9..f1cc85d9fe05 100644 --- a/snow/networking/benchlist/benchable.go +++ b/snow/networking/benchlist/benchable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package benchlist diff --git a/snow/networking/benchlist/benchlist.go b/snow/networking/benchlist/benchlist.go index 394899a1f37a..08f7e7d8d65e 100644 --- a/snow/networking/benchlist/benchlist.go +++ b/snow/networking/benchlist/benchlist.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package benchlist @@ -16,7 +16,6 @@ import ( "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/heap" "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/avalanchego/utils/timer" "github.com/ava-labs/avalanchego/utils/timer/mockable" safemath "github.com/ava-labs/avalanchego/utils/math" @@ -54,9 +53,8 @@ type benchlist struct { ctx *snow.ConsensusContext metrics metrics - // Fires when the next validator should leave the bench - // Calls [update] when it fires - timer *timer.Timer + // Used to notify the timer that it should recalculate when it should fire + resetTimer chan struct{} // Tells the time. Can be faked for testing. clock mockable.Clock @@ -105,8 +103,10 @@ func NewBenchlist( if maxPortion < 0 || maxPortion >= 1 { return nil, fmt.Errorf("max portion of benched stake must be in [0,1) but got %f", maxPortion) } + benchlist := &benchlist{ ctx: ctx, + resetTimer: make(chan struct{}, 1), failureStreaks: make(map[ids.NodeID]failureStreak), benchlistSet: set.Set[ids.NodeID]{}, benchable: benchable, @@ -117,38 +117,77 @@ func NewBenchlist( duration: duration, maxPortion: maxPortion, } - benchlist.timer = timer.NewTimer(benchlist.update) - go benchlist.timer.Dispatch() - return benchlist, benchlist.metrics.Initialize(ctx.Registerer) + if err := benchlist.metrics.Initialize(ctx.Registerer); err != nil { + return nil, err + } + + go benchlist.run() + return benchlist, nil +} + +// TODO: Close this goroutine during node shutdown +func (b *benchlist) run() { + timer := time.NewTimer(0) + defer timer.Stop() + + for { + // Invariant: The [timer] is not stopped. + select { + case <-timer.C: + case <-b.resetTimer: + if !timer.Stop() { + <-timer.C + } + } + + b.waitForBenchedNodes() + + b.removedExpiredNodes() + + // Note: If there are no nodes to remove, [duration] will be 0 and we + // will immediately wait until there are benched nodes. + duration := b.durationToSleep() + timer.Reset(duration) + } } -// Update removes benched validators whose time on the bench is over -func (b *benchlist) update() { +func (b *benchlist) waitForBenchedNodes() { + for { + b.lock.RLock() + _, _, ok := b.benchedHeap.Peek() + b.lock.RUnlock() + if ok { + return + } + + // Invariant: Whenever a new node is benched we ensure that resetTimer + // has a pending message while the write lock is held. + <-b.resetTimer + } +} + +func (b *benchlist) removedExpiredNodes() { b.lock.Lock() defer b.lock.Unlock() now := b.clock.Time() for { - if !b.canUnbench(now) { + _, next, ok := b.benchedHeap.Peek() + if !ok { + break + } + if now.Before(next) { break } - b.remove() - } - // Set next time update will be called - b.setNextLeaveTime() -} -// Removes the next node from the benchlist -// Assumes [b.lock] is held -func (b *benchlist) remove() { - nodeID, _, _ := b.benchedHeap.Pop() - b.ctx.Log.Debug("removing node from benchlist", - zap.Stringer("nodeID", nodeID), - ) - b.benchlistSet.Remove(nodeID) - b.benchable.Unbenched(b.ctx.ChainID, nodeID) + nodeID, _, _ := b.benchedHeap.Pop() + b.ctx.Log.Debug("removing node from benchlist", + zap.Stringer("nodeID", nodeID), + ) + b.benchlistSet.Remove(nodeID) + b.benchable.Unbenched(b.ctx.ChainID, nodeID) + } - // Update metrics b.metrics.numBenched.Set(float64(b.benchedHeap.Len())) benchedStake, err := b.vdrs.SubsetWeight(b.ctx.SubnetID, b.benchlistSet) if err != nil { @@ -161,56 +200,37 @@ func (b *benchlist) remove() { b.metrics.weightBenched.Set(float64(benchedStake)) } -// Returns if a validator should leave the bench at time [now]. -// False if no validator should. -// Assumes [b.lock] is held -func (b *benchlist) canUnbench(now time.Time) bool { - _, next, ok := b.benchedHeap.Peek() - if !ok { - return false - } - return now.After(next) -} +func (b *benchlist) durationToSleep() time.Duration { + b.lock.RLock() + defer b.lock.RUnlock() -// Set [b.timer] to fire when the next validator should leave the bench -// Assumes [b.lock] is held -func (b *benchlist) setNextLeaveTime() { _, next, ok := b.benchedHeap.Peek() if !ok { - b.timer.Cancel() - return + return 0 } + now := b.clock.Time() - nextLeave := next.Sub(now) - b.timer.SetTimeoutIn(nextLeave) + return next.Sub(now) } -// IsBenched returns true if messages to [nodeID] -// should not be sent over the network and should immediately fail. +// IsBenched returns true if messages to [nodeID] should not be sent over the +// network and should immediately fail. func (b *benchlist) IsBenched(nodeID ids.NodeID) bool { b.lock.RLock() defer b.lock.RUnlock() - return b.isBenched(nodeID) -} -// isBenched checks if [nodeID] is currently benched -// and calls cleanup if its benching period has elapsed -// Assumes [b.lock] is held. -func (b *benchlist) isBenched(nodeID ids.NodeID) bool { - if _, ok := b.benchlistSet[nodeID]; ok { - return true - } - return false + return b.benchlistSet.Contains(nodeID) } -// RegisterResponse notes that we received a response from validator [validatorID] +// RegisterResponse notes that we received a response from [nodeID] func (b *benchlist) RegisterResponse(nodeID ids.NodeID) { b.streaklock.Lock() defer b.streaklock.Unlock() + delete(b.failureStreaks, nodeID) } -// RegisterResponse notes that a request to validator [validatorID] timed out +// RegisterResponse notes that a request to [nodeID] timed out func (b *benchlist) RegisterFailure(nodeID ids.NodeID) { b.lock.Lock() defer b.lock.Unlock() @@ -295,6 +315,12 @@ func (b *benchlist) bench(nodeID ids.NodeID) { diff := maxBenchedUntil.Sub(minBenchedUntil) benchedUntil := minBenchedUntil.Add(time.Duration(rand.Float64() * float64(diff))) // #nosec G404 + b.ctx.Log.Debug("benching validator after consecutive failed queries", + zap.Stringer("nodeID", nodeID), + zap.Duration("benchDuration", benchedUntil.Sub(now)), + zap.Int("numFailedQueries", b.threshold), + ) + // Add to benchlist times with randomized delay b.benchlistSet.Add(nodeID) b.benchable.Benched(b.ctx.ChainID, nodeID) @@ -304,14 +330,12 @@ func (b *benchlist) bench(nodeID ids.NodeID) { b.streaklock.Unlock() b.benchedHeap.Push(nodeID, benchedUntil) - b.ctx.Log.Debug("benching validator after consecutive failed queries", - zap.Stringer("nodeID", nodeID), - zap.Duration("benchDuration", benchedUntil.Sub(now)), - zap.Int("numFailedQueries", b.threshold), - ) - // Set [b.timer] to fire when next validator should leave bench - b.setNextLeaveTime() + // Update the timer to account for the newly benched node. + select { + case b.resetTimer <- struct{}{}: + default: + } // Update metrics b.metrics.numBenched.Set(float64(b.benchedHeap.Len())) diff --git a/snow/networking/benchlist/benchlist_test.go b/snow/networking/benchlist/benchlist_test.go index 75df4f454292..45568392297e 100644 --- a/snow/networking/benchlist/benchlist_test.go +++ b/snow/networking/benchlist/benchlist_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package benchlist @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" ) @@ -20,7 +20,8 @@ var minimumFailingDuration = 5 * time.Minute func TestBenchlistAdd(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() vdrID0 := ids.GenerateTestNodeID() vdrID1 := ids.GenerateTestNodeID() @@ -51,20 +52,14 @@ func TestBenchlistAdd(t *testing.T) { ) require.NoError(err) b := benchIntf.(*benchlist) - defer b.timer.Stop() now := time.Now() b.clock.Set(now) // Nobody should be benched at the start b.lock.Lock() - require.False(b.isBenched(vdrID0)) - require.False(b.isBenched(vdrID1)) - require.False(b.isBenched(vdrID2)) - require.False(b.isBenched(vdrID3)) - require.False(b.isBenched(vdrID4)) + require.Empty(b.benchlistSet) require.Empty(b.failureStreaks) require.Zero(b.benchedHeap.Len()) - require.Empty(b.benchlistSet) b.lock.Unlock() // Register [threshold - 1] failures in a row for vdr0 @@ -73,9 +68,8 @@ func TestBenchlistAdd(t *testing.T) { } // Still shouldn't be benched due to not enough consecutive failure - require.False(b.isBenched(vdrID0)) - require.Zero(b.benchedHeap.Len()) require.Empty(b.benchlistSet) + require.Zero(b.benchedHeap.Len()) require.Len(b.failureStreaks, 1) fs := b.failureStreaks[vdrID0] require.Equal(threshold-1, fs.consecutive) @@ -87,9 +81,8 @@ func TestBenchlistAdd(t *testing.T) { // Still shouldn't be benched because not enough time (any in this case) // has passed since the first failure b.lock.Lock() - require.False(b.isBenched(vdrID0)) - require.Zero(b.benchedHeap.Len()) require.Empty(b.benchlistSet) + require.Zero(b.benchedHeap.Len()) b.lock.Unlock() // Move the time up @@ -108,9 +101,9 @@ func TestBenchlistAdd(t *testing.T) { // Now this validator should be benched b.lock.Lock() - require.True(b.isBenched(vdrID0)) - require.Equal(b.benchedHeap.Len(), 1) - require.Equal(b.benchlistSet.Len(), 1) + require.Contains(b.benchlistSet, vdrID0) + require.Equal(1, b.benchedHeap.Len()) + require.Equal(1, b.benchlistSet.Len()) nodeID, benchedUntil, ok := b.benchedHeap.Peek() require.True(ok) @@ -133,10 +126,9 @@ func TestBenchlistAdd(t *testing.T) { // vdr1 shouldn't be benched // The response should have cleared its consecutive failures b.lock.Lock() - require.True(b.isBenched(vdrID0)) - require.False(b.isBenched(vdrID1)) - require.Equal(b.benchedHeap.Len(), 1) - require.Equal(b.benchlistSet.Len(), 1) + require.Contains(b.benchlistSet, vdrID0) + require.Equal(1, b.benchedHeap.Len()) + require.Equal(1, b.benchlistSet.Len()) require.Empty(b.failureStreaks) b.lock.Unlock() @@ -153,7 +145,8 @@ func TestBenchlistAdd(t *testing.T) { func TestBenchlistMaxStake(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() vdrID0 := ids.GenerateTestNodeID() vdrID1 := ids.GenerateTestNodeID() @@ -183,7 +176,6 @@ func TestBenchlistMaxStake(t *testing.T) { ) require.NoError(err) b := benchIntf.(*benchlist) - defer b.timer.Stop() now := time.Now() b.clock.Set(now) @@ -209,11 +201,10 @@ func TestBenchlistMaxStake(t *testing.T) { // Benching vdr2 (weight 1000) would cause the amount benched // to exceed the maximum b.lock.Lock() - require.True(b.isBenched(vdrID0)) - require.True(b.isBenched(vdrID1)) - require.False(b.isBenched(vdrID2)) - require.Equal(b.benchedHeap.Len(), 2) - require.Equal(b.benchlistSet.Len(), 2) + require.Contains(b.benchlistSet, vdrID0) + require.Contains(b.benchlistSet, vdrID1) + require.Equal(2, b.benchedHeap.Len()) + require.Equal(2, b.benchlistSet.Len()) require.Len(b.failureStreaks, 1) fs := b.failureStreaks[vdrID2] fs.consecutive = threshold @@ -236,9 +227,9 @@ func TestBenchlistMaxStake(t *testing.T) { // vdr4 should be benched now b.lock.Lock() - require.True(b.isBenched(vdrID0)) - require.True(b.isBenched(vdrID1)) - require.True(b.isBenched(vdrID4)) + require.Contains(b.benchlistSet, vdrID0) + require.Contains(b.benchlistSet, vdrID1) + require.Contains(b.benchlistSet, vdrID4) require.Equal(3, b.benchedHeap.Len()) require.Equal(3, b.benchlistSet.Len()) require.Contains(b.benchlistSet, vdrID0) @@ -254,10 +245,9 @@ func TestBenchlistMaxStake(t *testing.T) { } b.lock.Lock() - require.True(b.isBenched(vdrID0)) - require.True(b.isBenched(vdrID1)) - require.True(b.isBenched(vdrID4)) - require.False(b.isBenched(vdrID2)) + require.Contains(b.benchlistSet, vdrID0) + require.Contains(b.benchlistSet, vdrID1) + require.Contains(b.benchlistSet, vdrID4) require.Equal(3, b.benchedHeap.Len()) require.Equal(3, b.benchlistSet.Len()) require.Len(b.failureStreaks, 1) @@ -269,7 +259,8 @@ func TestBenchlistMaxStake(t *testing.T) { func TestBenchlistRemove(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() vdrID0 := ids.GenerateTestNodeID() vdrID1 := ids.GenerateTestNodeID() @@ -307,7 +298,6 @@ func TestBenchlistRemove(t *testing.T) { ) require.NoError(err) b := benchIntf.(*benchlist) - defer b.timer.Stop() now := time.Now() b.lock.Lock() b.clock.Set(now) @@ -332,9 +322,9 @@ func TestBenchlistRemove(t *testing.T) { // All 3 should be benched b.lock.Lock() - require.True(b.isBenched(vdrID0)) - require.True(b.isBenched(vdrID1)) - require.True(b.isBenched(vdrID2)) + require.Contains(b.benchlistSet, vdrID0) + require.Contains(b.benchlistSet, vdrID1) + require.Contains(b.benchlistSet, vdrID2) require.Equal(3, b.benchedHeap.Len()) require.Equal(3, b.benchlistSet.Len()) require.Empty(b.failureStreaks) diff --git a/snow/networking/benchlist/manager.go b/snow/networking/benchlist/manager.go index 7a42e8245267..e6ac45da4400 100644 --- a/snow/networking/benchlist/manager.go +++ b/snow/networking/benchlist/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package benchlist diff --git a/snow/networking/benchlist/metrics.go b/snow/networking/benchlist/metrics.go index 12da52d396a0..25f9e50f7da8 100644 --- a/snow/networking/benchlist/metrics.go +++ b/snow/networking/benchlist/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package benchlist diff --git a/snow/networking/benchlist/test_benchable.go b/snow/networking/benchlist/test_benchable.go index 5e179763d2d1..dabfab564829 100644 --- a/snow/networking/benchlist/test_benchable.go +++ b/snow/networking/benchlist/test_benchable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package benchlist diff --git a/snow/networking/handler/engine.go b/snow/networking/handler/engine.go index 94ae54ff08c6..e3de84ac8989 100644 --- a/snow/networking/handler/engine.go +++ b/snow/networking/handler/engine.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler diff --git a/snow/networking/handler/engine_test.go b/snow/networking/handler/engine_test.go index 142441cfda6d..e9b2b8ae0162 100644 --- a/snow/networking/handler/engine_test.go +++ b/snow/networking/handler/engine_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler diff --git a/snow/networking/handler/handler.go b/snow/networking/handler/handler.go index 68ec1e5a0f36..35dc40f57f98 100644 --- a/snow/networking/handler/handler.go +++ b/snow/networking/handler/handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler @@ -271,8 +271,8 @@ func (h *handler) Start(ctx context.Context, recoverPanic bool) { // Push the message onto the handler's queue func (h *handler) Push(ctx context.Context, msg Message) { switch msg.Op() { - case message.AppRequestOp, message.AppRequestFailedOp, message.AppResponseOp, message.AppGossipOp, - message.CrossChainAppRequestOp, message.CrossChainAppRequestFailedOp, message.CrossChainAppResponseOp: + case message.AppRequestOp, message.AppErrorOp, message.AppResponseOp, message.AppGossipOp, + message.CrossChainAppRequestOp, message.CrossChainAppErrorOp, message.CrossChainAppResponseOp: h.asyncMessageQueue.Push(ctx, msg) default: h.syncMessageQueue.Push(ctx, msg) @@ -842,8 +842,18 @@ func (h *handler) executeAsyncMsg(ctx context.Context, msg Message) error { case *p2p.AppResponse: return engine.AppResponse(ctx, nodeID, m.RequestId, m.AppBytes) - case *message.AppRequestFailed: - return engine.AppRequestFailed(ctx, nodeID, m.RequestID) + case *p2p.AppError: + err := &common.AppError{ + Code: m.ErrorCode, + Message: m.ErrorMessage, + } + + return engine.AppRequestFailed( + ctx, + nodeID, + m.RequestId, + err, + ) case *p2p.AppGossip: return engine.AppGossip(ctx, nodeID, m.AppBytes) @@ -866,10 +876,16 @@ func (h *handler) executeAsyncMsg(ctx context.Context, msg Message) error { ) case *message.CrossChainAppRequestFailed: + err := &common.AppError{ + Code: m.ErrorCode, + Message: m.ErrorMessage, + } + return engine.CrossChainAppRequestFailed( ctx, m.SourceChainID, m.RequestID, + err, ) default: diff --git a/snow/networking/handler/handler_test.go b/snow/networking/handler/handler_test.go index bb434e2017d4..1f51aa4f1d23 100644 --- a/snow/networking/handler/handler_test.go +++ b/snow/networking/handler/handler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler @@ -22,6 +22,7 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/networking/tracker" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/subnets" "github.com/ava-labs/avalanchego/utils/math/meter" @@ -40,7 +41,8 @@ func TestHandlerDropsTimedOutMessages(t *testing.T) { called := make(chan struct{}) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() vdr0 := ids.GenerateTestNodeID() @@ -135,7 +137,8 @@ func TestHandlerClosesOnError(t *testing.T) { require := require.New(t) closed := make(chan struct{}, 1) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) @@ -226,7 +229,8 @@ func TestHandlerDropsGossipDuringBootstrapping(t *testing.T) { require := require.New(t) closed := make(chan struct{}, 1) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) @@ -303,7 +307,8 @@ func TestHandlerDropsGossipDuringBootstrapping(t *testing.T) { func TestHandlerDispatchInternal(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) msgFromVMChan := make(chan common.Message) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) @@ -372,7 +377,8 @@ func TestHandlerDispatchInternal(t *testing.T) { func TestHandlerSubnetConnector(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) @@ -547,7 +553,8 @@ func TestDynamicEngineTypeDispatch(t *testing.T) { require := require.New(t) messageReceived := make(chan struct{}) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) @@ -620,7 +627,8 @@ func TestDynamicEngineTypeDispatch(t *testing.T) { func TestHandlerStartError(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) resourceTracker, err := tracker.NewResourceTracker( prometheus.NewRegistry(), resource.NoUsage, diff --git a/snow/networking/handler/health.go b/snow/networking/handler/health.go index b68ead089639..3f4af4299d1c 100644 --- a/snow/networking/handler/health.go +++ b/snow/networking/handler/health.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler diff --git a/snow/networking/handler/health_test.go b/snow/networking/handler/health_test.go index 63b7dbea140c..f3fe456fa023 100644 --- a/snow/networking/handler/health_test.go +++ b/snow/networking/handler/health_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowball" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/networking/tracker" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/subnets" "github.com/ava-labs/avalanchego/utils/math/meter" @@ -47,7 +48,8 @@ func TestHealthCheckSubnet(t *testing.T) { t.Run(name, func(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() diff --git a/snow/networking/handler/message_queue.go b/snow/networking/handler/message_queue.go index 6fe4137b940e..58e4f2b3b29e 100644 --- a/snow/networking/handler/message_queue.go +++ b/snow/networking/handler/message_queue.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/networking/tracker" "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/buffer" "github.com/ava-labs/avalanchego/utils/timer/mockable" ) @@ -69,7 +70,7 @@ type messageQueue struct { // Node ID --> Messages this node has in [msgs] nodeToUnprocessedMsgs map[ids.NodeID]int // Unprocessed messages - msgAndCtxs []*msgAndContext + msgAndCtxs buffer.Deque[*msgAndContext] } func NewMessageQueue( @@ -85,6 +86,7 @@ func NewMessageQueue( cpuTracker: cpuTracker, cond: sync.NewCond(&sync.Mutex{}), nodeToUnprocessedMsgs: make(map[ids.NodeID]int), + msgAndCtxs: buffer.NewUnboundedDeque[*msgAndContext](1 /*=initSize*/), } return m, m.metrics.initialize(metricsNamespace, ctx.Registerer, ops) } @@ -99,7 +101,7 @@ func (m *messageQueue) Push(ctx context.Context, msg Message) { } // Add the message to the queue - m.msgAndCtxs = append(m.msgAndCtxs, &msgAndContext{ + m.msgAndCtxs.PushRight(&msgAndContext{ msg: msg, ctx: ctx, }) @@ -124,13 +126,13 @@ func (m *messageQueue) Pop() (context.Context, Message, bool) { if m.closed { return nil, Message{}, false } - if len(m.msgAndCtxs) != 0 { + if m.msgAndCtxs.Len() != 0 { break } m.cond.Wait() } - n := len(m.msgAndCtxs) + n := m.msgAndCtxs.Len() // note that n > 0 i := 0 for { if i == n { @@ -140,20 +142,14 @@ func (m *messageQueue) Pop() (context.Context, Message, bool) { } var ( - msgAndCtx = m.msgAndCtxs[0] - msg = msgAndCtx.msg - ctx = msgAndCtx.ctx - nodeID = msg.NodeID() + msgAndCtx, _ = m.msgAndCtxs.PopLeft() + msg = msgAndCtx.msg + ctx = msgAndCtx.ctx + nodeID = msg.NodeID() ) - m.msgAndCtxs[0] = nil // See if it's OK to process [msg] next if m.canPop(msg) || i == n { // i should never == n but handle anyway as a fail-safe - if cap(m.msgAndCtxs) == 1 { - m.msgAndCtxs = nil // Give back memory if possible - } else { - m.msgAndCtxs = m.msgAndCtxs[1:] - } m.nodeToUnprocessedMsgs[nodeID]-- if m.nodeToUnprocessedMsgs[nodeID] == 0 { delete(m.nodeToUnprocessedMsgs, nodeID) @@ -165,8 +161,7 @@ func (m *messageQueue) Pop() (context.Context, Message, bool) { } // [msg.nodeID] is causing excessive CPU usage. // Push [msg] to back of [m.msgs] and handle it later. - m.msgAndCtxs = append(m.msgAndCtxs, msgAndCtx) - m.msgAndCtxs = m.msgAndCtxs[1:] + m.msgAndCtxs.PushRight(msgAndCtx) i++ m.metrics.numExcessiveCPU.Inc() } @@ -176,7 +171,7 @@ func (m *messageQueue) Len() int { m.cond.L.Lock() defer m.cond.L.Unlock() - return len(m.msgAndCtxs) + return m.msgAndCtxs.Len() } func (m *messageQueue) Shutdown() { @@ -184,10 +179,10 @@ func (m *messageQueue) Shutdown() { defer m.cond.L.Unlock() // Remove all the current messages from the queue - for _, msg := range m.msgAndCtxs { - msg.msg.OnFinishedHandling() + for m.msgAndCtxs.Len() > 0 { + msgAndCtx, _ := m.msgAndCtxs.PopLeft() + msgAndCtx.msg.OnFinishedHandling() } - m.msgAndCtxs = nil m.nodeToUnprocessedMsgs = nil // Update metrics diff --git a/snow/networking/handler/message_queue_metrics.go b/snow/networking/handler/message_queue_metrics.go index ce28769a41ca..429295ae04cb 100644 --- a/snow/networking/handler/message_queue_metrics.go +++ b/snow/networking/handler/message_queue_metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler diff --git a/snow/networking/handler/message_queue_test.go b/snow/networking/handler/message_queue_test.go index 1eabfd96c410..457ba86ceda1 100644 --- a/snow/networking/handler/message_queue_test.go +++ b/snow/networking/handler/message_queue_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler @@ -15,8 +15,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/message" "github.com/ava-labs/avalanchego/proto/pb/p2p" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/networking/tracker" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" ) @@ -26,7 +26,8 @@ func TestQueue(t *testing.T) { ctrl := gomock.NewController(t) require := require.New(t) cpuTracker := tracker.NewMockTracker(ctrl) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() vdr1ID, vdr2ID := ids.GenerateTestNodeID(), ids.GenerateTestNodeID() require.NoError(vdrs.AddStaker(ctx.SubnetID, vdr1ID, nil, ids.Empty, 1)) diff --git a/snow/networking/handler/metrics.go b/snow/networking/handler/metrics.go index a8776b30832e..3fe9f2d9a2b3 100644 --- a/snow/networking/handler/metrics.go +++ b/snow/networking/handler/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler diff --git a/snow/networking/handler/mock_handler.go b/snow/networking/handler/mock_handler.go index dd231641e8ac..517fbcd85537 100644 --- a/snow/networking/handler/mock_handler.go +++ b/snow/networking/handler/mock_handler.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/networking/handler (interfaces: Handler) +// +// Generated by this command: +// +// mockgen -package=handler -destination=snow/networking/handler/mock_handler.go github.com/ava-labs/avalanchego/snow/networking/handler Handler +// // Package handler is a generated GoMock package. package handler @@ -50,7 +52,7 @@ func (m *MockHandler) AwaitStopped(arg0 context.Context) (time.Duration, error) } // AwaitStopped indicates an expected call of AwaitStopped. -func (mr *MockHandlerMockRecorder) AwaitStopped(arg0 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) AwaitStopped(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AwaitStopped", reflect.TypeOf((*MockHandler)(nil).AwaitStopped), arg0) } @@ -84,16 +86,16 @@ func (mr *MockHandlerMockRecorder) GetEngineManager() *gomock.Call { } // HealthCheck mocks base method. -func (m *MockHandler) HealthCheck(arg0 context.Context) (interface{}, error) { +func (m *MockHandler) HealthCheck(arg0 context.Context) (any, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HealthCheck", arg0) - ret0, _ := ret[0].(interface{}) + ret0, _ := ret[0].(any) ret1, _ := ret[1].(error) return ret0, ret1 } // HealthCheck indicates an expected call of HealthCheck. -func (mr *MockHandlerMockRecorder) HealthCheck(arg0 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) HealthCheck(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockHandler)(nil).HealthCheck), arg0) } @@ -119,7 +121,7 @@ func (m *MockHandler) Push(arg0 context.Context, arg1 Message) { } // Push indicates an expected call of Push. -func (mr *MockHandlerMockRecorder) Push(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) Push(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Push", reflect.TypeOf((*MockHandler)(nil).Push), arg0, arg1) } @@ -131,7 +133,7 @@ func (m *MockHandler) RegisterTimeout(arg0 time.Duration) { } // RegisterTimeout indicates an expected call of RegisterTimeout. -func (mr *MockHandlerMockRecorder) RegisterTimeout(arg0 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) RegisterTimeout(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterTimeout", reflect.TypeOf((*MockHandler)(nil).RegisterTimeout), arg0) } @@ -143,7 +145,7 @@ func (m *MockHandler) SetEngineManager(arg0 *EngineManager) { } // SetEngineManager indicates an expected call of SetEngineManager. -func (mr *MockHandlerMockRecorder) SetEngineManager(arg0 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) SetEngineManager(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetEngineManager", reflect.TypeOf((*MockHandler)(nil).SetEngineManager), arg0) } @@ -155,7 +157,7 @@ func (m *MockHandler) SetOnStopped(arg0 func()) { } // SetOnStopped indicates an expected call of SetOnStopped. -func (mr *MockHandlerMockRecorder) SetOnStopped(arg0 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) SetOnStopped(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetOnStopped", reflect.TypeOf((*MockHandler)(nil).SetOnStopped), arg0) } @@ -169,7 +171,7 @@ func (m *MockHandler) ShouldHandle(arg0 ids.NodeID) bool { } // ShouldHandle indicates an expected call of ShouldHandle. -func (mr *MockHandlerMockRecorder) ShouldHandle(arg0 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) ShouldHandle(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldHandle", reflect.TypeOf((*MockHandler)(nil).ShouldHandle), arg0) } @@ -181,7 +183,7 @@ func (m *MockHandler) Start(arg0 context.Context, arg1 bool) { } // Start indicates an expected call of Start. -func (mr *MockHandlerMockRecorder) Start(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) Start(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockHandler)(nil).Start), arg0, arg1) } @@ -193,7 +195,7 @@ func (m *MockHandler) Stop(arg0 context.Context) { } // Stop indicates an expected call of Stop. -func (mr *MockHandlerMockRecorder) Stop(arg0 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) Stop(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockHandler)(nil).Stop), arg0) } @@ -205,7 +207,7 @@ func (m *MockHandler) StopWithError(arg0 context.Context, arg1 error) { } // StopWithError indicates an expected call of StopWithError. -func (mr *MockHandlerMockRecorder) StopWithError(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockHandlerMockRecorder) StopWithError(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopWithError", reflect.TypeOf((*MockHandler)(nil).StopWithError), arg0, arg1) } diff --git a/snow/networking/handler/parser.go b/snow/networking/handler/parser.go index 148572484ef5..4dc954e4e9f2 100644 --- a/snow/networking/handler/parser.go +++ b/snow/networking/handler/parser.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package handler diff --git a/snow/networking/router/chain_router.go b/snow/networking/router/chain_router.go index b55e77f66f41..f2c6d11775dd 100644 --- a/snow/networking/router/chain_router.go +++ b/snow/networking/router/chain_router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package router diff --git a/snow/networking/router/chain_router_metrics.go b/snow/networking/router/chain_router_metrics.go index 58440377ba82..bc8f26223586 100644 --- a/snow/networking/router/chain_router_metrics.go +++ b/snow/networking/router/chain_router_metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package router diff --git a/snow/networking/router/chain_router_test.go b/snow/networking/router/chain_router_test.go index 1e37c7c1d799..1897aae89bc2 100644 --- a/snow/networking/router/chain_router_test.go +++ b/snow/networking/router/chain_router_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package router @@ -15,7 +15,6 @@ import ( "go.uber.org/mock/gomock" - "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/message" "github.com/ava-labs/avalanchego/proto/pb/p2p" @@ -25,6 +24,7 @@ import ( "github.com/ava-labs/avalanchego/snow/networking/handler" "github.com/ava-labs/avalanchego/snow/networking/timeout" "github.com/ava-labs/avalanchego/snow/networking/tracker" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/subnets" "github.com/ava-labs/avalanchego/utils/constants" @@ -43,10 +43,13 @@ const ( testThreadPoolSize = 2 ) +// TODO refactor tests in this file + func TestShutdown(t *testing.T) { require := require.New(t) - chainCtx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + chainCtx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(chainCtx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) benchlist := benchlist.NewNoBenchlist() @@ -181,7 +184,8 @@ func TestShutdown(t *testing.T) { func TestShutdownTimesOut(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) nodeID := ids.EmptyNodeID vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) @@ -375,7 +379,8 @@ func TestRouterTimeout(t *testing.T) { wg = sync.WaitGroup{} ) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) @@ -451,12 +456,12 @@ func TestRouterTimeout(t *testing.T) { calledQueryFailed = true return nil } - bootstrapper.AppRequestFailedF = func(context.Context, ids.NodeID, uint32) error { + bootstrapper.AppRequestFailedF = func(context.Context, ids.NodeID, uint32, *common.AppError) error { defer wg.Done() calledAppRequestFailed = true return nil } - bootstrapper.CrossChainAppRequestFailedF = func(context.Context, ids.ID, uint32) error { + bootstrapper.CrossChainAppRequestFailedF = func(context.Context, ids.ID, uint32, *common.AppError) error { defer wg.Done() calledCrossChainAppRequestFailed = true return nil @@ -634,10 +639,12 @@ func TestRouterTimeout(t *testing.T) { ctx.ChainID, requestID, message.AppResponseOp, - message.InternalAppRequestFailed( + message.InboundAppError( nodeID, ctx.ChainID, requestID, + common.ErrTimeout.Code, + common.ErrTimeout.Message, ), p2p.EngineType_ENGINE_TYPE_SNOWMAN, ) @@ -653,11 +660,13 @@ func TestRouterTimeout(t *testing.T) { ctx.ChainID, requestID, message.CrossChainAppResponseOp, - message.InternalCrossChainAppRequestFailed( + message.InternalCrossChainAppError( nodeID, ctx.ChainID, ctx.ChainID, requestID, + common.ErrTimeout.Code, + common.ErrTimeout.Message, ), p2p.EngineType_ENGINE_TYPE_SNOWMAN, ) @@ -720,7 +729,8 @@ func TestRouterHonorsRequestedEngine(t *testing.T) { h := handler.NewMockHandler(ctrl) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) h.EXPECT().Context().Return(ctx).AnyTimes() h.EXPECT().SetOnStopped(gomock.Any()).AnyTimes() h.EXPECT().Stop(gomock.Any()).AnyTimes() @@ -814,291 +824,91 @@ func TestRouterHonorsRequestedEngine(t *testing.T) { } func TestRouterClearTimeouts(t *testing.T) { - require := require.New(t) - - // Create a timeout manager - tm, err := timeout.NewManager( - &timer.AdaptiveTimeoutConfig{ - InitialTimeout: 3 * time.Second, - MinimumTimeout: 3 * time.Second, - MaximumTimeout: 5 * time.Minute, - TimeoutCoefficient: 1, - TimeoutHalflife: 5 * time.Minute, + requestID := uint32(123) + + tests := []struct { + name string + responseOp message.Op + responseMsg message.InboundMessage + timeoutMsg message.InboundMessage + }{ + { + name: "StateSummaryFrontier", + responseOp: message.StateSummaryFrontierOp, + responseMsg: message.InboundStateSummaryFrontier(ids.Empty, requestID, []byte("summary"), ids.EmptyNodeID), + timeoutMsg: message.InternalGetStateSummaryFrontierFailed(ids.EmptyNodeID, ids.Empty, requestID), }, - benchlist.NewNoBenchlist(), - "", - prometheus.NewRegistry(), - ) - require.NoError(err) - - go tm.Dispatch() - defer tm.Stop() - - // Create a router - chainRouter := ChainRouter{} - require.NoError(chainRouter.Initialize( - ids.EmptyNodeID, - logging.NoLog{}, - tm, - time.Millisecond, - set.Set[ids.ID]{}, - true, - set.Set[ids.ID]{}, - nil, - HealthConfig{}, - "", - prometheus.NewRegistry(), - )) - defer chainRouter.Shutdown(context.Background()) - - // Create bootstrapper, engine and handler - ctx := snow.DefaultConsensusContextTest() - vdrs := validators.NewManager() - require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) - - resourceTracker, err := tracker.NewResourceTracker( - prometheus.NewRegistry(), - resource.NoUsage, - meter.ContinuousFactory{}, - time.Second, - ) - require.NoError(err) - h, err := handler.New( - ctx, - vdrs, - nil, - time.Second, - testThreadPoolSize, - resourceTracker, - validators.UnhandledSubnetConnector, - subnets.New(ctx.NodeID, subnets.Config{}), - commontracker.NewPeers(), - ) - require.NoError(err) - - bootstrapper := &common.BootstrapperTest{ - EngineTest: common.EngineTest{ - T: t, + { + name: "AcceptedStateSummary", + responseOp: message.AcceptedStateSummaryOp, + responseMsg: message.InboundAcceptedStateSummary(ids.Empty, requestID, []ids.ID{ids.GenerateTestID()}, ids.EmptyNodeID), + timeoutMsg: message.InternalGetAcceptedStateSummaryFailed(ids.EmptyNodeID, ids.Empty, requestID), }, - } - bootstrapper.Default(false) - bootstrapper.ContextF = func() *snow.ConsensusContext { - return ctx - } - - engine := &common.EngineTest{T: t} - engine.Default(false) - engine.ContextF = func() *snow.ConsensusContext { - return ctx - } - h.SetEngineManager(&handler.EngineManager{ - Avalanche: &handler.Engine{ - StateSyncer: nil, - Bootstrapper: bootstrapper, - Consensus: engine, + { + name: "AcceptedFrontierOp", + responseOp: message.AcceptedFrontierOp, + responseMsg: message.InboundAcceptedFrontier(ids.Empty, requestID, ids.GenerateTestID(), ids.EmptyNodeID), + timeoutMsg: message.InternalGetAcceptedFrontierFailed(ids.EmptyNodeID, ids.Empty, requestID, engineType), }, - Snowman: &handler.Engine{ - StateSyncer: nil, - Bootstrapper: bootstrapper, - Consensus: engine, + { + name: "Accepted", + responseOp: message.AcceptedOp, + responseMsg: message.InboundAccepted(ids.Empty, requestID, []ids.ID{ids.GenerateTestID()}, ids.EmptyNodeID), + timeoutMsg: message.InternalGetAcceptedFailed(ids.EmptyNodeID, ids.Empty, requestID, engineType), + }, + { + name: "Chits", + responseOp: message.ChitsOp, + responseMsg: message.InboundChits(ids.Empty, requestID, ids.GenerateTestID(), ids.GenerateTestID(), ids.GenerateTestID(), ids.EmptyNodeID), + timeoutMsg: message.InternalQueryFailed(ids.EmptyNodeID, ids.Empty, requestID, engineType), + }, + { + name: "AppResponse", + responseOp: message.AppResponseOp, + responseMsg: message.InboundAppResponse(ids.Empty, requestID, []byte("responseMsg"), ids.EmptyNodeID), + timeoutMsg: message.InboundAppError(ids.EmptyNodeID, ids.Empty, requestID, 123, "error"), + }, + { + name: "AppError", + responseOp: message.AppResponseOp, + responseMsg: message.InboundAppError(ids.EmptyNodeID, ids.Empty, requestID, 1234, "custom error"), + timeoutMsg: message.InboundAppError(ids.EmptyNodeID, ids.Empty, requestID, 123, "error"), + }, + { + name: "CrossChainAppResponse", + responseOp: message.CrossChainAppResponseOp, + responseMsg: message.InternalCrossChainAppResponse(ids.EmptyNodeID, ids.Empty, ids.Empty, requestID, []byte("responseMsg")), + timeoutMsg: message.InternalCrossChainAppError(ids.EmptyNodeID, ids.Empty, ids.Empty, requestID, 123, "error"), + }, + { + name: "CrossChainAppError", + responseOp: message.CrossChainAppResponseOp, + responseMsg: message.InternalCrossChainAppError(ids.EmptyNodeID, ids.Empty, ids.Empty, requestID, 1234, "custom error"), + timeoutMsg: message.InternalCrossChainAppError(ids.EmptyNodeID, ids.Empty, ids.Empty, requestID, 123, "error"), }, - }) - ctx.State.Set(snow.EngineState{ - Type: p2p.EngineType_ENGINE_TYPE_SNOWMAN, - State: snow.NormalOp, // assumed bootstrapping is done - }) - - chainRouter.AddChain(context.Background(), h) - - bootstrapper.StartF = func(context.Context, uint32) error { - return nil - } - h.Start(context.Background(), false) - - nodeID := ids.GenerateTestNodeID() - requestID := uint32(0) - { - chainRouter.RegisterRequest( - context.Background(), - nodeID, - ctx.ChainID, - ctx.ChainID, - requestID, - message.StateSummaryFrontierOp, - message.InternalGetStateSummaryFrontierFailed( - nodeID, - ctx.ChainID, - requestID, - ), - engineType, - ) - msg := message.InboundStateSummaryFrontier( - ctx.ChainID, - requestID, - nil, - nodeID, - ) - chainRouter.HandleInbound(context.Background(), msg) - } - - { - requestID++ - chainRouter.RegisterRequest( - context.Background(), - nodeID, - ctx.ChainID, - ctx.ChainID, - requestID, - message.AcceptedStateSummaryOp, - message.InternalGetAcceptedStateSummaryFailed( - nodeID, - ctx.ChainID, - requestID, - ), - engineType, - ) - msg := message.InboundAcceptedStateSummary( - ctx.ChainID, - requestID, - nil, - nodeID, - ) - chainRouter.HandleInbound(context.Background(), msg) } - { - requestID++ - chainRouter.RegisterRequest( - context.Background(), - nodeID, - ctx.ChainID, - ctx.ChainID, - requestID, - message.AcceptedFrontierOp, - message.InternalGetAcceptedFrontierFailed( - nodeID, - ctx.ChainID, - requestID, - engineType, - ), - engineType, - ) - msg := message.InboundAcceptedFrontier( - ctx.ChainID, - requestID, - ids.Empty, - nodeID, - ) - chainRouter.HandleInbound(context.Background(), msg) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) - { - requestID++ - chainRouter.RegisterRequest( - context.Background(), - nodeID, - ctx.ChainID, - ctx.ChainID, - requestID, - message.AcceptedOp, - message.InternalGetAcceptedFailed( - nodeID, - ctx.ChainID, - requestID, - engineType, - ), - engineType, - ) - msg := message.InboundAccepted( - ctx.ChainID, - requestID, - nil, - nodeID, - ) - chainRouter.HandleInbound(context.Background(), msg) - } + chainRouter, _ := newChainRouterTest(t) - { - requestID++ - chainRouter.RegisterRequest( - context.Background(), - nodeID, - ctx.ChainID, - ctx.ChainID, - requestID, - message.ChitsOp, - message.InternalQueryFailed( - nodeID, - ctx.ChainID, + chainRouter.RegisterRequest( + context.Background(), + ids.EmptyNodeID, + ids.Empty, + ids.Empty, requestID, + tt.responseOp, + tt.timeoutMsg, engineType, - ), - engineType, - ) - msg := message.InboundChits( - ctx.ChainID, - requestID, - ids.Empty, - ids.Empty, - ids.Empty, - nodeID, - ) - chainRouter.HandleInbound(context.Background(), msg) - } + ) - { - requestID++ - chainRouter.RegisterRequest( - context.Background(), - nodeID, - ctx.ChainID, - ctx.ChainID, - requestID, - message.AppResponseOp, - message.InternalAppRequestFailed( - nodeID, - ctx.ChainID, - requestID, - ), - engineType, - ) - msg := message.InboundAppResponse( - ctx.ChainID, - requestID, - nil, - nodeID, - ) - chainRouter.HandleInbound(context.Background(), msg) - } - - { - requestID++ - chainRouter.RegisterRequest( - context.Background(), - nodeID, - ctx.ChainID, - ctx.ChainID, - requestID, - message.CrossChainAppResponseOp, - message.InternalCrossChainAppRequestFailed( - nodeID, - ctx.ChainID, - ctx.ChainID, - requestID, - ), - p2p.EngineType_ENGINE_TYPE_UNSPECIFIED, - ) - msg := message.InternalCrossChainAppResponse( - nodeID, - ctx.ChainID, - ctx.ChainID, - requestID, - nil, - ) - chainRouter.HandleInbound(context.Background(), msg) + chainRouter.HandleInbound(context.Background(), tt.responseMsg) + require.Zero(chainRouter.timedRequests.Len()) + }) } - - require.Zero(chainRouter.timedRequests.Len()) } func TestValidatorOnlyMessageDrops(t *testing.T) { @@ -1144,7 +954,8 @@ func TestValidatorOnlyMessageDrops(t *testing.T) { calledF := false wg := sync.WaitGroup{} - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) sb := subnets.New(ctx.NodeID, subnets.Config{ValidatorOnly: true}) vdrs := validators.NewManager() vID := ids.GenerateTestNodeID() @@ -1253,179 +1064,6 @@ func TestValidatorOnlyMessageDrops(t *testing.T) { require.True(calledF) // should be called since this is a validator request } -func TestRouterCrossChainMessages(t *testing.T) { - require := require.New(t) - - tm, err := timeout.NewManager( - &timer.AdaptiveTimeoutConfig{ - InitialTimeout: 3 * time.Second, - MinimumTimeout: 3 * time.Second, - MaximumTimeout: 5 * time.Minute, - TimeoutCoefficient: 1, - TimeoutHalflife: 5 * time.Minute, - }, - benchlist.NewNoBenchlist(), - "timeoutManager", - prometheus.NewRegistry(), - ) - require.NoError(err) - - go tm.Dispatch() - defer tm.Stop() - - // Create chain router - nodeID := ids.GenerateTestNodeID() - chainRouter := ChainRouter{} - require.NoError(chainRouter.Initialize( - nodeID, - logging.NoLog{}, - tm, - time.Millisecond, - set.Set[ids.ID]{}, - true, - set.Set[ids.ID]{}, - nil, - HealthConfig{}, - "", - prometheus.NewRegistry(), - )) - defer chainRouter.Shutdown(context.Background()) - - requester := snow.DefaultConsensusContextTest() - requester.ChainID = ids.GenerateTestID() - requester.Registerer = prometheus.NewRegistry() - requester.Metrics = metrics.NewOptionalGatherer() - requester.Executing.Set(false) - - // Set up validators - vdrs := validators.NewManager() - require.NoError(vdrs.AddStaker(requester.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) - - // Create bootstrapper, engine and handler - resourceTracker, err := tracker.NewResourceTracker( - prometheus.NewRegistry(), - resource.NoUsage, - meter.ContinuousFactory{}, - time.Second, - ) - require.NoError(err) - - requesterHandler, err := handler.New( - requester, - vdrs, - nil, - time.Second, - testThreadPoolSize, - resourceTracker, - validators.UnhandledSubnetConnector, - subnets.New(requester.NodeID, subnets.Config{}), - commontracker.NewPeers(), - ) - require.NoError(err) - requesterHandler.SetEngineManager(&handler.EngineManager{ - Avalanche: &handler.Engine{ - StateSyncer: nil, - Bootstrapper: &common.BootstrapperTest{}, - Consensus: &common.EngineTest{}, - }, - Snowman: &handler.Engine{ - StateSyncer: nil, - Bootstrapper: &common.BootstrapperTest{}, - Consensus: &common.EngineTest{}, - }, - }) - - responder := snow.DefaultConsensusContextTest() - responder.ChainID = ids.GenerateTestID() - responder.Registerer = prometheus.NewRegistry() - responder.Metrics = metrics.NewOptionalGatherer() - responder.Executing.Set(false) - - responderHandler, err := handler.New( - responder, - vdrs, - nil, - time.Second, - testThreadPoolSize, - resourceTracker, - validators.UnhandledSubnetConnector, - subnets.New(responder.NodeID, subnets.Config{}), - commontracker.NewPeers(), - ) - require.NoError(err) - responderHandler.SetEngineManager(&handler.EngineManager{ - Avalanche: &handler.Engine{ - StateSyncer: nil, - Bootstrapper: &common.BootstrapperTest{}, - Consensus: &common.EngineTest{}, - }, - Snowman: &handler.Engine{ - StateSyncer: nil, - Bootstrapper: &common.BootstrapperTest{}, - Consensus: &common.EngineTest{}, - }, - }) - - // assumed bootstrapping is done - responder.State.Set(snow.EngineState{ - Type: engineType, - State: snow.NormalOp, - }) - requester.State.Set(snow.EngineState{ - Type: engineType, - State: snow.NormalOp, - }) - - // router tracks two chains - one will send a message to the other - chainRouter.AddChain(context.Background(), requesterHandler) - chainRouter.AddChain(context.Background(), responderHandler) - - // Each chain should start off with a connected message - require.Equal(1, chainRouter.chainHandlers[requester.ChainID].Len()) - require.Equal(1, chainRouter.chainHandlers[responder.ChainID].Len()) - - // Requester sends a request to the responder - msgBytes := []byte("foobar") - msg := message.InternalCrossChainAppRequest( - requester.NodeID, - requester.ChainID, - responder.ChainID, - uint32(1), - time.Minute, - msgBytes, - ) - chainRouter.HandleInbound(context.Background(), msg) - require.Equal(2, chainRouter.chainHandlers[responder.ChainID].Len()) - - // We register the cross-chain response on the requester-side so we don't - // drop it. - chainRouter.RegisterRequest( - context.Background(), - nodeID, - requester.ChainID, - responder.ChainID, - uint32(1), - message.CrossChainAppResponseOp, - message.InternalCrossChainAppRequestFailed( - nodeID, - responder.ChainID, - requester.ChainID, - uint32(1), - ), - p2p.EngineType_ENGINE_TYPE_UNSPECIFIED, - ) - // Responder sends a response back to the requester. - msg = message.InternalCrossChainAppResponse( - nodeID, - responder.ChainID, - requester.ChainID, - uint32(1), - msgBytes, - ) - chainRouter.HandleInbound(context.Background(), msg) - require.Equal(2, chainRouter.chainHandlers[requester.ChainID].Len()) -} - func TestConnectedSubnet(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -1469,13 +1107,10 @@ func TestConnectedSubnet(t *testing.T) { )) // Create bootstrapper, engine and handler - platform := snow.DefaultConsensusContextTest() - platform.ChainID = constants.PlatformChainID - platform.SubnetID = constants.PrimaryNetworkID - platform.Registerer = prometheus.NewRegistry() - platform.Metrics = metrics.NewOptionalGatherer() - platform.Executing.Set(false) - platform.State.Set(snow.EngineState{ + snowCtx := snowtest.Context(t, snowtest.PChainID) + ctx := snowtest.ConsensusContext(snowCtx) + ctx.Executing.Set(false) + ctx.State.Set(snow.EngineState{ Type: engineType, State: snow.NormalOp, }) @@ -1494,7 +1129,7 @@ func TestConnectedSubnet(t *testing.T) { } platformHandler := handler.NewMockHandler(ctrl) - platformHandler.EXPECT().Context().Return(platform).AnyTimes() + platformHandler.EXPECT().Context().Return(ctx).AnyTimes() platformHandler.EXPECT().SetOnStopped(gomock.Any()).AnyTimes() platformHandler.EXPECT().Push(gomock.Any(), myConnectedMsg).Times(1) platformHandler.EXPECT().Push(gomock.Any(), mySubnetConnectedMsg0).Times(1) @@ -1588,7 +1223,8 @@ func TestValidatorOnlyAllowedNodeMessageDrops(t *testing.T) { calledF := false wg := sync.WaitGroup{} - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) allowedID := ids.GenerateTestNodeID() allowedSet := set.Of(allowedID) sb := subnets.New(ctx.NodeID, subnets.Config{ValidatorOnly: true, AllowedNodes: allowedSet}) @@ -1713,3 +1349,275 @@ func TestValidatorOnlyAllowedNodeMessageDrops(t *testing.T) { wg.Wait() require.True(calledF) // should be called since this is a validator request } + +// Tests that a response, peer error, or a timeout clears the timeout and calls +// the handler +func TestAppRequest(t *testing.T) { + wantRequestID := uint32(123) + wantResponse := []byte("response") + + errFoo := common.AppError{ + Code: 456, + Message: "foo", + } + + tests := []struct { + name string + responseOp message.Op + timeoutMsg message.InboundMessage + inboundMsg message.InboundMessage + }{ + { + name: "AppRequest - chain response", + responseOp: message.AppResponseOp, + timeoutMsg: message.InboundAppError(ids.EmptyNodeID, ids.Empty, wantRequestID, errFoo.Code, errFoo.Message), + inboundMsg: message.InboundAppResponse(ids.Empty, wantRequestID, wantResponse, ids.EmptyNodeID), + }, + { + name: "AppRequest - chain error", + responseOp: message.AppResponseOp, + timeoutMsg: message.InboundAppError(ids.EmptyNodeID, ids.Empty, wantRequestID, errFoo.Code, errFoo.Message), + inboundMsg: message.InboundAppError(ids.EmptyNodeID, ids.Empty, wantRequestID, errFoo.Code, errFoo.Message), + }, + { + name: "AppRequest - timeout", + responseOp: message.AppResponseOp, + timeoutMsg: message.InboundAppError(ids.EmptyNodeID, ids.Empty, wantRequestID, errFoo.Code, errFoo.Message), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + wg := &sync.WaitGroup{} + chainRouter, engine := newChainRouterTest(t) + + wg.Add(1) + if tt.inboundMsg == nil || tt.inboundMsg.Op() == message.AppErrorOp { + engine.AppRequestFailedF = func(_ context.Context, nodeID ids.NodeID, requestID uint32, appErr *common.AppError) error { + defer wg.Done() + require.Zero(chainRouter.timedRequests.Len()) + + require.Equal(ids.EmptyNodeID, nodeID) + require.Equal(wantRequestID, requestID) + require.Equal(errFoo.Code, appErr.Code) + require.Equal(errFoo.Message, appErr.Message) + + return nil + } + } else if tt.inboundMsg.Op() == message.AppResponseOp { + engine.AppResponseF = func(ctx context.Context, nodeID ids.NodeID, requestID uint32, msg []byte) error { + defer wg.Done() + require.Zero(chainRouter.timedRequests.Len()) + + require.Equal(ids.EmptyNodeID, nodeID) + require.Equal(wantRequestID, requestID) + require.Equal(wantResponse, msg) + + return nil + } + } + + ctx := context.Background() + chainRouter.RegisterRequest(ctx, ids.EmptyNodeID, ids.Empty, ids.Empty, wantRequestID, tt.responseOp, tt.timeoutMsg, engineType) + require.Equal(1, chainRouter.timedRequests.Len()) + + if tt.inboundMsg != nil { + chainRouter.HandleInbound(ctx, tt.inboundMsg) + } + + wg.Wait() + }) + } +} + +// Tests that a response, peer error, or a timeout clears the timeout and calls +// the handler +func TestCrossChainAppRequest(t *testing.T) { + wantRequestID := uint32(123) + wantResponse := []byte("response") + + errFoo := common.AppError{ + Code: 456, + Message: "foo", + } + + tests := []struct { + name string + responseOp message.Op + timeoutMsg message.InboundMessage + inboundMsg message.InboundMessage + }{ + { + name: "CrossChainAppRequest - chain response", + responseOp: message.CrossChainAppResponseOp, + timeoutMsg: message.InternalCrossChainAppError(ids.EmptyNodeID, ids.Empty, ids.Empty, wantRequestID, errFoo.Code, errFoo.Message), + inboundMsg: message.InternalCrossChainAppResponse(ids.EmptyNodeID, ids.Empty, ids.Empty, wantRequestID, wantResponse), + }, + { + name: "CrossChainAppRequest - chain error", + responseOp: message.CrossChainAppResponseOp, + timeoutMsg: message.InternalCrossChainAppError(ids.EmptyNodeID, ids.Empty, ids.Empty, wantRequestID, errFoo.Code, errFoo.Message), + inboundMsg: message.InternalCrossChainAppError(ids.EmptyNodeID, ids.Empty, ids.Empty, wantRequestID, errFoo.Code, errFoo.Message), + }, + { + name: "CrossChainAppRequest - timeout", + responseOp: message.CrossChainAppResponseOp, + timeoutMsg: message.InternalCrossChainAppError(ids.EmptyNodeID, ids.Empty, ids.Empty, wantRequestID, errFoo.Code, errFoo.Message), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + wg := &sync.WaitGroup{} + chainRouter, engine := newChainRouterTest(t) + + wg.Add(1) + if tt.inboundMsg == nil || tt.inboundMsg.Op() == message.CrossChainAppErrorOp { + engine.CrossChainAppRequestFailedF = func(_ context.Context, chainID ids.ID, requestID uint32, appErr *common.AppError) error { + defer wg.Done() + require.Zero(chainRouter.timedRequests.Len()) + + require.Equal(ids.Empty, chainID) + require.Equal(wantRequestID, requestID) + require.Equal(errFoo.Code, appErr.Code) + require.Equal(errFoo.Message, appErr.Message) + + return nil + } + } else if tt.inboundMsg.Op() == message.CrossChainAppResponseOp { + engine.CrossChainAppResponseF = func(ctx context.Context, chainID ids.ID, requestID uint32, msg []byte) error { + defer wg.Done() + require.Zero(chainRouter.timedRequests.Len()) + + require.Equal(ids.Empty, chainID) + require.Equal(wantRequestID, requestID) + require.Equal(wantResponse, msg) + + return nil + } + } + + ctx := context.Background() + chainRouter.RegisterRequest(ctx, ids.EmptyNodeID, ids.Empty, ids.Empty, wantRequestID, tt.responseOp, tt.timeoutMsg, engineType) + require.Equal(1, chainRouter.timedRequests.Len()) + + if tt.inboundMsg != nil { + chainRouter.HandleInbound(ctx, tt.inboundMsg) + } + + wg.Wait() + }) + } +} + +func newChainRouterTest(t *testing.T) (*ChainRouter, *common.EngineTest) { + // Create a timeout manager + tm, err := timeout.NewManager( + &timer.AdaptiveTimeoutConfig{ + InitialTimeout: 3 * time.Second, + MinimumTimeout: 3 * time.Second, + MaximumTimeout: 5 * time.Minute, + TimeoutCoefficient: 1, + TimeoutHalflife: 5 * time.Minute, + }, + benchlist.NewNoBenchlist(), + "", + prometheus.NewRegistry(), + ) + require.NoError(t, err) + + go tm.Dispatch() + + // Create a router + chainRouter := &ChainRouter{} + require.NoError(t, chainRouter.Initialize( + ids.EmptyNodeID, + logging.NoLog{}, + tm, + time.Millisecond, + set.Set[ids.ID]{}, + true, + set.Set[ids.ID]{}, + nil, + HealthConfig{}, + "", + prometheus.NewRegistry(), + )) + + // Create bootstrapper, engine and handler + snowCtx := snowtest.Context(t, snowtest.PChainID) + ctx := snowtest.ConsensusContext(snowCtx) + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) + + resourceTracker, err := tracker.NewResourceTracker( + prometheus.NewRegistry(), + resource.NoUsage, + meter.ContinuousFactory{}, + time.Second, + ) + require.NoError(t, err) + h, err := handler.New( + ctx, + vdrs, + nil, + time.Second, + testThreadPoolSize, + resourceTracker, + validators.UnhandledSubnetConnector, + subnets.New(ctx.NodeID, subnets.Config{}), + commontracker.NewPeers(), + ) + require.NoError(t, err) + + bootstrapper := &common.BootstrapperTest{ + EngineTest: common.EngineTest{ + T: t, + }, + } + bootstrapper.Default(false) + bootstrapper.ContextF = func() *snow.ConsensusContext { + return ctx + } + + engine := &common.EngineTest{T: t} + engine.Default(false) + engine.ContextF = func() *snow.ConsensusContext { + return ctx + } + h.SetEngineManager(&handler.EngineManager{ + Avalanche: &handler.Engine{ + StateSyncer: nil, + Bootstrapper: bootstrapper, + Consensus: engine, + }, + Snowman: &handler.Engine{ + StateSyncer: nil, + Bootstrapper: bootstrapper, + Consensus: engine, + }, + }) + ctx.State.Set(snow.EngineState{ + Type: p2p.EngineType_ENGINE_TYPE_SNOWMAN, + State: snow.NormalOp, // assumed bootstrapping is done + }) + + chainRouter.AddChain(context.Background(), h) + + bootstrapper.StartF = func(context.Context, uint32) error { + return nil + } + + h.Start(context.Background(), false) + + t.Cleanup(func() { + tm.Stop() + chainRouter.Shutdown(context.Background()) + }) + + return chainRouter, engine +} diff --git a/snow/networking/router/health.go b/snow/networking/router/health.go index d678f0f19aa1..3968f981d084 100644 --- a/snow/networking/router/health.go +++ b/snow/networking/router/health.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package router diff --git a/snow/networking/router/inbound_handler.go b/snow/networking/router/inbound_handler.go index cfd6d5fa222f..81d2d9b810be 100644 --- a/snow/networking/router/inbound_handler.go +++ b/snow/networking/router/inbound_handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package router diff --git a/snow/networking/router/main_test.go b/snow/networking/router/main_test.go index afc1dddb173e..4398ad2eefeb 100644 --- a/snow/networking/router/main_test.go +++ b/snow/networking/router/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package router diff --git a/snow/networking/router/mock_router.go b/snow/networking/router/mock_router.go index e644edd2d6b2..c9146a777138 100644 --- a/snow/networking/router/mock_router.go +++ b/snow/networking/router/mock_router.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/snow/networking/router (interfaces: Router) +// Source: snow/networking/router/router.go +// +// Generated by this command: +// +// mockgen -source=snow/networking/router/router.go -destination=snow/networking/router/mock_router.go -package=router -exclude_interfaces=InternalHandler +// // Package router is a generated GoMock package. package router @@ -20,8 +22,8 @@ import ( logging "github.com/ava-labs/avalanchego/utils/logging" set "github.com/ava-labs/avalanchego/utils/set" version "github.com/ava-labs/avalanchego/version" - gomock "go.uber.org/mock/gomock" prometheus "github.com/prometheus/client_golang/prometheus" + gomock "go.uber.org/mock/gomock" ) // MockRouter is a mock of Router interface. @@ -48,51 +50,51 @@ func (m *MockRouter) EXPECT() *MockRouterMockRecorder { } // AddChain mocks base method. -func (m *MockRouter) AddChain(arg0 context.Context, arg1 handler.Handler) { +func (m *MockRouter) AddChain(ctx context.Context, chain handler.Handler) { m.ctrl.T.Helper() - m.ctrl.Call(m, "AddChain", arg0, arg1) + m.ctrl.Call(m, "AddChain", ctx, chain) } // AddChain indicates an expected call of AddChain. -func (mr *MockRouterMockRecorder) AddChain(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) AddChain(ctx, chain any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddChain", reflect.TypeOf((*MockRouter)(nil).AddChain), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddChain", reflect.TypeOf((*MockRouter)(nil).AddChain), ctx, chain) } // Benched mocks base method. -func (m *MockRouter) Benched(arg0 ids.ID, arg1 ids.NodeID) { +func (m *MockRouter) Benched(chainID ids.ID, validatorID ids.NodeID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Benched", arg0, arg1) + m.ctrl.Call(m, "Benched", chainID, validatorID) } // Benched indicates an expected call of Benched. -func (mr *MockRouterMockRecorder) Benched(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) Benched(chainID, validatorID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Benched", reflect.TypeOf((*MockRouter)(nil).Benched), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Benched", reflect.TypeOf((*MockRouter)(nil).Benched), chainID, validatorID) } // Connected mocks base method. -func (m *MockRouter) Connected(arg0 ids.NodeID, arg1 *version.Application, arg2 ids.ID) { +func (m *MockRouter) Connected(nodeID ids.NodeID, nodeVersion *version.Application, subnetID ids.ID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Connected", arg0, arg1, arg2) + m.ctrl.Call(m, "Connected", nodeID, nodeVersion, subnetID) } // Connected indicates an expected call of Connected. -func (mr *MockRouterMockRecorder) Connected(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) Connected(nodeID, nodeVersion, subnetID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connected", reflect.TypeOf((*MockRouter)(nil).Connected), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connected", reflect.TypeOf((*MockRouter)(nil).Connected), nodeID, nodeVersion, subnetID) } // Disconnected mocks base method. -func (m *MockRouter) Disconnected(arg0 ids.NodeID) { +func (m *MockRouter) Disconnected(nodeID ids.NodeID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Disconnected", arg0) + m.ctrl.Call(m, "Disconnected", nodeID) } // Disconnected indicates an expected call of Disconnected. -func (mr *MockRouterMockRecorder) Disconnected(arg0 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) Disconnected(nodeID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnected", reflect.TypeOf((*MockRouter)(nil).Disconnected), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnected", reflect.TypeOf((*MockRouter)(nil).Disconnected), nodeID) } // HandleInbound mocks base method. @@ -102,50 +104,50 @@ func (m *MockRouter) HandleInbound(arg0 context.Context, arg1 message.InboundMes } // HandleInbound indicates an expected call of HandleInbound. -func (mr *MockRouterMockRecorder) HandleInbound(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) HandleInbound(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleInbound", reflect.TypeOf((*MockRouter)(nil).HandleInbound), arg0, arg1) } // HealthCheck mocks base method. -func (m *MockRouter) HealthCheck(arg0 context.Context) (interface{}, error) { +func (m *MockRouter) HealthCheck(arg0 context.Context) (any, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HealthCheck", arg0) - ret0, _ := ret[0].(interface{}) + ret0, _ := ret[0].(any) ret1, _ := ret[1].(error) return ret0, ret1 } // HealthCheck indicates an expected call of HealthCheck. -func (mr *MockRouterMockRecorder) HealthCheck(arg0 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) HealthCheck(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockRouter)(nil).HealthCheck), arg0) } // Initialize mocks base method. -func (m *MockRouter) Initialize(arg0 ids.NodeID, arg1 logging.Logger, arg2 timeout.Manager, arg3 time.Duration, arg4 set.Set[ids.ID], arg5 bool, arg6 set.Set[ids.ID], arg7 func(int), arg8 HealthConfig, arg9 string, arg10 prometheus.Registerer) error { +func (m *MockRouter) Initialize(nodeID ids.NodeID, log logging.Logger, timeouts timeout.Manager, shutdownTimeout time.Duration, criticalChains set.Set[ids.ID], sybilProtectionEnabled bool, trackedSubnets set.Set[ids.ID], onFatal func(int), healthConfig HealthConfig, metricsNamespace string, metricsRegisterer prometheus.Registerer) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Initialize", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + ret := m.ctrl.Call(m, "Initialize", nodeID, log, timeouts, shutdownTimeout, criticalChains, sybilProtectionEnabled, trackedSubnets, onFatal, healthConfig, metricsNamespace, metricsRegisterer) ret0, _ := ret[0].(error) return ret0 } // Initialize indicates an expected call of Initialize. -func (mr *MockRouterMockRecorder) Initialize(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) Initialize(nodeID, log, timeouts, shutdownTimeout, criticalChains, sybilProtectionEnabled, trackedSubnets, onFatal, healthConfig, metricsNamespace, metricsRegisterer any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockRouter)(nil).Initialize), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockRouter)(nil).Initialize), nodeID, log, timeouts, shutdownTimeout, criticalChains, sybilProtectionEnabled, trackedSubnets, onFatal, healthConfig, metricsNamespace, metricsRegisterer) } // RegisterRequest mocks base method. -func (m *MockRouter) RegisterRequest(arg0 context.Context, arg1 ids.NodeID, arg2, arg3 ids.ID, arg4 uint32, arg5 message.Op, arg6 message.InboundMessage, arg7 p2p.EngineType) { +func (m *MockRouter) RegisterRequest(ctx context.Context, nodeID ids.NodeID, sourceChainID, destinationChainID ids.ID, requestID uint32, op message.Op, failedMsg message.InboundMessage, engineType p2p.EngineType) { m.ctrl.T.Helper() - m.ctrl.Call(m, "RegisterRequest", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + m.ctrl.Call(m, "RegisterRequest", ctx, nodeID, sourceChainID, destinationChainID, requestID, op, failedMsg, engineType) } // RegisterRequest indicates an expected call of RegisterRequest. -func (mr *MockRouterMockRecorder) RegisterRequest(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) RegisterRequest(ctx, nodeID, sourceChainID, destinationChainID, requestID, op, failedMsg, engineType any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRequest", reflect.TypeOf((*MockRouter)(nil).RegisterRequest), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRequest", reflect.TypeOf((*MockRouter)(nil).RegisterRequest), ctx, nodeID, sourceChainID, destinationChainID, requestID, op, failedMsg, engineType) } // Shutdown mocks base method. @@ -155,19 +157,19 @@ func (m *MockRouter) Shutdown(arg0 context.Context) { } // Shutdown indicates an expected call of Shutdown. -func (mr *MockRouterMockRecorder) Shutdown(arg0 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) Shutdown(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockRouter)(nil).Shutdown), arg0) } // Unbenched mocks base method. -func (m *MockRouter) Unbenched(arg0 ids.ID, arg1 ids.NodeID) { +func (m *MockRouter) Unbenched(chainID ids.ID, validatorID ids.NodeID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Unbenched", arg0, arg1) + m.ctrl.Call(m, "Unbenched", chainID, validatorID) } // Unbenched indicates an expected call of Unbenched. -func (mr *MockRouterMockRecorder) Unbenched(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockRouterMockRecorder) Unbenched(chainID, validatorID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unbenched", reflect.TypeOf((*MockRouter)(nil).Unbenched), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unbenched", reflect.TypeOf((*MockRouter)(nil).Unbenched), chainID, validatorID) } diff --git a/snow/networking/router/router.go b/snow/networking/router/router.go index bba00eb7ae06..4df5614c25fb 100644 --- a/snow/networking/router/router.go +++ b/snow/networking/router/router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package router diff --git a/snow/networking/router/traced_router.go b/snow/networking/router/traced_router.go index fe493e6717a8..955ccb43bbed 100644 --- a/snow/networking/router/traced_router.go +++ b/snow/networking/router/traced_router.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package router diff --git a/snow/networking/sender/external_sender.go b/snow/networking/sender/external_sender.go index 72d9539d41e5..7d279889e3af 100644 --- a/snow/networking/sender/external_sender.go +++ b/snow/networking/sender/external_sender.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sender diff --git a/snow/networking/sender/mock_external_sender.go b/snow/networking/sender/mock_external_sender.go index d3d4717b2959..9dc0a50d1af9 100644 --- a/snow/networking/sender/mock_external_sender.go +++ b/snow/networking/sender/mock_external_sender.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/snow/networking/sender (interfaces: ExternalSender) +// Source: snow/networking/sender/external_sender.go +// +// Generated by this command: +// +// mockgen -source=snow/networking/sender/external_sender.go -destination=snow/networking/sender/mock_external_sender.go -package=sender -exclude_interfaces= +// // Package sender is a generated GoMock package. package sender @@ -41,29 +43,29 @@ func (m *MockExternalSender) EXPECT() *MockExternalSenderMockRecorder { } // Gossip mocks base method. -func (m *MockExternalSender) Gossip(arg0 message.OutboundMessage, arg1 ids.ID, arg2, arg3, arg4 int, arg5 subnets.Allower) set.Set[ids.NodeID] { +func (m *MockExternalSender) Gossip(msg message.OutboundMessage, subnetID ids.ID, numValidatorsToSend, numNonValidatorsToSend, numPeersToSend int, allower subnets.Allower) set.Set[ids.NodeID] { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Gossip", arg0, arg1, arg2, arg3, arg4, arg5) + ret := m.ctrl.Call(m, "Gossip", msg, subnetID, numValidatorsToSend, numNonValidatorsToSend, numPeersToSend, allower) ret0, _ := ret[0].(set.Set[ids.NodeID]) return ret0 } // Gossip indicates an expected call of Gossip. -func (mr *MockExternalSenderMockRecorder) Gossip(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockExternalSenderMockRecorder) Gossip(msg, subnetID, numValidatorsToSend, numNonValidatorsToSend, numPeersToSend, allower any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Gossip", reflect.TypeOf((*MockExternalSender)(nil).Gossip), arg0, arg1, arg2, arg3, arg4, arg5) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Gossip", reflect.TypeOf((*MockExternalSender)(nil).Gossip), msg, subnetID, numValidatorsToSend, numNonValidatorsToSend, numPeersToSend, allower) } // Send mocks base method. -func (m *MockExternalSender) Send(arg0 message.OutboundMessage, arg1 set.Set[ids.NodeID], arg2 ids.ID, arg3 subnets.Allower) set.Set[ids.NodeID] { +func (m *MockExternalSender) Send(msg message.OutboundMessage, nodeIDs set.Set[ids.NodeID], subnetID ids.ID, allower subnets.Allower) set.Set[ids.NodeID] { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Send", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "Send", msg, nodeIDs, subnetID, allower) ret0, _ := ret[0].(set.Set[ids.NodeID]) return ret0 } // Send indicates an expected call of Send. -func (mr *MockExternalSenderMockRecorder) Send(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockExternalSenderMockRecorder) Send(msg, nodeIDs, subnetID, allower any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockExternalSender)(nil).Send), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockExternalSender)(nil).Send), msg, nodeIDs, subnetID, allower) } diff --git a/snow/networking/sender/sender.go b/snow/networking/sender/sender.go index b30e267a19bf..08a05305029a 100644 --- a/snow/networking/sender/sender.go +++ b/snow/networking/sender/sender.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sender @@ -1210,11 +1210,13 @@ func (s *sender) SendCrossChainAppRequest(ctx context.Context, chainID ids.ID, r ctx = utils.Detach(ctx) // The failed message is treated as if it was sent by the requested chain - failedMsg := message.InternalCrossChainAppRequestFailed( + failedMsg := message.InternalCrossChainAppError( s.ctx.NodeID, chainID, s.ctx.ChainID, requestID, + common.ErrTimeout.Code, + common.ErrTimeout.Message, ) s.router.RegisterRequest( ctx, @@ -1262,10 +1264,12 @@ func (s *sender) SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID] // to send them a message, to avoid busy looping when disconnected from // the internet. for nodeID := range nodeIDs { - inMsg := message.InternalAppRequestFailed( + inMsg := message.InboundAppError( nodeID, s.ctx.ChainID, requestID, + common.ErrTimeout.Code, + common.ErrTimeout.Message, ) s.router.RegisterRequest( ctx, @@ -1308,10 +1312,12 @@ func (s *sender) SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID] // Immediately register a failure. Do so asynchronously to avoid // deadlock. - inMsg := message.InternalAppRequestFailed( + inMsg := message.InboundAppError( nodeID, s.ctx.ChainID, requestID, + common.ErrTimeout.Code, + common.ErrTimeout.Message, ) go s.router.HandleInbound(ctx, inMsg) } @@ -1366,10 +1372,12 @@ func (s *sender) SendAppRequest(ctx context.Context, nodeIDs set.Set[ids.NodeID] // Register failures for nodes we didn't send a request to. s.timeouts.RegisterRequestToUnreachableValidator() - inMsg := message.InternalAppRequestFailed( + inMsg := message.InboundAppError( nodeID, s.ctx.ChainID, requestID, + common.ErrTimeout.Code, + common.ErrTimeout.Message, ) go s.router.HandleInbound(ctx, inMsg) } diff --git a/snow/networking/sender/sender_test.go b/snow/networking/sender/sender_test.go index a6da8738e1fa..5ad019731857 100644 --- a/snow/networking/sender/sender_test.go +++ b/snow/networking/sender/sender_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sender @@ -26,6 +26,7 @@ import ( "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/snow/networking/timeout" "github.com/ava-labs/avalanchego/snow/networking/tracker" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/subnets" "github.com/ava-labs/avalanchego/utils/constants" @@ -53,7 +54,8 @@ var defaultSubnetConfig = subnets.Config{ func TestTimeout(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) benchlist := benchlist.NewNoBenchlist() @@ -112,7 +114,7 @@ func TestTimeout(t *testing.T) { ) require.NoError(err) - ctx2 := snow.DefaultConsensusContextTest() + ctx2 := snowtest.ConsensusContext(snowCtx) resourceTracker, err := tracker.NewResourceTracker( prometheus.NewRegistry(), resource.NoUsage, @@ -201,8 +203,18 @@ func TestTimeout(t *testing.T) { bootstrapper.GetAncestorsFailedF = failed bootstrapper.GetFailedF = failed bootstrapper.QueryFailedF = failed - bootstrapper.AppRequestFailedF = failed - bootstrapper.CrossChainAppRequestFailedF = func(ctx context.Context, chainID ids.ID, _ uint32) error { + bootstrapper.AppRequestFailedF = func(ctx context.Context, nodeID ids.NodeID, _ uint32, appErr *common.AppError) error { + require.NoError(ctx.Err()) + + failedLock.Lock() + defer failedLock.Unlock() + + failedVDRs.Add(nodeID) + wg.Done() + return nil + } + + bootstrapper.CrossChainAppRequestFailedF = func(ctx context.Context, chainID ids.ID, _ uint32, _ *common.AppError) error { require.NoError(ctx.Err()) failedLock.Lock() @@ -307,7 +319,8 @@ func TestTimeout(t *testing.T) { func TestReliableMessages(t *testing.T) { require := require.New(t) - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.BuildTestNodeID([]byte{1}), nil, ids.Empty, 1)) benchlist := benchlist.NewNoBenchlist() @@ -367,7 +380,7 @@ func TestReliableMessages(t *testing.T) { ) require.NoError(err) - ctx2 := snow.DefaultConsensusContextTest() + ctx2 := snowtest.ConsensusContext(snowCtx) resourceTracker, err := tracker.NewResourceTracker( prometheus.NewRegistry(), resource.NoUsage, @@ -453,7 +466,8 @@ func TestReliableMessagesToMyself(t *testing.T) { require := require.New(t) benchlist := benchlist.NewNoBenchlist() - ctx := snow.DefaultConsensusContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) + ctx := snowtest.ConsensusContext(snowCtx) vdrs := validators.NewManager() require.NoError(vdrs.AddStaker(ctx.SubnetID, ids.GenerateTestNodeID(), nil, ids.Empty, 1)) tm, err := timeout.NewManager( @@ -512,7 +526,7 @@ func TestReliableMessagesToMyself(t *testing.T) { ) require.NoError(err) - ctx2 := snow.DefaultConsensusContextTest() + ctx2 := snowtest.ConsensusContext(snowCtx) resourceTracker, err := tracker.NewResourceTracker( prometheus.NewRegistry(), resource.NoUsage, @@ -596,26 +610,16 @@ func TestReliableMessagesToMyself(t *testing.T) { func TestSender_Bootstrap_Requests(t *testing.T) { var ( - chainID = ids.GenerateTestID() - subnetID = ids.GenerateTestID() - myNodeID = ids.GenerateTestNodeID() successNodeID = ids.GenerateTestNodeID() failedNodeID = ids.GenerateTestNodeID() deadline = time.Second requestID = uint32(1337) - ctx = snow.DefaultContextTest() heights = []uint64{1, 2, 3} containerIDs = []ids.ID{ids.GenerateTestID(), ids.GenerateTestID()} engineType = p2p.EngineType_ENGINE_TYPE_SNOWMAN ) - ctx.ChainID = chainID - ctx.SubnetID = subnetID - ctx.NodeID = myNodeID - snowCtx := &snow.ConsensusContext{ - Context: ctx, - Registerer: prometheus.NewRegistry(), - AvalancheRegisterer: prometheus.NewRegistry(), - } + snowCtx := snowtest.Context(t, snowtest.PChainID) + ctx := snowtest.ConsensusContext(snowCtx) type test struct { name string @@ -634,21 +638,21 @@ func TestSender_Bootstrap_Requests(t *testing.T) { failedMsgF: func(nodeID ids.NodeID) message.InboundMessage { return message.InternalGetStateSummaryFrontierFailed( nodeID, - chainID, + ctx.ChainID, requestID, ) }, assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&p2p.GetStateSummaryFrontier{}, msg.Message()) innerMsg := msg.Message().(*p2p.GetStateSummaryFrontier) - require.Equal(chainID[:], innerMsg.ChainId) + require.Equal(ctx.ChainID[:], innerMsg.ChainId) require.Equal(requestID, innerMsg.RequestId) require.Equal(uint64(deadline), innerMsg.Deadline) }, expectedResponseOp: message.StateSummaryFrontierOp, setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().GetStateSummaryFrontier( - chainID, + ctx.ChainID, requestID, deadline, ).Return(nil, nil) @@ -658,7 +662,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { gomock.Any(), // Outbound message // Note [myNodeID] is not in this set set.Of(successNodeID, failedNodeID), - subnetID, // Subnet ID + ctx.SubnetID, // Subnet ID gomock.Any(), ).Return(set.Of(successNodeID)) }, @@ -675,14 +679,14 @@ func TestSender_Bootstrap_Requests(t *testing.T) { failedMsgF: func(nodeID ids.NodeID) message.InboundMessage { return message.InternalGetAcceptedStateSummaryFailed( nodeID, - chainID, + ctx.ChainID, requestID, ) }, assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&p2p.GetAcceptedStateSummary{}, msg.Message()) innerMsg := msg.Message().(*p2p.GetAcceptedStateSummary) - require.Equal(chainID[:], innerMsg.ChainId) + require.Equal(ctx.ChainID[:], innerMsg.ChainId) require.Equal(requestID, innerMsg.RequestId) require.Equal(uint64(deadline), innerMsg.Deadline) require.Equal(heights, innerMsg.Heights) @@ -690,7 +694,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { expectedResponseOp: message.AcceptedStateSummaryOp, setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().GetAcceptedStateSummary( - chainID, + ctx.ChainID, requestID, deadline, heights, @@ -701,7 +705,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { gomock.Any(), // Outbound message // Note [myNodeID] is not in this set set.Of(successNodeID, failedNodeID), - subnetID, // Subnet ID + ctx.SubnetID, // Subnet ID gomock.Any(), ).Return(set.Of(successNodeID)) }, @@ -714,7 +718,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { failedMsgF: func(nodeID ids.NodeID) message.InboundMessage { return message.InternalGetAcceptedFrontierFailed( nodeID, - chainID, + ctx.ChainID, requestID, engineType, ) @@ -722,7 +726,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&p2p.GetAcceptedFrontier{}, msg.Message()) innerMsg := msg.Message().(*p2p.GetAcceptedFrontier) - require.Equal(chainID[:], innerMsg.ChainId) + require.Equal(ctx.ChainID[:], innerMsg.ChainId) require.Equal(requestID, innerMsg.RequestId) require.Equal(uint64(deadline), innerMsg.Deadline) require.Equal(engineType, innerMsg.EngineType) @@ -730,7 +734,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { expectedResponseOp: message.AcceptedFrontierOp, setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().GetAcceptedFrontier( - chainID, + ctx.ChainID, requestID, deadline, engineType, @@ -741,7 +745,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { gomock.Any(), // Outbound message // Note [myNodeID] is not in this set set.Of(successNodeID, failedNodeID), - subnetID, // Subnet ID + ctx.SubnetID, // Subnet ID gomock.Any(), ).Return(set.Of(successNodeID)) }, @@ -755,7 +759,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { failedMsgF: func(nodeID ids.NodeID) message.InboundMessage { return message.InternalGetAcceptedFailed( nodeID, - chainID, + ctx.ChainID, requestID, engineType, ) @@ -763,7 +767,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&p2p.GetAccepted{}, msg.Message()) innerMsg := msg.Message().(*p2p.GetAccepted) - require.Equal(chainID[:], innerMsg.ChainId) + require.Equal(ctx.ChainID[:], innerMsg.ChainId) require.Equal(requestID, innerMsg.RequestId) require.Equal(uint64(deadline), innerMsg.Deadline) require.Equal(engineType, innerMsg.EngineType) @@ -771,7 +775,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { expectedResponseOp: message.AcceptedOp, setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().GetAccepted( - chainID, + ctx.ChainID, requestID, deadline, containerIDs, @@ -783,7 +787,7 @@ func TestSender_Bootstrap_Requests(t *testing.T) { gomock.Any(), // Outbound message // Note [myNodeID] is not in this set set.Of(successNodeID, failedNodeID), - subnetID, // Subnet ID + ctx.SubnetID, // Subnet ID gomock.Any(), ).Return(set.Of(successNodeID)) }, @@ -804,14 +808,17 @@ func TestSender_Bootstrap_Requests(t *testing.T) { externalSender = NewMockExternalSender(ctrl) timeoutManager = timeout.NewMockManager(ctrl) router = router.NewMockRouter(ctrl) - nodeIDs = set.Of(successNodeID, failedNodeID, myNodeID) + nodeIDs = set.Of(successNodeID, failedNodeID, ctx.NodeID) nodeIDsCopy set.Set[ids.NodeID] ) nodeIDsCopy.Union(nodeIDs) - snowCtx.Registerer = prometheus.NewRegistry() + + // Instantiate new registerers to avoid duplicate metrics + // registration + ctx.Registerer = prometheus.NewRegistry() sender, err := New( - snowCtx, + ctx, msgCreator, externalSender, router, @@ -830,8 +837,8 @@ func TestSender_Bootstrap_Requests(t *testing.T) { router.EXPECT().RegisterRequest( gomock.Any(), // Context nodeID, // Node ID - chainID, // Source Chain - chainID, // Destination Chain + ctx.ChainID, // Source Chain + ctx.ChainID, // Destination Chain requestID, // Request ID tt.expectedResponseOp, // Operation expectedFailedMsg, // Failure Message @@ -868,25 +875,15 @@ func TestSender_Bootstrap_Requests(t *testing.T) { func TestSender_Bootstrap_Responses(t *testing.T) { var ( - chainID = ids.GenerateTestID() - subnetID = ids.GenerateTestID() - myNodeID = ids.GenerateTestNodeID() destinationNodeID = ids.GenerateTestNodeID() deadline = time.Second requestID = uint32(1337) - ctx = snow.DefaultContextTest() summaryIDs = []ids.ID{ids.GenerateTestID(), ids.GenerateTestID()} summary = []byte{1, 2, 3} engineType = p2p.EngineType_ENGINE_TYPE_AVALANCHE ) - ctx.ChainID = chainID - ctx.SubnetID = subnetID - ctx.NodeID = myNodeID - snowCtx := &snow.ConsensusContext{ - Context: ctx, - Registerer: prometheus.NewRegistry(), - AvalancheRegisterer: prometheus.NewRegistry(), - } + snowCtx := snowtest.Context(t, snowtest.PChainID) + ctx := snowtest.ConsensusContext(snowCtx) type test struct { name string @@ -901,7 +898,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { name: "StateSummaryFrontier", setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().StateSummaryFrontier( - chainID, + ctx.ChainID, requestID, summary, ).Return(nil, nil) // Don't care about the message @@ -909,7 +906,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&p2p.StateSummaryFrontier{}, msg.Message()) innerMsg := msg.Message().(*p2p.StateSummaryFrontier) - require.Equal(chainID[:], innerMsg.ChainId) + require.Equal(ctx.ChainID[:], innerMsg.ChainId) require.Equal(requestID, innerMsg.RequestId) require.Equal(summary, innerMsg.Summary) }, @@ -917,7 +914,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { externalSender.EXPECT().Send( gomock.Any(), // Outbound message set.Of(destinationNodeID), // Node IDs - subnetID, // Subnet ID + ctx.SubnetID, // Subnet ID gomock.Any(), ).Return(nil) }, @@ -929,7 +926,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { name: "AcceptedStateSummary", setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().AcceptedStateSummary( - chainID, + ctx.ChainID, requestID, summaryIDs, ).Return(nil, nil) // Don't care about the message @@ -937,7 +934,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&p2p.AcceptedStateSummary{}, msg.Message()) innerMsg := msg.Message().(*p2p.AcceptedStateSummary) - require.Equal(chainID[:], innerMsg.ChainId) + require.Equal(ctx.ChainID[:], innerMsg.ChainId) require.Equal(requestID, innerMsg.RequestId) for i, summaryID := range summaryIDs { require.Equal(summaryID[:], innerMsg.SummaryIds[i]) @@ -947,7 +944,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { externalSender.EXPECT().Send( gomock.Any(), // Outbound message set.Of(destinationNodeID), // Node IDs - subnetID, // Subnet ID + ctx.SubnetID, // Subnet ID gomock.Any(), ).Return(nil) }, @@ -959,7 +956,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { name: "AcceptedFrontier", setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().AcceptedFrontier( - chainID, + ctx.ChainID, requestID, summaryIDs[0], ).Return(nil, nil) // Don't care about the message @@ -967,7 +964,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&p2p.AcceptedFrontier{}, msg.Message()) innerMsg := msg.Message().(*p2p.AcceptedFrontier) - require.Equal(chainID[:], innerMsg.ChainId) + require.Equal(ctx.ChainID[:], innerMsg.ChainId) require.Equal(requestID, innerMsg.RequestId) require.Equal(summaryIDs[0][:], innerMsg.ContainerId) }, @@ -975,7 +972,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { externalSender.EXPECT().Send( gomock.Any(), // Outbound message set.Of(destinationNodeID), // Node IDs - subnetID, // Subnet ID + ctx.SubnetID, // Subnet ID gomock.Any(), ).Return(nil) }, @@ -987,7 +984,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { name: "Accepted", setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().Accepted( - chainID, + ctx.ChainID, requestID, summaryIDs, ).Return(nil, nil) // Don't care about the message @@ -995,7 +992,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&p2p.Accepted{}, msg.Message()) innerMsg := msg.Message().(*p2p.Accepted) - require.Equal(chainID[:], innerMsg.ChainId) + require.Equal(ctx.ChainID[:], innerMsg.ChainId) require.Equal(requestID, innerMsg.RequestId) for i, summaryID := range summaryIDs { require.Equal(summaryID[:], innerMsg.ContainerIds[i]) @@ -1005,7 +1002,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { externalSender.EXPECT().Send( gomock.Any(), // Outbound message set.Of(destinationNodeID), // Node IDs - subnetID, // Subnet ID + ctx.SubnetID, // Subnet ID gomock.Any(), ).Return(nil) }, @@ -1029,11 +1026,11 @@ func TestSender_Bootstrap_Responses(t *testing.T) { // Instantiate new registerers to avoid duplicate metrics // registration - snowCtx.Registerer = prometheus.NewRegistry() - snowCtx.AvalancheRegisterer = prometheus.NewRegistry() + ctx.Registerer = prometheus.NewRegistry() + ctx.AvalancheRegisterer = prometheus.NewRegistry() sender, err := New( - snowCtx, + ctx, msgCreator, externalSender, router, @@ -1057,7 +1054,7 @@ func TestSender_Bootstrap_Responses(t *testing.T) { close(calledHandleInbound) }, ) - tt.sendF(require, sender, myNodeID) + tt.sendF(require, sender, ctx.NodeID) <-calledHandleInbound } @@ -1076,24 +1073,14 @@ func TestSender_Bootstrap_Responses(t *testing.T) { func TestSender_Single_Request(t *testing.T) { var ( - chainID = ids.GenerateTestID() - subnetID = ids.GenerateTestID() - myNodeID = ids.GenerateTestNodeID() destinationNodeID = ids.GenerateTestNodeID() deadline = time.Second requestID = uint32(1337) - ctx = snow.DefaultContextTest() containerID = ids.GenerateTestID() engineType = p2p.EngineType_ENGINE_TYPE_SNOWMAN ) - ctx.ChainID = chainID - ctx.SubnetID = subnetID - ctx.NodeID = myNodeID - snowCtx := &snow.ConsensusContext{ - Context: ctx, - Registerer: prometheus.NewRegistry(), - AvalancheRegisterer: prometheus.NewRegistry(), - } + snowCtx := snowtest.Context(t, snowtest.PChainID) + ctx := snowtest.ConsensusContext(snowCtx) type test struct { name string @@ -1111,7 +1098,7 @@ func TestSender_Single_Request(t *testing.T) { failedMsgF: func(nodeID ids.NodeID) message.InboundMessage { return message.InternalGetAncestorsFailed( nodeID, - chainID, + ctx.ChainID, requestID, engineType, ) @@ -1119,14 +1106,14 @@ func TestSender_Single_Request(t *testing.T) { assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&message.GetAncestorsFailed{}, msg.Message()) innerMsg := msg.Message().(*message.GetAncestorsFailed) - require.Equal(chainID, innerMsg.ChainID) + require.Equal(ctx.ChainID, innerMsg.ChainID) require.Equal(requestID, innerMsg.RequestID) require.Equal(engineType, innerMsg.EngineType) }, expectedResponseOp: message.AncestorsOp, setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().GetAncestors( - chainID, + ctx.ChainID, requestID, deadline, containerID, @@ -1137,7 +1124,7 @@ func TestSender_Single_Request(t *testing.T) { externalSender.EXPECT().Send( gomock.Any(), // Outbound message set.Of(destinationNodeID), // Node IDs - subnetID, + ctx.SubnetID, gomock.Any(), ).Return(sentTo) }, @@ -1150,7 +1137,7 @@ func TestSender_Single_Request(t *testing.T) { failedMsgF: func(nodeID ids.NodeID) message.InboundMessage { return message.InternalGetFailed( nodeID, - chainID, + ctx.ChainID, requestID, engineType, ) @@ -1158,14 +1145,14 @@ func TestSender_Single_Request(t *testing.T) { assertMsgToMyself: func(require *require.Assertions, msg message.InboundMessage) { require.IsType(&message.GetFailed{}, msg.Message()) innerMsg := msg.Message().(*message.GetFailed) - require.Equal(chainID, innerMsg.ChainID) + require.Equal(ctx.ChainID, innerMsg.ChainID) require.Equal(requestID, innerMsg.RequestID) require.Equal(engineType, innerMsg.EngineType) }, expectedResponseOp: message.PutOp, setMsgCreatorExpect: func(msgCreator *message.MockOutboundMsgBuilder) { msgCreator.EXPECT().Get( - chainID, + ctx.ChainID, requestID, deadline, containerID, @@ -1176,7 +1163,7 @@ func TestSender_Single_Request(t *testing.T) { externalSender.EXPECT().Send( gomock.Any(), // Outbound message set.Of(destinationNodeID), // Node IDs - subnetID, + ctx.SubnetID, gomock.Any(), ).Return(sentTo) }, @@ -1197,10 +1184,13 @@ func TestSender_Single_Request(t *testing.T) { timeoutManager = timeout.NewMockManager(ctrl) router = router.NewMockRouter(ctrl) ) - snowCtx.Registerer = prometheus.NewRegistry() + + // Instantiate new registerers to avoid duplicate metrics + // registration + ctx.Registerer = prometheus.NewRegistry() sender, err := New( - snowCtx, + ctx, msgCreator, externalSender, router, @@ -1216,12 +1206,12 @@ func TestSender_Single_Request(t *testing.T) { // Case: sending to myself { // Make sure we register requests with the router - expectedFailedMsg := tt.failedMsgF(myNodeID) + expectedFailedMsg := tt.failedMsgF(ctx.NodeID) router.EXPECT().RegisterRequest( gomock.Any(), // Context - myNodeID, // Node ID - chainID, // Source Chain - chainID, // Destination Chain + ctx.NodeID, // Node ID + ctx.ChainID, // Source Chain + ctx.ChainID, // Destination Chain requestID, // Request ID tt.expectedResponseOp, // Operation expectedFailedMsg, // Failure Message @@ -1240,14 +1230,14 @@ func TestSender_Single_Request(t *testing.T) { }, ) - tt.sendF(require, sender, myNodeID) + tt.sendF(require, sender, ctx.NodeID) <-calledHandleInbound } // Case: Node is benched { - timeoutManager.EXPECT().IsBenched(destinationNodeID, chainID).Return(true) + timeoutManager.EXPECT().IsBenched(destinationNodeID, ctx.ChainID).Return(true) timeoutManager.EXPECT().RegisterRequestToUnreachableValidator() @@ -1256,8 +1246,8 @@ func TestSender_Single_Request(t *testing.T) { router.EXPECT().RegisterRequest( gomock.Any(), // Context destinationNodeID, // Node ID - chainID, // Source Chain - chainID, // Destination Chain + ctx.ChainID, // Source Chain + ctx.ChainID, // Destination Chain requestID, // Request ID tt.expectedResponseOp, // Operation expectedFailedMsg, // Failure Message @@ -1283,7 +1273,7 @@ func TestSender_Single_Request(t *testing.T) { // Case: Node is not myself, not benched and send fails { - timeoutManager.EXPECT().IsBenched(destinationNodeID, chainID).Return(false) + timeoutManager.EXPECT().IsBenched(destinationNodeID, ctx.ChainID).Return(false) timeoutManager.EXPECT().RegisterRequestToUnreachableValidator() @@ -1292,8 +1282,8 @@ func TestSender_Single_Request(t *testing.T) { router.EXPECT().RegisterRequest( gomock.Any(), // Context destinationNodeID, // Node ID - chainID, // Source Chain - chainID, // Destination Chain + ctx.ChainID, // Source Chain + ctx.ChainID, // Destination Chain requestID, // Request ID tt.expectedResponseOp, // Operation expectedFailedMsg, // Failure Message diff --git a/snow/networking/sender/test_external_sender.go b/snow/networking/sender/test_external_sender.go index 7b8bef90e8eb..ae06187216bf 100644 --- a/snow/networking/sender/test_external_sender.go +++ b/snow/networking/sender/test_external_sender.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sender diff --git a/snow/networking/sender/traced_sender.go b/snow/networking/sender/traced_sender.go index 2011cdc38fb1..c5fdf6dcbc54 100644 --- a/snow/networking/sender/traced_sender.go +++ b/snow/networking/sender/traced_sender.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sender diff --git a/snow/networking/timeout/main_test.go b/snow/networking/timeout/main_test.go index f3bee130e58b..c8a597fa91b1 100644 --- a/snow/networking/timeout/main_test.go +++ b/snow/networking/timeout/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timeout diff --git a/snow/networking/timeout/manager.go b/snow/networking/timeout/manager.go index f1db8a1e01a0..95a3be25e166 100644 --- a/snow/networking/timeout/manager.go +++ b/snow/networking/timeout/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timeout diff --git a/snow/networking/timeout/manager_test.go b/snow/networking/timeout/manager_test.go index 582da3a9ea1b..73313322a81a 100644 --- a/snow/networking/timeout/manager_test.go +++ b/snow/networking/timeout/manager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timeout diff --git a/snow/networking/timeout/metrics.go b/snow/networking/timeout/metrics.go index 6be45fd2a97e..def073b56558 100644 --- a/snow/networking/timeout/metrics.go +++ b/snow/networking/timeout/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timeout diff --git a/snow/networking/timeout/mock_manager.go b/snow/networking/timeout/mock_manager.go index 5a1bda7cb0b6..8eeac4c6f8dc 100644 --- a/snow/networking/timeout/mock_manager.go +++ b/snow/networking/timeout/mock_manager.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/networking/timeout (interfaces: Manager) +// +// Generated by this command: +// +// mockgen -package=timeout -destination=snow/networking/timeout/mock_manager.go github.com/ava-labs/avalanchego/snow/networking/timeout Manager +// // Package timeout is a generated GoMock package. package timeout @@ -61,7 +63,7 @@ func (m *MockManager) IsBenched(arg0 ids.NodeID, arg1 ids.ID) bool { } // IsBenched indicates an expected call of IsBenched. -func (mr *MockManagerMockRecorder) IsBenched(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) IsBenched(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsBenched", reflect.TypeOf((*MockManager)(nil).IsBenched), arg0, arg1) } @@ -75,7 +77,7 @@ func (m *MockManager) RegisterChain(arg0 *snow.ConsensusContext) error { } // RegisterChain indicates an expected call of RegisterChain. -func (mr *MockManagerMockRecorder) RegisterChain(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) RegisterChain(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterChain", reflect.TypeOf((*MockManager)(nil).RegisterChain), arg0) } @@ -87,7 +89,7 @@ func (m *MockManager) RegisterRequest(arg0 ids.NodeID, arg1 ids.ID, arg2 bool, a } // RegisterRequest indicates an expected call of RegisterRequest. -func (mr *MockManagerMockRecorder) RegisterRequest(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) RegisterRequest(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterRequest", reflect.TypeOf((*MockManager)(nil).RegisterRequest), arg0, arg1, arg2, arg3, arg4) } @@ -111,7 +113,7 @@ func (m *MockManager) RegisterResponse(arg0 ids.NodeID, arg1 ids.ID, arg2 ids.Re } // RegisterResponse indicates an expected call of RegisterResponse. -func (mr *MockManagerMockRecorder) RegisterResponse(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) RegisterResponse(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterResponse", reflect.TypeOf((*MockManager)(nil).RegisterResponse), arg0, arg1, arg2, arg3, arg4) } @@ -123,7 +125,7 @@ func (m *MockManager) RemoveRequest(arg0 ids.RequestID) { } // RemoveRequest indicates an expected call of RemoveRequest. -func (mr *MockManagerMockRecorder) RemoveRequest(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) RemoveRequest(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveRequest", reflect.TypeOf((*MockManager)(nil).RemoveRequest), arg0) } diff --git a/snow/networking/tracker/mock_resource_tracker.go b/snow/networking/tracker/mock_resource_tracker.go index 4ba16ec98997..438bd44d9b4f 100644 --- a/snow/networking/tracker/mock_resource_tracker.go +++ b/snow/networking/tracker/mock_resource_tracker.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/networking/tracker (interfaces: Tracker) +// +// Generated by this command: +// +// mockgen -package=tracker -destination=snow/networking/tracker/mock_resource_tracker.go github.com/ava-labs/avalanchego/snow/networking/tracker Tracker +// // Package tracker is a generated GoMock package. package tracker @@ -47,7 +49,7 @@ func (m *MockTracker) TimeUntilUsage(arg0 ids.NodeID, arg1 time.Time, arg2 float } // TimeUntilUsage indicates an expected call of TimeUntilUsage. -func (mr *MockTrackerMockRecorder) TimeUntilUsage(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockTrackerMockRecorder) TimeUntilUsage(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TimeUntilUsage", reflect.TypeOf((*MockTracker)(nil).TimeUntilUsage), arg0, arg1, arg2) } @@ -75,7 +77,7 @@ func (m *MockTracker) Usage(arg0 ids.NodeID, arg1 time.Time) float64 { } // Usage indicates an expected call of Usage. -func (mr *MockTrackerMockRecorder) Usage(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockTrackerMockRecorder) Usage(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Usage", reflect.TypeOf((*MockTracker)(nil).Usage), arg0, arg1) } diff --git a/snow/networking/tracker/mock_targeter.go b/snow/networking/tracker/mock_targeter.go index d6fe7b540c8f..7e260fe69d6d 100644 --- a/snow/networking/tracker/mock_targeter.go +++ b/snow/networking/tracker/mock_targeter.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/networking/tracker (interfaces: Targeter) +// +// Generated by this command: +// +// mockgen -package=tracker -destination=snow/networking/tracker/mock_targeter.go github.com/ava-labs/avalanchego/snow/networking/tracker Targeter +// // Package tracker is a generated GoMock package. package tracker @@ -46,7 +48,7 @@ func (m *MockTargeter) TargetUsage(arg0 ids.NodeID) float64 { } // TargetUsage indicates an expected call of TargetUsage. -func (mr *MockTargeterMockRecorder) TargetUsage(arg0 interface{}) *gomock.Call { +func (mr *MockTargeterMockRecorder) TargetUsage(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TargetUsage", reflect.TypeOf((*MockTargeter)(nil).TargetUsage), arg0) } diff --git a/snow/networking/tracker/resource_tracker.go b/snow/networking/tracker/resource_tracker.go index 7910c2fff475..b4b14a7561cf 100644 --- a/snow/networking/tracker/resource_tracker.go +++ b/snow/networking/tracker/resource_tracker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracker diff --git a/snow/networking/tracker/resource_tracker_test.go b/snow/networking/tracker/resource_tracker_test.go index 64a897589f90..a87958708cb1 100644 --- a/snow/networking/tracker/resource_tracker_test.go +++ b/snow/networking/tracker/resource_tracker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracker diff --git a/snow/networking/tracker/targeter.go b/snow/networking/tracker/targeter.go index 4c69ab9508c1..39a7398e391c 100644 --- a/snow/networking/tracker/targeter.go +++ b/snow/networking/tracker/targeter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracker diff --git a/snow/networking/tracker/targeter_test.go b/snow/networking/tracker/targeter_test.go index 55974dbf4ac6..cc533791cf91 100644 --- a/snow/networking/tracker/targeter_test.go +++ b/snow/networking/tracker/targeter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracker diff --git a/snow/snowtest/snowtest.go b/snow/snowtest/snowtest.go new file mode 100644 index 000000000000..83fd98925d90 --- /dev/null +++ b/snow/snowtest/snowtest.go @@ -0,0 +1,99 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package snowtest + +import ( + "context" + "errors" + "testing" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/api/metrics" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/logging" +) + +var ( + XChainID = ids.GenerateTestID() + CChainID = ids.GenerateTestID() + PChainID = constants.PlatformChainID + AVAXAssetID = ids.GenerateTestID() + + errMissing = errors.New("missing") + + _ snow.Acceptor = noOpAcceptor{} +) + +type noOpAcceptor struct{} + +func (noOpAcceptor) Accept(*snow.ConsensusContext, ids.ID, []byte) error { + return nil +} + +func ConsensusContext(ctx *snow.Context) *snow.ConsensusContext { + return &snow.ConsensusContext{ + Context: ctx, + Registerer: prometheus.NewRegistry(), + AvalancheRegisterer: prometheus.NewRegistry(), + BlockAcceptor: noOpAcceptor{}, + TxAcceptor: noOpAcceptor{}, + VertexAcceptor: noOpAcceptor{}, + } +} + +func Context(tb testing.TB, chainID ids.ID) *snow.Context { + require := require.New(tb) + + secretKey, err := bls.NewSecretKey() + require.NoError(err) + publicKey := bls.PublicFromSecretKey(secretKey) + + aliaser := ids.NewAliaser() + require.NoError(aliaser.Alias(constants.PlatformChainID, "P")) + require.NoError(aliaser.Alias(constants.PlatformChainID, constants.PlatformChainID.String())) + require.NoError(aliaser.Alias(XChainID, "X")) + require.NoError(aliaser.Alias(XChainID, XChainID.String())) + require.NoError(aliaser.Alias(CChainID, "C")) + require.NoError(aliaser.Alias(CChainID, CChainID.String())) + + validatorState := &validators.TestState{ + GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { + subnetID, ok := map[ids.ID]ids.ID{ + constants.PlatformChainID: constants.PrimaryNetworkID, + XChainID: constants.PrimaryNetworkID, + CChainID: constants.PrimaryNetworkID, + }[chainID] + if !ok { + return ids.Empty, errMissing + } + return subnetID, nil + }, + } + + return &snow.Context{ + NetworkID: constants.UnitTestID, + SubnetID: constants.PrimaryNetworkID, + ChainID: chainID, + NodeID: ids.EmptyNodeID, + PublicKey: publicKey, + + XChainID: XChainID, + CChainID: CChainID, + AVAXAssetID: AVAXAssetID, + + Log: logging.NoLog{}, + BCLookup: aliaser, + Metrics: metrics.NewOptionalGatherer(), + + ValidatorState: validatorState, + ChainDataDir: "", + } +} diff --git a/snow/state.go b/snow/state.go index bb671f26e672..091cd31f50f1 100644 --- a/snow/state.go +++ b/snow/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package snow diff --git a/snow/uptime/locked_calculator.go b/snow/uptime/locked_calculator.go index 687b5f5905f5..884878ab24f6 100644 --- a/snow/uptime/locked_calculator.go +++ b/snow/uptime/locked_calculator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package uptime diff --git a/snow/uptime/locked_calculator_test.go b/snow/uptime/locked_calculator_test.go index 254497f62d02..3b073726e654 100644 --- a/snow/uptime/locked_calculator_test.go +++ b/snow/uptime/locked_calculator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package uptime diff --git a/snow/uptime/manager.go b/snow/uptime/manager.go index 2fc2e1605298..a64b71ca62de 100644 --- a/snow/uptime/manager.go +++ b/snow/uptime/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package uptime diff --git a/snow/uptime/manager_test.go b/snow/uptime/manager_test.go index de2c038086d5..e04fcc3a9fbe 100644 --- a/snow/uptime/manager_test.go +++ b/snow/uptime/manager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package uptime diff --git a/snow/uptime/mock_calculator.go b/snow/uptime/mock_calculator.go index 389c029e68fe..cc5b5942e639 100644 --- a/snow/uptime/mock_calculator.go +++ b/snow/uptime/mock_calculator.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/uptime (interfaces: Calculator) +// +// Generated by this command: +// +// mockgen -package=uptime -destination=snow/uptime/mock_calculator.go github.com/ava-labs/avalanchego/snow/uptime Calculator +// // Package uptime is a generated GoMock package. package uptime @@ -49,7 +51,7 @@ func (m *MockCalculator) CalculateUptime(arg0 ids.NodeID, arg1 ids.ID) (time.Dur } // CalculateUptime indicates an expected call of CalculateUptime. -func (mr *MockCalculatorMockRecorder) CalculateUptime(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockCalculatorMockRecorder) CalculateUptime(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateUptime", reflect.TypeOf((*MockCalculator)(nil).CalculateUptime), arg0, arg1) } @@ -64,7 +66,7 @@ func (m *MockCalculator) CalculateUptimePercent(arg0 ids.NodeID, arg1 ids.ID) (f } // CalculateUptimePercent indicates an expected call of CalculateUptimePercent. -func (mr *MockCalculatorMockRecorder) CalculateUptimePercent(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockCalculatorMockRecorder) CalculateUptimePercent(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateUptimePercent", reflect.TypeOf((*MockCalculator)(nil).CalculateUptimePercent), arg0, arg1) } @@ -79,7 +81,7 @@ func (m *MockCalculator) CalculateUptimePercentFrom(arg0 ids.NodeID, arg1 ids.ID } // CalculateUptimePercentFrom indicates an expected call of CalculateUptimePercentFrom. -func (mr *MockCalculatorMockRecorder) CalculateUptimePercentFrom(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockCalculatorMockRecorder) CalculateUptimePercentFrom(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CalculateUptimePercentFrom", reflect.TypeOf((*MockCalculator)(nil).CalculateUptimePercentFrom), arg0, arg1, arg2) } diff --git a/snow/uptime/no_op_calculator.go b/snow/uptime/no_op_calculator.go index 44c688e31be7..fb308f4f6030 100644 --- a/snow/uptime/no_op_calculator.go +++ b/snow/uptime/no_op_calculator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package uptime diff --git a/snow/uptime/state.go b/snow/uptime/state.go index 5b2592acc70d..f9edeb76a3ee 100644 --- a/snow/uptime/state.go +++ b/snow/uptime/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package uptime diff --git a/snow/uptime/test_state.go b/snow/uptime/test_state.go index 58687e1671b8..23879b5cb3a9 100644 --- a/snow/uptime/test_state.go +++ b/snow/uptime/test_state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package uptime diff --git a/snow/validators/connector.go b/snow/validators/connector.go index abb28d084c20..e3e7e1f94ed4 100644 --- a/snow/validators/connector.go +++ b/snow/validators/connector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/snow/validators/gvalidators/validator_state_client.go b/snow/validators/gvalidators/validator_state_client.go index 51e68592c001..49fa1e641417 100644 --- a/snow/validators/gvalidators/validator_state_client.go +++ b/snow/validators/gvalidators/validator_state_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gvalidators diff --git a/snow/validators/gvalidators/validator_state_server.go b/snow/validators/gvalidators/validator_state_server.go index ad9b75197947..5476dca4db99 100644 --- a/snow/validators/gvalidators/validator_state_server.go +++ b/snow/validators/gvalidators/validator_state_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gvalidators diff --git a/snow/validators/gvalidators/validator_state_test.go b/snow/validators/gvalidators/validator_state_test.go index da8a66570f0b..9b6e692d8645 100644 --- a/snow/validators/gvalidators/validator_state_test.go +++ b/snow/validators/gvalidators/validator_state_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gvalidators diff --git a/snow/validators/logger.go b/snow/validators/logger.go index 2e672a1827ba..40613b76b68d 100644 --- a/snow/validators/logger.go +++ b/snow/validators/logger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/snow/validators/manager.go b/snow/validators/manager.go index c42ea779d96b..5844c1e7f185 100644 --- a/snow/validators/manager.go +++ b/snow/validators/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/snow/validators/manager_test.go b/snow/validators/manager_test.go index f93ab719e18a..781d2e784e1d 100644 --- a/snow/validators/manager_test.go +++ b/snow/validators/manager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators @@ -216,30 +216,30 @@ func TestLen(t *testing.T) { m := NewManager() subnetID := ids.GenerateTestID() - len := m.Count(subnetID) - require.Zero(len) + count := m.Count(subnetID) + require.Zero(count) nodeID0 := ids.GenerateTestNodeID() require.NoError(m.AddStaker(subnetID, nodeID0, nil, ids.Empty, 1)) - len = m.Count(subnetID) - require.Equal(1, len) + count = m.Count(subnetID) + require.Equal(1, count) nodeID1 := ids.GenerateTestNodeID() require.NoError(m.AddStaker(subnetID, nodeID1, nil, ids.Empty, 1)) - len = m.Count(subnetID) - require.Equal(2, len) + count = m.Count(subnetID) + require.Equal(2, count) require.NoError(m.RemoveWeight(subnetID, nodeID1, 1)) - len = m.Count(subnetID) - require.Equal(1, len) + count = m.Count(subnetID) + require.Equal(1, count) require.NoError(m.RemoveWeight(subnetID, nodeID0, 1)) - len = m.Count(subnetID) - require.Zero(len) + count = m.Count(subnetID) + require.Zero(count) } func TestGetMap(t *testing.T) { @@ -398,12 +398,12 @@ func TestString(t *testing.T) { require.NoError(m.AddStaker(subnetID0, nodeID1, nil, ids.Empty, math.MaxInt64-1)) require.NoError(m.AddStaker(subnetID1, nodeID1, nil, ids.Empty, 1)) - expected := "Validator Manager: (Size = 2)\n" + - " Subnet[TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES]: Validator Set: (Size = 2, Weight = 9223372036854775807)\n" + - " Validator[0]: NodeID-111111111111111111116DBWJs, 1\n" + - " Validator[1]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 9223372036854775806\n" + - " Subnet[2mcwQKiD8VEspmMJpL1dc7okQQ5dDVAWeCBZ7FWBFAbxpv3t7w]: Validator Set: (Size = 1, Weight = 1)\n" + - " Validator[0]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 1" + expected := `Validator Manager: (Size = 2) + Subnet[TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES]: Validator Set: (Size = 2, Weight = 9223372036854775807) + Validator[0]: NodeID-111111111111111111116DBWJs, 1 + Validator[1]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 9223372036854775806 + Subnet[2mcwQKiD8VEspmMJpL1dc7okQQ5dDVAWeCBZ7FWBFAbxpv3t7w]: Validator Set: (Size = 1, Weight = 1) + Validator[0]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 1` result := m.String() require.Equal(expected, result) } diff --git a/snow/validators/mock_manager.go b/snow/validators/mock_manager.go index 2b99245710fb..b622ba11223a 100644 --- a/snow/validators/mock_manager.go +++ b/snow/validators/mock_manager.go @@ -1,9 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Do not include this in mocks.mockgen.txt as bls package won't be available. // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/snow/validators (interfaces: Manager) +// Source: snow/validators/manager.go +// +// Generated by this command: +// +// mockgen -source=snow/validators/manager.go -destination=snow/validators/mock_manager.go -package=validators -exclude_interfaces=SetCallbackListener +// // Package validators is a generated GoMock package. package validators @@ -41,158 +42,143 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder { } // AddStaker mocks base method. -func (m *MockManager) AddStaker(arg0 ids.ID, arg1 ids.NodeID, arg2 *bls.PublicKey, arg3 ids.ID, arg4 uint64) error { +func (m *MockManager) AddStaker(subnetID ids.ID, nodeID ids.NodeID, pk *bls.PublicKey, txID ids.ID, weight uint64) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddStaker", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "AddStaker", subnetID, nodeID, pk, txID, weight) ret0, _ := ret[0].(error) return ret0 } // AddStaker indicates an expected call of AddStaker. -func (mr *MockManagerMockRecorder) AddStaker(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) AddStaker(subnetID, nodeID, pk, txID, weight any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddStaker", reflect.TypeOf((*MockManager)(nil).AddStaker), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddStaker", reflect.TypeOf((*MockManager)(nil).AddStaker), subnetID, nodeID, pk, txID, weight) } // AddWeight mocks base method. -func (m *MockManager) AddWeight(arg0 ids.ID, arg1 ids.NodeID, arg2 uint64) error { +func (m *MockManager) AddWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddWeight", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "AddWeight", subnetID, nodeID, weight) ret0, _ := ret[0].(error) return ret0 } // AddWeight indicates an expected call of AddWeight. -func (mr *MockManagerMockRecorder) AddWeight(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) AddWeight(subnetID, nodeID, weight any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddWeight", reflect.TypeOf((*MockManager)(nil).AddWeight), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddWeight", reflect.TypeOf((*MockManager)(nil).AddWeight), subnetID, nodeID, weight) } -// Contains mocks base method. -func (m *MockManager) Contains(arg0 ids.ID, arg1 ids.NodeID) bool { +// Count mocks base method. +func (m *MockManager) Count(subnetID ids.ID) int { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Contains", arg0, arg1) - ret0, _ := ret[0].(bool) + ret := m.ctrl.Call(m, "Count", subnetID) + ret0, _ := ret[0].(int) return ret0 } -// Contains indicates an expected call of Contains. -func (mr *MockManagerMockRecorder) Contains(arg0, arg1 interface{}) *gomock.Call { +// Count indicates an expected call of Count. +func (mr *MockManagerMockRecorder) Count(subnetID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Contains", reflect.TypeOf((*MockManager)(nil).Contains), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockManager)(nil).Count), subnetID) } // GetMap mocks base method. -func (m *MockManager) GetMap(arg0 ids.ID) map[ids.NodeID]*GetValidatorOutput { +func (m *MockManager) GetMap(subnetID ids.ID) map[ids.NodeID]*GetValidatorOutput { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMap", arg0) + ret := m.ctrl.Call(m, "GetMap", subnetID) ret0, _ := ret[0].(map[ids.NodeID]*GetValidatorOutput) return ret0 } // GetMap indicates an expected call of GetMap. -func (mr *MockManagerMockRecorder) GetMap(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetMap(subnetID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMap", reflect.TypeOf((*MockManager)(nil).GetMap), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMap", reflect.TypeOf((*MockManager)(nil).GetMap), subnetID) } // GetValidator mocks base method. -func (m *MockManager) GetValidator(arg0 ids.ID, arg1 ids.NodeID) (*Validator, bool) { +func (m *MockManager) GetValidator(subnetID ids.ID, nodeID ids.NodeID) (*Validator, bool) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetValidator", arg0, arg1) + ret := m.ctrl.Call(m, "GetValidator", subnetID, nodeID) ret0, _ := ret[0].(*Validator) ret1, _ := ret[1].(bool) return ret0, ret1 } // GetValidator indicates an expected call of GetValidator. -func (mr *MockManagerMockRecorder) GetValidator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetValidator(subnetID, nodeID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidator", reflect.TypeOf((*MockManager)(nil).GetValidator), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidator", reflect.TypeOf((*MockManager)(nil).GetValidator), subnetID, nodeID) } // GetValidatorIDs mocks base method. -func (m *MockManager) GetValidatorIDs(arg0 ids.ID) ([]ids.NodeID, error) { +func (m *MockManager) GetValidatorIDs(subnetID ids.ID) []ids.NodeID { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetValidatorIDs", arg0) + ret := m.ctrl.Call(m, "GetValidatorIDs", subnetID) ret0, _ := ret[0].([]ids.NodeID) - ret1, _ := ret[1].(error) - return ret0, ret1 + return ret0 } // GetValidatorIDs indicates an expected call of GetValidatorIDs. -func (mr *MockManagerMockRecorder) GetValidatorIDs(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetValidatorIDs(subnetID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorIDs", reflect.TypeOf((*MockManager)(nil).GetValidatorIDs), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorIDs", reflect.TypeOf((*MockManager)(nil).GetValidatorIDs), subnetID) } // GetWeight mocks base method. -func (m *MockManager) GetWeight(arg0 ids.ID, arg1 ids.NodeID) uint64 { +func (m *MockManager) GetWeight(subnetID ids.ID, nodeID ids.NodeID) uint64 { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetWeight", arg0, arg1) + ret := m.ctrl.Call(m, "GetWeight", subnetID, nodeID) ret0, _ := ret[0].(uint64) return ret0 } // GetWeight indicates an expected call of GetWeight. -func (mr *MockManagerMockRecorder) GetWeight(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWeight", reflect.TypeOf((*MockManager)(nil).GetWeight), arg0, arg1) -} - -// Len mocks base method. -func (m *MockManager) Len(arg0 ids.ID) int { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Len", arg0) - ret0, _ := ret[0].(int) - return ret0 -} - -// Len indicates an expected call of Len. -func (mr *MockManagerMockRecorder) Len(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetWeight(subnetID, nodeID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockManager)(nil).Len), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWeight", reflect.TypeOf((*MockManager)(nil).GetWeight), subnetID, nodeID) } // RegisterCallbackListener mocks base method. -func (m *MockManager) RegisterCallbackListener(arg0 ids.ID, arg1 SetCallbackListener) { +func (m *MockManager) RegisterCallbackListener(subnetID ids.ID, listener SetCallbackListener) { m.ctrl.T.Helper() - m.ctrl.Call(m, "RegisterCallbackListener", arg0, arg1) + m.ctrl.Call(m, "RegisterCallbackListener", subnetID, listener) } // RegisterCallbackListener indicates an expected call of RegisterCallbackListener. -func (mr *MockManagerMockRecorder) RegisterCallbackListener(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) RegisterCallbackListener(subnetID, listener any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterCallbackListener", reflect.TypeOf((*MockManager)(nil).RegisterCallbackListener), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterCallbackListener", reflect.TypeOf((*MockManager)(nil).RegisterCallbackListener), subnetID, listener) } // RemoveWeight mocks base method. -func (m *MockManager) RemoveWeight(arg0 ids.ID, arg1 ids.NodeID, arg2 uint64) error { +func (m *MockManager) RemoveWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveWeight", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "RemoveWeight", subnetID, nodeID, weight) ret0, _ := ret[0].(error) return ret0 } // RemoveWeight indicates an expected call of RemoveWeight. -func (mr *MockManagerMockRecorder) RemoveWeight(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) RemoveWeight(subnetID, nodeID, weight any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveWeight", reflect.TypeOf((*MockManager)(nil).RemoveWeight), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveWeight", reflect.TypeOf((*MockManager)(nil).RemoveWeight), subnetID, nodeID, weight) } // Sample mocks base method. -func (m *MockManager) Sample(arg0 ids.ID, arg1 int) ([]ids.NodeID, error) { +func (m *MockManager) Sample(subnetID ids.ID, size int) ([]ids.NodeID, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Sample", arg0, arg1) + ret := m.ctrl.Call(m, "Sample", subnetID, size) ret0, _ := ret[0].([]ids.NodeID) ret1, _ := ret[1].(error) return ret0, ret1 } // Sample indicates an expected call of Sample. -func (mr *MockManagerMockRecorder) Sample(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) Sample(subnetID, size any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sample", reflect.TypeOf((*MockManager)(nil).Sample), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sample", reflect.TypeOf((*MockManager)(nil).Sample), subnetID, size) } // String mocks base method. @@ -210,31 +196,31 @@ func (mr *MockManagerMockRecorder) String() *gomock.Call { } // SubsetWeight mocks base method. -func (m *MockManager) SubsetWeight(arg0 ids.ID, arg1 set.Set[ids.NodeID]) (uint64, error) { +func (m *MockManager) SubsetWeight(subnetID ids.ID, validatorIDs set.Set[ids.NodeID]) (uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubsetWeight", arg0, arg1) + ret := m.ctrl.Call(m, "SubsetWeight", subnetID, validatorIDs) ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) return ret0, ret1 } // SubsetWeight indicates an expected call of SubsetWeight. -func (mr *MockManagerMockRecorder) SubsetWeight(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) SubsetWeight(subnetID, validatorIDs any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubsetWeight", reflect.TypeOf((*MockManager)(nil).SubsetWeight), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubsetWeight", reflect.TypeOf((*MockManager)(nil).SubsetWeight), subnetID, validatorIDs) } // TotalWeight mocks base method. -func (m *MockManager) TotalWeight(arg0 ids.ID) (uint64, error) { +func (m *MockManager) TotalWeight(subnetID ids.ID) (uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "TotalWeight", arg0) + ret := m.ctrl.Call(m, "TotalWeight", subnetID) ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) return ret0, ret1 } // TotalWeight indicates an expected call of TotalWeight. -func (mr *MockManagerMockRecorder) TotalWeight(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) TotalWeight(subnetID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalWeight", reflect.TypeOf((*MockManager)(nil).TotalWeight), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalWeight", reflect.TypeOf((*MockManager)(nil).TotalWeight), subnetID) } diff --git a/snow/validators/mock_state.go b/snow/validators/mock_state.go index a438b0eb46ef..6bed638becd8 100644 --- a/snow/validators/mock_state.go +++ b/snow/validators/mock_state.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/validators (interfaces: State) +// +// Generated by this command: +// +// mockgen -package=validators -destination=snow/validators/mock_state.go github.com/ava-labs/avalanchego/snow/validators State +// // Package validators is a generated GoMock package. package validators @@ -48,7 +50,7 @@ func (m *MockState) GetCurrentHeight(arg0 context.Context) (uint64, error) { } // GetCurrentHeight indicates an expected call of GetCurrentHeight. -func (mr *MockStateMockRecorder) GetCurrentHeight(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetCurrentHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentHeight", reflect.TypeOf((*MockState)(nil).GetCurrentHeight), arg0) } @@ -63,7 +65,7 @@ func (m *MockState) GetMinimumHeight(arg0 context.Context) (uint64, error) { } // GetMinimumHeight indicates an expected call of GetMinimumHeight. -func (mr *MockStateMockRecorder) GetMinimumHeight(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetMinimumHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMinimumHeight", reflect.TypeOf((*MockState)(nil).GetMinimumHeight), arg0) } @@ -78,7 +80,7 @@ func (m *MockState) GetSubnetID(arg0 context.Context, arg1 ids.ID) (ids.ID, erro } // GetSubnetID indicates an expected call of GetSubnetID. -func (mr *MockStateMockRecorder) GetSubnetID(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetSubnetID(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetID", reflect.TypeOf((*MockState)(nil).GetSubnetID), arg0, arg1) } @@ -93,7 +95,7 @@ func (m *MockState) GetValidatorSet(arg0 context.Context, arg1 uint64, arg2 ids. } // GetValidatorSet indicates an expected call of GetValidatorSet. -func (mr *MockStateMockRecorder) GetValidatorSet(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetValidatorSet(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorSet", reflect.TypeOf((*MockState)(nil).GetValidatorSet), arg0, arg1, arg2) } diff --git a/snow/validators/mock_subnet_connector.go b/snow/validators/mock_subnet_connector.go index e5c985bd56cc..b9f3ee0519b8 100644 --- a/snow/validators/mock_subnet_connector.go +++ b/snow/validators/mock_subnet_connector.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/snow/validators (interfaces: SubnetConnector) +// +// Generated by this command: +// +// mockgen -package=validators -destination=snow/validators/mock_subnet_connector.go github.com/ava-labs/avalanchego/snow/validators SubnetConnector +// // Package validators is a generated GoMock package. package validators @@ -47,7 +49,7 @@ func (m *MockSubnetConnector) ConnectedSubnet(arg0 context.Context, arg1 ids.Nod } // ConnectedSubnet indicates an expected call of ConnectedSubnet. -func (mr *MockSubnetConnectorMockRecorder) ConnectedSubnet(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockSubnetConnectorMockRecorder) ConnectedSubnet(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConnectedSubnet", reflect.TypeOf((*MockSubnetConnector)(nil).ConnectedSubnet), arg0, arg1, arg2) } diff --git a/snow/validators/set.go b/snow/validators/set.go index dfa294a70bbe..5e7c81a2310e 100644 --- a/snow/validators/set.go +++ b/snow/validators/set.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/snow/validators/set_test.go b/snow/validators/set_test.go index 0067a520b5a9..4554f930fa37 100644 --- a/snow/validators/set_test.go +++ b/snow/validators/set_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators @@ -166,30 +166,30 @@ func TestSetLen(t *testing.T) { s := newSet() - len := s.Len() - require.Zero(len) + setLen := s.Len() + require.Zero(setLen) nodeID0 := ids.GenerateTestNodeID() require.NoError(s.Add(nodeID0, nil, ids.Empty, 1)) - len = s.Len() - require.Equal(1, len) + setLen = s.Len() + require.Equal(1, setLen) nodeID1 := ids.GenerateTestNodeID() require.NoError(s.Add(nodeID1, nil, ids.Empty, 1)) - len = s.Len() - require.Equal(2, len) + setLen = s.Len() + require.Equal(2, setLen) require.NoError(s.RemoveWeight(nodeID1, 1)) - len = s.Len() - require.Equal(1, len) + setLen = s.Len() + require.Equal(1, setLen) require.NoError(s.RemoveWeight(nodeID0, 1)) - len = s.Len() - require.Zero(len) + setLen = s.Len() + require.Zero(setLen) } func TestSetMap(t *testing.T) { @@ -342,9 +342,9 @@ func TestSetString(t *testing.T) { require.NoError(s.Add(nodeID1, nil, ids.Empty, math.MaxInt64-1)) - expected := "Validator Set: (Size = 2, Weight = 9223372036854775807)\n" + - " Validator[0]: NodeID-111111111111111111116DBWJs, 1\n" + - " Validator[1]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 9223372036854775806" + expected := `Validator Set: (Size = 2, Weight = 9223372036854775807) + Validator[0]: NodeID-111111111111111111116DBWJs, 1 + Validator[1]: NodeID-QLbz7JHiBTspS962RLKV8GndWFwdYhk6V, 9223372036854775806` result := s.String() require.Equal(expected, result) } diff --git a/snow/validators/state.go b/snow/validators/state.go index fa9ef2783165..3f92df35231b 100644 --- a/snow/validators/state.go +++ b/snow/validators/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/snow/validators/subnet_connector.go b/snow/validators/subnet_connector.go index 6b4a24bd85e5..06b02ff90820 100644 --- a/snow/validators/subnet_connector.go +++ b/snow/validators/subnet_connector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/snow/validators/test_state.go b/snow/validators/test_state.go index b27e6d972613..ee4102cf7194 100644 --- a/snow/validators/test_state.go +++ b/snow/validators/test_state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators @@ -23,7 +23,7 @@ var ( var _ State = (*TestState)(nil) type TestState struct { - T *testing.T + T testing.TB CantGetMinimumHeight, CantGetCurrentHeight, diff --git a/snow/validators/traced_state.go b/snow/validators/traced_state.go index e1f5472001e2..126a2b009eb0 100644 --- a/snow/validators/traced_state.go +++ b/snow/validators/traced_state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/snow/validators/unhandled_subnet_connector.go b/snow/validators/unhandled_subnet_connector.go index de7225aa2a80..08447c4582ad 100644 --- a/snow/validators/unhandled_subnet_connector.go +++ b/snow/validators/unhandled_subnet_connector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/snow/validators/validator.go b/snow/validators/validator.go index 56664ddc00a1..499b5189e424 100644 --- a/snow/validators/validator.go +++ b/snow/validators/validator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/staking/asn1.go b/staking/asn1.go index 13579600eb3c..afd817a95cd6 100644 --- a/staking/asn1.go +++ b/staking/asn1.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package staking @@ -6,6 +6,7 @@ package staking import ( "crypto" "crypto/x509" + "encoding/asn1" "fmt" // Explicitly import for the crypto.RegisterHash init side-effects. @@ -14,11 +15,28 @@ import ( _ "crypto/sha256" ) -// Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/x509.go#L326-L350 -var signatureAlgorithmVerificationDetails = map[x509.SignatureAlgorithm]x509.PublicKeyAlgorithm{ - x509.SHA256WithRSA: x509.RSA, - x509.ECDSAWithSHA256: x509.ECDSA, -} +var ( + // Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/x509.go#L433-L452 + // + // RFC 3279, 2.3 Public Key Algorithms + // + // pkcs-1 OBJECT IDENTIFIER ::== { iso(1) member-body(2) us(840) + // rsadsi(113549) pkcs(1) 1 } + // + // rsaEncryption OBJECT IDENTIFIER ::== { pkcs1-1 1 } + oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + // RFC 5480, 2.1.1 Unrestricted Algorithm Identifier and Parameters + // + // id-ecPublicKey OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 } + oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} + + // Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/x509.go#L326-L350 + signatureAlgorithmVerificationDetails = map[x509.SignatureAlgorithm]x509.PublicKeyAlgorithm{ + x509.SHA256WithRSA: x509.RSA, + x509.ECDSAWithSHA256: x509.ECDSA, + } +) func init() { if !crypto.SHA256.Available() { diff --git a/staking/certificate.go b/staking/certificate.go index 9521f43abef0..b3e1a511f63f 100644 --- a/staking/certificate.go +++ b/staking/certificate.go @@ -1,13 +1,17 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package staking -import "crypto/x509" +import ( + "crypto" + "crypto/x509" +) type Certificate struct { - Raw []byte - PublicKey any + Raw []byte + PublicKey crypto.PublicKey + // TODO: Remove after v1.11.x activates. SignatureAlgorithm x509.SignatureAlgorithm } diff --git a/staking/large_rsa_key.sig b/staking/large_rsa_key.sig deleted file mode 100644 index 61000a9903cf..000000000000 Binary files a/staking/large_rsa_key.sig and /dev/null differ diff --git a/staking/parse.go b/staking/parse.go index 04b47aba5a32..fd21c3cbe38e 100644 --- a/staking/parse.go +++ b/staking/parse.go @@ -1,15 +1,181 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package staking -import "crypto/x509" +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "errors" + "fmt" + "math/big" + + "golang.org/x/crypto/cryptobyte" + + cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" + + "github.com/ava-labs/avalanchego/utils/units" +) + +const ( + MaxCertificateLen = 2 * units.KiB + + allowedRSASmallModulusLen = 2048 + allowedRSALargeModulusLen = 4096 + allowedRSAPublicExponentValue = 65537 +) + +var ( + ErrCertificateTooLarge = fmt.Errorf("staking: certificate length is greater than %d", MaxCertificateLen) + ErrMalformedCertificate = errors.New("staking: malformed certificate") + ErrMalformedTBSCertificate = errors.New("staking: malformed tbs certificate") + ErrMalformedVersion = errors.New("staking: malformed version") + ErrMalformedSerialNumber = errors.New("staking: malformed serial number") + ErrMalformedSignatureAlgorithmIdentifier = errors.New("staking: malformed signature algorithm identifier") + ErrMalformedIssuer = errors.New("staking: malformed issuer") + ErrMalformedValidity = errors.New("staking: malformed validity") + ErrMalformedSPKI = errors.New("staking: malformed spki") + ErrMalformedPublicKeyAlgorithmIdentifier = errors.New("staking: malformed public key algorithm identifier") + ErrMalformedSubjectPublicKey = errors.New("staking: malformed subject public key") + ErrMalformedOID = errors.New("staking: malformed oid") + ErrInvalidRSAPublicKey = errors.New("staking: invalid RSA public key") + ErrInvalidRSAModulus = errors.New("staking: invalid RSA modulus") + ErrInvalidRSAPublicExponent = errors.New("staking: invalid RSA public exponent") + ErrRSAModulusNotPositive = errors.New("staking: RSA modulus is not a positive number") + ErrUnsupportedRSAModulusBitLen = errors.New("staking: unsupported RSA modulus bitlen") + ErrRSAModulusIsEven = errors.New("staking: RSA modulus is an even number") + ErrUnsupportedRSAPublicExponent = errors.New("staking: unsupported RSA public exponent") + ErrFailedUnmarshallingEllipticCurvePoint = errors.New("staking: failed to unmarshal elliptic curve point") + ErrUnknownPublicKeyAlgorithm = errors.New("staking: unknown public key algorithm") +) // ParseCertificate parses a single certificate from the given ASN.1 DER data. +// +// TODO: Remove after v1.11.x activates. func ParseCertificate(der []byte) (*Certificate, error) { - cert, err := x509.ParseCertificate(der) + x509Cert, err := x509.ParseCertificate(der) if err != nil { return nil, err } - return CertificateFromX509(cert), nil + stakingCert := CertificateFromX509(x509Cert) + return stakingCert, ValidateCertificate(stakingCert) +} + +// ParseCertificatePermissive parses a single certificate from the given ASN.1. +// +// This function does not validate that the certificate is valid to be used +// against normal TLS implementations. +// +// Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/parser.go#L789-L968 +func ParseCertificatePermissive(bytes []byte) (*Certificate, error) { + if len(bytes) > MaxCertificateLen { + return nil, ErrCertificateTooLarge + } + + input := cryptobyte.String(bytes) + // Consume the length and tag bytes. + if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { + return nil, ErrMalformedCertificate + } + + // Read the "to be signed" certificate into input. + if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { + return nil, ErrMalformedTBSCertificate + } + if !input.SkipOptionalASN1(cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) { + return nil, ErrMalformedVersion + } + if !input.SkipASN1(cryptobyte_asn1.INTEGER) { + return nil, ErrMalformedSerialNumber + } + if !input.SkipASN1(cryptobyte_asn1.SEQUENCE) { + return nil, ErrMalformedSignatureAlgorithmIdentifier + } + if !input.SkipASN1(cryptobyte_asn1.SEQUENCE) { + return nil, ErrMalformedIssuer + } + if !input.SkipASN1(cryptobyte_asn1.SEQUENCE) { + return nil, ErrMalformedValidity + } + if !input.SkipASN1(cryptobyte_asn1.SEQUENCE) { + return nil, ErrMalformedIssuer + } + + // Read the "subject public key info" into input. + if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { + return nil, ErrMalformedSPKI + } + + // Read the public key algorithm identifier. + var pkAISeq cryptobyte.String + if !input.ReadASN1(&pkAISeq, cryptobyte_asn1.SEQUENCE) { + return nil, ErrMalformedPublicKeyAlgorithmIdentifier + } + var pkAI asn1.ObjectIdentifier + if !pkAISeq.ReadASN1ObjectIdentifier(&pkAI) { + return nil, ErrMalformedOID + } + + // Note: Unlike the x509 package, we require parsing the public key. + + var spk asn1.BitString + if !input.ReadASN1BitString(&spk) { + return nil, ErrMalformedSubjectPublicKey + } + publicKey, signatureAlgorithm, err := parsePublicKey(pkAI, spk) + return &Certificate{ + Raw: bytes, + SignatureAlgorithm: signatureAlgorithm, + PublicKey: publicKey, + }, err +} + +// Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/parser.go#L215-L306 +func parsePublicKey(oid asn1.ObjectIdentifier, publicKey asn1.BitString) (crypto.PublicKey, x509.SignatureAlgorithm, error) { + der := cryptobyte.String(publicKey.RightAlign()) + switch { + case oid.Equal(oidPublicKeyRSA): + pub := &rsa.PublicKey{N: new(big.Int)} + if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { + return nil, 0, ErrInvalidRSAPublicKey + } + if !der.ReadASN1Integer(pub.N) { + return nil, 0, ErrInvalidRSAModulus + } + if !der.ReadASN1Integer(&pub.E) { + return nil, 0, ErrInvalidRSAPublicExponent + } + + if pub.N.Sign() <= 0 { + return nil, 0, ErrRSAModulusNotPositive + } + + if bitLen := pub.N.BitLen(); bitLen != allowedRSALargeModulusLen && bitLen != allowedRSASmallModulusLen { + return nil, 0, fmt.Errorf("%w: %d", ErrUnsupportedRSAModulusBitLen, bitLen) + } + if pub.N.Bit(0) == 0 { + return nil, 0, ErrRSAModulusIsEven + } + if pub.E != allowedRSAPublicExponentValue { + return nil, 0, fmt.Errorf("%w: %d", ErrUnsupportedRSAPublicExponent, pub.E) + } + return pub, x509.SHA256WithRSA, nil + case oid.Equal(oidPublicKeyECDSA): + namedCurve := elliptic.P256() + x, y := elliptic.Unmarshal(namedCurve, der) + if x == nil { + return nil, 0, ErrFailedUnmarshallingEllipticCurvePoint + } + return &ecdsa.PublicKey{ + Curve: namedCurve, + X: x, + Y: y, + }, x509.ECDSAWithSHA256, nil + default: + return nil, 0, ErrUnknownPublicKeyAlgorithm + } } diff --git a/staking/parse_test.go b/staking/parse_test.go new file mode 100644 index 000000000000..e9006e4ddcc0 --- /dev/null +++ b/staking/parse_test.go @@ -0,0 +1,89 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package staking + +import ( + "testing" + + _ "embed" + + "github.com/stretchr/testify/require" +) + +var ( + //go:embed large_rsa_key.cert + largeRSAKeyCert []byte + + parsers = []struct { + name string + parse func([]byte) (*Certificate, error) + }{ + { + name: "ParseCertificate", + parse: ParseCertificate, + }, + { + name: "ParseCertificatePermissive", + parse: ParseCertificatePermissive, + }, + } +) + +func TestParseCheckLargeCert(t *testing.T) { + for _, parser := range parsers { + t.Run(parser.name, func(t *testing.T) { + _, err := parser.parse(largeRSAKeyCert) + require.ErrorIs(t, err, ErrCertificateTooLarge) + }) + } +} + +func BenchmarkParse(b *testing.B) { + tlsCert, err := NewTLSCert() + require.NoError(b, err) + + bytes := tlsCert.Leaf.Raw + for _, parser := range parsers { + b.Run(parser.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err = parser.parse(bytes) + require.NoError(b, err) + } + }) + } +} + +func FuzzParseCertificate(f *testing.F) { + tlsCert, err := NewTLSCert() + require.NoError(f, err) + + f.Add(tlsCert.Leaf.Raw) + f.Add(largeRSAKeyCert) + f.Fuzz(func(t *testing.T, certBytes []byte) { + require := require.New(t) + + // Verify that any certificate that can be parsed by ParseCertificate + // can also be parsed by ParseCertificatePermissive. + { + strictCert, err := ParseCertificate(certBytes) + if err == nil { + permissiveCert, err := ParseCertificatePermissive(certBytes) + require.NoError(err) + require.Equal(strictCert, permissiveCert) + } + } + + // Verify that any certificate that can't be parsed by + // ParseCertificatePermissive also can't be parsed by ParseCertificate. + { + cert, err := ParseCertificatePermissive(certBytes) + if err == nil { + require.NoError(ValidateCertificate(cert)) + } else { + _, err = ParseCertificate(certBytes) + require.Error(err) //nolint:forbidigo + } + } + }) +} diff --git a/staking/tls.go b/staking/tls.go index fe461d52e32e..fbb5d9e488ae 100644 --- a/staking/tls.go +++ b/staking/tls.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package staking diff --git a/staking/tls_test.go b/staking/tls_test.go index 2282090b4c8e..6de376c2a538 100644 --- a/staking/tls_test.go +++ b/staking/tls_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package staking diff --git a/staking/verify.go b/staking/verify.go index 8da442e8b998..dd4255455ff0 100644 --- a/staking/verify.go +++ b/staking/verify.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package staking @@ -11,28 +11,13 @@ import ( "crypto/x509" "errors" "fmt" - - "github.com/ava-labs/avalanchego/utils/units" -) - -// MaxRSAKeyBitLen is the maximum RSA key size in bits that we are willing to -// parse. -// -// https://github.com/golang/go/blob/go1.19.12/src/crypto/tls/handshake_client.go#L860-L862 -const ( - MaxCertificateLen = 16 * units.KiB - MaxRSAKeyByteLen = units.KiB - MaxRSAKeyBitLen = 8 * MaxRSAKeyByteLen ) var ( - ErrCertificateTooLarge = fmt.Errorf("staking: certificate length is greater than %d", MaxCertificateLen) - ErrUnsupportedAlgorithm = errors.New("staking: cannot verify signature: unsupported algorithm") - ErrPublicKeyAlgoMismatch = errors.New("staking: signature algorithm specified different public key type") - ErrInvalidRSAPublicKey = errors.New("staking: invalid RSA public key") - ErrInvalidECDSAPublicKey = errors.New("staking: invalid ECDSA public key") - ErrECDSAVerificationFailure = errors.New("staking: ECDSA verification failure") - ErrED25519VerificationFailure = errors.New("staking: Ed25519 verification failure") + ErrUnsupportedAlgorithm = errors.New("staking: cannot verify signature: unsupported algorithm") + ErrPublicKeyAlgoMismatch = errors.New("staking: signature algorithm specified different public key type") + ErrInvalidECDSAPublicKey = errors.New("staking: invalid ECDSA public key") + ErrECDSAVerificationFailure = errors.New("staking: ECDSA verification failure") ) // CheckSignature verifies that the signature is a valid signature over signed @@ -41,10 +26,6 @@ var ( // Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/x509.go#L793-L797 // Ref: https://github.com/golang/go/blob/go1.19.12/src/crypto/x509/x509.go#L816-L879 func CheckSignature(cert *Certificate, msg []byte, signature []byte) error { - if err := ValidateCertificate(cert); err != nil { - return err - } - hasher := crypto.SHA256.New() _, err := hasher.Write(msg) if err != nil { @@ -67,6 +48,8 @@ func CheckSignature(cert *Certificate, msg []byte, signature []byte) error { // ValidateCertificate verifies that this certificate conforms to the required // staking format assuming that it was already able to be parsed. +// +// TODO: Remove after v1.11.x activates. func ValidateCertificate(cert *Certificate) error { if len(cert.Raw) > MaxCertificateLen { return ErrCertificateTooLarge @@ -82,8 +65,14 @@ func ValidateCertificate(cert *Certificate) error { if pubkeyAlgo != x509.RSA { return signaturePublicKeyAlgoMismatchError(pubkeyAlgo, pub) } - if bitLen := pub.N.BitLen(); bitLen > MaxRSAKeyBitLen { - return fmt.Errorf("%w: bitLen=%d > maxBitLen=%d", ErrInvalidRSAPublicKey, bitLen, MaxRSAKeyBitLen) + if bitLen := pub.N.BitLen(); bitLen != allowedRSALargeModulusLen && bitLen != allowedRSASmallModulusLen { + return fmt.Errorf("%w: %d", ErrUnsupportedRSAModulusBitLen, bitLen) + } + if pub.N.Bit(0) == 0 { + return ErrRSAModulusIsEven + } + if pub.E != allowedRSAPublicExponentValue { + return fmt.Errorf("%w: %d", ErrUnsupportedRSAPublicExponent, pub.E) } return nil case *ecdsa.PublicKey: diff --git a/staking/verify_test.go b/staking/verify_test.go deleted file mode 100644 index e7cee91c1b43..000000000000 --- a/staking/verify_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package staking - -import ( - "testing" - - _ "embed" - - "github.com/stretchr/testify/require" -) - -var ( - //go:embed large_rsa_key.cert - largeRSAKeyCert []byte - //go:embed large_rsa_key.sig - largeRSAKeySig []byte -) - -func TestCheckSignatureLargePublicKey(t *testing.T) { - require := require.New(t) - - cert, err := ParseCertificate(largeRSAKeyCert) - require.NoError(err) - - msg := []byte("TODO: put something clever") - err = CheckSignature(cert, msg, largeRSAKeySig) - require.ErrorIs(err, ErrInvalidRSAPublicKey) -} diff --git a/subnets/config.go b/subnets/config.go index 3ccbab79c50e..9a12c550b833 100644 --- a/subnets/config.go +++ b/subnets/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package subnets diff --git a/subnets/config_test.go b/subnets/config_test.go index 2294a1e5176a..fdb10c4e072a 100644 --- a/subnets/config_test.go +++ b/subnets/config_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package subnets diff --git a/subnets/no_op_allower.go b/subnets/no_op_allower.go index 9d2d51ea26d3..9cb7115e910d 100644 --- a/subnets/no_op_allower.go +++ b/subnets/no_op_allower.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package subnets diff --git a/subnets/subnet.go b/subnets/subnet.go index 31bc9dcb562b..95425ba30500 100644 --- a/subnets/subnet.go +++ b/subnets/subnet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package subnets diff --git a/subnets/subnet_test.go b/subnets/subnet_test.go index 98a75dfd2813..3a816a158f04 100644 --- a/subnets/subnet_test.go +++ b/subnets/subnet_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package subnets diff --git a/tests/colors.go b/tests/colors.go index 3aa935a5fce9..6cfec4df3dc0 100644 --- a/tests/colors.go +++ b/tests/colors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tests diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 032795b6436e..50ab608a3c4f 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -42,7 +42,7 @@ Define any flags/configurations in [`e2e.go`](./e2e.go). Create a new package to implement feature-specific tests, or add tests to an existing package. For example: ``` -. +tests └── e2e ├── README.md ├── e2e.go @@ -66,7 +66,7 @@ across multiple test runs. This can increase the speed of iteration by removing the requirement to start a new network for every invocation of the test under development. -To create an temporary network for use across test runs: +To create a temporary network for use across test runs: ```bash # From the root of the avalanchego repo diff --git a/tests/e2e/banff/suites.go b/tests/e2e/banff/suites.go index 37d0aa90156a..009bad3494b3 100644 --- a/tests/e2e/banff/suites.go +++ b/tests/e2e/banff/suites.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Implements tests for the banff network upgrade. diff --git a/tests/e2e/c/dynamic_fees.go b/tests/e2e/c/dynamic_fees.go index 38bbd668079b..0978bddc91d2 100644 --- a/tests/e2e/c/dynamic_fees.go +++ b/tests/e2e/c/dynamic_fees.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c @@ -42,14 +42,14 @@ var _ = e2e.DescribeCChain("[Dynamic Fees]", func() { privateNetwork := e2e.Env.NewPrivateNetwork() ginkgo.By("allocating a pre-funded key") - key := privateNetwork.GetConfig().FundedKeys[0] + key := privateNetwork.PreFundedKeys[0] ethAddress := evm.GetEthAddress(key) ginkgo.By("initializing a coreth client") - node := privateNetwork.GetNodes()[0] + node := privateNetwork.Nodes[0] nodeURI := tmpnet.NodeURI{ - NodeID: node.GetID(), - URI: node.GetProcessContext().URI, + NodeID: node.NodeID, + URI: node.URI, } ethClient := e2e.NewEthClient(nodeURI) diff --git a/tests/e2e/c/hashing_contract.go b/tests/e2e/c/hashing_contract.go index af5e81eb9057..7bf1db76c7cf 100644 --- a/tests/e2e/c/hashing_contract.go +++ b/tests/e2e/c/hashing_contract.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // AUTOMATICALLY GENERATED. DO NOT EDIT! diff --git a/tests/e2e/c/interchain_workflow.go b/tests/e2e/c/interchain_workflow.go index 2c9bd198ec39..0ce0ace59113 100644 --- a/tests/e2e/c/interchain_workflow.go +++ b/tests/e2e/c/interchain_workflow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c @@ -37,7 +37,7 @@ var _ = e2e.DescribeCChain("[Interchain Workflow]", func() { ethClient := e2e.NewEthClient(nodeURI) ginkgo.By("allocating a pre-funded key to send from and a recipient key to deliver to") - senderKey := e2e.Env.AllocateFundedKey() + senderKey := e2e.Env.AllocatePreFundedKey() senderEthAddress := evm.GetEthAddress(senderKey) recipientKey, err := secp256k1.NewPrivateKey() require.NoError(err) diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 3245516262d2..d363ff775086 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package e2e_test @@ -11,13 +11,13 @@ import ( "github.com/onsi/gomega" "github.com/ava-labs/avalanchego/tests/fixture/e2e" + "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" // ensure test packages are scanned by ginkgo _ "github.com/ava-labs/avalanchego/tests/e2e/banff" _ "github.com/ava-labs/avalanchego/tests/e2e/c" _ "github.com/ava-labs/avalanchego/tests/e2e/faultinjection" _ "github.com/ava-labs/avalanchego/tests/e2e/p" - _ "github.com/ava-labs/avalanchego/tests/e2e/static-handlers" _ "github.com/ava-labs/avalanchego/tests/e2e/x" _ "github.com/ava-labs/avalanchego/tests/e2e/x/transfer" ) @@ -35,7 +35,7 @@ func init() { var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { // Run only once in the first ginkgo process - return e2e.NewTestEnvironment(flagVars).Marshal() + return e2e.NewTestEnvironment(flagVars, &tmpnet.Network{}).Marshal() }, func(envBytes []byte) { // Run in every ginkgo process diff --git a/tests/e2e/faultinjection/duplicate_node_id.go b/tests/e2e/faultinjection/duplicate_node_id.go index 9278c1bd5b8d..d20ef1a28c0c 100644 --- a/tests/e2e/faultinjection/duplicate_node_id.go +++ b/tests/e2e/faultinjection/duplicate_node_id.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package faultinjection @@ -24,17 +24,16 @@ var _ = ginkgo.Describe("Duplicate node handling", func() { ginkgo.It("should ensure that a given Node ID (i.e. staking keypair) can be used at most once on a network", func() { network := e2e.Env.GetNetwork() - nodes := network.GetNodes() ginkgo.By("creating new node") node1 := e2e.AddEphemeralNode(network, tmpnet.FlagsMap{}) e2e.WaitForHealthy(node1) ginkgo.By("checking that the new node is connected to its peers") - checkConnectedPeers(nodes, node1) + checkConnectedPeers(network.Nodes, node1) ginkgo.By("creating a second new node with the same staking keypair as the first new node") - node1Flags := node1.GetConfig().Flags + node1Flags := node1.Flags node2Flags := tmpnet.FlagsMap{ config.StakingTLSKeyContentKey: node1Flags[config.StakingTLSKeyContentKey], config.StakingCertContentKey: node1Flags[config.StakingCertContentKey], @@ -50,24 +49,24 @@ var _ = ginkgo.Describe("Duplicate node handling", func() { require.ErrorIs(err, context.DeadlineExceeded) ginkgo.By("stopping the first new node") - require.NoError(node1.Stop()) + require.NoError(node1.Stop(e2e.DefaultContext())) ginkgo.By("checking that the second new node becomes healthy within timeout") e2e.WaitForHealthy(node2) ginkgo.By("checking that the second new node is connected to its peers") - checkConnectedPeers(nodes, node2) + checkConnectedPeers(network.Nodes, node2) // A bootstrap check was already performed by the second node. }) }) // Check that a new node is connected to existing nodes and vice versa -func checkConnectedPeers(existingNodes []tmpnet.Node, newNode tmpnet.Node) { +func checkConnectedPeers(existingNodes []*tmpnet.Node, newNode *tmpnet.Node) { require := require.New(ginkgo.GinkgoT()) // Collect the node ids of the new node's peers - infoClient := info.NewClient(newNode.GetProcessContext().URI) + infoClient := info.NewClient(newNode.URI) peers, err := infoClient.Peers(e2e.DefaultContext()) require.NoError(err) peerIDs := set.NewSet[ids.NodeID](len(existingNodes)) @@ -75,18 +74,17 @@ func checkConnectedPeers(existingNodes []tmpnet.Node, newNode tmpnet.Node) { peerIDs.Add(peer.ID) } - newNodeID := newNode.GetID() for _, existingNode := range existingNodes { // Check that the existing node is a peer of the new node - require.True(peerIDs.Contains(existingNode.GetID())) + require.True(peerIDs.Contains(existingNode.NodeID)) // Check that the new node is a peer - infoClient := info.NewClient(existingNode.GetProcessContext().URI) + infoClient := info.NewClient(existingNode.URI) peers, err := infoClient.Peers(e2e.DefaultContext()) require.NoError(err) isPeer := false for _, peer := range peers { - if peer.ID == newNodeID { + if peer.ID == newNode.NodeID { isPeer = true break } diff --git a/tests/e2e/ignore.go b/tests/e2e/ignore.go index 50332a1ac80e..ddf89c5d1bcc 100644 --- a/tests/e2e/ignore.go +++ b/tests/e2e/ignore.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package e2e diff --git a/tests/e2e/p/interchain_workflow.go b/tests/e2e/p/interchain_workflow.go index 678f9b5cc204..8983c46adcbd 100644 --- a/tests/e2e/p/interchain_workflow.go +++ b/tests/e2e/p/interchain_workflow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p @@ -43,7 +43,7 @@ var _ = e2e.DescribePChain("[Interchain Workflow]", ginkgo.Label(e2e.UsesCChainL network := e2e.Env.GetNetwork() ginkgo.By("checking that the network has a compatible minimum stake duration", func() { - minStakeDuration := cast.ToDuration(network.GetConfig().DefaultFlags[config.MinStakeDurationKey]) + minStakeDuration := cast.ToDuration(network.DefaultFlags[config.MinStakeDurationKey]) require.Equal(tmpnet.DefaultMinStakeDuration, minStakeDuration) }) @@ -91,17 +91,13 @@ var _ = e2e.DescribePChain("[Interchain Workflow]", ginkgo.Label(e2e.UsesCChainL e2e.WaitForHealthy(node) ginkgo.By("retrieving new node's id and pop") - infoClient := info.NewClient(node.GetProcessContext().URI) + infoClient := info.NewClient(node.URI) nodeID, nodePOP, err := infoClient.GetNodeID(e2e.DefaultContext()) require.NoError(err) + // Adding a validator should not break interchain transfer. + endTime := time.Now().Add(30 * time.Second) ginkgo.By("adding the new node as a validator", func() { - startTime := time.Now().Add(e2e.DefaultValidatorStartTimeDiff) - // Validation duration doesn't actually matter to this - // test - it is only ensuring that adding a validator - // doesn't break interchain transfer. - endTime := startTime.Add(30 * time.Second) - rewardKey, err := secp256k1.NewPrivateKey() require.NoError(err) @@ -114,7 +110,6 @@ var _ = e2e.DescribePChain("[Interchain Workflow]", ginkgo.Label(e2e.UsesCChainL &txs.SubnetValidator{ Validator: txs.Validator{ NodeID: nodeID, - Start: uint64(startTime.Unix()), End: uint64(endTime.Unix()), Wght: weight, }, @@ -136,13 +131,8 @@ var _ = e2e.DescribePChain("[Interchain Workflow]", ginkgo.Label(e2e.UsesCChainL require.NoError(err) }) + // Adding a delegator should not break interchain transfer. ginkgo.By("adding a delegator to the new node", func() { - startTime := time.Now().Add(e2e.DefaultValidatorStartTimeDiff) - // Delegation duration doesn't actually matter to this - // test - it is only ensuring that adding a delegator - // doesn't break interchain transfer. - endTime := startTime.Add(15 * time.Second) - rewardKey, err := secp256k1.NewPrivateKey() require.NoError(err) @@ -150,7 +140,6 @@ var _ = e2e.DescribePChain("[Interchain Workflow]", ginkgo.Label(e2e.UsesCChainL &txs.SubnetValidator{ Validator: txs.Validator{ NodeID: nodeID, - Start: uint64(startTime.Unix()), End: uint64(endTime.Unix()), Wght: weight, }, @@ -220,7 +209,7 @@ var _ = e2e.DescribePChain("[Interchain Workflow]", ginkgo.Label(e2e.UsesCChainL require.Positive(balance.Cmp(big.NewInt(0))) ginkgo.By("stopping validator node to free up resources for a bootstrap check") - require.NoError(node.Stop()) + require.NoError(node.Stop(e2e.DefaultContext())) e2e.CheckBootstrapIsPossible(network) }) diff --git a/tests/e2e/p/permissionless_subnets.go b/tests/e2e/p/permissionless_subnets.go index 0521306b9b40..ebb9dc602e6c 100644 --- a/tests/e2e/p/permissionless_subnets.go +++ b/tests/e2e/p/permissionless_subnets.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p @@ -134,14 +134,13 @@ var _ = e2e.DescribePChain("[Permissionless Subnets]", func() { require.NoError(err) }) - validatorStartTime := time.Now().Add(time.Minute) + endTime := time.Now().Add(time.Minute) ginkgo.By("add permissionless validator", func() { _, err := pWallet.IssueAddPermissionlessValidatorTx( &txs.SubnetValidator{ Validator: txs.Validator{ NodeID: validatorID, - Start: uint64(validatorStartTime.Unix()), - End: uint64(validatorStartTime.Add(5 * time.Second).Unix()), + End: uint64(endTime.Unix()), Wght: 25 * units.MegaAvax, }, Subnet: subnetID, @@ -156,14 +155,12 @@ var _ = e2e.DescribePChain("[Permissionless Subnets]", func() { require.NoError(err) }) - delegatorStartTime := validatorStartTime ginkgo.By("add permissionless delegator", func() { _, err := pWallet.IssueAddPermissionlessDelegatorTx( &txs.SubnetValidator{ Validator: txs.Validator{ NodeID: validatorID, - Start: uint64(delegatorStartTime.Unix()), - End: uint64(delegatorStartTime.Add(5 * time.Second).Unix()), + End: uint64(endTime.Unix()), Wght: 25 * units.MegaAvax, }, Subnet: subnetID, diff --git a/tests/e2e/p/staking_rewards.go b/tests/e2e/p/staking_rewards.go index f05b3bfbf64a..43b64456de96 100644 --- a/tests/e2e/p/staking_rewards.go +++ b/tests/e2e/p/staking_rewards.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p @@ -31,8 +31,8 @@ import ( ) const ( - delegationPeriod = 15 * time.Second - validationPeriod = 30 * time.Second + targetDelegationPeriod = 15 * time.Second + targetValidationPeriod = 30 * time.Second ) var _ = ginkgo.Describe("[Staking Rewards]", func() { @@ -42,7 +42,7 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() { network := e2e.Env.GetNetwork() ginkgo.By("checking that the network has a compatible minimum stake duration", func() { - minStakeDuration := cast.ToDuration(network.GetConfig().DefaultFlags[config.MinStakeDurationKey]) + minStakeDuration := cast.ToDuration(network.DefaultFlags[config.MinStakeDurationKey]) require.Equal(tmpnet.DefaultMinStakeDuration, minStakeDuration) }) @@ -58,6 +58,16 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() { ginkgo.By("waiting until beta node is healthy") e2e.WaitForHealthy(betaNode) + ginkgo.By("retrieving alpha node id and pop") + alphaInfoClient := info.NewClient(alphaNode.URI) + alphaNodeID, alphaPOP, err := alphaInfoClient.GetNodeID(e2e.DefaultContext()) + require.NoError(err) + + ginkgo.By("retrieving beta node id and pop") + betaInfoClient := info.NewClient(betaNode.URI) + betaNodeID, betaPOP, err := betaInfoClient.GetNodeID(e2e.DefaultContext()) + require.NoError(err) + ginkgo.By("generating reward keys") alphaValidationRewardKey, err := secp256k1.NewPrivateKey() @@ -87,39 +97,36 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() { ginkgo.By("creating keychain and P-Chain wallet") keychain := secp256k1fx.NewKeychain(rewardKeys...) - fundedKey := e2e.Env.AllocateFundedKey() + fundedKey := e2e.Env.AllocatePreFundedKey() keychain.Add(fundedKey) - nodeURI := e2e.Env.GetRandomNodeURI() + nodeURI := tmpnet.NodeURI{ + NodeID: alphaNodeID, + URI: alphaNode.URI, + } baseWallet := e2e.NewWallet(keychain, nodeURI) pWallet := baseWallet.P() - ginkgo.By("retrieving alpha node id and pop") - alphaInfoClient := info.NewClient(alphaNode.GetProcessContext().URI) - alphaNodeID, alphaPOP, err := alphaInfoClient.GetNodeID(e2e.DefaultContext()) - require.NoError(err) - - ginkgo.By("retrieving beta node id and pop") - betaInfoClient := info.NewClient(betaNode.GetProcessContext().URI) - betaNodeID, betaPOP, err := betaInfoClient.GetNodeID(e2e.DefaultContext()) - require.NoError(err) - const ( delegationPercent = 0.10 // 10% delegationShare = reward.PercentDenominator * delegationPercent weight = 2_000 * units.Avax ) - alphaValidatorStartTime := time.Now().Add(e2e.DefaultValidatorStartTimeDiff) - alphaValidatorEndTime := alphaValidatorStartTime.Add(validationPeriod) - tests.Outf("alpha node validation period starting at: %v\n", alphaValidatorStartTime) + pvmClient := platformvm.NewClient(alphaNode.URI) + + ginkgo.By("retrieving supply before inserting validators") + supplyAtValidatorsStart, _, err := pvmClient.GetCurrentSupply(e2e.DefaultContext(), constants.PrimaryNetworkID) + require.NoError(err) + + alphaValidatorsEndTime := time.Now().Add(targetValidationPeriod) + tests.Outf("alpha node validation period ending at: %v\n", alphaValidatorsEndTime) ginkgo.By("adding alpha node as a validator", func() { _, err := pWallet.IssueAddPermissionlessValidatorTx( &txs.SubnetValidator{ Validator: txs.Validator{ NodeID: alphaNodeID, - Start: uint64(alphaValidatorStartTime.Unix()), - End: uint64(alphaValidatorEndTime.Unix()), + End: uint64(alphaValidatorsEndTime.Unix()), Wght: weight, }, Subnet: constants.PrimaryNetworkID, @@ -140,16 +147,14 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() { require.NoError(err) }) - betaValidatorStartTime := time.Now().Add(e2e.DefaultValidatorStartTimeDiff) - betaValidatorEndTime := betaValidatorStartTime.Add(validationPeriod) - tests.Outf("beta node validation period starting at: %v\n", betaValidatorStartTime) + betaValidatorEndTime := time.Now().Add(targetValidationPeriod) + tests.Outf("beta node validation period ending at: %v\n", betaValidatorEndTime) ginkgo.By("adding beta node as a validator", func() { _, err := pWallet.IssueAddPermissionlessValidatorTx( &txs.SubnetValidator{ Validator: txs.Validator{ NodeID: betaNodeID, - Start: uint64(betaValidatorStartTime.Unix()), End: uint64(betaValidatorEndTime.Unix()), Wght: weight, }, @@ -171,16 +176,19 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() { require.NoError(err) }) - gammaDelegatorStartTime := time.Now().Add(e2e.DefaultValidatorStartTimeDiff) - tests.Outf("gamma delegation period starting at: %v\n", gammaDelegatorStartTime) + ginkgo.By("retrieving supply before inserting delegators") + supplyAtDelegatorsStart, _, err := pvmClient.GetCurrentSupply(e2e.DefaultContext(), constants.PrimaryNetworkID) + require.NoError(err) + + gammaDelegatorEndTime := time.Now().Add(targetDelegationPeriod) + tests.Outf("gamma delegation period ending at: %v\n", gammaDelegatorEndTime) ginkgo.By("adding gamma as delegator to the alpha node", func() { _, err := pWallet.IssueAddPermissionlessDelegatorTx( &txs.SubnetValidator{ Validator: txs.Validator{ NodeID: alphaNodeID, - Start: uint64(gammaDelegatorStartTime.Unix()), - End: uint64(gammaDelegatorStartTime.Add(delegationPeriod).Unix()), + End: uint64(gammaDelegatorEndTime.Unix()), Wght: weight, }, Subnet: constants.PrimaryNetworkID, @@ -195,16 +203,15 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() { require.NoError(err) }) - deltaDelegatorStartTime := time.Now().Add(e2e.DefaultValidatorStartTimeDiff) - tests.Outf("delta delegation period starting at: %v\n", deltaDelegatorStartTime) + deltaDelegatorEndTime := time.Now().Add(targetDelegationPeriod) + tests.Outf("delta delegation period ending at: %v\n", deltaDelegatorEndTime) ginkgo.By("adding delta as delegator to the beta node", func() { _, err := pWallet.IssueAddPermissionlessDelegatorTx( &txs.SubnetValidator{ Validator: txs.Validator{ NodeID: betaNodeID, - Start: uint64(deltaDelegatorStartTime.Unix()), - End: uint64(deltaDelegatorStartTime.Add(delegationPeriod).Unix()), + End: uint64(deltaDelegatorEndTime.Unix()), Wght: weight, }, Subnet: constants.PrimaryNetworkID, @@ -220,15 +227,21 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() { }) ginkgo.By("stopping beta node to prevent it and its delegator from receiving a validation reward") - require.NoError(betaNode.Stop()) + require.NoError(betaNode.Stop(e2e.DefaultContext())) + + ginkgo.By("retrieving staking periods from the chain") + data, err := pvmClient.GetCurrentValidators(e2e.DefaultContext(), constants.PlatformChainID, []ids.NodeID{alphaNodeID}) + require.NoError(err) + require.Len(data, 1) + actualAlphaValidationPeriod := time.Duration(data[0].EndTime-data[0].StartTime) * time.Second + delegatorData := data[0].Delegators[0] + actualGammaDelegationPeriod := time.Duration(delegatorData.EndTime-delegatorData.StartTime) * time.Second ginkgo.By("waiting until all validation periods are over") // The beta validator was the last added and so has the latest end time. The // delegation periods are shorter than the validation periods. time.Sleep(time.Until(betaValidatorEndTime)) - pvmClient := platformvm.NewClient(alphaNode.GetProcessContext().URI) - ginkgo.By("waiting until the alpha and beta nodes are no longer validators") e2e.Eventually(func() bool { validators, err := pvmClient.GetCurrentValidators(e2e.DefaultContext(), constants.PrimaryNetworkID, nil) @@ -270,11 +283,9 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() { require.Len(rewardBalances, len(rewardKeys)) ginkgo.By("determining expected validation and delegation rewards") - currentSupply, _, err := pvmClient.GetCurrentSupply(e2e.DefaultContext(), constants.PrimaryNetworkID) - require.NoError(err) calculator := reward.NewCalculator(rewardConfig) - expectedValidationReward := calculator.Calculate(validationPeriod, weight, currentSupply) - potentialDelegationReward := calculator.Calculate(delegationPeriod, weight, currentSupply) + expectedValidationReward := calculator.Calculate(actualAlphaValidationPeriod, weight, supplyAtValidatorsStart) + potentialDelegationReward := calculator.Calculate(actualGammaDelegationPeriod, weight, supplyAtDelegatorsStart) expectedDelegationFee, expectedDelegatorReward := reward.Split(potentialDelegationReward, delegationShare) ginkgo.By("checking expected rewards against actual rewards") @@ -291,7 +302,7 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() { } ginkgo.By("stopping alpha to free up resources for a bootstrap check") - require.NoError(alphaNode.Stop()) + require.NoError(alphaNode.Stop(e2e.DefaultContext())) e2e.CheckBootstrapIsPossible(network) }) diff --git a/tests/e2e/p/workflow.go b/tests/e2e/p/workflow.go index 8cc7c109d3a4..8bf7efca2c2c 100644 --- a/tests/e2e/p/workflow.go +++ b/tests/e2e/p/workflow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p @@ -68,9 +68,6 @@ var _ = e2e.DescribePChain("[Workflow]", func() { require.NoError(err) require.GreaterOrEqual(pBalance, minBalance) }) - // create validator data - validatorStartTimeDiff := 30 * time.Second - vdrStartTime := time.Now().Add(validatorStartTimeDiff) // Use a random node ID to ensure that repeated test runs // will succeed against a network that persists across runs. @@ -79,8 +76,7 @@ var _ = e2e.DescribePChain("[Workflow]", func() { vdr := &txs.Validator{ NodeID: validatorID, - Start: uint64(vdrStartTime.Unix()), - End: uint64(vdrStartTime.Add(72 * time.Hour).Unix()), + End: uint64(time.Now().Add(72 * time.Hour).Unix()), Wght: minValStake, } rewardOwner := &secp256k1fx.OutputOwners{ diff --git a/tests/e2e/static-handlers/suites.go b/tests/e2e/static-handlers/suites.go deleted file mode 100644 index 67b12a0cbb04..000000000000 --- a/tests/e2e/static-handlers/suites.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Implements static handlers tests for avm and platformvm -package statichandlers - -import ( - "time" - - ginkgo "github.com/onsi/ginkgo/v2" - - "github.com/stretchr/testify/require" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/tests/fixture/e2e" - "github.com/ava-labs/avalanchego/utils/cb58" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/utils/formatting" - "github.com/ava-labs/avalanchego/utils/formatting/address" - "github.com/ava-labs/avalanchego/utils/json" - "github.com/ava-labs/avalanchego/utils/units" - "github.com/ava-labs/avalanchego/vms/avm" - "github.com/ava-labs/avalanchego/vms/platformvm/api" - "github.com/ava-labs/avalanchego/vms/platformvm/reward" -) - -var _ = ginkgo.Describe("[StaticHandlers]", func() { - require := require.New(ginkgo.GinkgoT()) - - ginkgo.It("can make calls to avm static api", - func() { - addrMap := map[string]string{} - for _, addrStr := range []string{ - "A9bTQjfYGBFK3JPRJqF2eh3JYL7cHocvy", - "6mxBGnjGDCKgkVe7yfrmvMA7xE7qCv3vv", - "6ncQ19Q2U4MamkCYzshhD8XFjfwAWFzTa", - "Jz9ayEDt7dx9hDx45aXALujWmL9ZUuqe7", - } { - addr, err := ids.ShortFromString(addrStr) - require.NoError(err) - addrMap[addrStr], err = address.FormatBech32(constants.NetworkIDToHRP[constants.LocalID], addr[:]) - require.NoError(err) - } - avmArgs := avm.BuildGenesisArgs{ - Encoding: formatting.Hex, - GenesisData: map[string]avm.AssetDefinition{ - "asset1": { - Name: "myFixedCapAsset", - Symbol: "MFCA", - Denomination: 8, - InitialState: map[string][]interface{}{ - "fixedCap": { - avm.Holder{ - Amount: 100000, - Address: addrMap["A9bTQjfYGBFK3JPRJqF2eh3JYL7cHocvy"], - }, - avm.Holder{ - Amount: 100000, - Address: addrMap["6mxBGnjGDCKgkVe7yfrmvMA7xE7qCv3vv"], - }, - avm.Holder{ - Amount: json.Uint64(50000), - Address: addrMap["6ncQ19Q2U4MamkCYzshhD8XFjfwAWFzTa"], - }, - avm.Holder{ - Amount: json.Uint64(50000), - Address: addrMap["Jz9ayEDt7dx9hDx45aXALujWmL9ZUuqe7"], - }, - }, - }, - }, - "asset2": { - Name: "myVarCapAsset", - Symbol: "MVCA", - InitialState: map[string][]interface{}{ - "variableCap": { - avm.Owners{ - Threshold: 1, - Minters: []string{ - addrMap["A9bTQjfYGBFK3JPRJqF2eh3JYL7cHocvy"], - addrMap["6mxBGnjGDCKgkVe7yfrmvMA7xE7qCv3vv"], - }, - }, - avm.Owners{ - Threshold: 2, - Minters: []string{ - addrMap["6ncQ19Q2U4MamkCYzshhD8XFjfwAWFzTa"], - addrMap["Jz9ayEDt7dx9hDx45aXALujWmL9ZUuqe7"], - }, - }, - }, - }, - }, - "asset3": { - Name: "myOtherVarCapAsset", - InitialState: map[string][]interface{}{ - "variableCap": { - avm.Owners{ - Threshold: 1, - Minters: []string{ - addrMap["A9bTQjfYGBFK3JPRJqF2eh3JYL7cHocvy"], - }, - }, - }, - }, - }, - }, - } - staticClient := avm.NewStaticClient(e2e.Env.GetRandomNodeURI().URI) - resp, err := staticClient.BuildGenesis(e2e.DefaultContext(), &avmArgs) - require.NoError(err) - require.Equal(resp.Bytes, "0x0000000000030006617373657431000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f6d794669786564436170417373657400044d4643410800000001000000000000000400000007000000000000c350000000000000000000000001000000013f78e510df62bc48b0829ec06d6a6b98062d695300000007000000000000c35000000000000000000000000100000001c54903de5177a16f7811771ef2f4659d9e8646710000000700000000000186a0000000000000000000000001000000013f58fda2e9ea8d9e4b181832a07b26dae286f2cb0000000700000000000186a000000000000000000000000100000001645938bb7ae2193270e6ffef009e3664d11e07c10006617373657432000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d6d79566172436170417373657400044d5643410000000001000000000000000200000006000000000000000000000001000000023f58fda2e9ea8d9e4b181832a07b26dae286f2cb645938bb7ae2193270e6ffef009e3664d11e07c100000006000000000000000000000001000000023f78e510df62bc48b0829ec06d6a6b98062d6953c54903de5177a16f7811771ef2f4659d9e864671000661737365743300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000126d794f7468657256617243617041737365740000000000000100000000000000010000000600000000000000000000000100000001645938bb7ae2193270e6ffef009e3664d11e07c1279fa028") - }) - - ginkgo.It("can make calls to platformvm static api", func() { - keys := []*secp256k1.PrivateKey{} - for _, key := range []string{ - "24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5", - "2MMvUMsxx6zsHSNXJdFD8yc5XkancvwyKPwpw4xUK3TCGDuNBY", - "cxb7KpGWhDMALTjNNSJ7UQkkomPesyWAPUaWRGdyeBNzR6f35", - "ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN", - "2RWLv6YVEXDiWLpaCbXhhqxtLbnFaKQsWPSSMSPhpWo47uJAeV", - } { - privKeyBytes, err := cb58.Decode(key) - require.NoError(err) - pk, err := secp256k1.ToPrivateKey(privKeyBytes) - require.NoError(err) - keys = append(keys, pk) - } - - genesisUTXOs := make([]api.UTXO, len(keys)) - hrp := constants.NetworkIDToHRP[constants.UnitTestID] - for i, key := range keys { - id := key.PublicKey().Address() - addr, err := address.FormatBech32(hrp, id.Bytes()) - require.NoError(err) - genesisUTXOs[i] = api.UTXO{ - Amount: json.Uint64(50000 * units.MilliAvax), - Address: addr, - } - } - - genesisValidators := make([]api.GenesisPermissionlessValidator, len(keys)) - for i, key := range keys { - id := key.PublicKey().Address() - addr, err := address.FormatBech32(hrp, id.Bytes()) - require.NoError(err) - genesisValidators[i] = api.GenesisPermissionlessValidator{ - GenesisValidator: api.GenesisValidator{ - StartTime: json.Uint64(time.Date(1997, 1, 1, 0, 0, 0, 0, time.UTC).Unix()), - EndTime: json.Uint64(time.Date(1997, 1, 30, 0, 0, 0, 0, time.UTC).Unix()), - NodeID: ids.BuildTestNodeID(id[:]), - }, - RewardOwner: &api.Owner{ - Threshold: 1, - Addresses: []string{addr}, - }, - Staked: []api.UTXO{{ - Amount: json.Uint64(10000), - Address: addr, - }}, - DelegationFee: reward.PercentDenominator, - } - } - - buildGenesisArgs := api.BuildGenesisArgs{ - NetworkID: json.Uint32(constants.UnitTestID), - AvaxAssetID: ids.ID{'a', 'v', 'a', 'x'}, - UTXOs: genesisUTXOs, - Validators: genesisValidators, - Chains: nil, - Time: json.Uint64(time.Date(1997, 1, 1, 0, 0, 0, 0, time.UTC).Unix()), - InitialSupply: json.Uint64(360 * units.MegaAvax), - Encoding: formatting.Hex, - } - - staticClient := api.NewStaticClient(e2e.Env.GetRandomNodeURI().URI) - resp, err := staticClient.BuildGenesis(e2e.DefaultContext(), &buildGenesisArgs) - require.NoError(err) - require.Equal(resp.Bytes, "0x0000000000050000000000000000000000000000000000000000000000000000000000000000000000006176617800000000000000000000000000000000000000000000000000000000000000070000000ba43b740000000000000000000000000100000001fceda8f90fcb5d30614b99d79fc4baa293077626000000000000000000000000000000000000000000000000000000000000000000000000000000016176617800000000000000000000000000000000000000000000000000000000000000070000000ba43b7400000000000000000000000001000000016ead693c17abb1be422bb50b30b9711ff98d667e000000000000000000000000000000000000000000000000000000000000000000000000000000026176617800000000000000000000000000000000000000000000000000000000000000070000000ba43b740000000000000000000000000100000001f2420846876e69f473dda256172967e992f0ee31000000000000000000000000000000000000000000000000000000000000000000000000000000036176617800000000000000000000000000000000000000000000000000000000000000070000000ba43b7400000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c000000000000000000000000000000000000000000000000000000000000000000000000000000046176617800000000000000000000000000000000000000000000000000000000000000070000000ba43b74000000000000000000000000010000000187c4ec0736fdad03fd9ec8c3ba609de958601a7b00000000000000050000000c0000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fceda8f90fcb5d30614b99d79fc4baa2930776260000000032c9a9000000000032efe480000000000000271000000001617661780000000000000000000000000000000000000000000000000000000000000007000000000000271000000000000000000000000100000001fceda8f90fcb5d30614b99d79fc4baa2930776260000000b00000000000000000000000100000001fceda8f90fcb5d30614b99d79fc4baa29307762600000000000000000000000c0000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ead693c17abb1be422bb50b30b9711ff98d667e0000000032c9a9000000000032efe4800000000000002710000000016176617800000000000000000000000000000000000000000000000000000000000000070000000000002710000000000000000000000001000000016ead693c17abb1be422bb50b30b9711ff98d667e0000000b000000000000000000000001000000016ead693c17abb1be422bb50b30b9711ff98d667e00000000000000000000000c0000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2420846876e69f473dda256172967e992f0ee310000000032c9a9000000000032efe480000000000000271000000001617661780000000000000000000000000000000000000000000000000000000000000007000000000000271000000000000000000000000100000001f2420846876e69f473dda256172967e992f0ee310000000b00000000000000000000000100000001f2420846876e69f473dda256172967e992f0ee3100000000000000000000000c0000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c0000000032c9a9000000000032efe4800000000000002710000000016176617800000000000000000000000000000000000000000000000000000000000000070000000000002710000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c0000000b000000000000000000000001000000013cb7d3842e8cee6a0ebd09f1fe884f6861e1b29c00000000000000000000000c0000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000087c4ec0736fdad03fd9ec8c3ba609de958601a7b0000000032c9a9000000000032efe48000000000000027100000000161766178000000000000000000000000000000000000000000000000000000000000000700000000000027100000000000000000000000010000000187c4ec0736fdad03fd9ec8c3ba609de958601a7b0000000b0000000000000000000000010000000187c4ec0736fdad03fd9ec8c3ba609de958601a7b0000000000000000000000000000000032c9a90004fefa17b724000000008e96cbef") - }) -}) diff --git a/tests/e2e/x/interchain_workflow.go b/tests/e2e/x/interchain_workflow.go index f0c2951feb84..eec7b3427c19 100644 --- a/tests/e2e/x/interchain_workflow.go +++ b/tests/e2e/x/interchain_workflow.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x diff --git a/tests/e2e/x/transfer/virtuous.go b/tests/e2e/x/transfer/virtuous.go index 7a1eb1bb6b91..9fc8ae89ec44 100644 --- a/tests/e2e/x/transfer/virtuous.go +++ b/tests/e2e/x/transfer/virtuous.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // Implements X-chain transfer tests. @@ -69,7 +69,7 @@ var _ = e2e.DescribeXChainSerial("[Virtuous Transfer Tx AVAX]", func() { // Ensure the same set of 10 keys is used for all tests // by retrieving them outside of runFunc. - testKeys := e2e.Env.AllocateFundedKeys(10) + testKeys := e2e.Env.AllocatePreFundedKeys(10) runFunc := func(round int) { tests.Outf("{{green}}\n\n\n\n\n\n---\n[ROUND #%02d]:{{/}}\n", round) diff --git a/tests/fixture/e2e/describe.go b/tests/fixture/e2e/describe.go index 5475a7114c96..2810117758c6 100644 --- a/tests/fixture/e2e/describe.go +++ b/tests/fixture/e2e/describe.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package e2e diff --git a/tests/fixture/e2e/env.go b/tests/fixture/e2e/env.go index 07c24866a9f0..9019c9438b9e 100644 --- a/tests/fixture/e2e/env.go +++ b/tests/fixture/e2e/env.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package e2e @@ -14,10 +14,11 @@ import ( "github.com/stretchr/testify/require" + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/tests" "github.com/ava-labs/avalanchego/tests/fixture" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet/local" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -30,10 +31,9 @@ var Env *TestEnvironment func InitSharedTestEnvironment(envBytes []byte) { require := require.New(ginkgo.GinkgoT()) require.Nil(Env, "env already initialized") - Env = &TestEnvironment{ - require: require, - } + Env = &TestEnvironment{} require.NoError(json.Unmarshal(envBytes, Env)) + Env.require = require } type TestEnvironment struct { @@ -54,28 +54,62 @@ func (te *TestEnvironment) Marshal() []byte { } // Initialize a new test environment with a shared network (either pre-existing or newly created). -func NewTestEnvironment(flagVars *FlagVars) *TestEnvironment { +func NewTestEnvironment(flagVars *FlagVars, desiredNetwork *tmpnet.Network) *TestEnvironment { require := require.New(ginkgo.GinkgoT()) networkDir := flagVars.NetworkDir() // Load or create a test network - var network *local.LocalNetwork + var network *tmpnet.Network if len(networkDir) > 0 { var err error - network, err = local.ReadNetwork(networkDir) + network, err = tmpnet.ReadNetwork(networkDir) require.NoError(err) tests.Outf("{{yellow}}Using an existing network configured at %s{{/}}\n", network.Dir) + + // Set the desired subnet configuration to ensure subsequent creation. + for _, subnet := range desiredNetwork.Subnets { + if existing := network.GetSubnet(subnet.Name); existing != nil { + // Already present + continue + } + network.Subnets = append(network.Subnets, subnet) + } } else { - network = StartLocalNetwork(flagVars.AvalancheGoExecPath(), DefaultNetworkDir) + network = desiredNetwork + StartNetwork(network, DefaultNetworkDir, flagVars.AvalancheGoExecPath(), flagVars.PluginDir()) } - uris := network.GetURIs() + // A new network will always need subnet creation and an existing + // network will also need subnets to be created the first time it + // is used. + require.NoError(network.CreateSubnets(DefaultContext(), ginkgo.GinkgoWriter)) + + // Wait for chains to have bootstrapped on all nodes + Eventually(func() bool { + for _, subnet := range network.Subnets { + for _, validatorID := range subnet.ValidatorIDs { + uri, err := network.GetURIForNodeID(validatorID) + require.NoError(err) + infoClient := info.NewClient(uri) + for _, chain := range subnet.Chains { + isBootstrapped, err := infoClient.IsBootstrapped(DefaultContext(), chain.ChainID.String()) + // Ignore errors since a chain id that is not yet known will result in a recoverable error. + if err != nil || !isBootstrapped { + return false + } + } + } + } + return true + }, DefaultTimeout, DefaultPollingInterval, "failed to see all chains bootstrap before timeout") + + uris := network.GetNodeURIs() require.NotEmpty(uris, "network contains no nodes") tests.Outf("{{green}}network URIs: {{/}} %+v\n", uris) testDataServerURI, err := fixture.ServeTestData(fixture.TestData{ - FundedKeys: network.FundedKeys, + PreFundedKeys: network.PreFundedKeys, }) tests.Outf("{{green}}test data server URI: {{/}} %+v\n", testDataServerURI) require.NoError(err) @@ -84,6 +118,7 @@ func NewTestEnvironment(flagVars *FlagVars) *TestEnvironment { NetworkDir: network.Dir, URIs: uris, TestDataServerURI: testDataServerURI, + require: require, } } @@ -97,41 +132,53 @@ func (te *TestEnvironment) GetRandomNodeURI() tmpnet.NodeURI { } // Retrieve the network to target for testing. -func (te *TestEnvironment) GetNetwork() tmpnet.Network { - network, err := local.ReadNetwork(te.NetworkDir) +func (te *TestEnvironment) GetNetwork() *tmpnet.Network { + network, err := tmpnet.ReadNetwork(te.NetworkDir) te.require.NoError(err) return network } // Retrieve the specified number of funded keys allocated for the caller's exclusive use. -func (te *TestEnvironment) AllocateFundedKeys(count int) []*secp256k1.PrivateKey { - keys, err := fixture.AllocateFundedKeys(te.TestDataServerURI, count) +func (te *TestEnvironment) AllocatePreFundedKeys(count int) []*secp256k1.PrivateKey { + keys, err := fixture.AllocatePreFundedKeys(te.TestDataServerURI, count) te.require.NoError(err) - tests.Outf("{{blue}} allocated funded key(s): %+v{{/}}\n", keys) + tests.Outf("{{blue}} allocated pre-funded key(s): %+v{{/}}\n", keys) return keys } // Retrieve a funded key allocated for the caller's exclusive use. -func (te *TestEnvironment) AllocateFundedKey() *secp256k1.PrivateKey { - return te.AllocateFundedKeys(1)[0] +func (te *TestEnvironment) AllocatePreFundedKey() *secp256k1.PrivateKey { + return te.AllocatePreFundedKeys(1)[0] } // Create a new keychain with the specified number of test keys. func (te *TestEnvironment) NewKeychain(count int) *secp256k1fx.Keychain { - keys := te.AllocateFundedKeys(count) + keys := te.AllocatePreFundedKeys(count) return secp256k1fx.NewKeychain(keys...) } // Create a new private network that is not shared with other tests. -func (te *TestEnvironment) NewPrivateNetwork() tmpnet.Network { +func (te *TestEnvironment) NewPrivateNetwork() *tmpnet.Network { // Load the shared network to retrieve its path and exec path - sharedNetwork, err := local.ReadNetwork(te.NetworkDir) + sharedNetwork, err := tmpnet.ReadNetwork(te.NetworkDir) te.require.NoError(err) + network := &tmpnet.Network{} + // The private networks dir is under the shared network dir to ensure it // will be included in the artifact uploaded in CI. privateNetworksDir := filepath.Join(sharedNetwork.Dir, PrivateNetworksDirName) te.require.NoError(os.MkdirAll(privateNetworksDir, perms.ReadWriteExecute)) - return StartLocalNetwork(sharedNetwork.ExecPath, privateNetworksDir) + pluginDir, err := sharedNetwork.DefaultFlags.GetStringVal(config.PluginDirKey) + te.require.NoError(err) + + StartNetwork( + network, + privateNetworksDir, + sharedNetwork.DefaultRuntimeConfig.AvalancheGoPath, + pluginDir, + ) + + return network } diff --git a/tests/fixture/e2e/flags.go b/tests/fixture/e2e/flags.go index 23952b5dcd91..2a00df97a885 100644 --- a/tests/fixture/e2e/flags.go +++ b/tests/fixture/e2e/flags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package e2e @@ -8,15 +8,24 @@ import ( "fmt" "os" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet/local" + "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" ) type FlagVars struct { avalancheGoExecPath string + pluginDir string networkDir string useExistingNetwork bool } +func (v *FlagVars) AvalancheGoExecPath() string { + return v.avalancheGoExecPath +} + +func (v *FlagVars) PluginDir() string { + return v.pluginDir +} + func (v *FlagVars) NetworkDir() string { if !v.useExistingNetwork { return "" @@ -24,11 +33,7 @@ func (v *FlagVars) NetworkDir() string { if len(v.networkDir) > 0 { return v.networkDir } - return os.Getenv(local.NetworkDirEnvName) -} - -func (v *FlagVars) AvalancheGoExecPath() string { - return v.avalancheGoExecPath + return os.Getenv(tmpnet.NetworkDirEnvName) } func (v *FlagVars) UseExistingNetwork() bool { @@ -40,14 +45,20 @@ func RegisterFlags() *FlagVars { flag.StringVar( &vars.avalancheGoExecPath, "avalanchego-path", - os.Getenv(local.AvalancheGoPathEnvName), - fmt.Sprintf("avalanchego executable path (required if not using an existing network). Also possible to configure via the %s env variable.", local.AvalancheGoPathEnvName), + os.Getenv(tmpnet.AvalancheGoPathEnvName), + fmt.Sprintf("avalanchego executable path (required if not using an existing network). Also possible to configure via the %s env variable.", tmpnet.AvalancheGoPathEnvName), + ) + flag.StringVar( + &vars.pluginDir, + "plugin-dir", + os.ExpandEnv("$HOME/.avalanchego/plugins"), + "[optional] the dir containing VM plugins.", ) flag.StringVar( &vars.networkDir, "network-dir", "", - fmt.Sprintf("[optional] the dir containing the configuration of an existing network to target for testing. Will only be used if --use-existing-network is specified. Also possible to configure via the %s env variable.", local.NetworkDirEnvName), + fmt.Sprintf("[optional] the dir containing the configuration of an existing network to target for testing. Will only be used if --use-existing-network is specified. Also possible to configure via the %s env variable.", tmpnet.NetworkDirEnvName), ) flag.BoolVar( &vars.useExistingNetwork, diff --git a/tests/fixture/e2e/helpers.go b/tests/fixture/e2e/helpers.go index 8b7eb5260b8c..c1d87a4beba8 100644 --- a/tests/fixture/e2e/helpers.go +++ b/tests/fixture/e2e/helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package e2e @@ -23,8 +23,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/tests" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet/local" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/wallet/subnet/primary" "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" @@ -36,19 +34,14 @@ const ( // contention. DefaultTimeout = 2 * time.Minute - // Interval appropriate for network operations that should be - // retried periodically but not too often. - DefaultPollingInterval = 500 * time.Millisecond + DefaultPollingInterval = tmpnet.DefaultPollingInterval // Setting this env will disable post-test bootstrap // checks. Useful for speeding up iteration during test // development. SkipBootstrapChecksEnvName = "E2E_SKIP_BOOTSTRAP_CHECKS" - // Validator start time must be a minimum of SyncBound from the - // current time for validator addition to succeed, and adding 20 - // seconds provides a buffer in case of any delay in processing. - DefaultValidatorStartTimeDiff = executor.SyncBound + 20*time.Second + DefaultValidatorStartTimeDiff = tmpnet.DefaultValidatorStartTimeDiff DefaultGasLimit = uint64(21000) // Standard gas limit @@ -125,27 +118,24 @@ func Eventually(condition func() bool, waitFor time.Duration, tick time.Duration } } -// Add an ephemeral node that is only intended to be used by a single test. Its ID and -// URI are not intended to be returned from the Network instance to minimize -// accessibility from other tests. -func AddEphemeralNode(network tmpnet.Network, flags tmpnet.FlagsMap) tmpnet.Node { +// Adds an ephemeral node intended to be used by a single test. +func AddEphemeralNode(network *tmpnet.Network, flags tmpnet.FlagsMap) *tmpnet.Node { require := require.New(ginkgo.GinkgoT()) - node, err := network.AddEphemeralNode(ginkgo.GinkgoWriter, flags) + node, err := network.AddEphemeralNode(DefaultContext(), ginkgo.GinkgoWriter, flags) require.NoError(err) - // Ensure node is stopped on teardown. It's configuration is not removed to enable - // collection in CI to aid in troubleshooting failures. ginkgo.DeferCleanup(func() { - tests.Outf("Shutting down ephemeral node %s\n", node.GetID()) - require.NoError(node.Stop()) + tests.Outf("shutting down ephemeral node %q\n", node.NodeID) + ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout) + defer cancel() + require.NoError(node.Stop(ctx)) }) - return node } // Wait for the given node to report healthy. -func WaitForHealthy(node tmpnet.Node) { +func WaitForHealthy(node *tmpnet.Node) { // Need to use explicit context (vs DefaultContext()) to support use with DeferCleanup ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout) defer cancel() @@ -196,8 +186,9 @@ func WithSuggestedGasPrice(ethClient ethclient.Client) common.Option { return common.WithBaseFee(baseFee) } -// Verify that a new node can bootstrap into the network. -func CheckBootstrapIsPossible(network tmpnet.Network) { +// Verify that a new node can bootstrap into the network. This function is safe to call +// from `Teardown` by virtue of not depending on ginkgo.DeferCleanup. +func CheckBootstrapIsPossible(network *tmpnet.Network) { require := require.New(ginkgo.GinkgoT()) if len(os.Getenv(SkipBootstrapChecksEnvName)) > 0 { @@ -206,44 +197,47 @@ func CheckBootstrapIsPossible(network tmpnet.Network) { } ginkgo.By("checking if bootstrap is possible with the current network state") - // Call network.AddEphemeralNode instead of AddEphemeralNode to support - // checking for bootstrap implicitly on teardown via a function registered - // with ginkgo.DeferCleanup. It's not possible to call DeferCleanup from - // within a function called by DeferCleanup. - node, err := network.AddEphemeralNode(ginkgo.GinkgoWriter, tmpnet.FlagsMap{}) + ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout) + defer cancel() + + node, err := network.AddEphemeralNode(ctx, ginkgo.GinkgoWriter, tmpnet.FlagsMap{}) + // AddEphemeralNode will initiate node stop if an error is encountered during start, + // so no further cleanup effort is required if an error is seen here. require.NoError(err) + // Ensure the node is always stopped at the end of the check defer func() { - tests.Outf("Shutting down ephemeral node %s\n", node.GetID()) - require.NoError(node.Stop()) + ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout) + defer cancel() + require.NoError(node.Stop(ctx)) }() - WaitForHealthy(node) + // Check that the node becomes healthy within timeout + require.NoError(tmpnet.WaitForHealthy(ctx, node)) } -// Start a local test-managed network with the provided avalanchego binary. -func StartLocalNetwork(avalancheGoExecPath string, networkDir string) *local.LocalNetwork { +// Start a temporary network with the provided avalanchego binary. +func StartNetwork(network *tmpnet.Network, rootNetworkDir string, avalancheGoExecPath string, pluginDir string) { require := require.New(ginkgo.GinkgoT()) - network, err := local.StartNetwork( - DefaultContext(), - ginkgo.GinkgoWriter, - networkDir, - &local.LocalNetwork{ - LocalConfig: local.LocalConfig{ - ExecPath: avalancheGoExecPath, - }, - }, - tmpnet.DefaultNodeCount, - tmpnet.DefaultFundedKeyCount, + require.NoError( + tmpnet.StartNewNetwork( + DefaultContext(), + ginkgo.GinkgoWriter, + network, + rootNetworkDir, + avalancheGoExecPath, + pluginDir, + tmpnet.DefaultNodeCount, + ), ) - require.NoError(err) + ginkgo.DeferCleanup(func() { tests.Outf("Shutting down network\n") - require.NoError(network.Stop()) + ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout) + defer cancel() + require.NoError(network.Stop(ctx)) }) tests.Outf("{{green}}Successfully started network{{/}}\n") - - return network } diff --git a/tests/fixture/test_data_server.go b/tests/fixture/test_data_server.go index 5a39baab8c9c..b79dcc2bb26b 100644 --- a/tests/fixture/test_data_server.go +++ b/tests/fixture/test_data_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package fixture @@ -33,7 +33,7 @@ var ( ) type TestData struct { - FundedKeys []*secp256k1.PrivateKey + PreFundedKeys []*secp256k1.PrivateKey } // http server allocating resources to tests potentially executing in parallel @@ -68,14 +68,14 @@ func (s *testDataServer) allocateKeys(w http.ResponseWriter, r *http.Request) { defer s.lock.Unlock() // Only fulfill requests for available keys - if keyCount > len(s.FundedKeys) { + if keyCount > len(s.PreFundedKeys) { http.Error(w, requestedKeyCountExceedsAvailable, http.StatusInternalServerError) return } // Allocate the requested number of keys - remainingKeys := len(s.FundedKeys) - keyCount - allocatedKeys := s.FundedKeys[remainingKeys:] + remainingKeys := len(s.PreFundedKeys) - keyCount + allocatedKeys := s.PreFundedKeys[remainingKeys:] keysDoc := &keysDocument{ Keys: allocatedKeys, @@ -88,7 +88,7 @@ func (s *testDataServer) allocateKeys(w http.ResponseWriter, r *http.Request) { // Forget the allocated keys utils.ZeroSlice(allocatedKeys) - s.FundedKeys = s.FundedKeys[:remainingKeys] + s.PreFundedKeys = s.PreFundedKeys[:remainingKeys] } // Serve test data via http to ensure allocation is synchronized even when @@ -122,9 +122,9 @@ func ServeTestData(testData TestData) (string, error) { return address, nil } -// Retrieve the specified number of funded test keys from the provided URI. A given +// Retrieve the specified number of pre-funded test keys from the provided URI. A given // key is allocated at most once during the life of the test data server. -func AllocateFundedKeys(baseURI string, count int) ([]*secp256k1.PrivateKey, error) { +func AllocatePreFundedKeys(baseURI string, count int) ([]*secp256k1.PrivateKey, error) { if count <= 0 { return nil, errInvalidKeyCount } @@ -144,13 +144,13 @@ func AllocateFundedKeys(baseURI string, count int) ([]*secp256k1.PrivateKey, err resp, err := http.DefaultClient.Do(req) if err != nil { - return nil, fmt.Errorf("failed to request funded keys: %w", err) + return nil, fmt.Errorf("failed to request pre-funded keys: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed to read response for funded keys: %w", err) + return nil, fmt.Errorf("failed to read response for pre-funded keys: %w", err) } if resp.StatusCode != http.StatusOK { if strings.TrimSpace(string(body)) == requestedKeyCountExceedsAvailable { @@ -161,7 +161,7 @@ func AllocateFundedKeys(baseURI string, count int) ([]*secp256k1.PrivateKey, err keysDoc := &keysDocument{} if err := json.Unmarshal(body, keysDoc); err != nil { - return nil, fmt.Errorf("failed to unmarshal funded keys: %w", err) + return nil, fmt.Errorf("failed to unmarshal pre-funded keys: %w", err) } return keysDoc.Keys, nil } diff --git a/tests/fixture/test_data_server_test.go b/tests/fixture/test_data_server_test.go index 979c927fea7f..6ad7644264d7 100644 --- a/tests/fixture/test_data_server_test.go +++ b/tests/fixture/test_data_server_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package fixture @@ -14,7 +14,7 @@ import ( // Check that funded test keys can be served from an http server to // ensure at-most-once allocation when tests are executed in parallel. -func TestAllocateFundedKeys(t *testing.T) { +func TestAllocatePreFundedKeys(t *testing.T) { require := require.New(t) keys := make([]*secp256k1.PrivateKey, 5) @@ -25,7 +25,7 @@ func TestAllocateFundedKeys(t *testing.T) { } uri, err := ServeTestData(TestData{ - FundedKeys: keys, + PreFundedKeys: keys, }) require.NoError(err) @@ -63,7 +63,7 @@ func TestAllocateFundedKeys(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - keys, err := AllocateFundedKeys(uri, tc.count) + keys, err := AllocatePreFundedKeys(uri, tc.count) require.ErrorIs(err, tc.expectedError) addresses := make([]ids.ShortID, len(keys)) diff --git a/tests/fixture/tmpnet/README.md b/tests/fixture/tmpnet/README.md index ca48d553105e..909a29c6ee12 100644 --- a/tests/fixture/tmpnet/README.md +++ b/tests/fixture/tmpnet/README.md @@ -1,11 +1,11 @@ -# tmpnet (temporary network fixture) +# tmpnet - temporary network orchestration -This package contains configuration and interfaces that are -independent of a given orchestration mechanism -(e.g. [local](local/README.md)). The intent is to enable tests to be -written against the interfaces defined in this package and for -implementation-specific details of test network orchestration to be -limited to test setup and teardown. +This package implements a simple orchestrator for the avalanchego +nodes of a temporary network. Configuration is stored on disk, and +nodes run as independent processes whose process details are also +written to disk. Using the filesystem to store configuration and +process details allows for the `tmpnetctl` cli and e2e test fixture to +orchestrate the same temporary networks without the use of an rpc daemon. ## What's in a name? @@ -18,3 +18,214 @@ To avoid confusion, the name was changed to `tmpnet` and its cli networks it deploys are likely to live for a limited duration in support of the development and testing of avalanchego and its related repositories. + +## Package details + +The functionality in this package is grouped by logical purpose into +the following non-test files: + +| Filename | Types | Purpose | +|:------------------|:------------|:-----------------------------------------------| +| defaults.go | | Defines common default configuration | +| flags.go | FlagsMap | Simplifies configuration of avalanchego flags | +| genesis.go | | Creates test genesis | +| network.go | Network | Orchestrates and configures temporary networks | +| network_config.go | Network | Reads and writes network configuration | +| node.go | Node | Orchestrates and configures nodes | +| node_config.go | Node | Reads and writes node configuration | +| node_process.go | NodeProcess | Orchestrates node processes | +| subnet.go | Subnet | Orchestrates subnets | +| utils.go | | Defines shared utility functions | + +## Usage + +### Via tmpnetctl + +A temporary network can be managed by the `tmpnetctl` cli tool: + +```bash +# From the root of the avalanchego repo + +# Build the tmpnetctl binary +$ ./scripts/build_tmpnetctl.sh + +# Start a new network +$ ./build/tmpnetctl start-network --avalanchego-path=/path/to/avalanchego +... +Started network 1000 @ /home/me/.tmpnet/networks/1000 + +Configure tmpnetctl to target this network by default with one of the following statements: + - source /home/me/.tmpnet/networks/1000/network.env + - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/1000 + - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/latest + +# Stop the network +$ ./build/tmpnetctl stop-network --network-dir=/path/to/network +``` + +Note the export of the path ending in `latest`. This is a symlink that +is set to the last network created by `tmpnetctl start-network`. Setting +the `TMPNET_NETWORK_DIR` env var to this symlink ensures that +`tmpnetctl` commands and e2e execution with +`--use-existing-network` will target the most recently deployed temporary +network. + +### Via code + +A temporary network can be managed in code: + +```golang +network := &tmpnet.Network{ // Configure non-default values for the new network + DefaultFlags: tmpnet.FlagsMap{ + config.LogLevelKey: "INFO", // Change one of the network's defaults + }, + Subnets: []*tmpnet.Subnet{ // Subnets to create on the new network once it is running + { + Name: "xsvm-a", // User-defined name used to reference subnet in code and on disk + Chains: []*tmpnet.Chain{ + { + VMName: "xsvm", // Name of the VM the chain will run, will be used to derive the name of the VM binary + Genesis: , // Genesis bytes used to initialize the custom chain + PreFundedKey: , // (Optional) A private key that is funded in the genesis bytes + }, + }, + }, + }, +} + +_ := tmpnet.StartNewNetwork( // Start the network + ctx, // Context used to limit duration of waiting for network health + ginkgo.GinkgoWriter, // Writer to report progress of initialization + network, + "", // Empty string uses the default network path (~/tmpnet/networks) + "/path/to/avalanchego", // The path to the binary that nodes will execute + "/path/to/plugins", // The path nodes will use for plugin binaries (suggested value ~/.avalanchego/plugins) + 5, // Number of initial validating nodes +) + +uris := network.GetNodeURIs() + +// Use URIs to interact with the network + +// Stop all nodes in the network +network.Stop(context.Background()) +``` + +## Networking configuration + +By default, nodes in a temporary network will be started with staking and +API ports set to `0` to ensure that ports will be dynamically +chosen. The tmpnet fixture discovers the ports used by a given node +by reading the `[base-data-dir]/process.json` file written by +avalanchego on node start. The use of dynamic ports supports testing +with many temporary networks without having to manually select compatible +port ranges. + +## Configuration on disk + +A temporary network relies on configuration written to disk in the following structure: + +``` +HOME +└── .tmpnet // Root path for the temporary network fixture + └── networks // Default parent directory for temporary networks + └── 1000 // The networkID is used to name the network dir and starts at 1000 + ├── NodeID-37E8UK3x2YFsHE3RdALmfWcppcZ1eTuj9 // The ID of a node is the name of its data dir + │ ├── chainData + │ │ └── ... + │ ├── config.json // Node runtime configuration + │ ├── db + │ │ └── ... + │ ├── flags.json // Node flags + │ ├── logs + │ │ └── ... + │ ├── plugins + │ │ └── ... + │ └── process.json // Node process details (PID, API URI, staking address) + ├── chains + │ ├── C + │ │ └── config.json // C-Chain config for all nodes + │ └── raZ51bwfepaSaZ1MNSRNYNs3ZPfj...U7pa3 + │ └── config.json // Custom chain configuration for all nodes + ├── config.json // Common configuration (including defaults and pre-funded keys) + ├── genesis.json // Genesis for all nodes + ├── network.env // Sets network dir env var to simplify network usage + └── subnets // Parent directory for subnet definitions + ├─ subnet-a.json // Configuration for subnet-a and its chain(s) + └─ subnet-b.json // Configuration for subnet-b and its chain(s) +``` + +### Common networking configuration + +Network configuration such as default flags (e.g. `--log-level=`), +runtime defaults (e.g. avalanchego path) and pre-funded private keys +are stored at `[network-dir]/config.json`. A given default will only +be applied to a new node on its addition to the network if the node +does not explicitly set a given value. + +### Genesis + +The genesis file is stored at `[network-dir]/genesis.json` and +referenced by default by all nodes in the network. The genesis file +content will be generated with reasonable defaults if not +supplied. Each node in the network can override the default by setting +an explicit value for `--genesis-file` or `--genesis-file-content`. + +### Chain configuration + +The chain configuration for a temporary network is stored at +`[network-dir]/chains/[chain alias or ID]/config.json` and referenced +by all nodes in the network. The C-Chain config will be generated with +reasonable defaults if not supplied. X-Chain and P-Chain will use +implicit defaults. The configuration for custom chains can be provided +with subnet configuration and will be writen to the appropriate path. + +Each node in the network can override network-level chain +configuration by setting `--chain-config-dir` to an explicit value and +ensuring that configuration files for all chains exist at +`[custom-chain-config-dir]/[chain alias or ID]/config.json`. + +### Network env + +A shell script that sets the `TMPNET_NETWORK_DIR` env var to the +path of the network is stored at `[network-dir]/network.env`. Sourcing +this file (i.e. `source network.env`) in a shell will configure ginkgo +e2e and the `tmpnetctl` cli to target the network path specified in +the env var. + +Set `TMPNET_ROOT_DIR` to specify the root directory in which to create +the configuration directory of new networks +(e.g. `$TMPNET_ROOT_DIR/[network-dir]`). The default root directory is +`~/.tmpdir/networks`. Configuring the root directory is only relevant +when creating new networks as the path of existing networks will +already have been set. + +### Node configuration + +The data dir for a node is set by default to +`[network-path]/[node-id]`. A node can be configured to use a +non-default path by explicitly setting the `--data-dir` +flag. + +#### Runtime config + +The details required to configure a node's execution are written to +`[network-path]/[node-id]/config.json`. This file contains the +runtime-specific details like the path of the avalanchego binary to +start the node with. + +#### Flags + +All flags used to configure a node are written to +`[network-path]/[node-id]/flags.json` so that a node can be +configured with only a single argument: +`--config-file=/path/to/flags.json`. This simplifies node launch and +ensures all parameters used to launch a node can be modified by +editing the config file. + +#### Process details + +The process details of a node are written by avalanchego to +`[base-data-dir]/process.json`. The file contains the PID of the node +process, the URI of the node's API, and the address other nodes can +use to bootstrap themselves (aka staking address). diff --git a/tests/fixture/tmpnet/cmd/main.go b/tests/fixture/tmpnet/cmd/main.go index a9f5c1865291..dd59c300bbb3 100644 --- a/tests/fixture/tmpnet/cmd/main.go +++ b/tests/fixture/tmpnet/cmd/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main @@ -10,26 +10,28 @@ import ( "io/fs" "os" "path/filepath" + "time" "github.com/spf13/cobra" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet/local" "github.com/ava-labs/avalanchego/version" ) const cliVersion = "0.0.1" var ( - errAvalancheGoRequired = fmt.Errorf("--avalanchego-path or %s are required", local.AvalancheGoPathEnvName) - errNetworkDirRequired = fmt.Errorf("--network-dir or %s are required", local.NetworkDirEnvName) + errAvalancheGoRequired = fmt.Errorf("--avalanchego-path or %s are required", tmpnet.AvalancheGoPathEnvName) + errNetworkDirRequired = fmt.Errorf("--network-dir or %s are required", tmpnet.NetworkDirEnvName) ) func main() { + var networkDir string rootCmd := &cobra.Command{ Use: "tmpnetctl", Short: "tmpnetctl commands", } + rootCmd.PersistentFlags().StringVar(&networkDir, "network-dir", os.Getenv(tmpnet.NetworkDirEnvName), "The path to the configuration directory of a temporary network") versionCmd := &cobra.Command{ Use: "version", @@ -46,29 +48,37 @@ func main() { rootCmd.AddCommand(versionCmd) var ( - rootDir string - execPath string - nodeCount uint8 - fundedKeyCount uint8 + rootDir string + avalancheGoPath string + pluginDir string + nodeCount uint8 ) startNetworkCmd := &cobra.Command{ Use: "start-network", - Short: "Start a new local network", + Short: "Start a new temporary network", RunE: func(*cobra.Command, []string) error { - if len(execPath) == 0 { + if len(avalancheGoPath) == 0 { return errAvalancheGoRequired } // Root dir will be defaulted on start if not provided - network := &local.LocalNetwork{ - LocalConfig: local.LocalConfig{ - ExecPath: execPath, - }, - } - ctx, cancel := context.WithTimeout(context.Background(), local.DefaultNetworkStartTimeout) + network := &tmpnet.Network{} + + // Extreme upper bound, should never take this long + networkStartTimeout := 2 * time.Minute + + ctx, cancel := context.WithTimeout(context.Background(), networkStartTimeout) defer cancel() - network, err := local.StartNetwork(ctx, os.Stdout, rootDir, network, int(nodeCount), int(fundedKeyCount)) + err := tmpnet.StartNewNetwork( + ctx, + os.Stdout, + network, + rootDir, + avalancheGoPath, + pluginDir, + int(nodeCount), + ) if err != nil { return err } @@ -84,38 +94,52 @@ func main() { return err } - fmt.Fprintf(os.Stdout, "\nConfigure tmpnetctl to target this network by default with one of the following statements:") - fmt.Fprintf(os.Stdout, "\n - source %s\n", network.EnvFilePath()) + fmt.Fprintf(os.Stdout, "\nConfigure tmpnetctl to target this network by default with one of the following statements:\n") + fmt.Fprintf(os.Stdout, " - source %s\n", network.EnvFilePath()) fmt.Fprintf(os.Stdout, " - %s\n", network.EnvFileContents()) - fmt.Fprintf(os.Stdout, " - export %s=%s\n", local.NetworkDirEnvName, latestSymlinkPath) + fmt.Fprintf(os.Stdout, " - export %s=%s\n", tmpnet.NetworkDirEnvName, latestSymlinkPath) return nil }, } - startNetworkCmd.PersistentFlags().StringVar(&rootDir, "root-dir", os.Getenv(local.RootDirEnvName), "The path to the root directory for local networks") - startNetworkCmd.PersistentFlags().StringVar(&execPath, "avalanchego-path", os.Getenv(local.AvalancheGoPathEnvName), "The path to an avalanchego binary") + startNetworkCmd.PersistentFlags().StringVar(&rootDir, "root-dir", os.Getenv(tmpnet.RootDirEnvName), "The path to the root directory for temporary networks") + startNetworkCmd.PersistentFlags().StringVar(&avalancheGoPath, "avalanchego-path", os.Getenv(tmpnet.AvalancheGoPathEnvName), "The path to an avalanchego binary") + startNetworkCmd.PersistentFlags().StringVar(&pluginDir, "plugin-dir", os.ExpandEnv("$HOME/.avalanchego/plugins"), "[optional] the dir containing VM plugins") startNetworkCmd.PersistentFlags().Uint8Var(&nodeCount, "node-count", tmpnet.DefaultNodeCount, "Number of nodes the network should initially consist of") - startNetworkCmd.PersistentFlags().Uint8Var(&fundedKeyCount, "funded-key-count", tmpnet.DefaultFundedKeyCount, "Number of funded keys the network should start with") rootCmd.AddCommand(startNetworkCmd) - var networkDir string stopNetworkCmd := &cobra.Command{ Use: "stop-network", - Short: "Stop a local network", + Short: "Stop a temporary network", RunE: func(*cobra.Command, []string) error { if len(networkDir) == 0 { return errNetworkDirRequired } - if err := local.StopNetwork(networkDir); err != nil { + ctx, cancel := context.WithTimeout(context.Background(), tmpnet.DefaultNetworkTimeout) + defer cancel() + if err := tmpnet.StopNetwork(ctx, networkDir); err != nil { return err } fmt.Fprintf(os.Stdout, "Stopped network configured at: %s\n", networkDir) return nil }, } - stopNetworkCmd.PersistentFlags().StringVar(&networkDir, "network-dir", os.Getenv(local.NetworkDirEnvName), "The path to the configuration directory of a local network") rootCmd.AddCommand(stopNetworkCmd) + restartNetworkCmd := &cobra.Command{ + Use: "restart-network", + Short: "Restart a temporary network", + RunE: func(*cobra.Command, []string) error { + if len(networkDir) == 0 { + return errNetworkDirRequired + } + ctx, cancel := context.WithTimeout(context.Background(), tmpnet.DefaultNetworkTimeout) + defer cancel() + return tmpnet.RestartNetwork(ctx, os.Stdout, networkDir) + }, + } + rootCmd.AddCommand(restartNetworkCmd) + if err := rootCmd.Execute(); err != nil { fmt.Fprintf(os.Stderr, "tmpnetctl failed: %v\n", err) os.Exit(1) diff --git a/tests/fixture/tmpnet/common.go b/tests/fixture/tmpnet/common.go deleted file mode 100644 index 4b0281f45242..000000000000 --- a/tests/fixture/tmpnet/common.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package tmpnet - -import ( - "context" - "errors" - "fmt" - "time" -) - -const ( - DefaultNodeTickerInterval = 50 * time.Millisecond -) - -var ErrNotRunning = errors.New("not running") - -// WaitForHealthy blocks until Node.IsHealthy returns true or an error (including context timeout) is observed. -func WaitForHealthy(ctx context.Context, node Node) error { - if _, ok := ctx.Deadline(); !ok { - return fmt.Errorf("unable to wait for health for node %q with a context without a deadline", node.GetID()) - } - ticker := time.NewTicker(DefaultNodeTickerInterval) - defer ticker.Stop() - - for { - healthy, err := node.IsHealthy(ctx) - if err != nil && !errors.Is(err, ErrNotRunning) { - return fmt.Errorf("failed to wait for health of node %q: %w", node.GetID(), err) - } - if healthy { - return nil - } - - select { - case <-ctx.Done(): - return fmt.Errorf("failed to wait for health of node %q before timeout: %w", node.GetID(), ctx.Err()) - case <-ticker.C: - } - } -} diff --git a/tests/fixture/tmpnet/config.go b/tests/fixture/tmpnet/config.go deleted file mode 100644 index f504eb84d20d..000000000000 --- a/tests/fixture/tmpnet/config.go +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package tmpnet - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "math/big" - "os" - "strings" - "time" - - "github.com/spf13/cast" - - "github.com/ava-labs/coreth/core" - "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/plugin/evm" - - "github.com/ava-labs/avalanchego/config" - "github.com/ava-labs/avalanchego/genesis" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/staking" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/utils/formatting/address" - "github.com/ava-labs/avalanchego/utils/perms" - "github.com/ava-labs/avalanchego/utils/units" - "github.com/ava-labs/avalanchego/vms/platformvm/reward" -) - -const ( - DefaultNodeCount = 2 // Minimum required to ensure connectivity-based health checks will pass - DefaultFundedKeyCount = 50 - - DefaultGasLimit = uint64(100_000_000) // Gas limit is arbitrary - - // Arbitrarily large amount of AVAX to fund keys on the X-Chain for testing - DefaultFundedKeyXChainAmount = 30 * units.MegaAvax - - // A short min stake duration enables testing of staking logic. - DefaultMinStakeDuration = time.Second -) - -var ( - // Arbitrarily large amount of AVAX (10^12) to fund keys on the C-Chain for testing - DefaultFundedKeyCChainAmount = new(big.Int).Exp(big.NewInt(10), big.NewInt(30), nil) - - errEmptyValidatorsForGenesis = errors.New("failed to generate genesis: empty validator IDs") - errNoKeysForGenesis = errors.New("failed to generate genesis: no keys to fund") - errInvalidNetworkIDForGenesis = errors.New("network ID can't be mainnet, testnet or local network ID") - errMissingValidatorsForGenesis = errors.New("no genesis validators provided") - errMissingBalancesForGenesis = errors.New("no genesis balances given") - errMissingTLSKeyForNodeID = fmt.Errorf("failed to ensure node ID: missing value for %q", config.StakingTLSKeyContentKey) - errMissingCertForNodeID = fmt.Errorf("failed to ensure node ID: missing value for %q", config.StakingCertContentKey) - errInvalidKeypair = fmt.Errorf("%q and %q must be provided together or not at all", config.StakingTLSKeyContentKey, config.StakingCertContentKey) -) - -// Defines a mapping of flag keys to values intended to be supplied to -// an invocation of an AvalancheGo node. -type FlagsMap map[string]interface{} - -// SetDefaults ensures the effectiveness of flag overrides by only -// setting values supplied in the defaults map that are not already -// explicitly set. -func (f FlagsMap) SetDefaults(defaults FlagsMap) { - for key, value := range defaults { - if _, ok := f[key]; !ok { - f[key] = value - } - } -} - -// GetStringVal simplifies retrieving a map value as a string. -func (f FlagsMap) GetStringVal(key string) (string, error) { - rawVal, ok := f[key] - if !ok { - return "", nil - } - - val, err := cast.ToStringE(rawVal) - if err != nil { - return "", fmt.Errorf("failed to cast value for %q: %w", key, err) - } - return val, nil -} - -// Write simplifies writing a FlagsMap to the provided path. The -// description is used in error messages. -func (f FlagsMap) Write(path string, description string) error { - bytes, err := DefaultJSONMarshal(f) - if err != nil { - return fmt.Errorf("failed to marshal %s: %w", description, err) - } - if err := os.WriteFile(path, bytes, perms.ReadWrite); err != nil { - return fmt.Errorf("failed to write %s: %w", description, err) - } - return nil -} - -// Utility function simplifying construction of a FlagsMap from a file. -func ReadFlagsMap(path string, description string) (*FlagsMap, error) { - bytes, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("failed to read %s: %w", description, err) - } - flagsMap := &FlagsMap{} - if err := json.Unmarshal(bytes, flagsMap); err != nil { - return nil, fmt.Errorf("failed to unmarshal %s: %w", description, err) - } - return flagsMap, nil -} - -// Marshal to json with default prefix and indent. -func DefaultJSONMarshal(v interface{}) ([]byte, error) { - return json.MarshalIndent(v, "", " ") -} - -// NetworkConfig defines configuration shared or -// common to all nodes in a given network. -type NetworkConfig struct { - Genesis *genesis.UnparsedConfig - CChainConfig FlagsMap - DefaultFlags FlagsMap - FundedKeys []*secp256k1.PrivateKey -} - -// Ensure genesis is generated if not already present. -func (c *NetworkConfig) EnsureGenesis(networkID uint32, validatorIDs []ids.NodeID) error { - if c.Genesis != nil { - return nil - } - - if len(validatorIDs) == 0 { - return errEmptyValidatorsForGenesis - } - if len(c.FundedKeys) == 0 { - return errNoKeysForGenesis - } - - // Ensure pre-funded keys have arbitrary large balances on both chains to support testing - xChainBalances := make(XChainBalanceMap, len(c.FundedKeys)) - cChainBalances := make(core.GenesisAlloc, len(c.FundedKeys)) - for _, key := range c.FundedKeys { - xChainBalances[key.Address()] = DefaultFundedKeyXChainAmount - cChainBalances[evm.GetEthAddress(key)] = core.GenesisAccount{ - Balance: DefaultFundedKeyCChainAmount, - } - } - - genesis, err := NewTestGenesis(networkID, xChainBalances, cChainBalances, validatorIDs) - if err != nil { - return err - } - - c.Genesis = genesis - return nil -} - -// NodeURI associates a node ID with its API URI. -type NodeURI struct { - NodeID ids.NodeID - URI string -} - -// NodeConfig defines configuration for an AvalancheGo node. -type NodeConfig struct { - NodeID ids.NodeID - Flags FlagsMap -} - -func NewNodeConfig() *NodeConfig { - return &NodeConfig{ - Flags: FlagsMap{}, - } -} - -// Convenience method for setting networking flags. -func (nc *NodeConfig) SetNetworkingConfigDefaults( - httpPort uint16, - stakingPort uint16, - bootstrapIDs []string, - bootstrapIPs []string, -) { - nc.Flags.SetDefaults(FlagsMap{ - config.HTTPPortKey: httpPort, - config.StakingPortKey: stakingPort, - config.BootstrapIDsKey: strings.Join(bootstrapIDs, ","), - config.BootstrapIPsKey: strings.Join(bootstrapIPs, ","), - }) -} - -// Ensures staking and signing keys are generated if not already present and -// that the node ID (derived from the staking keypair) is set. -func (nc *NodeConfig) EnsureKeys() error { - if err := nc.EnsureBLSSigningKey(); err != nil { - return err - } - if err := nc.EnsureStakingKeypair(); err != nil { - return err - } - // Once a staking keypair is guaranteed it is safe to derive the node ID - return nc.EnsureNodeID() -} - -// Ensures a BLS signing key is generated if not already present. -func (nc *NodeConfig) EnsureBLSSigningKey() error { - // Attempt to retrieve an existing key - existingKey, err := nc.Flags.GetStringVal(config.StakingSignerKeyContentKey) - if err != nil { - return err - } - if len(existingKey) > 0 { - // Nothing to do - return nil - } - - // Generate a new signing key - newKey, err := bls.NewSecretKey() - if err != nil { - return fmt.Errorf("failed to generate staking signer key: %w", err) - } - nc.Flags[config.StakingSignerKeyContentKey] = base64.StdEncoding.EncodeToString(bls.SerializeSecretKey(newKey)) - return nil -} - -// Ensures a staking keypair is generated if not already present. -func (nc *NodeConfig) EnsureStakingKeypair() error { - keyKey := config.StakingTLSKeyContentKey - certKey := config.StakingCertContentKey - - key, err := nc.Flags.GetStringVal(keyKey) - if err != nil { - return err - } - - cert, err := nc.Flags.GetStringVal(certKey) - if err != nil { - return err - } - - if len(key) == 0 && len(cert) == 0 { - // Generate new keypair - tlsCertBytes, tlsKeyBytes, err := staking.NewCertAndKeyBytes() - if err != nil { - return fmt.Errorf("failed to generate staking keypair: %w", err) - } - nc.Flags[keyKey] = base64.StdEncoding.EncodeToString(tlsKeyBytes) - nc.Flags[certKey] = base64.StdEncoding.EncodeToString(tlsCertBytes) - } else if len(key) == 0 || len(cert) == 0 { - // Only one of key and cert was provided - return errInvalidKeypair - } - - err = nc.EnsureNodeID() - if err != nil { - return fmt.Errorf("failed to derive a node ID: %w", err) - } - - return nil -} - -// Attempt to derive the node ID from the node configuration. -func (nc *NodeConfig) EnsureNodeID() error { - keyKey := config.StakingTLSKeyContentKey - certKey := config.StakingCertContentKey - - key, err := nc.Flags.GetStringVal(keyKey) - if err != nil { - return err - } - if len(key) == 0 { - return errMissingTLSKeyForNodeID - } - keyBytes, err := base64.StdEncoding.DecodeString(key) - if err != nil { - return fmt.Errorf("failed to ensure node ID: failed to base64 decode value for %q: %w", keyKey, err) - } - - cert, err := nc.Flags.GetStringVal(certKey) - if err != nil { - return err - } - if len(cert) == 0 { - return errMissingCertForNodeID - } - certBytes, err := base64.StdEncoding.DecodeString(cert) - if err != nil { - return fmt.Errorf("failed to ensure node ID: failed to base64 decode value for %q: %w", certKey, err) - } - - tlsCert, err := staking.LoadTLSCertFromBytes(keyBytes, certBytes) - if err != nil { - return fmt.Errorf("failed to ensure node ID: failed to load tls cert: %w", err) - } - stakingCert := staking.CertificateFromX509(tlsCert.Leaf) - nc.NodeID = ids.NodeIDFromCert(stakingCert) - - return nil -} - -// Helper type to simplify configuring X-Chain genesis balances -type XChainBalanceMap map[ids.ShortID]uint64 - -// Create a genesis struct valid for bootstrapping a test -// network. Note that many of the genesis fields (e.g. reward -// addresses) are randomly generated or hard-coded. -func NewTestGenesis( - networkID uint32, - xChainBalances XChainBalanceMap, - cChainBalances core.GenesisAlloc, - validatorIDs []ids.NodeID, -) (*genesis.UnparsedConfig, error) { - // Validate inputs - switch networkID { - case constants.TestnetID, constants.MainnetID, constants.LocalID: - return nil, errInvalidNetworkIDForGenesis - } - if len(validatorIDs) == 0 { - return nil, errMissingValidatorsForGenesis - } - if len(xChainBalances) == 0 || len(cChainBalances) == 0 { - return nil, errMissingBalancesForGenesis - } - - // Address that controls stake doesn't matter -- generate it randomly - stakeAddress, err := address.Format( - "X", - constants.GetHRP(networkID), - ids.GenerateTestShortID().Bytes(), - ) - if err != nil { - return nil, fmt.Errorf("failed to format stake address: %w", err) - } - - // Ensure the total stake allows a MegaAvax per validator - totalStake := uint64(len(validatorIDs)) * units.MegaAvax - - // The eth address is only needed to link pre-mainnet assets. Until that capability - // becomes necessary for testing, use a bogus address. - // - // Reference: https://github.com/ava-labs/avalanchego/issues/1365#issuecomment-1511508767 - ethAddress := "0x0000000000000000000000000000000000000000" - - now := time.Now() - - config := &genesis.UnparsedConfig{ - NetworkID: networkID, - Allocations: []genesis.UnparsedAllocation{ - { - ETHAddr: ethAddress, - AVAXAddr: stakeAddress, - InitialAmount: 0, - UnlockSchedule: []genesis.LockedAmount{ // Provides stake to validators - { - Amount: totalStake, - Locktime: uint64(now.Add(7 * 24 * time.Hour).Unix()), // 1 Week - }, - }, - }, - }, - StartTime: uint64(now.Unix()), - InitialStakedFunds: []string{stakeAddress}, - InitialStakeDuration: 365 * 24 * 60 * 60, // 1 year - InitialStakeDurationOffset: 90 * 60, // 90 minutes - Message: "hello avalanche!", - } - - // Set X-Chain balances - for xChainAddress, balance := range xChainBalances { - avaxAddr, err := address.Format("X", constants.GetHRP(networkID), xChainAddress[:]) - if err != nil { - return nil, fmt.Errorf("failed to format X-Chain address: %w", err) - } - config.Allocations = append( - config.Allocations, - genesis.UnparsedAllocation{ - ETHAddr: ethAddress, - AVAXAddr: avaxAddr, - InitialAmount: balance, - UnlockSchedule: []genesis.LockedAmount{ - { - Amount: 20 * units.MegaAvax, - }, - { - Amount: totalStake, - Locktime: uint64(now.Add(7 * 24 * time.Hour).Unix()), // 1 Week - }, - }, - }, - ) - } - - // Define C-Chain genesis - cChainGenesis := &core.Genesis{ - Config: ¶ms.ChainConfig{ - ChainID: big.NewInt(43112), // Arbitrary chain ID is arbitrary - }, - Difficulty: big.NewInt(0), // Difficulty is a mandatory field - GasLimit: DefaultGasLimit, - Alloc: cChainBalances, - } - cChainGenesisBytes, err := json.Marshal(cChainGenesis) - if err != nil { - return nil, fmt.Errorf("failed to marshal C-Chain genesis: %w", err) - } - config.CChainGenesis = string(cChainGenesisBytes) - - // Give staking rewards for initial validators to a random address. Any testing of staking rewards - // will be easier to perform with nodes other than the initial validators since the timing of - // staking can be more easily controlled. - rewardAddr, err := address.Format("X", constants.GetHRP(networkID), ids.GenerateTestShortID().Bytes()) - if err != nil { - return nil, fmt.Errorf("failed to format reward address: %w", err) - } - - // Configure provided validator node IDs as initial stakers - for _, validatorID := range validatorIDs { - config.InitialStakers = append( - config.InitialStakers, - genesis.UnparsedStaker{ - NodeID: validatorID, - RewardAddress: rewardAddr, - DelegationFee: .01 * reward.PercentDenominator, - }, - ) - } - - return config, nil -} diff --git a/tests/fixture/tmpnet/defaults.go b/tests/fixture/tmpnet/defaults.go new file mode 100644 index 000000000000..2b88ef49afc1 --- /dev/null +++ b/tests/fixture/tmpnet/defaults.go @@ -0,0 +1,67 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "time" + + "github.com/ava-labs/avalanchego/config" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" +) + +const ( + // Interval appropriate for network operations that should be + // retried periodically but not too often. + DefaultPollingInterval = 500 * time.Millisecond + + // Validator start time must be a minimum of SyncBound from the + // current time for validator addition to succeed, and adding 20 + // seconds provides a buffer in case of any delay in processing. + DefaultValidatorStartTimeDiff = executor.SyncBound + 20*time.Second + + DefaultNetworkTimeout = 2 * time.Minute + + // Minimum required to ensure connectivity-based health checks will pass + DefaultNodeCount = 2 + + // Arbitrary number of pre-funded keys to create by default + DefaultPreFundedKeyCount = 50 + + // A short minimum stake duration enables testing of staking logic. + DefaultMinStakeDuration = time.Second + + defaultConfigFilename = "config.json" +) + +// A set of flags appropriate for testing. +func DefaultFlags() FlagsMap { + // Supply only non-default configuration to ensure that default values will be used. + return FlagsMap{ + config.NetworkPeerListGossipFreqKey: "250ms", + config.NetworkMaxReconnectDelayKey: "1s", + config.PublicIPKey: "127.0.0.1", + config.HTTPHostKey: "127.0.0.1", + config.StakingHostKey: "127.0.0.1", + config.HealthCheckFreqKey: "2s", + config.AdminAPIEnabledKey: true, + config.IpcAPIEnabledKey: true, + config.IndexEnabledKey: true, + config.LogDisplayLevelKey: "INFO", + config.LogLevelKey: "DEBUG", + config.MinStakeDurationKey: DefaultMinStakeDuration.String(), + } +} + +// A set of chain configurations appropriate for testing. +func DefaultChainConfigs() map[string]FlagsMap { + return map[string]FlagsMap{ + // Supply only non-default configuration to ensure that default + // values will be used. Available C-Chain configuration options are + // defined in the `github.com/ava-labs/coreth/evm` package. + "C": { + "warp-api-enabled": true, + "log-level": "trace", + }, + } +} diff --git a/tests/fixture/tmpnet/flags.go b/tests/fixture/tmpnet/flags.go new file mode 100644 index 000000000000..3084982ea704 --- /dev/null +++ b/tests/fixture/tmpnet/flags.go @@ -0,0 +1,69 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/spf13/cast" + + "github.com/ava-labs/avalanchego/utils/perms" +) + +// Defines a mapping of flag keys to values intended to be supplied to +// an invocation of an AvalancheGo node. +type FlagsMap map[string]interface{} + +// Utility function simplifying construction of a FlagsMap from a file. +func ReadFlagsMap(path string, description string) (*FlagsMap, error) { + bytes, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read %s: %w", description, err) + } + flagsMap := &FlagsMap{} + if err := json.Unmarshal(bytes, flagsMap); err != nil { + return nil, fmt.Errorf("failed to unmarshal %s: %w", description, err) + } + return flagsMap, nil +} + +// SetDefaults ensures the effectiveness of flag overrides by only +// setting values supplied in the defaults map that are not already +// explicitly set. +func (f FlagsMap) SetDefaults(defaults FlagsMap) { + for key, value := range defaults { + if _, ok := f[key]; !ok { + f[key] = value + } + } +} + +// GetStringVal simplifies retrieving a map value as a string. +func (f FlagsMap) GetStringVal(key string) (string, error) { + rawVal, ok := f[key] + if !ok { + return "", nil + } + + val, err := cast.ToStringE(rawVal) + if err != nil { + return "", fmt.Errorf("failed to cast value for %q: %w", key, err) + } + return val, nil +} + +// Write simplifies writing a FlagsMap to the provided path. The +// description is used in error messages. +func (f FlagsMap) Write(path string, description string) error { + bytes, err := DefaultJSONMarshal(f) + if err != nil { + return fmt.Errorf("failed to marshal %s: %w", description, err) + } + if err := os.WriteFile(path, bytes, perms.ReadWrite); err != nil { + return fmt.Errorf("failed to write %s: %w", description, err) + } + return nil +} diff --git a/tests/fixture/tmpnet/genesis.go b/tests/fixture/tmpnet/genesis.go new file mode 100644 index 000000000000..a9c85fe8b441 --- /dev/null +++ b/tests/fixture/tmpnet/genesis.go @@ -0,0 +1,191 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + "time" + + "github.com/ava-labs/coreth/core" + "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm" + + "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/formatting/address" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/platformvm/reward" +) + +const ( + defaultGasLimit = uint64(100_000_000) // Gas limit is arbitrary + + // Arbitrarily large amount of AVAX to fund keys on the X-Chain for testing + defaultFundedKeyXChainAmount = 30 * units.MegaAvax +) + +var ( + // Arbitrarily large amount of AVAX (10^12) to fund keys on the C-Chain for testing + defaultFundedKeyCChainAmount = new(big.Int).Exp(big.NewInt(10), big.NewInt(30), nil) + + errNoKeysForGenesis = errors.New("no keys to fund for genesis") + errInvalidNetworkIDForGenesis = errors.New("network ID can't be mainnet, testnet or local network ID for genesis") + errMissingStakersForGenesis = errors.New("no stakers provided for genesis") +) + +// Helper type to simplify configuring X-Chain genesis balances +type XChainBalanceMap map[ids.ShortID]uint64 + +// Create a genesis struct valid for bootstrapping a test +// network. Note that many of the genesis fields (e.g. reward +// addresses) are randomly generated or hard-coded. +func NewTestGenesis( + networkID uint32, + nodes []*Node, + keysToFund []*secp256k1.PrivateKey, +) (*genesis.UnparsedConfig, error) { + // Validate inputs + switch networkID { + case constants.TestnetID, constants.MainnetID, constants.LocalID: + return nil, errInvalidNetworkIDForGenesis + } + if len(nodes) == 0 { + return nil, errMissingStakersForGenesis + } + if len(keysToFund) == 0 { + return nil, errNoKeysForGenesis + } + + initialStakers, err := stakersForNodes(networkID, nodes) + if err != nil { + return nil, fmt.Errorf("failed to configure stakers for nodes: %w", err) + } + + // Address that controls stake doesn't matter -- generate it randomly + stakeAddress, err := address.Format( + "X", + constants.GetHRP(networkID), + ids.GenerateTestShortID().Bytes(), + ) + if err != nil { + return nil, fmt.Errorf("failed to format stake address: %w", err) + } + + // Ensure the total stake allows a MegaAvax per staker + totalStake := uint64(len(initialStakers)) * units.MegaAvax + + // The eth address is only needed to link pre-mainnet assets. Until that capability + // becomes necessary for testing, use a bogus address. + // + // Reference: https://github.com/ava-labs/avalanchego/issues/1365#issuecomment-1511508767 + ethAddress := "0x0000000000000000000000000000000000000000" + + now := time.Now() + + config := &genesis.UnparsedConfig{ + NetworkID: networkID, + Allocations: []genesis.UnparsedAllocation{ + { + ETHAddr: ethAddress, + AVAXAddr: stakeAddress, + InitialAmount: 0, + UnlockSchedule: []genesis.LockedAmount{ // Provides stake to validators + { + Amount: totalStake, + Locktime: uint64(now.Add(7 * 24 * time.Hour).Unix()), // 1 Week + }, + }, + }, + }, + StartTime: uint64(now.Unix()), + InitialStakedFunds: []string{stakeAddress}, + InitialStakeDuration: 365 * 24 * 60 * 60, // 1 year + InitialStakeDurationOffset: 90 * 60, // 90 minutes + Message: "hello avalanche!", + InitialStakers: initialStakers, + } + + // Ensure pre-funded keys have arbitrary large balances on both chains to support testing + xChainBalances := make(XChainBalanceMap, len(keysToFund)) + cChainBalances := make(core.GenesisAlloc, len(keysToFund)) + for _, key := range keysToFund { + xChainBalances[key.Address()] = defaultFundedKeyXChainAmount + cChainBalances[evm.GetEthAddress(key)] = core.GenesisAccount{ + Balance: defaultFundedKeyCChainAmount, + } + } + + // Set X-Chain balances + for xChainAddress, balance := range xChainBalances { + avaxAddr, err := address.Format("X", constants.GetHRP(networkID), xChainAddress[:]) + if err != nil { + return nil, fmt.Errorf("failed to format X-Chain address: %w", err) + } + config.Allocations = append( + config.Allocations, + genesis.UnparsedAllocation{ + ETHAddr: ethAddress, + AVAXAddr: avaxAddr, + InitialAmount: balance, + UnlockSchedule: []genesis.LockedAmount{ + { + Amount: 20 * units.MegaAvax, + }, + { + Amount: totalStake, + Locktime: uint64(now.Add(7 * 24 * time.Hour).Unix()), // 1 Week + }, + }, + }, + ) + } + + // Define C-Chain genesis + cChainGenesis := &core.Genesis{ + Config: params.AvalancheLocalChainConfig, + Difficulty: big.NewInt(0), // Difficulty is a mandatory field + GasLimit: defaultGasLimit, + Alloc: cChainBalances, + } + cChainGenesisBytes, err := json.Marshal(cChainGenesis) + if err != nil { + return nil, fmt.Errorf("failed to marshal C-Chain genesis: %w", err) + } + config.CChainGenesis = string(cChainGenesisBytes) + + return config, nil +} + +// Returns staker configuration for the given set of nodes. +func stakersForNodes(networkID uint32, nodes []*Node) ([]genesis.UnparsedStaker, error) { + // Give staking rewards for initial validators to a random address. Any testing of staking rewards + // will be easier to perform with nodes other than the initial validators since the timing of + // staking can be more easily controlled. + rewardAddr, err := address.Format("X", constants.GetHRP(networkID), ids.GenerateTestShortID().Bytes()) + if err != nil { + return nil, fmt.Errorf("failed to format reward address: %w", err) + } + + // Configure provided nodes as initial stakers + initialStakers := make([]genesis.UnparsedStaker, len(nodes)) + for i, node := range nodes { + pop, err := node.GetProofOfPossession() + if err != nil { + return nil, fmt.Errorf("failed to derive proof of possession for node %s: %w", node.NodeID, err) + } + initialStakers[i] = genesis.UnparsedStaker{ + NodeID: node.NodeID, + RewardAddress: rewardAddr, + DelegationFee: .01 * reward.PercentDenominator, + Signer: pop, + } + } + + return initialStakers, nil +} diff --git a/tests/fixture/tmpnet/interfaces.go b/tests/fixture/tmpnet/interfaces.go deleted file mode 100644 index 2fd03cbc2a98..000000000000 --- a/tests/fixture/tmpnet/interfaces.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package tmpnet - -import ( - "context" - "io" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/node" -) - -// Defines network capabilities supportable regardless of how a network is orchestrated. -type Network interface { - GetConfig() NetworkConfig - GetNodes() []Node - AddEphemeralNode(w io.Writer, flags FlagsMap) (Node, error) -} - -// Defines node capabilities supportable regardless of how a network is orchestrated. -type Node interface { - GetID() ids.NodeID - GetConfig() NodeConfig - GetProcessContext() node.NodeProcessContext - IsHealthy(ctx context.Context) (bool, error) - Stop() error -} diff --git a/tests/fixture/tmpnet/local/README.md b/tests/fixture/tmpnet/local/README.md deleted file mode 100644 index 91af35a9b805..000000000000 --- a/tests/fixture/tmpnet/local/README.md +++ /dev/null @@ -1,219 +0,0 @@ -# Local network orchestration - -This package implements a simple orchestrator for the avalanchego -nodes of a local network. Configuration is stored on disk, and nodes -run as independent processes whose process details are also written to -disk. Using the filesystem to store configuration and process details -allows for the `tmpnetctl` cli and e2e test fixture to orchestrate -the same local networks without the use of an rpc daemon. - -## Package details - -The functionality in this package is grouped by logical purpose into -the following non-test files: - -| Filename | Types | Purpose | -|:-----------|:-------------------|:----------------------------------------------| -| config.go | | Common configuration | -| network.go | LocalNetwork | Network-level orchestration and configuration | -| node.go | Local{Config,Node} | Node-level orchestration and configuration | - - -This package depends on its parent package for implementation-agnostic -network and node configuration. Only configuration and code specific -to orchestrating local networks belongs in this package to ensure that -other orchestration implementations can reuse the shared configuration -abstractions. - -## Usage - -### Via tmpnetctl - -A local network can be managed by the `tmpnetctl` cli tool: - -```bash -# From the root of the avalanchego repo - -# Build the tmpnetctl binary -$ ./scripts/build_tmpnetctl.sh - -# Start a new network -$ ./build/tmpnetctl start-network --avalanchego-path=/path/to/avalanchego -... -Started network 1000 @ /home/me/.tmpnet/networks/1000 - -Configure tmpnetctl to target this network by default with one of the following statements: - - source /home/me/.tmpnet/networks/1000/network.env - - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/1000 - - export TMPNET_NETWORK_DIR=/home/me/.tmpnet/networks/latest - -# Stop the network -$ ./build/tmpnetctl stop-network --network-dir=/path/to/network -``` - -Note the export of the path ending in `latest`. This is a symlink that -is set to the last network created by `tmpnetctl start-network`. Setting -the `TMPNET_NETWORK_DIR` env var to this symlink ensures that -`tmpnetctl` commands and e2e execution with -`--use-existing-network` will target the most recently deployed local -network. - -### Via code - -A local network can be managed in code: - -```golang -network, _ := local.StartNetwork( - ctx, // Context used to limit duration of waiting for network health - ginkgo.GinkgoWriter, // Writer to report progress of network start - "", // Use default root dir (~/.tmpnet) - &local.LocalNetwork{ - LocalConfig: local.LocalConfig{ - ExecPath: "/path/to/avalanchego", // Defining the avalanchego exec path is required - }, - }, - 5, // Number of initial validating nodes - 50, // Number of pre-funded keys to create -) - -uris := network.GetURIs() - -// Use URIs to interact with the network - -// Stop all nodes in the network -network.Stop() -``` - -If non-default node behavior is required, the `LocalNetwork` instance -supplied to `StartNetwork()` can be initialized with explicit node -configuration and by supplying a nodeCount argument of `0`: - -```golang -network, _ := local.StartNetwork( - ctx, - ginkgo.GinkgoWriter, - "", - &local.LocalNetwork{ - LocalConfig: local.LocalConfig{ - ExecPath: "/path/to/avalanchego", - }, - Nodes: []*LocalNode{ - { // node1 configuration is customized - Flags: FlagsMap{ // Any and all node flags can be configured here - config.DataDirKey: "/custom/path/to/node/data", - } - }, - }, - {}, // node2 uses default configuration - {}, // node3 uses default configuration - {}, // node4 uses default configuration - {}, // node5 uses default configuration - }, - 0, // Node count must be zero when setting node config - 50, -) -``` - -Further examples of code-based usage are located in the [e2e -tests](../../../e2e/e2e_test.go). - -## Networking configuration - -By default, nodes in a local network will be started with staking and -API ports set to `0` to ensure that ports will be dynamically -chosen. The tmpnet fixture discovers the ports used by a given node -by reading the `[base-data-dir]/process.json` file written by -avalanchego on node start. The use of dynamic ports supports testing -with many local networks without having to manually select compatible -port ranges. - -## Configuration on disk - -A local network relies on configuration written to disk in the following structure: - -``` -HOME -└── .tmpnet // Root path for the temporary network fixture - └── networks // Default parent directory for local networks - └── 1000 // The networkID is used to name the network dir and starts at 1000 - ├── NodeID-37E8UK3x2YFsHE3RdALmfWcppcZ1eTuj9 // The ID of a node is the name of its data dir - │ ├── chainData - │ │ └── ... - │ ├── config.json // Node flags - │ ├── db - │ │ └── ... - │ ├── logs - │ │ └── ... - │ ├── plugins - │ │ └── ... - │ └── process.json // Node process details (PID, API URI, staking address) - ├── chains - │ └── C - │ └── config.json // C-Chain config for all nodes - ├── defaults.json // Default flags and configuration for network - ├── genesis.json // Genesis for all nodes - ├── network.env // Sets network dir env to simplify use of network - └── ephemeral // Parent directory for ephemeral nodes (e.g. created by tests) - └─ NodeID-FdxnAvr4jK9XXAwsYZPgWAHW2QnwSZ // Data dir for an ephemeral node - └── ... - -``` - -### Default flags and configuration - -The default avalanchego node flags (e.g. `--staking-port=`) and -default configuration like the avalanchego path are stored at -`[network-dir]/defaults.json`. The value for a given defaulted flag -will be set on initial and subsequently added nodes that do not supply -values for a given defaulted flag. - -### Genesis - -The genesis file is stored at `[network-dir]/genesis.json` and -referenced by default by all nodes in the network. The genesis file -content will be generated with reasonable defaults if not -supplied. Each node in the network can override the default by setting -an explicit value for `--genesis-file` or `--genesis-file-content`. - -### C-Chain config - -The C-Chain config for a local network is stored at -`[network-dir]/chains/C/config.json` and referenced by default by all -nodes in the network. The C-Chain config will be generated with -reasonable defaults if not supplied. Each node in the network can -override the default by setting an explicit value for -`--chain-config-dir` and ensuring the C-Chain config file exists at -`[chain-config-dir]/C/config.json`. - -TODO(marun) Enable configuration of X-Chain and P-Chain. - -### Network env - -A shell script that sets the `TMPNET_NETWORK_DIR` env var to the -path of the network is stored at `[network-dir]/network.env`. Sourcing -this file (i.e. `source network.env`) in a shell will configure ginkgo -e2e and the `tmpnetctl` cli to target the network path specified in -the env var. - -### Node configuration - -The data dir for a node is set by default to -`[network-path]/[node-id]`. A node can be configured to use a -non-default path by explicitly setting the `--data-dir` -flag. - -#### Flags - -All flags used to configure a node are written to -`[network-path]/[node-id]/config.json` so that a node can be -configured with only a single argument: -`--config-file=/path/to/config.json`. This simplifies node launch and -ensures all parameters used to launch a node can be modified by -editing the config file. - -#### Process details - -The process details of a node are written by avalanchego to -`[base-data-dir]/process.json`. The file contains the PID of the node -process, the URI of the node's API, and the address other nodes can -use to bootstrap themselves (aka staking address). diff --git a/tests/fixture/tmpnet/local/config.go b/tests/fixture/tmpnet/local/config.go deleted file mode 100644 index 70ef9a443185..000000000000 --- a/tests/fixture/tmpnet/local/config.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package local - -import ( - "time" - - "github.com/ava-labs/avalanchego/config" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" -) - -const ( - // Constants defining the names of shell variables whose value can - // configure local network orchestration. - AvalancheGoPathEnvName = "AVALANCHEGO_PATH" - NetworkDirEnvName = "TMPNET_NETWORK_DIR" - RootDirEnvName = "TMPNET_ROOT_DIR" - - DefaultNetworkStartTimeout = 2 * time.Minute - DefaultNodeInitTimeout = 10 * time.Second - DefaultNodeStopTimeout = 5 * time.Second -) - -// A set of flags appropriate for local testing. -func LocalFlags() tmpnet.FlagsMap { - // Supply only non-default configuration to ensure that default values will be used. - return tmpnet.FlagsMap{ - config.NetworkPeerListGossipFreqKey: "250ms", - config.NetworkMaxReconnectDelayKey: "1s", - config.PublicIPKey: "127.0.0.1", - config.HTTPHostKey: "127.0.0.1", - config.StakingHostKey: "127.0.0.1", - config.HealthCheckFreqKey: "2s", - config.AdminAPIEnabledKey: true, - config.IpcAPIEnabledKey: true, - config.IndexEnabledKey: true, - config.LogDisplayLevelKey: "INFO", - config.LogLevelKey: "DEBUG", - config.MinStakeDurationKey: tmpnet.DefaultMinStakeDuration.String(), - } -} - -// C-Chain config for local testing. -func LocalCChainConfig() tmpnet.FlagsMap { - // Supply only non-default configuration to ensure that default - // values will be used. Available C-Chain configuration options are - // defined in the `github.com/ava-labs/coreth/evm` package. - return tmpnet.FlagsMap{ - "log-level": "trace", - } -} diff --git a/tests/fixture/tmpnet/local/network.go b/tests/fixture/tmpnet/local/network.go deleted file mode 100644 index 70411d4afcde..000000000000 --- a/tests/fixture/tmpnet/local/network.go +++ /dev/null @@ -1,715 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package local - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "io/fs" - "os" - "path/filepath" - "strconv" - "time" - - "github.com/ava-labs/avalanchego/config" - "github.com/ava-labs/avalanchego/genesis" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/utils/perms" - "github.com/ava-labs/avalanchego/utils/set" -) - -const ( - // This interval was chosen to avoid spamming node APIs during - // startup, as smaller intervals (e.g. 50ms) seemed to noticeably - // increase the time for a network's nodes to be seen as healthy. - networkHealthCheckInterval = 200 * time.Millisecond - - defaultEphemeralDirName = "ephemeral" -) - -var ( - errInvalidNodeCount = errors.New("failed to populate local network config: non-zero node count is only valid for a network without nodes") - errInvalidKeyCount = errors.New("failed to populate local network config: non-zero key count is only valid for a network without keys") - errLocalNetworkDirNotSet = errors.New("local network directory not set - has Create() been called?") - errInvalidNetworkDir = errors.New("failed to write local network: invalid network directory") - errMissingBootstrapNodes = errors.New("failed to add node due to missing bootstrap nodes") -) - -// Default root dir for storing networks and their configuration. -func GetDefaultRootDir() (string, error) { - homeDir, err := os.UserHomeDir() - if err != nil { - return "", err - } - return filepath.Join(homeDir, ".tmpnet", "networks"), nil -} - -// Find the next available network ID by attempting to create a -// directory numbered from 1000 until creation succeeds. Returns the -// network id and the full path of the created directory. -func FindNextNetworkID(rootDir string) (uint32, string, error) { - var ( - networkID uint32 = 1000 - dirPath string - ) - for { - _, reserved := constants.NetworkIDToNetworkName[networkID] - if reserved { - networkID++ - continue - } - - dirPath = filepath.Join(rootDir, strconv.FormatUint(uint64(networkID), 10)) - err := os.Mkdir(dirPath, perms.ReadWriteExecute) - if err == nil { - return networkID, dirPath, nil - } - - if !errors.Is(err, fs.ErrExist) { - return 0, "", fmt.Errorf("failed to create network directory: %w", err) - } - - // Directory already exists, keep iterating - networkID++ - } -} - -// Defines the configuration required for a local network (i.e. one composed of local processes). -type LocalNetwork struct { - tmpnet.NetworkConfig - LocalConfig - - // Nodes with local configuration - Nodes []*LocalNode - - // Path where network configuration will be stored - Dir string -} - -// Returns the configuration of the network in backend-agnostic form. -func (ln *LocalNetwork) GetConfig() tmpnet.NetworkConfig { - return ln.NetworkConfig -} - -// Returns the nodes of the network in backend-agnostic form. -func (ln *LocalNetwork) GetNodes() []tmpnet.Node { - nodes := make([]tmpnet.Node, 0, len(ln.Nodes)) - for _, node := range ln.Nodes { - nodes = append(nodes, node) - } - return nodes -} - -// Adds a backend-agnostic ephemeral node to the network -func (ln *LocalNetwork) AddEphemeralNode(w io.Writer, flags tmpnet.FlagsMap) (tmpnet.Node, error) { - if flags == nil { - flags = tmpnet.FlagsMap{} - } - return ln.AddLocalNode(w, &LocalNode{ - NodeConfig: tmpnet.NodeConfig{ - Flags: flags, - }, - }, true /* isEphemeral */) -} - -// Starts a new network stored under the provided root dir. Required -// configuration will be defaulted if not provided. -func StartNetwork( - ctx context.Context, - w io.Writer, - rootDir string, - network *LocalNetwork, - nodeCount int, - keyCount int, -) (*LocalNetwork, error) { - if _, err := fmt.Fprintf(w, "Preparing configuration for new local network with %s\n", network.ExecPath); err != nil { - return nil, err - } - - if len(rootDir) == 0 { - // Use the default root dir - var err error - rootDir, err = GetDefaultRootDir() - if err != nil { - return nil, err - } - } - - // Ensure creation of the root dir - if err := os.MkdirAll(rootDir, perms.ReadWriteExecute); err != nil { - return nil, fmt.Errorf("failed to create root network dir: %w", err) - } - - // Determine the network path and ID - var ( - networkDir string - networkID uint32 - ) - if network.Genesis != nil && network.Genesis.NetworkID > 0 { - // Use the network ID defined in the provided genesis - networkID = network.Genesis.NetworkID - } - if networkID > 0 { - // Use a directory with a random suffix - var err error - networkDir, err = os.MkdirTemp(rootDir, fmt.Sprintf("%d.", network.Genesis.NetworkID)) - if err != nil { - return nil, fmt.Errorf("failed to create network dir: %w", err) - } - } else { - // Find the next available network ID based on the contents of the root dir - var err error - networkID, networkDir, err = FindNextNetworkID(rootDir) - if err != nil { - return nil, err - } - } - - // Setting the network dir before populating config ensures the - // nodes know where to write their configuration. - network.Dir = networkDir - - if err := network.PopulateLocalNetworkConfig(networkID, nodeCount, keyCount); err != nil { - return nil, err - } - - if err := network.WriteAll(); err != nil { - return nil, err - } - if _, err := fmt.Fprintf(w, "Starting network %d @ %s\n", network.Genesis.NetworkID, network.Dir); err != nil { - return nil, err - } - if err := network.Start(w); err != nil { - return nil, err - } - if _, err := fmt.Fprintf(w, "Waiting for all nodes to report healthy...\n\n"); err != nil { - return nil, err - } - if err := network.WaitForHealthy(ctx, w); err != nil { - return nil, err - } - if _, err := fmt.Fprintf(w, "\nStarted network %d @ %s\n", network.Genesis.NetworkID, network.Dir); err != nil { - return nil, err - } - return network, nil -} - -// Read a network from the provided directory. -func ReadNetwork(dir string) (*LocalNetwork, error) { - network := &LocalNetwork{Dir: dir} - if err := network.ReadAll(); err != nil { - return nil, fmt.Errorf("failed to read local network: %w", err) - } - return network, nil -} - -// Stop the nodes of the network configured in the provided directory. -func StopNetwork(dir string) error { - network, err := ReadNetwork(dir) - if err != nil { - return err - } - return network.Stop() -} - -// Ensure the network has the configuration it needs to start. -func (ln *LocalNetwork) PopulateLocalNetworkConfig(networkID uint32, nodeCount int, keyCount int) error { - if len(ln.Nodes) > 0 && nodeCount > 0 { - return errInvalidNodeCount - } - if len(ln.FundedKeys) > 0 && keyCount > 0 { - return errInvalidKeyCount - } - - if nodeCount > 0 { - // Add the specified number of nodes - nodes := make([]*LocalNode, 0, nodeCount) - for i := 0; i < nodeCount; i++ { - nodes = append(nodes, NewLocalNode("")) - } - ln.Nodes = nodes - } - - // Ensure each node has keys and an associated node ID. This - // ensures the availability of validator node IDs for genesis - // generation. - for _, node := range ln.Nodes { - if err := node.EnsureKeys(); err != nil { - return err - } - } - - // Assume all initial nodes are validator ids - validatorIDs := make([]ids.NodeID, 0, len(ln.Nodes)) - for _, node := range ln.Nodes { - validatorIDs = append(validatorIDs, node.NodeID) - } - - if keyCount > 0 { - // Ensure there are keys for genesis generation to fund - keys := make([]*secp256k1.PrivateKey, 0, keyCount) - for i := 0; i < keyCount; i++ { - key, err := secp256k1.NewPrivateKey() - if err != nil { - return fmt.Errorf("failed to generate private key: %w", err) - } - keys = append(keys, key) - } - ln.FundedKeys = keys - } - - if err := ln.EnsureGenesis(networkID, validatorIDs); err != nil { - return err - } - - if ln.CChainConfig == nil { - ln.CChainConfig = LocalCChainConfig() - } - - // Default flags need to be set in advance of node config - // population to ensure correct node configuration. - if ln.DefaultFlags == nil { - ln.DefaultFlags = LocalFlags() - } - - for _, node := range ln.Nodes { - // Ensure the node is configured for use with the network and - // knows where to write its configuration. - if err := ln.PopulateNodeConfig(node, ln.Dir); err != nil { - return err - } - } - - return nil -} - -// Ensure the provided node has the configuration it needs to start. If the data dir is -// not set, it will be defaulted to [nodeParentDir]/[node ID]. Requires that the -// network has valid genesis data. -func (ln *LocalNetwork) PopulateNodeConfig(node *LocalNode, nodeParentDir string) error { - flags := node.Flags - - // Set values common to all nodes - flags.SetDefaults(ln.DefaultFlags) - flags.SetDefaults(tmpnet.FlagsMap{ - config.GenesisFileKey: ln.GetGenesisPath(), - config.ChainConfigDirKey: ln.GetChainConfigDir(), - }) - - // Convert the network id to a string to ensure consistency in JSON round-tripping. - flags[config.NetworkNameKey] = strconv.FormatUint(uint64(ln.Genesis.NetworkID), 10) - - // Ensure keys are added if necessary - if err := node.EnsureKeys(); err != nil { - return err - } - - // Ensure the node's data dir is configured - dataDir := node.GetDataDir() - if len(dataDir) == 0 { - // NodeID will have been set by EnsureKeys - dataDir = filepath.Join(nodeParentDir, node.NodeID.String()) - flags[config.DataDirKey] = dataDir - } - - return nil -} - -// Starts a network for the first time -func (ln *LocalNetwork) Start(w io.Writer) error { - if len(ln.Dir) == 0 { - return errLocalNetworkDirNotSet - } - - // Ensure configuration on disk is current - if err := ln.WriteAll(); err != nil { - return err - } - - // Accumulate bootstrap nodes such that each subsequently started - // node bootstraps from the nodes previously started. - // - // e.g. - // 1st node: no bootstrap nodes - // 2nd node: 1st node - // 3rd node: 1st and 2nd nodes - // ... - // - bootstrapIDs := make([]string, 0, len(ln.Nodes)) - bootstrapIPs := make([]string, 0, len(ln.Nodes)) - - // Configure networking and start each node - for _, node := range ln.Nodes { - // Update network configuration - node.SetNetworkingConfigDefaults(0, 0, bootstrapIDs, bootstrapIPs) - - // Write configuration to disk in preparation for node start - if err := node.WriteConfig(); err != nil { - return err - } - - // Start waits for the process context to be written which - // indicates that the node will be accepting connections on - // its staking port. The network will start faster with this - // synchronization due to the avoidance of exponential backoff - // if a node tries to connect to a beacon that is not ready. - if err := node.Start(w, ln.ExecPath); err != nil { - return err - } - - // Collect bootstrap nodes for subsequently started nodes to use - bootstrapIDs = append(bootstrapIDs, node.NodeID.String()) - bootstrapIPs = append(bootstrapIPs, node.StakingAddress) - } - - return nil -} - -// Wait until all nodes in the network are healthy. -func (ln *LocalNetwork) WaitForHealthy(ctx context.Context, w io.Writer) error { - ticker := time.NewTicker(networkHealthCheckInterval) - defer ticker.Stop() - - healthyNodes := set.NewSet[ids.NodeID](len(ln.Nodes)) - for healthyNodes.Len() < len(ln.Nodes) { - for _, node := range ln.Nodes { - if healthyNodes.Contains(node.NodeID) { - continue - } - - healthy, err := node.IsHealthy(ctx) - if err != nil && !errors.Is(err, tmpnet.ErrNotRunning) { - return err - } - if !healthy { - continue - } - - healthyNodes.Add(node.NodeID) - if _, err := fmt.Fprintf(w, "%s is healthy @ %s\n", node.NodeID, node.URI); err != nil { - return err - } - } - - select { - case <-ctx.Done(): - return fmt.Errorf("failed to see all nodes healthy before timeout: %w", ctx.Err()) - case <-ticker.C: - } - } - return nil -} - -// Retrieve API URIs for all running primary validator nodes. URIs for -// ephemeral nodes are not returned. -func (ln *LocalNetwork) GetURIs() []tmpnet.NodeURI { - uris := make([]tmpnet.NodeURI, 0, len(ln.Nodes)) - for _, node := range ln.Nodes { - // Only append URIs that are not empty. A node may have an - // empty URI if it was not running at the time - // node.ReadProcessContext() was called. - if len(node.URI) > 0 { - uris = append(uris, tmpnet.NodeURI{ - NodeID: node.NodeID, - URI: node.URI, - }) - } - } - return uris -} - -// Stop all nodes in the network. -func (ln *LocalNetwork) Stop() error { - var errs []error - // Assume the nodes are loaded and the pids are current - for _, node := range ln.Nodes { - if err := node.Stop(); err != nil { - errs = append(errs, fmt.Errorf("failed to stop node %s: %w", node.NodeID, err)) - } - } - if len(errs) > 0 { - return fmt.Errorf("failed to stop network:\n%w", errors.Join(errs...)) - } - return nil -} - -func (ln *LocalNetwork) GetGenesisPath() string { - return filepath.Join(ln.Dir, "genesis.json") -} - -func (ln *LocalNetwork) ReadGenesis() error { - bytes, err := os.ReadFile(ln.GetGenesisPath()) - if err != nil { - return fmt.Errorf("failed to read genesis: %w", err) - } - genesis := genesis.UnparsedConfig{} - if err := json.Unmarshal(bytes, &genesis); err != nil { - return fmt.Errorf("failed to unmarshal genesis: %w", err) - } - ln.Genesis = &genesis - return nil -} - -func (ln *LocalNetwork) WriteGenesis() error { - bytes, err := tmpnet.DefaultJSONMarshal(ln.Genesis) - if err != nil { - return fmt.Errorf("failed to marshal genesis: %w", err) - } - if err := os.WriteFile(ln.GetGenesisPath(), bytes, perms.ReadWrite); err != nil { - return fmt.Errorf("failed to write genesis: %w", err) - } - return nil -} - -func (ln *LocalNetwork) GetChainConfigDir() string { - return filepath.Join(ln.Dir, "chains") -} - -func (ln *LocalNetwork) GetCChainConfigPath() string { - return filepath.Join(ln.GetChainConfigDir(), "C", "config.json") -} - -func (ln *LocalNetwork) ReadCChainConfig() error { - chainConfig, err := tmpnet.ReadFlagsMap(ln.GetCChainConfigPath(), "C-Chain config") - if err != nil { - return err - } - ln.CChainConfig = *chainConfig - return nil -} - -func (ln *LocalNetwork) WriteCChainConfig() error { - path := ln.GetCChainConfigPath() - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, perms.ReadWriteExecute); err != nil { - return fmt.Errorf("failed to create C-Chain config dir: %w", err) - } - return ln.CChainConfig.Write(path, "C-Chain config") -} - -// Used to marshal/unmarshal persistent local network defaults. -type localDefaults struct { - Flags tmpnet.FlagsMap - ExecPath string - FundedKeys []*secp256k1.PrivateKey -} - -func (ln *LocalNetwork) GetDefaultsPath() string { - return filepath.Join(ln.Dir, "defaults.json") -} - -func (ln *LocalNetwork) ReadDefaults() error { - bytes, err := os.ReadFile(ln.GetDefaultsPath()) - if err != nil { - return fmt.Errorf("failed to read defaults: %w", err) - } - defaults := localDefaults{} - if err := json.Unmarshal(bytes, &defaults); err != nil { - return fmt.Errorf("failed to unmarshal defaults: %w", err) - } - ln.DefaultFlags = defaults.Flags - ln.ExecPath = defaults.ExecPath - ln.FundedKeys = defaults.FundedKeys - return nil -} - -func (ln *LocalNetwork) WriteDefaults() error { - defaults := localDefaults{ - Flags: ln.DefaultFlags, - ExecPath: ln.ExecPath, - FundedKeys: ln.FundedKeys, - } - bytes, err := tmpnet.DefaultJSONMarshal(defaults) - if err != nil { - return fmt.Errorf("failed to marshal defaults: %w", err) - } - if err := os.WriteFile(ln.GetDefaultsPath(), bytes, perms.ReadWrite); err != nil { - return fmt.Errorf("failed to write defaults: %w", err) - } - return nil -} - -func (ln *LocalNetwork) EnvFilePath() string { - return filepath.Join(ln.Dir, "network.env") -} - -func (ln *LocalNetwork) EnvFileContents() string { - return fmt.Sprintf("export %s=%s", NetworkDirEnvName, ln.Dir) -} - -// Write an env file that sets the network dir env when sourced. -func (ln *LocalNetwork) WriteEnvFile() error { - if err := os.WriteFile(ln.EnvFilePath(), []byte(ln.EnvFileContents()), perms.ReadWrite); err != nil { - return fmt.Errorf("failed to write local network env file: %w", err) - } - return nil -} - -func (ln *LocalNetwork) WriteNodes() error { - for _, node := range ln.Nodes { - if err := node.WriteConfig(); err != nil { - return err - } - } - return nil -} - -// Write network configuration to disk. -func (ln *LocalNetwork) WriteAll() error { - if len(ln.Dir) == 0 { - return errInvalidNetworkDir - } - if err := ln.WriteGenesis(); err != nil { - return err - } - if err := ln.WriteCChainConfig(); err != nil { - return err - } - if err := ln.WriteDefaults(); err != nil { - return err - } - if err := ln.WriteEnvFile(); err != nil { - return err - } - return ln.WriteNodes() -} - -// Read network configuration from disk. -func (ln *LocalNetwork) ReadConfig() error { - if err := ln.ReadGenesis(); err != nil { - return err - } - if err := ln.ReadCChainConfig(); err != nil { - return err - } - return ln.ReadDefaults() -} - -// Read node configuration and process context from disk. -func (ln *LocalNetwork) ReadNodes() error { - nodes := []*LocalNode{} - - // Node configuration / process context is stored in child directories - entries, err := os.ReadDir(ln.Dir) - if err != nil { - return fmt.Errorf("failed to read network path: %w", err) - } - for _, entry := range entries { - if !entry.IsDir() { - continue - } - - nodeDir := filepath.Join(ln.Dir, entry.Name()) - node, err := ReadNode(nodeDir) - if errors.Is(err, os.ErrNotExist) { - // If no config file exists, assume this is not the path of a local node - continue - } else if err != nil { - return err - } - - nodes = append(nodes, node) - } - - ln.Nodes = nodes - - return nil -} - -// Read network and node configuration from disk. -func (ln *LocalNetwork) ReadAll() error { - if err := ln.ReadConfig(); err != nil { - return err - } - return ln.ReadNodes() -} - -func (ln *LocalNetwork) AddLocalNode(w io.Writer, node *LocalNode, isEphemeral bool) (*LocalNode, error) { - // Assume network configuration has been written to disk and is current in memory - - if node == nil { - // Set an empty data dir so that PopulateNodeConfig will know - // to set the default of `[network dir]/[node id]`. - node = NewLocalNode("") - } - - // Default to a data dir of [network-dir]/[node-ID] - nodeParentDir := ln.Dir - if isEphemeral { - // For an ephemeral node, default to a data dir of [network-dir]/[ephemeral-dir]/[node-ID] - // to provide a clear separation between nodes that are expected to expose stable API - // endpoints and those that will live for only a short time (e.g. a node started by a test - // and stopped on teardown). - // - // The data for an ephemeral node is still stored in the file tree rooted at the network - // dir to ensure that recursively archiving the network dir in CI will collect all node - // data used for a test run. - nodeParentDir = filepath.Join(ln.Dir, defaultEphemeralDirName) - } - - if err := ln.PopulateNodeConfig(node, nodeParentDir); err != nil { - return nil, err - } - - bootstrapIPs, bootstrapIDs, err := ln.GetBootstrapIPsAndIDs() - if err != nil { - return nil, err - } - - var ( - // Use dynamic port allocation. - httpPort uint16 = 0 - stakingPort uint16 = 0 - ) - node.SetNetworkingConfigDefaults(httpPort, stakingPort, bootstrapIDs, bootstrapIPs) - - if err := node.WriteConfig(); err != nil { - return nil, err - } - - err = node.Start(w, ln.ExecPath) - if err != nil { - // Attempt to stop an unhealthy node to provide some assurance to the caller - // that an error condition will not result in a lingering process. - stopErr := node.Stop() - if stopErr != nil { - err = errors.Join(err, stopErr) - } - return nil, err - } - - return node, nil -} - -func (ln *LocalNetwork) GetBootstrapIPsAndIDs() ([]string, []string, error) { - // Collect staking addresses of running nodes for use in bootstrapping a node - if err := ln.ReadNodes(); err != nil { - return nil, nil, fmt.Errorf("failed to read local network nodes: %w", err) - } - var ( - bootstrapIPs = make([]string, 0, len(ln.Nodes)) - bootstrapIDs = make([]string, 0, len(ln.Nodes)) - ) - for _, node := range ln.Nodes { - if len(node.StakingAddress) == 0 { - // Node is not running - continue - } - - bootstrapIPs = append(bootstrapIPs, node.StakingAddress) - bootstrapIDs = append(bootstrapIDs, node.NodeID.String()) - } - - if len(bootstrapIDs) == 0 { - return nil, nil, errMissingBootstrapNodes - } - - return bootstrapIPs, bootstrapIDs, nil -} diff --git a/tests/fixture/tmpnet/local/node.go b/tests/fixture/tmpnet/local/node.go deleted file mode 100644 index 908d3fd5f474..000000000000 --- a/tests/fixture/tmpnet/local/node.go +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package local - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "io/fs" - "net" - "os" - "os/exec" - "path/filepath" - "syscall" - "time" - - "github.com/spf13/cast" - - "github.com/ava-labs/avalanchego/api/health" - "github.com/ava-labs/avalanchego/config" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/node" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - "github.com/ava-labs/avalanchego/utils/perms" -) - -var errNodeAlreadyRunning = errors.New("failed to start local node: node is already running") - -// Defines local-specific node configuration. Supports setting default -// and node-specific values. -// -// TODO(marun) Support persisting this configuration per-node when -// node restart is implemented. Currently it can be supplied for node -// start but won't survive restart. -type LocalConfig struct { - // Path to avalanchego binary - ExecPath string -} - -// Stores the configuration and process details of a node in a local network. -type LocalNode struct { - tmpnet.NodeConfig - LocalConfig - node.NodeProcessContext - - // Configuration is intended to be stored at the path identified in NodeConfig.Flags[config.DataDirKey] -} - -func NewLocalNode(dataDir string) *LocalNode { - return &LocalNode{ - NodeConfig: tmpnet.NodeConfig{ - Flags: tmpnet.FlagsMap{ - config.DataDirKey: dataDir, - }, - }, - } -} - -// Attempt to read configuration and process details for a local node -// from the specified directory. -func ReadNode(dataDir string) (*LocalNode, error) { - node := NewLocalNode(dataDir) - if _, err := os.Stat(node.GetConfigPath()); err != nil { - return nil, fmt.Errorf("failed to read local node config file: %w", err) - } - return node, node.ReadAll() -} - -// Retrieve the ID of the node. The returned value may be nil if the -// node configuration has not yet been populated or read. -func (n *LocalNode) GetID() ids.NodeID { - return n.NodeConfig.NodeID -} - -// Retrieve backend-agnostic node configuration. -func (n *LocalNode) GetConfig() tmpnet.NodeConfig { - return n.NodeConfig -} - -// Retrieve backend-agnostic process details. -func (n *LocalNode) GetProcessContext() node.NodeProcessContext { - return n.NodeProcessContext -} - -func (n *LocalNode) GetDataDir() string { - return cast.ToString(n.Flags[config.DataDirKey]) -} - -func (n *LocalNode) GetConfigPath() string { - return filepath.Join(n.GetDataDir(), "config.json") -} - -func (n *LocalNode) ReadConfig() error { - bytes, err := os.ReadFile(n.GetConfigPath()) - if err != nil { - return fmt.Errorf("failed to read local node config: %w", err) - } - flags := tmpnet.FlagsMap{} - if err := json.Unmarshal(bytes, &flags); err != nil { - return fmt.Errorf("failed to unmarshal local node config: %w", err) - } - config := tmpnet.NodeConfig{Flags: flags} - if err := config.EnsureNodeID(); err != nil { - return err - } - n.NodeConfig = config - return nil -} - -func (n *LocalNode) WriteConfig() error { - if err := os.MkdirAll(n.GetDataDir(), perms.ReadWriteExecute); err != nil { - return fmt.Errorf("failed to create node dir: %w", err) - } - - bytes, err := tmpnet.DefaultJSONMarshal(n.Flags) - if err != nil { - return fmt.Errorf("failed to marshal local node config: %w", err) - } - - if err := os.WriteFile(n.GetConfigPath(), bytes, perms.ReadWrite); err != nil { - return fmt.Errorf("failed to write local node config: %w", err) - } - return nil -} - -func (n *LocalNode) GetProcessContextPath() string { - return filepath.Join(n.GetDataDir(), config.DefaultProcessContextFilename) -} - -func (n *LocalNode) ReadProcessContext() error { - path := n.GetProcessContextPath() - if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { - // The absence of the process context file indicates the node is not running - n.NodeProcessContext = node.NodeProcessContext{} - return nil - } - - bytes, err := os.ReadFile(path) - if err != nil { - return fmt.Errorf("failed to read local node process context: %w", err) - } - processContext := node.NodeProcessContext{} - if err := json.Unmarshal(bytes, &processContext); err != nil { - return fmt.Errorf("failed to unmarshal local node process context: %w", err) - } - n.NodeProcessContext = processContext - return nil -} - -func (n *LocalNode) ReadAll() error { - if err := n.ReadConfig(); err != nil { - return err - } - return n.ReadProcessContext() -} - -func (n *LocalNode) Start(w io.Writer, defaultExecPath string) error { - // Avoid attempting to start an already running node. - proc, err := n.GetProcess() - if err != nil { - return fmt.Errorf("failed to start local node: %w", err) - } - if proc != nil { - return errNodeAlreadyRunning - } - - // Ensure a stale process context file is removed so that the - // creation of a new file can indicate node start. - if err := os.Remove(n.GetProcessContextPath()); err != nil && !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("failed to remove stale process context file: %w", err) - } - - execPath := n.ExecPath - if len(execPath) == 0 { - execPath = defaultExecPath - } - - cmd := exec.Command(execPath, "--config-file", n.GetConfigPath()) - if err := cmd.Start(); err != nil { - return err - } - - // Determine appropriate level of node description detail - nodeDescription := fmt.Sprintf("node %q", n.NodeID) - isEphemeralNode := filepath.Base(filepath.Dir(n.GetDataDir())) == defaultEphemeralDirName - if isEphemeralNode { - nodeDescription = "ephemeral " + nodeDescription - } - nonDefaultNodeDir := filepath.Base(n.GetDataDir()) != n.NodeID.String() - if nonDefaultNodeDir { - // Only include the data dir if its base is not the default (the node ID) - nodeDescription = fmt.Sprintf("%s with path: %s", nodeDescription, n.GetDataDir()) - } - - go func() { - if err := cmd.Wait(); err != nil { - if err.Error() != "signal: killed" { - _, _ = fmt.Fprintf(w, "%s finished with error: %v\n", nodeDescription, err) - } - } - _, _ = fmt.Fprintf(w, "%s exited\n", nodeDescription) - }() - - // A node writes a process context file on start. If the file is not - // found in a reasonable amount of time, the node is unlikely to have - // started successfully. - if err := n.WaitForProcessContext(context.Background()); err != nil { - return fmt.Errorf("failed to start local node: %w", err) - } - - _, err = fmt.Fprintf(w, "Started %s\n", nodeDescription) - return err -} - -// Retrieve the node process if it is running. As part of determining -// process liveness, the node's process context will be refreshed if -// live or cleared if not running. -func (n *LocalNode) GetProcess() (*os.Process, error) { - // Read the process context to ensure freshness. The node may have - // stopped or been restarted since last read. - if err := n.ReadProcessContext(); err != nil { - return nil, fmt.Errorf("failed to read process context: %w", err) - } - - if n.PID == 0 { - // Process is not running - return nil, nil - } - - proc, err := os.FindProcess(n.PID) - if err != nil { - return nil, fmt.Errorf("failed to find process: %w", err) - } - - // Sending 0 will not actually send a signal but will perform - // error checking. - err = proc.Signal(syscall.Signal(0)) - if err == nil { - // Process is running - return proc, nil - } - if errors.Is(err, os.ErrProcessDone) { - // Process is not running - return nil, nil - } - return nil, fmt.Errorf("failed to determine process status: %w", err) -} - -// Signals the node process to stop and waits for the node process to -// stop running. -func (n *LocalNode) Stop() error { - proc, err := n.GetProcess() - if err != nil { - return fmt.Errorf("failed to retrieve process to stop: %w", err) - } - if proc == nil { - // Already stopped - return nil - } - if err := proc.Signal(syscall.SIGTERM); err != nil { - return fmt.Errorf("failed to send SIGTERM to pid %d: %w", n.PID, err) - } - - // Wait for the node process to stop - ticker := time.NewTicker(tmpnet.DefaultNodeTickerInterval) - defer ticker.Stop() - ctx, cancel := context.WithTimeout(context.Background(), DefaultNodeStopTimeout) - defer cancel() - for { - proc, err := n.GetProcess() - if err != nil { - return fmt.Errorf("failed to retrieve process: %w", err) - } - if proc == nil { - return nil - } - - select { - case <-ctx.Done(): - return fmt.Errorf("failed to see node process stop %q before timeout: %w", n.NodeID, ctx.Err()) - case <-ticker.C: - } - } -} - -func (n *LocalNode) IsHealthy(ctx context.Context) (bool, error) { - // Check that the node process is running as a precondition for - // checking health. GetProcess will also ensure that the node's - // API URI is current. - proc, err := n.GetProcess() - if err != nil { - return false, fmt.Errorf("failed to determine process status: %w", err) - } - if proc == nil { - return false, tmpnet.ErrNotRunning - } - - // Check that the node is reporting healthy - health, err := health.NewClient(n.URI).Health(ctx, nil) - if err == nil { - return health.Healthy, nil - } - - switch t := err.(type) { - case *net.OpError: - if t.Op == "read" { - // Connection refused - potentially recoverable - return false, nil - } - case syscall.Errno: - if t == syscall.ECONNREFUSED { - // Connection refused - potentially recoverable - return false, nil - } - } - // Assume all other errors are not recoverable - return false, fmt.Errorf("failed to query node health: %w", err) -} - -func (n *LocalNode) WaitForProcessContext(ctx context.Context) error { - ticker := time.NewTicker(tmpnet.DefaultNodeTickerInterval) - defer ticker.Stop() - - ctx, cancel := context.WithTimeout(ctx, DefaultNodeInitTimeout) - defer cancel() - for len(n.URI) == 0 { - err := n.ReadProcessContext() - if err != nil { - return fmt.Errorf("failed to read process context for node %q: %w", n.NodeID, err) - } - - select { - case <-ctx.Done(): - return fmt.Errorf("failed to load process context for node %q before timeout: %w", n.NodeID, ctx.Err()) - case <-ticker.C: - } - } - return nil -} diff --git a/tests/fixture/tmpnet/local/node_test.go b/tests/fixture/tmpnet/local/node_test.go deleted file mode 100644 index 64cd77928a4d..000000000000 --- a/tests/fixture/tmpnet/local/node_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package local - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNodeSerialization(t *testing.T) { - require := require.New(t) - - tmpDir := t.TempDir() - - node := NewLocalNode(tmpDir) - require.NoError(node.EnsureKeys()) - require.NoError(node.WriteConfig()) - - loadedNode, err := ReadNode(tmpDir) - require.NoError(err) - require.Equal(node, loadedNode) -} diff --git a/tests/fixture/tmpnet/network.go b/tests/fixture/tmpnet/network.go new file mode 100644 index 000000000000..01829da70da5 --- /dev/null +++ b/tests/fixture/tmpnet/network.go @@ -0,0 +1,694 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/ava-labs/avalanchego/config" + "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/perms" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/platformvm" +) + +// The Network type is defined in this file (orchestration) and +// network_config.go (reading/writing configuration). + +const ( + // Constants defining the names of shell variables whose value can + // configure network orchestration. + NetworkDirEnvName = "TMPNET_NETWORK_DIR" + RootDirEnvName = "TMPNET_ROOT_DIR" + + // This interval was chosen to avoid spamming node APIs during + // startup, as smaller intervals (e.g. 50ms) seemed to noticeably + // increase the time for a network's nodes to be seen as healthy. + networkHealthCheckInterval = 200 * time.Millisecond + + // eth address: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC + HardHatKeyStr = "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027" +) + +// HardhatKey is a legacy used for hardhat testing in subnet-evm +// TODO(marun) Remove when no longer needed. +var HardhatKey *secp256k1.PrivateKey + +func init() { + hardhatKeyBytes, err := hex.DecodeString(HardHatKeyStr) + if err != nil { + panic(err) + } + HardhatKey, err = secp256k1.ToPrivateKey(hardhatKeyBytes) + if err != nil { + panic(err) + } +} + +// Collects the configuration for running a temporary avalanchego network +type Network struct { + // Path where network configuration and data is stored + Dir string + + // Configuration common across nodes + Genesis *genesis.UnparsedConfig + ChainConfigs map[string]FlagsMap + + // Default configuration to use when creating new nodes + DefaultFlags FlagsMap + DefaultRuntimeConfig NodeRuntimeConfig + + // Keys pre-funded in the genesis on both the X-Chain and the C-Chain + PreFundedKeys []*secp256k1.PrivateKey + + // Nodes that constitute the network + Nodes []*Node + + // Subnets that have been enabled on the network + Subnets []*Subnet +} + +// Ensure a real and absolute network dir so that node +// configuration that embeds the network path will continue to +// work regardless of symlink and working directory changes. +func toCanonicalDir(dir string) (string, error) { + absDir, err := filepath.Abs(dir) + if err != nil { + return "", err + } + return filepath.EvalSymlinks(absDir) +} + +func StartNewNetwork( + ctx context.Context, + w io.Writer, + network *Network, + rootNetworkDir string, + avalancheGoExecPath string, + pluginDir string, + nodeCount int, +) error { + if err := network.EnsureDefaultConfig(w, avalancheGoExecPath, pluginDir, nodeCount); err != nil { + return err + } + if err := network.Create(rootNetworkDir); err != nil { + return err + } + return network.Start(ctx, w) +} + +// Stops the nodes of the network configured in the provided directory. +func StopNetwork(ctx context.Context, dir string) error { + network, err := ReadNetwork(dir) + if err != nil { + return err + } + return network.Stop(ctx) +} + +// Restarts the nodes of the network configured in the provided directory. +func RestartNetwork(ctx context.Context, w io.Writer, dir string) error { + network, err := ReadNetwork(dir) + if err != nil { + return err + } + return network.Restart(ctx, w) +} + +// Reads a network from the provided directory. +func ReadNetwork(dir string) (*Network, error) { + canonicalDir, err := toCanonicalDir(dir) + if err != nil { + return nil, err + } + network := &Network{ + Dir: canonicalDir, + } + if err := network.Read(); err != nil { + return nil, fmt.Errorf("failed to read network: %w", err) + } + return network, nil +} + +// Initializes a new network with default configuration. +func (n *Network) EnsureDefaultConfig(w io.Writer, avalancheGoPath string, pluginDir string, nodeCount int) error { + if _, err := fmt.Fprintf(w, "Preparing configuration for new network with %s\n", avalancheGoPath); err != nil { + return err + } + + // Ensure default flags + if n.DefaultFlags == nil { + n.DefaultFlags = FlagsMap{} + } + n.DefaultFlags.SetDefaults(DefaultFlags()) + + // Only configure the plugin dir with a non-empty value to ensure + // the use of the default value (`[datadir]/plugins`) when + // no plugin dir is configured. + if len(pluginDir) > 0 { + if _, ok := n.DefaultFlags[config.PluginDirKey]; !ok { + n.DefaultFlags[config.PluginDirKey] = pluginDir + } + } + + // Ensure pre-funded keys + if len(n.PreFundedKeys) == 0 { + keys, err := NewPrivateKeys(DefaultPreFundedKeyCount) + if err != nil { + return err + } + n.PreFundedKeys = keys + } + + // Ensure primary chains are configured + if n.ChainConfigs == nil { + n.ChainConfigs = map[string]FlagsMap{} + } + defaultChainConfigs := DefaultChainConfigs() + for alias, chainConfig := range defaultChainConfigs { + if _, ok := n.ChainConfigs[alias]; !ok { + n.ChainConfigs[alias] = FlagsMap{} + } + n.ChainConfigs[alias].SetDefaults(chainConfig) + } + + // Ensure runtime is configured + if len(n.DefaultRuntimeConfig.AvalancheGoPath) == 0 { + n.DefaultRuntimeConfig.AvalancheGoPath = avalancheGoPath + } + + // Ensure nodes are created + if len(n.Nodes) == 0 { + n.Nodes = NewNodes(nodeCount) + } + + // Ensure nodes are configured + for i := range n.Nodes { + if err := n.EnsureNodeConfig(n.Nodes[i]); err != nil { + return err + } + } + + return nil +} + +// Creates the network on disk, choosing its network id and generating its genesis in the process. +func (n *Network) Create(rootDir string) error { + if len(rootDir) == 0 { + // Use the default root dir + var err error + rootDir, err = getDefaultRootDir() + if err != nil { + return err + } + } + + // Ensure creation of the root dir + if err := os.MkdirAll(rootDir, perms.ReadWriteExecute); err != nil { + return fmt.Errorf("failed to create root network dir: %w", err) + } + + // Determine the network path and ID + var ( + networkDir string + networkID uint32 + ) + if n.Genesis != nil && n.Genesis.NetworkID > 0 { + // Use the network ID defined in the provided genesis + networkID = n.Genesis.NetworkID + } + if networkID > 0 { + // Use a directory with a random suffix + var err error + networkDir, err = os.MkdirTemp(rootDir, fmt.Sprintf("%d.", n.Genesis.NetworkID)) + if err != nil { + return fmt.Errorf("failed to create network dir: %w", err) + } + } else { + // Find the next available network ID based on the contents of the root dir + var err error + networkID, networkDir, err = findNextNetworkID(rootDir) + if err != nil { + return err + } + } + canonicalDir, err := toCanonicalDir(networkDir) + if err != nil { + return err + } + n.Dir = canonicalDir + + pluginDir, err := n.DefaultFlags.GetStringVal(config.PluginDirKey) + if err != nil { + return err + } + if len(pluginDir) > 0 { + // Ensure the existence of the plugin directory or nodes won't be able to start. + if err := os.MkdirAll(pluginDir, perms.ReadWriteExecute); err != nil { + return fmt.Errorf("failed to create plugin dir: %w", err) + } + } + + if n.Genesis == nil { + // Pre-fund known legacy keys to support ad-hoc testing. Usage of a legacy key will + // require knowing the key beforehand rather than retrieving it from the set of pre-funded + // keys exposed by a network. Since allocation will not be exclusive, a test using a + // legacy key is unlikely to be a good candidate for parallel execution. + keysToFund := []*secp256k1.PrivateKey{ + genesis.VMRQKey, + genesis.EWOQKey, + HardhatKey, + } + keysToFund = append(keysToFund, n.PreFundedKeys...) + + genesis, err := NewTestGenesis(networkID, n.Nodes, keysToFund) + if err != nil { + return err + } + n.Genesis = genesis + } + + for _, node := range n.Nodes { + // Ensure the node is configured for use with the network and + // knows where to write its configuration. + if err := n.EnsureNodeConfig(node); err != nil { + return nil + } + } + + // Ensure configuration on disk is current + return n.Write() +} + +// Starts all nodes in the network +func (n *Network) Start(ctx context.Context, w io.Writer) error { + if _, err := fmt.Fprintf(w, "Starting network %d @ %s\n", n.Genesis.NetworkID, n.Dir); err != nil { + return err + } + + // Configure the networking for each node and start + for _, node := range n.Nodes { + if err := n.StartNode(ctx, w, node); err != nil { + return err + } + } + + if _, err := fmt.Fprintf(w, "Waiting for all nodes to report healthy...\n\n"); err != nil { + return err + } + if err := n.WaitForHealthy(ctx, w); err != nil { + return err + } + if _, err := fmt.Fprintf(w, "\nStarted network %d @ %s\n", n.Genesis.NetworkID, n.Dir); err != nil { + return err + } + + return nil +} + +func (n *Network) AddEphemeralNode(ctx context.Context, w io.Writer, flags FlagsMap) (*Node, error) { + node := NewNode("") + node.Flags = flags + node.IsEphemeral = true + if err := n.StartNode(ctx, w, node); err != nil { + return nil, err + } + return node, nil +} + +// Starts the provided node after configuring it for the network. +func (n *Network) StartNode(ctx context.Context, w io.Writer, node *Node) error { + if err := n.EnsureNodeConfig(node); err != nil { + return err + } + + bootstrapIPs, bootstrapIDs, err := n.getBootstrapIPsAndIDs(node) + if err != nil { + return err + } + node.SetNetworkingConfig(bootstrapIDs, bootstrapIPs) + + if err := node.Write(); err != nil { + return err + } + + if err := node.Start(w); err != nil { + // Attempt to stop an unhealthy node to provide some assurance to the caller + // that an error condition will not result in a lingering process. + err = errors.Join(err, node.Stop(ctx)) + return err + } + + return nil +} + +// Waits until all nodes in the network are healthy. +func (n *Network) WaitForHealthy(ctx context.Context, w io.Writer) error { + ticker := time.NewTicker(networkHealthCheckInterval) + defer ticker.Stop() + + healthyNodes := set.NewSet[ids.NodeID](len(n.Nodes)) + for healthyNodes.Len() < len(n.Nodes) { + for _, node := range n.Nodes { + if healthyNodes.Contains(node.NodeID) { + continue + } + + healthy, err := node.IsHealthy(ctx) + if err != nil && !errors.Is(err, ErrNotRunning) { + return err + } + if !healthy { + continue + } + + healthyNodes.Add(node.NodeID) + if _, err := fmt.Fprintf(w, "%s is healthy @ %s\n", node.NodeID, node.URI); err != nil { + return err + } + } + + select { + case <-ctx.Done(): + return fmt.Errorf("failed to see all nodes healthy before timeout: %w", ctx.Err()) + case <-ticker.C: + } + } + return nil +} + +// Stops all nodes in the network. +func (n *Network) Stop(ctx context.Context) error { + // Target all nodes, including the ephemeral ones + nodes, err := ReadNodes(n.Dir, true /* includeEphemeral */) + if err != nil { + return err + } + + var errs []error + + // Initiate stop on all nodes + for _, node := range nodes { + if err := node.InitiateStop(ctx); err != nil { + errs = append(errs, fmt.Errorf("failed to stop node %s: %w", node.NodeID, err)) + } + } + + // Wait for stop to complete on all nodes + for _, node := range nodes { + if err := node.WaitForStopped(ctx); err != nil { + errs = append(errs, fmt.Errorf("failed to wait for node %s to stop: %w", node.NodeID, err)) + } + } + + if len(errs) > 0 { + return fmt.Errorf("failed to stop network:\n%w", errors.Join(errs...)) + } + return nil +} + +// Restarts all non-ephemeral nodes in the network. +func (n *Network) Restart(ctx context.Context, w io.Writer) error { + if _, err := fmt.Fprintf(w, " restarting network\n"); err != nil { + return err + } + for _, node := range n.Nodes { + if err := node.Stop(ctx); err != nil { + return fmt.Errorf("failed to stop node %s: %w", node.NodeID, err) + } + if err := n.StartNode(ctx, w, node); err != nil { + return fmt.Errorf("failed to start node %s: %w", node.NodeID, err) + } + if _, err := fmt.Fprintf(w, " waiting for node %s to report healthy\n", node.NodeID); err != nil { + return err + } + if err := WaitForHealthy(ctx, node); err != nil { + return err + } + } + return nil +} + +// Ensures the provided node has the configuration it needs to start. If the data dir is not +// set, it will be defaulted to [nodeParentDir]/[node ID]. For a not-yet-created network, +// no action will be taken. +// TODO(marun) Reword or refactor to account for the differing behavior pre- vs post-start +func (n *Network) EnsureNodeConfig(node *Node) error { + flags := node.Flags + + // Set the network name if available + if n.Genesis != nil && n.Genesis.NetworkID > 0 { + // Convert the network id to a string to ensure consistency in JSON round-tripping. + flags[config.NetworkNameKey] = strconv.FormatUint(uint64(n.Genesis.NetworkID), 10) + } + + if err := node.EnsureKeys(); err != nil { + return err + } + + flags.SetDefaults(n.DefaultFlags) + + // Set fields including the network path + if len(n.Dir) > 0 { + node.Flags.SetDefaults(FlagsMap{ + config.GenesisFileKey: n.getGenesisPath(), + config.ChainConfigDirKey: n.getChainConfigDir(), + }) + + // Ensure the node's data dir is configured + dataDir := node.getDataDir() + if len(dataDir) == 0 { + // NodeID will have been set by EnsureKeys + dataDir = filepath.Join(n.Dir, node.NodeID.String()) + flags[config.DataDirKey] = dataDir + } + } + + // Ensure the node runtime is configured + if node.RuntimeConfig == nil { + node.RuntimeConfig = &NodeRuntimeConfig{ + AvalancheGoPath: n.DefaultRuntimeConfig.AvalancheGoPath, + } + } + + // Ensure available subnets are tracked + subnetIDs := make([]string, 0, len(n.Subnets)) + for _, subnet := range n.Subnets { + if subnet.SubnetID == ids.Empty { + continue + } + subnetIDs = append(subnetIDs, subnet.SubnetID.String()) + } + flags[config.TrackSubnetsKey] = strings.Join(subnetIDs, ",") + + return nil +} + +func (n *Network) GetSubnet(name string) *Subnet { + for _, subnet := range n.Subnets { + if subnet.Name == name { + return subnet + } + } + return nil +} + +// Ensure that each subnet on the network is created and that it is validated by all non-ephemeral nodes. +func (n *Network) CreateSubnets(ctx context.Context, w io.Writer) error { + createdSubnets := make([]*Subnet, 0, len(n.Subnets)) + for _, subnet := range n.Subnets { + if _, err := fmt.Fprintf(w, "Creating subnet %q\n", subnet.Name); err != nil { + return err + } + if subnet.SubnetID != ids.Empty { + // The subnet already exists + continue + } + + if subnet.OwningKey == nil { + // Allocate a pre-funded key and remove it from the network so it won't be used for + // other purposes + if len(n.PreFundedKeys) == 0 { + return fmt.Errorf("no pre-funded keys available to create subnet %q", subnet.Name) + } + subnet.OwningKey = n.PreFundedKeys[len(n.PreFundedKeys)-1] + n.PreFundedKeys = n.PreFundedKeys[:len(n.PreFundedKeys)-1] + } + + // Create the subnet on the network + if err := subnet.Create(ctx, n.Nodes[0].URI); err != nil { + return err + } + + if _, err := fmt.Fprintf(w, " created subnet %q as %q\n", subnet.Name, subnet.SubnetID); err != nil { + return err + } + + // Persist the subnet configuration + if err := subnet.Write(n.getSubnetDir(), n.getChainConfigDir()); err != nil { + return err + } + + if _, err := fmt.Fprintf(w, " wrote configuration for subnet %q\n", subnet.Name); err != nil { + return err + } + + createdSubnets = append(createdSubnets, subnet) + } + + if len(createdSubnets) == 0 { + return nil + } + + // Ensure the in-memory subnet state + n.Subnets = append(n.Subnets, createdSubnets...) + + // Ensure the pre-funded key changes are persisted to disk + if err := n.Write(); err != nil { + return err + } + + // Reconfigure nodes for the new subnets and their chains + if _, err := fmt.Fprintf(w, "Configured nodes to track new subnet(s). Restart is required.\n"); err != nil { + return err + } + for _, node := range n.Nodes { + if err := n.EnsureNodeConfig(node); err != nil { + return err + } + } + + // Restart nodes to allow new configuration to take effect + if err := n.Restart(ctx, w); err != nil { + return err + } + + // Add each node as a subnet validator + for _, subnet := range createdSubnets { + if _, err := fmt.Fprintf(w, "Adding validators for subnet %q\n", subnet.Name); err != nil { + return err + } + if err := subnet.AddValidators(ctx, w, n.Nodes); err != nil { + return err + } + } + + // Wait for nodes to become subnet validators + pChainClient := platformvm.NewClient(n.Nodes[0].URI) + for _, subnet := range createdSubnets { + if err := waitForActiveValidators(ctx, w, pChainClient, subnet); err != nil { + return err + } + + // It should now be safe to create chains for the subnet + if err := subnet.CreateChains(ctx, w, n.Nodes[0].URI); err != nil { + return err + } + + // Persist the chain configuration + if err := subnet.Write(n.getSubnetDir(), n.getChainConfigDir()); err != nil { + return err + } + if _, err := fmt.Fprintf(w, " wrote chain configuration for subnet %q\n", subnet.Name); err != nil { + return err + } + } + + return nil +} + +func (n *Network) GetURIForNodeID(nodeID ids.NodeID) (string, error) { + for _, node := range n.Nodes { + if node.NodeID == nodeID { + return node.URI, nil + } + } + return "", fmt.Errorf("%s is not known to the network", nodeID) +} + +func (n *Network) GetNodeURIs() []NodeURI { + return GetNodeURIs(n.Nodes) +} + +// Retrieves bootstrap IPs and IDs for all nodes except the skipped one (this supports +// collecting the bootstrap details for restarting a node). +func (n *Network) getBootstrapIPsAndIDs(skippedNode *Node) ([]string, []string, error) { + // Collect staking addresses of non-ephemeral nodes for use in bootstrapping a node + nodes, err := ReadNodes(n.Dir, false /* includeEphemeral */) + if err != nil { + return nil, nil, fmt.Errorf("failed to read network's nodes: %w", err) + } + var ( + bootstrapIPs = make([]string, 0, len(nodes)) + bootstrapIDs = make([]string, 0, len(nodes)) + ) + for _, node := range nodes { + if skippedNode != nil && node.NodeID == skippedNode.NodeID { + continue + } + + if len(node.StakingAddress) == 0 { + // Node is not running + continue + } + + bootstrapIPs = append(bootstrapIPs, node.StakingAddress) + bootstrapIDs = append(bootstrapIDs, node.NodeID.String()) + } + + return bootstrapIPs, bootstrapIDs, nil +} + +// Retrieves the default root dir for storing networks and their +// configuration. +func getDefaultRootDir() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(homeDir, ".tmpnet", "networks"), nil +} + +// Finds the next available network ID by attempting to create a +// directory numbered from 1000 until creation succeeds. Returns the +// network id and the full path of the created directory. +func findNextNetworkID(rootDir string) (uint32, string, error) { + var ( + networkID uint32 = 1000 + dirPath string + ) + for { + _, reserved := constants.NetworkIDToNetworkName[networkID] + if reserved { + networkID++ + continue + } + + dirPath = filepath.Join(rootDir, strconv.FormatUint(uint64(networkID), 10)) + err := os.Mkdir(dirPath, perms.ReadWriteExecute) + if err == nil { + return networkID, dirPath, nil + } + + if !errors.Is(err, fs.ErrExist) { + return 0, "", fmt.Errorf("failed to create network directory: %w", err) + } + + // Directory already exists, keep iterating + networkID++ + } +} diff --git a/tests/fixture/tmpnet/network_config.go b/tests/fixture/tmpnet/network_config.go new file mode 100644 index 000000000000..c5bb603ed3e5 --- /dev/null +++ b/tests/fixture/tmpnet/network_config.go @@ -0,0 +1,236 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/perms" +) + +// The Network type is defined in this file (reading/writing configuration) and network.go +// (orchestration). + +var errMissingNetworkDir = errors.New("failed to write network: missing network directory") + +// Read network and node configuration from disk. +func (n *Network) Read() error { + if err := n.readNetwork(); err != nil { + return err + } + if err := n.readNodes(); err != nil { + return err + } + return n.readSubnets() +} + +// Write network configuration to disk. +func (n *Network) Write() error { + if len(n.Dir) == 0 { + return errMissingNetworkDir + } + if err := n.writeGenesis(); err != nil { + return err + } + if err := n.writeChainConfigs(); err != nil { + return err + } + if err := n.writeNetworkConfig(); err != nil { + return err + } + if err := n.writeEnvFile(); err != nil { + return err + } + return n.writeNodes() +} + +// Read network configuration from disk. +func (n *Network) readNetwork() error { + if err := n.readGenesis(); err != nil { + return err + } + if err := n.readChainConfigs(); err != nil { + return err + } + return n.readConfig() +} + +// Read the non-ephemeral nodes associated with the network from disk. +func (n *Network) readNodes() error { + nodes, err := ReadNodes(n.Dir, false /* includeEphemeral */) + if err != nil { + return err + } + n.Nodes = nodes + return nil +} + +func (n *Network) writeNodes() error { + for _, node := range n.Nodes { + if err := node.Write(); err != nil { + return err + } + } + return nil +} + +func (n *Network) getGenesisPath() string { + return filepath.Join(n.Dir, "genesis.json") +} + +func (n *Network) readGenesis() error { + bytes, err := os.ReadFile(n.getGenesisPath()) + if err != nil { + return fmt.Errorf("failed to read genesis: %w", err) + } + genesis := genesis.UnparsedConfig{} + if err := json.Unmarshal(bytes, &genesis); err != nil { + return fmt.Errorf("failed to unmarshal genesis: %w", err) + } + n.Genesis = &genesis + return nil +} + +func (n *Network) writeGenesis() error { + bytes, err := DefaultJSONMarshal(n.Genesis) + if err != nil { + return fmt.Errorf("failed to marshal genesis: %w", err) + } + if err := os.WriteFile(n.getGenesisPath(), bytes, perms.ReadWrite); err != nil { + return fmt.Errorf("failed to write genesis: %w", err) + } + return nil +} + +func (n *Network) getChainConfigDir() string { + return filepath.Join(n.Dir, "chains") +} + +func (n *Network) readChainConfigs() error { + baseChainConfigDir := n.getChainConfigDir() + entries, err := os.ReadDir(baseChainConfigDir) + if err != nil { + return fmt.Errorf("failed to read chain config dir: %w", err) + } + + // Clear the map of data that may end up stale (e.g. if a given + // chain is in the map but no longer exists on disk) + n.ChainConfigs = map[string]FlagsMap{} + + for _, entry := range entries { + if !entry.IsDir() { + // Chain config files are expected to be nested under a + // directory with the name of the chain alias. + continue + } + chainAlias := entry.Name() + configPath := filepath.Join(baseChainConfigDir, chainAlias, defaultConfigFilename) + if _, err := os.Stat(configPath); os.IsNotExist(err) { + // No config file present + continue + } + chainConfig, err := ReadFlagsMap(configPath, fmt.Sprintf("%s chain config", chainAlias)) + if err != nil { + return err + } + n.ChainConfigs[chainAlias] = *chainConfig + } + + return nil +} + +func (n *Network) writeChainConfigs() error { + baseChainConfigDir := n.getChainConfigDir() + + for chainAlias, chainConfig := range n.ChainConfigs { + // Create the directory + chainConfigDir := filepath.Join(baseChainConfigDir, chainAlias) + if err := os.MkdirAll(chainConfigDir, perms.ReadWriteExecute); err != nil { + return fmt.Errorf("failed to create %s chain config dir: %w", chainAlias, err) + } + + // Write the file + path := filepath.Join(chainConfigDir, defaultConfigFilename) + if err := chainConfig.Write(path, fmt.Sprintf("%s chain config", chainAlias)); err != nil { + return err + } + } + + // TODO(marun) Ensure the removal of chain aliases that aren't present in the map + + return nil +} + +func (n *Network) getConfigPath() string { + return filepath.Join(n.Dir, defaultConfigFilename) +} + +func (n *Network) readConfig() error { + bytes, err := os.ReadFile(n.getConfigPath()) + if err != nil { + return fmt.Errorf("failed to read network config: %w", err) + } + if err := json.Unmarshal(bytes, n); err != nil { + return fmt.Errorf("failed to unmarshal network config: %w", err) + } + return nil +} + +// The subset of network fields to store in the network config file. +type serializedNetworkConfig struct { + DefaultFlags FlagsMap + DefaultRuntimeConfig NodeRuntimeConfig + PreFundedKeys []*secp256k1.PrivateKey +} + +func (n *Network) writeNetworkConfig() error { + config := &serializedNetworkConfig{ + DefaultFlags: n.DefaultFlags, + DefaultRuntimeConfig: n.DefaultRuntimeConfig, + PreFundedKeys: n.PreFundedKeys, + } + bytes, err := DefaultJSONMarshal(config) + if err != nil { + return fmt.Errorf("failed to marshal network config: %w", err) + } + if err := os.WriteFile(n.getConfigPath(), bytes, perms.ReadWrite); err != nil { + return fmt.Errorf("failed to write network config: %w", err) + } + return nil +} + +func (n *Network) EnvFilePath() string { + return filepath.Join(n.Dir, "network.env") +} + +func (n *Network) EnvFileContents() string { + return fmt.Sprintf("export %s=%s", NetworkDirEnvName, n.Dir) +} + +// Write an env file that sets the network dir env when sourced. +func (n *Network) writeEnvFile() error { + if err := os.WriteFile(n.EnvFilePath(), []byte(n.EnvFileContents()), perms.ReadWrite); err != nil { + return fmt.Errorf("failed to write network env file: %w", err) + } + return nil +} + +func (n *Network) getSubnetDir() string { + return filepath.Join(n.Dir, defaultSubnetDirName) +} + +func (n *Network) readSubnets() error { + subnets, err := readSubnets(n.getSubnetDir()) + if err != nil { + return err + } + n.Subnets = subnets + return nil +} diff --git a/tests/fixture/tmpnet/local/network_test.go b/tests/fixture/tmpnet/network_test.go similarity index 50% rename from tests/fixture/tmpnet/local/network_test.go rename to tests/fixture/tmpnet/network_test.go index 8f7e66d37d5b..c04c497c2485 100644 --- a/tests/fixture/tmpnet/local/network_test.go +++ b/tests/fixture/tmpnet/network_test.go @@ -1,9 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package local +package tmpnet import ( + "bytes" "testing" "github.com/stretchr/testify/require" @@ -14,13 +15,15 @@ func TestNetworkSerialization(t *testing.T) { tmpDir := t.TempDir() - network := &LocalNetwork{Dir: tmpDir} - require.NoError(network.PopulateLocalNetworkConfig(1337, 1, 1)) - require.NoError(network.WriteAll()) + network := &Network{} + require.NoError(network.EnsureDefaultConfig(&bytes.Buffer{}, "/path/to/avalanche/go", "", 1)) + require.NoError(network.Create(tmpDir)) + // Ensure node runtime is initialized + require.NoError(network.readNodes()) - loadedNetwork, err := ReadNetwork(tmpDir) + loadedNetwork, err := ReadNetwork(network.Dir) require.NoError(err) - for _, key := range loadedNetwork.FundedKeys { + for _, key := range loadedNetwork.PreFundedKeys { // Address() enables comparison with the original network by // ensuring full population of a key's in-memory representation. _ = key.Address() diff --git a/tests/fixture/tmpnet/node.go b/tests/fixture/tmpnet/node.go new file mode 100644 index 000000000000..59025b649112 --- /dev/null +++ b/tests/fixture/tmpnet/node.go @@ -0,0 +1,337 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/spf13/cast" + + "github.com/ava-labs/avalanchego/config" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/staking" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/vms/platformvm/signer" +) + +// The Node type is defined in this file (node.go - orchestration) and +// node_config.go (reading/writing configuration). + +const ( + defaultNodeTickerInterval = 50 * time.Millisecond +) + +var ( + errMissingTLSKeyForNodeID = fmt.Errorf("failed to ensure node ID: missing value for %q", config.StakingTLSKeyContentKey) + errMissingCertForNodeID = fmt.Errorf("failed to ensure node ID: missing value for %q", config.StakingCertContentKey) + errInvalidKeypair = fmt.Errorf("%q and %q must be provided together or not at all", config.StakingTLSKeyContentKey, config.StakingCertContentKey) +) + +// NodeRuntime defines the methods required to support running a node. +type NodeRuntime interface { + readState() error + Start(w io.Writer) error + InitiateStop() error + WaitForStopped(ctx context.Context) error + IsHealthy(ctx context.Context) (bool, error) +} + +// Configuration required to configure a node runtime. +type NodeRuntimeConfig struct { + AvalancheGoPath string +} + +// Node supports configuring and running a node participating in a temporary network. +type Node struct { + // Set by EnsureNodeID which is also called when the node is read. + NodeID ids.NodeID + + // Flags that will be supplied to the node at startup + Flags FlagsMap + + // An ephemeral node is not expected to be a persistent member of the network and + // should therefore not be used as for bootstrapping purposes. + IsEphemeral bool + + // The configuration used to initialize the node runtime. + RuntimeConfig *NodeRuntimeConfig + + // Runtime state, intended to be set by NodeRuntime + URI string + StakingAddress string + + // Initialized on demand + runtime NodeRuntime +} + +// Initializes a new node with only the data dir set +func NewNode(dataDir string) *Node { + return &Node{ + Flags: FlagsMap{ + config.DataDirKey: dataDir, + }, + } +} + +// Initializes the specified number of nodes. +func NewNodes(count int) []*Node { + nodes := make([]*Node, count) + for i := range nodes { + nodes[i] = NewNode("") + } + return nodes +} + +// Reads a node's configuration from the specified directory. +func ReadNode(dataDir string) (*Node, error) { + node := NewNode(dataDir) + return node, node.Read() +} + +// Reads nodes from the specified network directory. +func ReadNodes(networkDir string, includeEphemeral bool) ([]*Node, error) { + nodes := []*Node{} + + // Node configuration is stored in child directories + entries, err := os.ReadDir(networkDir) + if err != nil { + return nil, fmt.Errorf("failed to read dir: %w", err) + } + for _, entry := range entries { + if !entry.IsDir() { + continue + } + + nodeDir := filepath.Join(networkDir, entry.Name()) + node, err := ReadNode(nodeDir) + if errors.Is(err, os.ErrNotExist) { + // If no config file exists, assume this is not the path of a node + continue + } else if err != nil { + return nil, err + } + + if !includeEphemeral && node.IsEphemeral { + continue + } + + nodes = append(nodes, node) + } + + return nodes, nil +} + +// Retrieves the runtime for the node. +func (n *Node) getRuntime() NodeRuntime { + if n.runtime == nil { + n.runtime = &NodeProcess{ + node: n, + } + } + return n.runtime +} + +// Runtime methods + +func (n *Node) IsHealthy(ctx context.Context) (bool, error) { + return n.getRuntime().IsHealthy(ctx) +} + +func (n *Node) Start(w io.Writer) error { + return n.getRuntime().Start(w) +} + +func (n *Node) InitiateStop(ctx context.Context) error { + if err := n.SaveMetricsSnapshot(ctx); err != nil { + return err + } + return n.getRuntime().InitiateStop() +} + +func (n *Node) WaitForStopped(ctx context.Context) error { + return n.getRuntime().WaitForStopped(ctx) +} + +func (n *Node) readState() error { + return n.getRuntime().readState() +} + +func (n *Node) getDataDir() string { + return cast.ToString(n.Flags[config.DataDirKey]) +} + +// Writes the current state of the metrics endpoint to disk +func (n *Node) SaveMetricsSnapshot(ctx context.Context) error { + if len(n.URI) == 0 { + // No URI to request metrics from + return nil + } + uri := n.URI + "/ext/metrics" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + return n.writeMetricsSnapshot(body) +} + +// Initiates node shutdown and waits for the node to stop. +func (n *Node) Stop(ctx context.Context) error { + if err := n.InitiateStop(ctx); err != nil { + return err + } + return n.WaitForStopped(ctx) +} + +// Sets networking configuration for the node. +// Convenience method for setting networking flags. +func (n *Node) SetNetworkingConfig(bootstrapIDs []string, bootstrapIPs []string) { + var ( + // Use dynamic port allocation. + httpPort uint16 = 0 + stakingPort uint16 = 0 + ) + n.Flags[config.HTTPPortKey] = httpPort + n.Flags[config.StakingPortKey] = stakingPort + n.Flags[config.BootstrapIDsKey] = strings.Join(bootstrapIDs, ",") + n.Flags[config.BootstrapIPsKey] = strings.Join(bootstrapIPs, ",") +} + +// Ensures staking and signing keys are generated if not already present and +// that the node ID (derived from the staking keypair) is set. +func (n *Node) EnsureKeys() error { + if err := n.EnsureBLSSigningKey(); err != nil { + return err + } + if err := n.EnsureStakingKeypair(); err != nil { + return err + } + return n.EnsureNodeID() +} + +// Ensures a BLS signing key is generated if not already present. +func (n *Node) EnsureBLSSigningKey() error { + // Attempt to retrieve an existing key + existingKey, err := n.Flags.GetStringVal(config.StakingSignerKeyContentKey) + if err != nil { + return err + } + if len(existingKey) > 0 { + // Nothing to do + return nil + } + + // Generate a new signing key + newKey, err := bls.NewSecretKey() + if err != nil { + return fmt.Errorf("failed to generate staking signer key: %w", err) + } + n.Flags[config.StakingSignerKeyContentKey] = base64.StdEncoding.EncodeToString(bls.SerializeSecretKey(newKey)) + return nil +} + +// Ensures a staking keypair is generated if not already present. +func (n *Node) EnsureStakingKeypair() error { + keyKey := config.StakingTLSKeyContentKey + certKey := config.StakingCertContentKey + + key, err := n.Flags.GetStringVal(keyKey) + if err != nil { + return err + } + + cert, err := n.Flags.GetStringVal(certKey) + if err != nil { + return err + } + + if len(key) == 0 && len(cert) == 0 { + // Generate new keypair + tlsCertBytes, tlsKeyBytes, err := staking.NewCertAndKeyBytes() + if err != nil { + return fmt.Errorf("failed to generate staking keypair: %w", err) + } + n.Flags[keyKey] = base64.StdEncoding.EncodeToString(tlsKeyBytes) + n.Flags[certKey] = base64.StdEncoding.EncodeToString(tlsCertBytes) + } else if len(key) == 0 || len(cert) == 0 { + // Only one of key and cert was provided + return errInvalidKeypair + } + + return nil +} + +// Derives the nodes proof-of-possession. Requires the node to have a +// BLS signing key. +func (n *Node) GetProofOfPossession() (*signer.ProofOfPossession, error) { + signingKey, err := n.Flags.GetStringVal(config.StakingSignerKeyContentKey) + if err != nil { + return nil, err + } + signingKeyBytes, err := base64.StdEncoding.DecodeString(signingKey) + if err != nil { + return nil, err + } + secretKey, err := bls.SecretKeyFromBytes(signingKeyBytes) + if err != nil { + return nil, err + } + return signer.NewProofOfPossession(secretKey), nil +} + +// Derives the node ID. Requires that a tls keypair is present. +func (n *Node) EnsureNodeID() error { + keyKey := config.StakingTLSKeyContentKey + certKey := config.StakingCertContentKey + + key, err := n.Flags.GetStringVal(keyKey) + if err != nil { + return err + } + if len(key) == 0 { + return errMissingTLSKeyForNodeID + } + keyBytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return fmt.Errorf("failed to ensure node ID: failed to base64 decode value for %q: %w", keyKey, err) + } + + cert, err := n.Flags.GetStringVal(certKey) + if err != nil { + return err + } + if len(cert) == 0 { + return errMissingCertForNodeID + } + certBytes, err := base64.StdEncoding.DecodeString(cert) + if err != nil { + return fmt.Errorf("failed to ensure node ID: failed to base64 decode value for %q: %w", certKey, err) + } + + tlsCert, err := staking.LoadTLSCertFromBytes(keyBytes, certBytes) + if err != nil { + return fmt.Errorf("failed to ensure node ID: failed to load tls cert: %w", err) + } + stakingCert := staking.CertificateFromX509(tlsCert.Leaf) + n.NodeID = ids.NodeIDFromCert(stakingCert) + + return nil +} diff --git a/tests/fixture/tmpnet/node_config.go b/tests/fixture/tmpnet/node_config.go new file mode 100644 index 000000000000..3ebbc01b6c32 --- /dev/null +++ b/tests/fixture/tmpnet/node_config.go @@ -0,0 +1,114 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/ava-labs/avalanchego/utils/perms" +) + +// The Node type is defined in this file node_config.go +// (reading/writing configuration) and node.go (orchestration). + +func (n *Node) getFlagsPath() string { + return filepath.Join(n.getDataDir(), "flags.json") +} + +func (n *Node) readFlags() error { + bytes, err := os.ReadFile(n.getFlagsPath()) + if err != nil { + return fmt.Errorf("failed to read node flags: %w", err) + } + flags := FlagsMap{} + if err := json.Unmarshal(bytes, &flags); err != nil { + return fmt.Errorf("failed to unmarshal node flags: %w", err) + } + n.Flags = flags + return n.EnsureNodeID() +} + +func (n *Node) writeFlags() error { + bytes, err := DefaultJSONMarshal(n.Flags) + if err != nil { + return fmt.Errorf("failed to marshal node flags: %w", err) + } + if err := os.WriteFile(n.getFlagsPath(), bytes, perms.ReadWrite); err != nil { + return fmt.Errorf("failed to write node flags: %w", err) + } + return nil +} + +func (n *Node) getConfigPath() string { + return filepath.Join(n.getDataDir(), defaultConfigFilename) +} + +func (n *Node) readConfig() error { + bytes, err := os.ReadFile(n.getConfigPath()) + if err != nil { + return fmt.Errorf("failed to read node config: %w", err) + } + if err := json.Unmarshal(bytes, n); err != nil { + return fmt.Errorf("failed to unmarshal node config: %w", err) + } + return nil +} + +type serializedNodeConfig struct { + IsEphemeral bool + RuntimeConfig *NodeRuntimeConfig +} + +func (n *Node) writeConfig() error { + config := serializedNodeConfig{ + IsEphemeral: n.IsEphemeral, + RuntimeConfig: n.RuntimeConfig, + } + bytes, err := DefaultJSONMarshal(config) + if err != nil { + return fmt.Errorf("failed to marshal node config: %w", err) + } + if err := os.WriteFile(n.getConfigPath(), bytes, perms.ReadWrite); err != nil { + return fmt.Errorf("failed to write node config: %w", err) + } + return nil +} + +func (n *Node) Read() error { + if err := n.readFlags(); err != nil { + return err + } + if err := n.readConfig(); err != nil { + return err + } + return n.readState() +} + +func (n *Node) Write() error { + if err := os.MkdirAll(n.getDataDir(), perms.ReadWriteExecute); err != nil { + return fmt.Errorf("failed to create node dir: %w", err) + } + + if err := n.writeFlags(); err != nil { + return nil + } + return n.writeConfig() +} + +func (n *Node) writeMetricsSnapshot(data []byte) error { + metricsDir := filepath.Join(n.getDataDir(), "metrics") + if err := os.MkdirAll(metricsDir, perms.ReadWriteExecute); err != nil { + return fmt.Errorf("failed to create metrics dir: %w", err) + } + // Create a compatible filesystem from the current timestamp + ts := time.Now().UTC().Format(time.RFC3339) + ts = strings.ReplaceAll(strings.ReplaceAll(ts, ":", ""), "-", "") + metricsPath := filepath.Join(metricsDir, ts) + return os.WriteFile(metricsPath, data, perms.ReadWrite) +} diff --git a/tests/fixture/tmpnet/node_process.go b/tests/fixture/tmpnet/node_process.go new file mode 100644 index 000000000000..c2e2e33139bf --- /dev/null +++ b/tests/fixture/tmpnet/node_process.go @@ -0,0 +1,258 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "net" + "os" + "os/exec" + "path/filepath" + "syscall" + "time" + + "github.com/ava-labs/avalanchego/api/health" + "github.com/ava-labs/avalanchego/config" + "github.com/ava-labs/avalanchego/node" +) + +const ( + AvalancheGoPathEnvName = "AVALANCHEGO_PATH" + + defaultNodeInitTimeout = 10 * time.Second +) + +var errNodeAlreadyRunning = errors.New("failed to start node: node is already running") + +func checkNodeHealth(ctx context.Context, uri string) (bool, error) { + // Check that the node is reporting healthy + health, err := health.NewClient(uri).Health(ctx, nil) + if err == nil { + return health.Healthy, nil + } + + switch t := err.(type) { + case *net.OpError: + if t.Op == "read" { + // Connection refused - potentially recoverable + return false, nil + } + case syscall.Errno: + if t == syscall.ECONNREFUSED { + // Connection refused - potentially recoverable + return false, nil + } + } + // Assume all other errors are not recoverable + return false, fmt.Errorf("failed to query node health: %w", err) +} + +// Defines local-specific node configuration. Supports setting default +// and node-specific values. +type NodeProcess struct { + node *Node + + // PID of the node process + pid int +} + +func (p *NodeProcess) setProcessContext(processContext node.NodeProcessContext) { + p.pid = processContext.PID + p.node.URI = processContext.URI + p.node.StakingAddress = processContext.StakingAddress +} + +func (p *NodeProcess) readState() error { + path := p.getProcessContextPath() + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { + // The absence of the process context file indicates the node is not running + p.setProcessContext(node.NodeProcessContext{}) + return nil + } + + bytes, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read node process context: %w", err) + } + processContext := node.NodeProcessContext{} + if err := json.Unmarshal(bytes, &processContext); err != nil { + return fmt.Errorf("failed to unmarshal node process context: %w", err) + } + p.setProcessContext(processContext) + return nil +} + +// Start waits for the process context to be written which +// indicates that the node will be accepting connections on +// its staking port. The network will start faster with this +// synchronization due to the avoidance of exponential backoff +// if a node tries to connect to a beacon that is not ready. +func (p *NodeProcess) Start(w io.Writer) error { + // Avoid attempting to start an already running node. + proc, err := p.getProcess() + if err != nil { + return fmt.Errorf("failed to retrieve existing process: %w", err) + } + if proc != nil { + return errNodeAlreadyRunning + } + + // Ensure a stale process context file is removed so that the + // creation of a new file can indicate node start. + if err := os.Remove(p.getProcessContextPath()); err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("failed to remove stale process context file: %w", err) + } + + cmd := exec.Command(p.node.RuntimeConfig.AvalancheGoPath, "--config-file", p.node.getFlagsPath()) // #nosec G204 + if err := cmd.Start(); err != nil { + return err + } + + // Determine appropriate level of node description detail + dataDir := p.node.getDataDir() + nodeDescription := fmt.Sprintf("node %q", p.node.NodeID) + if p.node.IsEphemeral { + nodeDescription = "ephemeral " + nodeDescription + } + nonDefaultNodeDir := filepath.Base(dataDir) != p.node.NodeID.String() + if nonDefaultNodeDir { + // Only include the data dir if its base is not the default (the node ID) + nodeDescription = fmt.Sprintf("%s with path: %s", nodeDescription, dataDir) + } + + go func() { + if err := cmd.Wait(); err != nil { + if err.Error() != "signal: killed" { + _, _ = fmt.Fprintf(w, "%s finished with error: %v\n", nodeDescription, err) + } + } + _, _ = fmt.Fprintf(w, "%s exited\n", nodeDescription) + }() + + // A node writes a process context file on start. If the file is not + // found in a reasonable amount of time, the node is unlikely to have + // started successfully. + if err := p.waitForProcessContext(context.Background()); err != nil { + return fmt.Errorf("failed to start local node: %w", err) + } + + _, err = fmt.Fprintf(w, "Started %s\n", nodeDescription) + return err +} + +// Signals the node process to stop. +func (p *NodeProcess) InitiateStop() error { + proc, err := p.getProcess() + if err != nil { + return fmt.Errorf("failed to retrieve process to stop: %w", err) + } + if proc == nil { + // Already stopped + return nil + } + if err := proc.Signal(syscall.SIGTERM); err != nil { + return fmt.Errorf("failed to send SIGTERM to pid %d: %w", p.pid, err) + } + return nil +} + +// Waits for the node process to stop. +func (p *NodeProcess) WaitForStopped(ctx context.Context) error { + ticker := time.NewTicker(defaultNodeTickerInterval) + defer ticker.Stop() + for { + proc, err := p.getProcess() + if err != nil { + return fmt.Errorf("failed to retrieve process: %w", err) + } + if proc == nil { + return nil + } + + select { + case <-ctx.Done(): + return fmt.Errorf("failed to see node process stop %q before timeout: %w", p.node.NodeID, ctx.Err()) + case <-ticker.C: + } + } +} + +func (p *NodeProcess) IsHealthy(ctx context.Context) (bool, error) { + // Check that the node process is running as a precondition for + // checking health. getProcess will also ensure that the node's + // API URI is current. + proc, err := p.getProcess() + if err != nil { + return false, fmt.Errorf("failed to determine process status: %w", err) + } + if proc == nil { + return false, ErrNotRunning + } + + return checkNodeHealth(ctx, p.node.URI) +} + +func (p *NodeProcess) getProcessContextPath() string { + return filepath.Join(p.node.getDataDir(), config.DefaultProcessContextFilename) +} + +func (p *NodeProcess) waitForProcessContext(ctx context.Context) error { + ticker := time.NewTicker(defaultNodeTickerInterval) + defer ticker.Stop() + + ctx, cancel := context.WithTimeout(ctx, defaultNodeInitTimeout) + defer cancel() + for len(p.node.URI) == 0 { + err := p.readState() + if err != nil { + return fmt.Errorf("failed to read process context for node %q: %w", p.node.NodeID, err) + } + + select { + case <-ctx.Done(): + return fmt.Errorf("failed to load process context for node %q before timeout: %w", p.node.NodeID, ctx.Err()) + case <-ticker.C: + } + } + return nil +} + +// Retrieve the node process if it is running. As part of determining +// process liveness, the node's process context will be refreshed if +// live or cleared if not running. +func (p *NodeProcess) getProcess() (*os.Process, error) { + // Read the process context to ensure freshness. The node may have + // stopped or been restarted since last read. + if err := p.readState(); err != nil { + return nil, fmt.Errorf("failed to read process context: %w", err) + } + + if p.pid == 0 { + // Process is not running + return nil, nil + } + + proc, err := os.FindProcess(p.pid) + if err != nil { + return nil, fmt.Errorf("failed to find process: %w", err) + } + + // Sending 0 will not actually send a signal but will perform + // error checking. + err = proc.Signal(syscall.Signal(0)) + if err == nil { + // Process is running + return proc, nil + } + if errors.Is(err, os.ErrProcessDone) { + // Process is not running + return nil, nil + } + return nil, fmt.Errorf("failed to determine process status: %w", err) +} diff --git a/tests/fixture/tmpnet/subnet.go b/tests/fixture/tmpnet/subnet.go new file mode 100644 index 000000000000..1cfa0dc12aa0 --- /dev/null +++ b/tests/fixture/tmpnet/subnet.go @@ -0,0 +1,333 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/perms" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/platformvm" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" +) + +const defaultSubnetDirName = "subnets" + +type Chain struct { + // Set statically + VMID ids.ID + Config string + Genesis []byte + + // Set at runtime + ChainID ids.ID + PreFundedKey *secp256k1.PrivateKey +} + +// Write the chain configuration to the specified directory. +func (c *Chain) WriteConfig(chainDir string) error { + if len(c.Config) == 0 { + return nil + } + + chainConfigDir := filepath.Join(chainDir, c.ChainID.String()) + if err := os.MkdirAll(chainConfigDir, perms.ReadWriteExecute); err != nil { + return fmt.Errorf("failed to create chain config dir: %w", err) + } + + path := filepath.Join(chainConfigDir, defaultConfigFilename) + if err := os.WriteFile(path, []byte(c.Config), perms.ReadWrite); err != nil { + return fmt.Errorf("failed to write chain config: %w", err) + } + + return nil +} + +type Subnet struct { + // A unique string that can be used to refer to the subnet across different temporary + // networks (since the SubnetID will be different every time the subnet is created) + Name string + + // The ID of the transaction that created the subnet + SubnetID ids.ID + + // The private key that owns the subnet + OwningKey *secp256k1.PrivateKey + + // IDs of the nodes responsible for validating the subnet + ValidatorIDs []ids.NodeID + + Chains []*Chain +} + +// Retrieves a wallet configured for use with the subnet +func (s *Subnet) GetWallet(ctx context.Context, uri string) (primary.Wallet, error) { + keychain := secp256k1fx.NewKeychain(s.OwningKey) + + // Only fetch the subnet transaction if a subnet ID is present. This won't be true when + // the wallet is first used to create the subnet. + txIDs := set.Set[ids.ID]{} + if s.SubnetID != ids.Empty { + txIDs.Add(s.SubnetID) + } + + return primary.MakeWallet(ctx, &primary.WalletConfig{ + URI: uri, + AVAXKeychain: keychain, + EthKeychain: keychain, + PChainTxsToFetch: txIDs, + }) +} + +// Issues the subnet creation transaction and retains the result. The URI of a node is +// required to issue the transaction. +func (s *Subnet) Create(ctx context.Context, uri string) error { + wallet, err := s.GetWallet(ctx, uri) + if err != nil { + return err + } + pWallet := wallet.P() + + subnetTx, err := pWallet.IssueCreateSubnetTx( + &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + s.OwningKey.Address(), + }, + }, + common.WithContext(ctx), + ) + if err != nil { + return fmt.Errorf("failed to create subnet %s: %w", s.Name, err) + } + s.SubnetID = subnetTx.ID() + + return nil +} + +func (s *Subnet) CreateChains(ctx context.Context, w io.Writer, uri string) error { + wallet, err := s.GetWallet(ctx, uri) + if err != nil { + return err + } + pWallet := wallet.P() + + if _, err := fmt.Fprintf(w, "Creating chains for subnet %q\n", s.Name); err != nil { + return err + } + + for _, chain := range s.Chains { + createChainTx, err := pWallet.IssueCreateChainTx( + s.SubnetID, + chain.Genesis, + chain.VMID, + nil, + "", + common.WithContext(ctx), + ) + if err != nil { + return fmt.Errorf("failed to create chain: %w", err) + } + chain.ChainID = createChainTx.ID() + + if _, err := fmt.Fprintf(w, " created chain %q for VM %q on subnet %q\n", chain.ChainID, chain.VMID, s.Name); err != nil { + return err + } + } + return nil +} + +// Add validators to the subnet +func (s *Subnet) AddValidators(ctx context.Context, w io.Writer, nodes []*Node) error { + apiURI := nodes[0].URI + + wallet, err := s.GetWallet(ctx, apiURI) + if err != nil { + return err + } + pWallet := wallet.P() + + // Collect the end times for current validators to reuse for subnet validators + pvmClient := platformvm.NewClient(apiURI) + validators, err := pvmClient.GetCurrentValidators(ctx, constants.PrimaryNetworkID, nil) + if err != nil { + return err + } + endTimes := make(map[ids.NodeID]uint64) + for _, validator := range validators { + endTimes[validator.NodeID] = validator.EndTime + } + + startTime := time.Now().Add(DefaultValidatorStartTimeDiff) + for _, node := range nodes { + endTime, ok := endTimes[node.NodeID] + if !ok { + return fmt.Errorf("failed to find end time for %s", node.NodeID) + } + + _, err := pWallet.IssueAddSubnetValidatorTx( + &txs.SubnetValidator{ + Validator: txs.Validator{ + NodeID: node.NodeID, + Start: uint64(startTime.Unix()), + End: endTime, + Wght: units.Schmeckle, + }, + Subnet: s.SubnetID, + }, + common.WithContext(ctx), + ) + if err != nil { + return err + } + + if _, err := fmt.Fprintf(w, " added %s as validator for subnet `%s`\n", node.NodeID, s.Name); err != nil { + return err + } + + s.ValidatorIDs = append(s.ValidatorIDs, node.NodeID) + } + + return nil +} + +// Write the subnet configuration to disk +func (s *Subnet) Write(subnetDir string, chainDir string) error { + if err := os.MkdirAll(subnetDir, perms.ReadWriteExecute); err != nil { + return fmt.Errorf("failed to create subnet dir: %w", err) + } + path := filepath.Join(subnetDir, s.Name+".json") + + // Since subnets are expected to be serialized for the first time + // without their chains having been created (i.e. chains will have + // empty IDs), use the absence of chain IDs as a prompt for a + // subnet name uniquness check. + if len(s.Chains) > 0 && s.Chains[0].ChainID == ids.Empty { + _, err := os.Stat(path) + if err != nil && !os.IsNotExist(err) { + return err + } + if err == nil { + return fmt.Errorf("a subnet with name %s already exists", s.Name) + } + } + + bytes, err := DefaultJSONMarshal(s) + if err != nil { + return fmt.Errorf("failed to marshal subnet %s: %w", s.Name, err) + } + if err := os.WriteFile(path, bytes, perms.ReadWrite); err != nil { + return fmt.Errorf("failed to write subnet %s: %w", s.Name, err) + } + + for _, chain := range s.Chains { + if err := chain.WriteConfig(chainDir); err != nil { + return err + } + } + + return nil +} + +func waitForActiveValidators( + ctx context.Context, + w io.Writer, + pChainClient platformvm.Client, + subnet *Subnet, +) error { + ticker := time.NewTicker(DefaultPollingInterval) + defer ticker.Stop() + + if _, err := fmt.Fprintf(w, "Waiting for validators of subnet %q to become active\n", subnet.Name); err != nil { + return err + } + + if _, err := fmt.Fprintf(w, " "); err != nil { + return err + } + + for { + if _, err := fmt.Fprintf(w, "."); err != nil { + return err + } + validators, err := pChainClient.GetCurrentValidators(ctx, subnet.SubnetID, nil) + if err != nil { + return err + } + validatorSet := set.NewSet[ids.NodeID](len(validators)) + for _, validator := range validators { + validatorSet.Add(validator.NodeID) + } + allActive := true + for _, validatorID := range subnet.ValidatorIDs { + if !validatorSet.Contains(validatorID) { + allActive = false + } + } + if allActive { + if _, err := fmt.Fprintf(w, "\n saw the expected active validators of subnet %q\n", subnet.Name); err != nil { + return err + } + return nil + } + + select { + case <-ctx.Done(): + return fmt.Errorf("failed to see the expected active validators of subnet %q before timeout", subnet.Name) + case <-ticker.C: + } + } +} + +// Reads subnets from [network dir]/subnets/[subnet name].json +func readSubnets(subnetDir string) ([]*Subnet, error) { + if _, err := os.Stat(subnetDir); os.IsNotExist(err) { + return nil, nil + } else if err != nil { + return nil, err + } + + entries, err := os.ReadDir(subnetDir) + if err != nil { + return nil, fmt.Errorf("failed to read subnet dir: %w", err) + } + + subnets := []*Subnet{} + for _, entry := range entries { + if entry.IsDir() { + // Looking only for files + continue + } + if filepath.Ext(entry.Name()) != ".json" { + // Subnet files should have a .json extension + continue + } + + subnetPath := filepath.Join(subnetDir, entry.Name()) + bytes, err := os.ReadFile(subnetPath) + if err != nil { + return nil, fmt.Errorf("failed to read subnet file %s: %w", subnetPath, err) + } + subnet := &Subnet{} + if err := json.Unmarshal(bytes, subnet); err != nil { + return nil, fmt.Errorf("failed to unmarshal subnet from %s: %w", subnetPath, err) + } + subnets = append(subnets, subnet) + } + + return subnets, nil +} diff --git a/tests/fixture/tmpnet/utils.go b/tests/fixture/tmpnet/utils.go new file mode 100644 index 000000000000..b363bdec8671 --- /dev/null +++ b/tests/fixture/tmpnet/utils.go @@ -0,0 +1,89 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tmpnet + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" +) + +const ( + DefaultNodeTickerInterval = 50 * time.Millisecond +) + +var ErrNotRunning = errors.New("not running") + +// WaitForHealthy blocks until Node.IsHealthy returns true or an error (including context timeout) is observed. +func WaitForHealthy(ctx context.Context, node *Node) error { + if _, ok := ctx.Deadline(); !ok { + return fmt.Errorf("unable to wait for health for node %q with a context without a deadline", node.NodeID) + } + ticker := time.NewTicker(DefaultNodeTickerInterval) + defer ticker.Stop() + + for { + healthy, err := node.IsHealthy(ctx) + if err != nil && !errors.Is(err, ErrNotRunning) { + return fmt.Errorf("failed to wait for health of node %q: %w", node.NodeID, err) + } + if healthy { + return nil + } + + select { + case <-ctx.Done(): + return fmt.Errorf("failed to wait for health of node %q before timeout: %w", node.NodeID, ctx.Err()) + case <-ticker.C: + } + } +} + +// NodeURI associates a node ID with its API URI. +type NodeURI struct { + NodeID ids.NodeID + URI string +} + +func GetNodeURIs(nodes []*Node) []NodeURI { + uris := make([]NodeURI, 0, len(nodes)) + for _, node := range nodes { + if node.IsEphemeral { + // Avoid returning URIs for nodes whose lifespan is indeterminate + continue + } + // Only append URIs that are not empty. A node may have an + // empty URI if it is not currently running. + if len(node.URI) > 0 { + uris = append(uris, NodeURI{ + NodeID: node.NodeID, + URI: node.URI, + }) + } + } + return uris +} + +// Marshal to json with default prefix and indent. +func DefaultJSONMarshal(v interface{}) ([]byte, error) { + return json.MarshalIndent(v, "", " ") +} + +// Helper simplifying creation of a set of private keys +func NewPrivateKeys(keyCount int) ([]*secp256k1.PrivateKey, error) { + keys := make([]*secp256k1.PrivateKey, 0, keyCount) + for i := 0; i < keyCount; i++ { + key, err := secp256k1.NewPrivateKey() + if err != nil { + return nil, fmt.Errorf("failed to generate private key: %w", err) + } + keys = append(keys, key) + } + return keys, nil +} diff --git a/tests/http.go b/tests/http.go index 77c309f16f86..073b6d2df126 100644 --- a/tests/http.go +++ b/tests/http.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tests diff --git a/tests/upgrade/upgrade_test.go b/tests/upgrade/upgrade_test.go index b8f8147c831a..4dba94c17f23 100644 --- a/tests/upgrade/upgrade_test.go +++ b/tests/upgrade/upgrade_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package upgrade @@ -6,7 +6,6 @@ package upgrade import ( "flag" "fmt" - "strings" "testing" "github.com/onsi/ginkgo/v2" @@ -15,8 +14,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/tests/fixture/e2e" + "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" ) func TestUpgrade(t *testing.T) { @@ -48,30 +47,19 @@ var _ = ginkgo.Describe("[Upgrade]", func() { require := require.New(ginkgo.GinkgoT()) ginkgo.It("can upgrade versions", func() { - // TODO(marun) How many nodes should the target network have to best validate upgrade? - network := e2e.StartLocalNetwork(avalancheGoExecPath, e2e.DefaultNetworkDir) + network := &tmpnet.Network{} + e2e.StartNetwork(network, e2e.DefaultNetworkDir, avalancheGoExecPath, "" /* pluginDir */) ginkgo.By(fmt.Sprintf("restarting all nodes with %q binary", avalancheGoExecPathToUpgradeTo)) for _, node := range network.Nodes { - ginkgo.By(fmt.Sprintf("restarting node %q with %q binary", node.GetID(), avalancheGoExecPathToUpgradeTo)) - require.NoError(node.Stop()) - - // A node must start with sufficient bootstrap nodes to represent a quorum. Since the node's current - // bootstrap configuration may not satisfy this requirement (i.e. if on network start the node was one of - // the first validators), updating the node to bootstrap from all running validators maximizes the - // chances of a successful start. - // - // TODO(marun) Refactor node start to do this automatically - bootstrapIPs, bootstrapIDs, err := network.GetBootstrapIPsAndIDs() - require.NoError(err) - require.NotEmpty(bootstrapIDs) - node.Flags[config.BootstrapIDsKey] = strings.Join(bootstrapIDs, ",") - node.Flags[config.BootstrapIPsKey] = strings.Join(bootstrapIPs, ",") - require.NoError(node.WriteConfig()) - - require.NoError(node.Start(ginkgo.GinkgoWriter, avalancheGoExecPath)) - - ginkgo.By(fmt.Sprintf("waiting for node %q to report healthy after restart", node.GetID())) + ginkgo.By(fmt.Sprintf("restarting node %q with %q binary", node.NodeID, avalancheGoExecPathToUpgradeTo)) + require.NoError(node.Stop(e2e.DefaultContext())) + + node.RuntimeConfig.AvalancheGoPath = avalancheGoExecPathToUpgradeTo + + require.NoError(network.StartNode(e2e.DefaultContext(), ginkgo.GinkgoWriter, node)) + + ginkgo.By(fmt.Sprintf("waiting for node %q to report healthy after restart", node.NodeID)) e2e.WaitForHealthy(node) } diff --git a/trace/exporter.go b/trace/exporter.go index 252200975caf..4cca5fe3e53e 100644 --- a/trace/exporter.go +++ b/trace/exporter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package trace diff --git a/trace/exporter_type.go b/trace/exporter_type.go index 52d0124fe2b7..206731acc3bd 100644 --- a/trace/exporter_type.go +++ b/trace/exporter_type.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package trace diff --git a/trace/noop.go b/trace/noop.go index faa512b3429e..153934b143af 100644 --- a/trace/noop.go +++ b/trace/noop.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package trace diff --git a/trace/tracer.go b/trace/tracer.go index c511ff3bb0c9..1c8d40e8347f 100644 --- a/trace/tracer.go +++ b/trace/tracer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package trace diff --git a/utils/atomic.go b/utils/atomic.go index 2d75a4f47d6f..3bb125ee8af6 100644 --- a/utils/atomic.go +++ b/utils/atomic.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils diff --git a/utils/atomic_test.go b/utils/atomic_test.go index 1af2ba490f2f..3fa74063c18a 100644 --- a/utils/atomic_test.go +++ b/utils/atomic_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils diff --git a/utils/bag/bag.go b/utils/bag/bag.go index 496969a01b17..a9af1acbcf49 100644 --- a/utils/bag/bag.go +++ b/utils/bag/bag.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bag diff --git a/utils/bag/bag_benchmark_test.go b/utils/bag/bag_benchmark_test.go index 833ce755af93..e17b27b891bc 100644 --- a/utils/bag/bag_benchmark_test.go +++ b/utils/bag/bag_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bag diff --git a/utils/bag/bag_test.go b/utils/bag/bag_test.go index 1a42486560a6..3b6e0faa07f0 100644 --- a/utils/bag/bag_test.go +++ b/utils/bag/bag_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bag @@ -201,8 +201,8 @@ func TestBagString(t *testing.T) { bag.AddCount(elt0, 1337) - expected := "Bag[int]: (Size = 1337)\n" + - " 123: 1337" + expected := `Bag[int]: (Size = 1337) + 123: 1337` require.Equal(t, expected, bag.String()) } diff --git a/utils/bag/unique_bag.go b/utils/bag/unique_bag.go index 751159f16d9b..f5d679a5b816 100644 --- a/utils/bag/unique_bag.go +++ b/utils/bag/unique_bag.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bag diff --git a/utils/bag/unique_bag_test.go b/utils/bag/unique_bag_test.go index d15ecbf3a5cf..1562b5c9f04c 100644 --- a/utils/bag/unique_bag_test.go +++ b/utils/bag/unique_bag_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bag diff --git a/utils/beacon/beacon.go b/utils/beacon/beacon.go index 47e41032677e..38ac6df5b0f5 100644 --- a/utils/beacon/beacon.go +++ b/utils/beacon/beacon.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package beacon diff --git a/utils/beacon/set.go b/utils/beacon/set.go index 243f8399a915..8b6970b55421 100644 --- a/utils/beacon/set.go +++ b/utils/beacon/set.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package beacon diff --git a/utils/beacon/set_test.go b/utils/beacon/set_test.go index 3f4d6cbc4053..976d0582e3ff 100644 --- a/utils/beacon/set_test.go +++ b/utils/beacon/set_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package beacon diff --git a/utils/bimap/bimap.go b/utils/bimap/bimap.go index 28d20750bace..d0651ff36cd2 100644 --- a/utils/bimap/bimap.go +++ b/utils/bimap/bimap.go @@ -1,9 +1,23 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bimap -import "github.com/ava-labs/avalanchego/utils" +import ( + "bytes" + "encoding/json" + "errors" + + "github.com/ava-labs/avalanchego/utils" +) + +var ( + _ json.Marshaler = (*BiMap[int, int])(nil) + _ json.Unmarshaler = (*BiMap[int, int])(nil) + + nullBytes = []byte("null") + errNotBijective = errors.New("map not bijective") +) type Entry[K, V any] struct { Key K @@ -100,3 +114,28 @@ func (m *BiMap[K, V]) DeleteValue(val V) (K, bool) { func (m *BiMap[K, V]) Len() int { return len(m.keyToValue) } + +func (m *BiMap[K, V]) MarshalJSON() ([]byte, error) { + return json.Marshal(m.keyToValue) +} + +func (m *BiMap[K, V]) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, nullBytes) { + return nil + } + var keyToValue map[K]V + if err := json.Unmarshal(b, &keyToValue); err != nil { + return err + } + valueToKey := make(map[V]K, len(keyToValue)) + for k, v := range keyToValue { + valueToKey[v] = k + } + if len(keyToValue) != len(valueToKey) { + return errNotBijective + } + + m.keyToValue = keyToValue + m.valueToKey = valueToKey + return nil +} diff --git a/utils/bimap/bimap_test.go b/utils/bimap/bimap_test.go index 9914578c6070..9b4433a51c70 100644 --- a/utils/bimap/bimap_test.go +++ b/utils/bimap/bimap_test.go @@ -1,9 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bimap import ( + "encoding/json" "testing" "github.com/stretchr/testify/require" @@ -326,3 +327,30 @@ func TestBiMapLen(t *testing.T) { m.DeleteKey(1) require.Zero(m.Len()) } + +func TestBiMapJSON(t *testing.T) { + require := require.New(t) + + expectedMap := New[int, int]() + expectedMap.Put(1, 2) + expectedMap.Put(2, 3) + + jsonBytes, err := json.Marshal(expectedMap) + require.NoError(err) + + expectedJSONBytes := []byte(`{"1":2,"2":3}`) + require.Equal(expectedJSONBytes, jsonBytes) + + var unmarshalledMap BiMap[int, int] + require.NoError(json.Unmarshal(jsonBytes, &unmarshalledMap)) + require.Equal(expectedMap, &unmarshalledMap) +} + +func TestBiMapInvalidJSON(t *testing.T) { + require := require.New(t) + + invalidJSONBytes := []byte(`{"1":2,"2":2}`) + var unmarshalledMap BiMap[int, int] + err := json.Unmarshal(invalidJSONBytes, &unmarshalledMap) + require.ErrorIs(err, errNotBijective) +} diff --git a/utils/bloom/bloom_filter.go b/utils/bloom/bloom_filter.go deleted file mode 100644 index 498c57d3552f..000000000000 --- a/utils/bloom/bloom_filter.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package bloom - -import ( - "errors" - "sync" - - "github.com/spaolacci/murmur3" - - streakKnife "github.com/holiman/bloomfilter/v2" -) - -var errMaxBytes = errors.New("too large") - -type Filter interface { - // Add adds to filter, assumed thread safe - Add(...[]byte) - - // Check checks filter, assumed thread safe - Check([]byte) bool -} - -func New(maxN uint64, p float64, maxBytes uint64) (Filter, error) { - neededBytes := bytesSteakKnifeFilter(maxN, p) - if neededBytes > maxBytes { - return nil, errMaxBytes - } - return newSteakKnifeFilter(maxN, p) -} - -type steakKnifeFilter struct { - lock sync.RWMutex - filter *streakKnife.Filter -} - -func bytesSteakKnifeFilter(maxN uint64, p float64) uint64 { - m := streakKnife.OptimalM(maxN, p) - k := streakKnife.OptimalK(m, maxN) - - // This is pulled from bloomFilter.newBits and bloomfilter.newRandKeys. The - // calculation is the size of the bitset which would be created from this - // filter. - mSize := (m + 63) / 64 - totalSize := mSize + k - - return totalSize * 8 // 8 == sizeof(uint64)) -} - -func newSteakKnifeFilter(maxN uint64, p float64) (Filter, error) { - m := streakKnife.OptimalM(maxN, p) - k := streakKnife.OptimalK(m, maxN) - - filter, err := streakKnife.New(m, k) - return &steakKnifeFilter{filter: filter}, err -} - -func (f *steakKnifeFilter) Add(bl ...[]byte) { - f.lock.Lock() - defer f.lock.Unlock() - - for _, b := range bl { - h := murmur3.New64() - _, _ = h.Write(b) - f.filter.Add(h) - } -} - -func (f *steakKnifeFilter) Check(b []byte) bool { - f.lock.RLock() - defer f.lock.RUnlock() - - h := murmur3.New64() - _, _ = h.Write(b) - return f.filter.Contains(h) -} diff --git a/utils/bloom/filter.go b/utils/bloom/filter.go new file mode 100644 index 000000000000..7a0e3026087e --- /dev/null +++ b/utils/bloom/filter.go @@ -0,0 +1,147 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bloom + +import ( + "crypto/rand" + "encoding/binary" + "errors" + "fmt" + "math/bits" + "sync" +) + +const ( + minHashes = 1 + maxHashes = 16 // Supports a false positive probability of 2^-16 when using optimal size values + minEntries = 1 + + bitsPerByte = 8 + bytesPerUint64 = 8 + hashRotation = 17 +) + +var ( + errInvalidNumHashes = errors.New("invalid num hashes") + errTooFewHashes = errors.New("too few hashes") + errTooManyHashes = errors.New("too many hashes") + errTooFewEntries = errors.New("too few entries") +) + +type Filter struct { + // numBits is always equal to [bitsPerByte * len(entries)] + numBits uint64 + + lock sync.RWMutex + hashSeeds []uint64 + entries []byte + count int +} + +// New creates a new Filter with the specified number of hashes and bytes for +// entries. The returned bloom filter is safe for concurrent usage. +func New(numHashes, numEntries int) (*Filter, error) { + if numEntries < minEntries { + return nil, errTooFewEntries + } + + hashSeeds, err := newHashSeeds(numHashes) + if err != nil { + return nil, err + } + + return &Filter{ + numBits: uint64(numEntries * bitsPerByte), + hashSeeds: hashSeeds, + entries: make([]byte, numEntries), + count: 0, + }, nil +} + +func (f *Filter) Add(hash uint64) { + f.lock.Lock() + defer f.lock.Unlock() + + _ = 1 % f.numBits // hint to the compiler that numBits is not 0 + for _, seed := range f.hashSeeds { + hash = bits.RotateLeft64(hash, hashRotation) ^ seed + index := hash % f.numBits + byteIndex := index / bitsPerByte + bitIndex := index % bitsPerByte + f.entries[byteIndex] |= 1 << bitIndex + } + f.count++ +} + +// Count returns the number of elements that have been added to the bloom +// filter. +func (f *Filter) Count() int { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.count +} + +func (f *Filter) Contains(hash uint64) bool { + f.lock.RLock() + defer f.lock.RUnlock() + + return contains(f.hashSeeds, f.entries, hash) +} + +func (f *Filter) Marshal() []byte { + f.lock.RLock() + defer f.lock.RUnlock() + + return marshal(f.hashSeeds, f.entries) +} + +func newHashSeeds(count int) ([]uint64, error) { + switch { + case count < minHashes: + return nil, fmt.Errorf("%w: %d < %d", errTooFewHashes, count, minHashes) + case count > maxHashes: + return nil, fmt.Errorf("%w: %d > %d", errTooManyHashes, count, maxHashes) + } + + bytes := make([]byte, count*bytesPerUint64) + if _, err := rand.Reader.Read(bytes); err != nil { + return nil, err + } + + seeds := make([]uint64, count) + for i := range seeds { + seeds[i] = binary.BigEndian.Uint64(bytes[i*bytesPerUint64:]) + } + return seeds, nil +} + +func contains(hashSeeds []uint64, entries []byte, hash uint64) bool { + var ( + numBits = bitsPerByte * uint64(len(entries)) + _ = 1 % numBits // hint to the compiler that numBits is not 0 + accumulator byte = 1 + ) + for seedIndex := 0; seedIndex < len(hashSeeds) && accumulator != 0; seedIndex++ { + hash = bits.RotateLeft64(hash, hashRotation) ^ hashSeeds[seedIndex] + index := hash % numBits + byteIndex := index / bitsPerByte + bitIndex := index % bitsPerByte + accumulator &= entries[byteIndex] >> bitIndex + } + return accumulator != 0 +} + +func marshal(hashSeeds []uint64, entries []byte) []byte { + numHashes := len(hashSeeds) + entriesOffset := 1 + numHashes*bytesPerUint64 + + bytes := make([]byte, entriesOffset+len(entries)) + bytes[0] = byte(numHashes) + for i, seed := range hashSeeds { + binary.BigEndian.PutUint64(bytes[1+i*bytesPerUint64:], seed) + } + copy(bytes[entriesOffset:], entries) + return bytes +} diff --git a/utils/bloom/filter_test.go b/utils/bloom/filter_test.go new file mode 100644 index 000000000000..856797f8ab50 --- /dev/null +++ b/utils/bloom/filter_test.go @@ -0,0 +1,96 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bloom + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/utils/units" +) + +func TestNewErrors(t *testing.T) { + tests := []struct { + numHashes int + numEntries int + err error + }{ + { + numHashes: 0, + numEntries: 1, + err: errTooFewHashes, + }, + { + numHashes: 17, + numEntries: 1, + err: errTooManyHashes, + }, + { + numHashes: 8, + numEntries: 0, + err: errTooFewEntries, + }, + } + for _, test := range tests { + t.Run(test.err.Error(), func(t *testing.T) { + _, err := New(test.numHashes, test.numEntries) + require.ErrorIs(t, err, test.err) + }) + } +} + +func TestNormalUsage(t *testing.T) { + require := require.New(t) + + toAdd := make([]uint64, 1024) + for i := range toAdd { + toAdd[i] = rand.Uint64() //#nosec G404 + } + + initialNumHashes, initialNumBytes := OptimalParameters(1024, 0.01) + filter, err := New(initialNumHashes, initialNumBytes) + require.NoError(err) + + for i, elem := range toAdd { + filter.Add(elem) + for _, elem := range toAdd[:i] { + require.True(filter.Contains(elem)) + } + } + + require.Equal(len(toAdd), filter.Count()) + + filterBytes := filter.Marshal() + parsedFilter, err := Parse(filterBytes) + require.NoError(err) + + for _, elem := range toAdd { + require.True(parsedFilter.Contains(elem)) + } + + parsedFilterBytes := parsedFilter.Marshal() + require.Equal(filterBytes, parsedFilterBytes) +} + +func BenchmarkAdd(b *testing.B) { + f, err := New(8, 16*units.KiB) + require.NoError(b, err) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + f.Add(1) + } +} + +func BenchmarkMarshal(b *testing.B) { + f, err := New(OptimalParameters(10_000, .01)) + require.NoError(b, err) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + f.Marshal() + } +} diff --git a/utils/bloom/hasher.go b/utils/bloom/hasher.go new file mode 100644 index 000000000000..d5e3f5a6f5ec --- /dev/null +++ b/utils/bloom/hasher.go @@ -0,0 +1,31 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bloom + +import ( + "crypto/sha256" + "encoding/binary" +) + +func Add(f *Filter, key, salt []byte) { + f.Add(Hash(key, salt)) +} + +func Contains(c Checker, key, salt []byte) bool { + return c.Contains(Hash(key, salt)) +} + +type Checker interface { + Contains(hash uint64) bool +} + +func Hash(key, salt []byte) uint64 { + hash := sha256.New() + // sha256.Write never returns errors + _, _ = hash.Write(key) + _, _ = hash.Write(salt) + + output := make([]byte, 0, sha256.Size) + return binary.BigEndian.Uint64(hash.Sum(output)) +} diff --git a/utils/bloom/hasher_test.go b/utils/bloom/hasher_test.go new file mode 100644 index 000000000000..b262f1dbb5dc --- /dev/null +++ b/utils/bloom/hasher_test.go @@ -0,0 +1,34 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bloom + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/units" +) + +func TestCollisionResistance(t *testing.T) { + require := require.New(t) + + f, err := New(8, 16*units.KiB) + require.NoError(err) + + Add(f, []byte("hello world?"), []byte("so salty")) + collision := Contains(f, []byte("hello world!"), []byte("so salty")) + require.False(collision) +} + +func BenchmarkHash(b *testing.B) { + key := ids.GenerateTestID() + salt := ids.GenerateTestID() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + Hash(key[:], salt[:]) + } +} diff --git a/utils/bloom/optimal.go b/utils/bloom/optimal.go new file mode 100644 index 000000000000..fc434ca57987 --- /dev/null +++ b/utils/bloom/optimal.go @@ -0,0 +1,115 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bloom + +import ( + "math" + + safemath "github.com/ava-labs/avalanchego/utils/math" +) + +const ln2Squared = math.Ln2 * math.Ln2 + +// OptimalParameters calculates the optimal [numHashes] and [numEntries] that +// should be allocated for a bloom filter which will contain [count] and target +// [falsePositiveProbability]. +func OptimalParameters(count int, falsePositiveProbability float64) (int, int) { + numEntries := OptimalEntries(count, falsePositiveProbability) + numHashes := OptimalHashes(numEntries, count) + return numHashes, numEntries +} + +// OptimalHashes calculates the number of hashes which will minimize the false +// positive probability of a bloom filter with [numEntries] after [count] +// additions. +// +// It is guaranteed to return a value in the range [minHashes, maxHashes]. +// +// ref: https://en.wikipedia.org/wiki/Bloom_filter +func OptimalHashes(numEntries, count int) int { + switch { + case numEntries < minEntries: + return minHashes + case count <= 0: + return maxHashes + } + + numHashes := math.Ceil(float64(numEntries) * bitsPerByte * math.Ln2 / float64(count)) + // Converting a floating-point value to an int produces an undefined value + // if the floating-point value cannot be represented as an int. To avoid + // this undefined behavior, we explicitly check against MaxInt here. + // + // ref: https://go.dev/ref/spec#Conversions + if numHashes >= maxHashes { + return maxHashes + } + return safemath.Max(int(numHashes), minHashes) +} + +// OptimalEntries calculates the optimal number of entries to use when creating +// a new Bloom filter when targenting a size of [count] with +// [falsePositiveProbability] assuming that the optimal number of hashes is +// used. +// +// It is guaranteed to return a value in the range [minEntries, MaxInt]. +// +// ref: https://en.wikipedia.org/wiki/Bloom_filter +func OptimalEntries(count int, falsePositiveProbability float64) int { + switch { + case count <= 0: + return minEntries + case falsePositiveProbability >= 1: + return minEntries + case falsePositiveProbability <= 0: + return math.MaxInt + } + + entriesInBits := -float64(count) * math.Log(falsePositiveProbability) / ln2Squared + entries := (entriesInBits + bitsPerByte - 1) / bitsPerByte + // Converting a floating-point value to an int produces an undefined value + // if the floating-point value cannot be represented as an int. To avoid + // this undefined behavior, we explicitly check against MaxInt here. + // + // ref: https://go.dev/ref/spec#Conversions + if entries >= math.MaxInt { + return math.MaxInt + } + return safemath.Max(int(entries), minEntries) +} + +// EstimateCount estimates the number of additions a bloom filter with +// [numHashes] and [numEntries] must have to reach [falsePositiveProbability]. +// This is derived by inversing a lower-bound on the probability of false +// positives. For values where numBits >> numHashes, the predicted probability +// is fairly accurate. +// +// It is guaranteed to return a value in the range [0, MaxInt]. +// +// ref: https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=903775 +func EstimateCount(numHashes, numEntries int, falsePositiveProbability float64) int { + switch { + case numHashes < minHashes: + return 0 + case numEntries < minEntries: + return 0 + case falsePositiveProbability <= 0: + return 0 + case falsePositiveProbability >= 1: + return math.MaxInt + } + + invNumHashes := 1 / float64(numHashes) + numBits := float64(numEntries * 8) + exp := 1 - math.Pow(falsePositiveProbability, invNumHashes) + count := math.Ceil(-math.Log(exp) * numBits * invNumHashes) + // Converting a floating-point value to an int produces an undefined value + // if the floating-point value cannot be represented as an int. To avoid + // this undefined behavior, we explicitly check against MaxInt here. + // + // ref: https://go.dev/ref/spec#Conversions + if count >= math.MaxInt { + return math.MaxInt + } + return int(count) +} diff --git a/utils/bloom/optimal_test.go b/utils/bloom/optimal_test.go new file mode 100644 index 000000000000..b52356d5b307 --- /dev/null +++ b/utils/bloom/optimal_test.go @@ -0,0 +1,203 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bloom + +import ( + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/require" +) + +const largestFloat64LessThan1 float64 = 1 - 1e-16 + +func TestOptimalHashes(t *testing.T) { + tests := []struct { + numEntries int + count int + expectedHashes int + }{ + { // invalid params + numEntries: 0, + count: 1024, + expectedHashes: minHashes, + }, + { // invalid params + numEntries: 1024, + count: 0, + expectedHashes: maxHashes, + }, + { + numEntries: math.MaxInt, + count: 1, + expectedHashes: maxHashes, + }, + { + numEntries: 1, + count: math.MaxInt, + expectedHashes: minHashes, + }, + { + numEntries: 1024, + count: 1024, + expectedHashes: 6, + }, + } + for _, test := range tests { + t.Run(fmt.Sprintf("%d_%d", test.numEntries, test.count), func(t *testing.T) { + hashes := OptimalHashes(test.numEntries, test.count) + require.Equal(t, test.expectedHashes, hashes) + }) + } +} + +func TestOptimalEntries(t *testing.T) { + tests := []struct { + count int + falsePositiveProbability float64 + expectedEntries int + }{ + { // invalid params + count: 0, + falsePositiveProbability: .5, + expectedEntries: minEntries, + }, + { // invalid params + count: 1, + falsePositiveProbability: 0, + expectedEntries: math.MaxInt, + }, + { // invalid params + count: 1, + falsePositiveProbability: 1, + expectedEntries: minEntries, + }, + { + count: math.MaxInt, + falsePositiveProbability: math.SmallestNonzeroFloat64, + expectedEntries: math.MaxInt, + }, + { + count: 1024, + falsePositiveProbability: largestFloat64LessThan1, + expectedEntries: minEntries, + }, + { + count: 1024, + falsePositiveProbability: .01, + expectedEntries: 1227, + }, + } + for _, test := range tests { + t.Run(fmt.Sprintf("%d_%f", test.count, test.falsePositiveProbability), func(t *testing.T) { + entries := OptimalEntries(test.count, test.falsePositiveProbability) + require.Equal(t, test.expectedEntries, entries) + }) + } +} + +func TestEstimateEntries(t *testing.T) { + tests := []struct { + numHashes int + numEntries int + falsePositiveProbability float64 + expectedEntries int + }{ + { // invalid params + numHashes: 0, + numEntries: 2_048, + falsePositiveProbability: .5, + expectedEntries: 0, + }, + { // invalid params + numHashes: 1, + numEntries: 0, + falsePositiveProbability: .5, + expectedEntries: 0, + }, + { // invalid params + numHashes: 1, + numEntries: 1, + falsePositiveProbability: 2, + expectedEntries: math.MaxInt, + }, + { // invalid params + numHashes: 1, + numEntries: 1, + falsePositiveProbability: -1, + expectedEntries: 0, + }, + { + numHashes: 8, + numEntries: 2_048, + falsePositiveProbability: 0, + expectedEntries: 0, + }, + { // params from OptimalParameters(10_000, .01) + numHashes: 7, + numEntries: 11_982, + falsePositiveProbability: .01, + expectedEntries: 9_993, + }, + { // params from OptimalParameters(100_000, .001) + numHashes: 10, + numEntries: 179_720, + falsePositiveProbability: .001, + expectedEntries: 100_000, + }, + { // params from OptimalParameters(10_000, .01) + numHashes: 7, + numEntries: 11_982, + falsePositiveProbability: .05, + expectedEntries: 14_449, + }, + { // params from OptimalParameters(10_000, .01) + numHashes: 7, + numEntries: 11_982, + falsePositiveProbability: 1, + expectedEntries: math.MaxInt, + }, + { // params from OptimalParameters(10_000, .01) + numHashes: 7, + numEntries: 11_982, + falsePositiveProbability: math.SmallestNonzeroFloat64, + expectedEntries: 0, + }, + { // params from OptimalParameters(10_000, .01) + numHashes: 7, + numEntries: 11_982, + falsePositiveProbability: largestFloat64LessThan1, + expectedEntries: math.MaxInt, + }, + } + for _, test := range tests { + t.Run(fmt.Sprintf("%d_%d_%f", test.numHashes, test.numEntries, test.falsePositiveProbability), func(t *testing.T) { + entries := EstimateCount(test.numHashes, test.numEntries, test.falsePositiveProbability) + require.Equal(t, test.expectedEntries, entries) + }) + } +} + +func FuzzOptimalHashes(f *testing.F) { + f.Fuzz(func(t *testing.T, numEntries, count int) { + hashes := OptimalHashes(numEntries, count) + require.GreaterOrEqual(t, hashes, minHashes) + require.LessOrEqual(t, hashes, maxHashes) + }) +} + +func FuzzOptimalEntries(f *testing.F) { + f.Fuzz(func(t *testing.T, count int, falsePositiveProbability float64) { + entries := OptimalEntries(count, falsePositiveProbability) + require.GreaterOrEqual(t, entries, minEntries) + }) +} + +func FuzzEstimateEntries(f *testing.F) { + f.Fuzz(func(t *testing.T, numHashes, numEntries int, falsePositiveProbability float64) { + entries := EstimateCount(numHashes, numEntries, falsePositiveProbability) + require.GreaterOrEqual(t, entries, 0) + }) +} diff --git a/utils/bloom/read_filter.go b/utils/bloom/read_filter.go new file mode 100644 index 000000000000..075d77ed7a38 --- /dev/null +++ b/utils/bloom/read_filter.go @@ -0,0 +1,65 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bloom + +import ( + "encoding/binary" + "fmt" +) + +var ( + EmptyFilter = &ReadFilter{ + hashSeeds: make([]uint64, minHashes), + entries: make([]byte, minEntries), + } + FullFilter = &ReadFilter{ + hashSeeds: make([]uint64, minHashes), + entries: make([]byte, minEntries), + } +) + +func init() { + for i := range FullFilter.entries { + FullFilter.entries[i] = 0xFF + } +} + +type ReadFilter struct { + hashSeeds []uint64 + entries []byte +} + +// Parse [bytes] into a read-only bloom filter. +func Parse(bytes []byte) (*ReadFilter, error) { + if len(bytes) == 0 { + return nil, errInvalidNumHashes + } + numHashes := bytes[0] + entriesOffset := 1 + int(numHashes)*bytesPerUint64 + switch { + case numHashes < minHashes: + return nil, fmt.Errorf("%w: %d < %d", errTooFewHashes, numHashes, minHashes) + case numHashes > maxHashes: + return nil, fmt.Errorf("%w: %d > %d", errTooManyHashes, numHashes, maxHashes) + case len(bytes) < entriesOffset+minEntries: // numEntries = len(bytes) - entriesOffset + return nil, errTooFewEntries + } + + f := &ReadFilter{ + hashSeeds: make([]uint64, numHashes), + entries: bytes[entriesOffset:], + } + for i := range f.hashSeeds { + f.hashSeeds[i] = binary.BigEndian.Uint64(bytes[1+i*bytesPerUint64:]) + } + return f, nil +} + +func (f *ReadFilter) Contains(hash uint64) bool { + return contains(f.hashSeeds, f.entries, hash) +} + +func (f *ReadFilter) Marshal() []byte { + return marshal(f.hashSeeds, f.entries) +} diff --git a/utils/bloom/read_filter_test.go b/utils/bloom/read_filter_test.go new file mode 100644 index 000000000000..8ea83db092ca --- /dev/null +++ b/utils/bloom/read_filter_test.go @@ -0,0 +1,112 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package bloom + +import ( + "math" + "testing" + + "github.com/stretchr/testify/require" +) + +func NewMaliciousFilter(numHashes, numEntries int) *Filter { + f := &Filter{ + numBits: uint64(numEntries * bitsPerByte), + hashSeeds: make([]uint64, numHashes), + entries: make([]byte, numEntries), + count: 0, + } + for i := range f.entries { + f.entries[i] = math.MaxUint8 + } + return f +} + +func TestParseErrors(t *testing.T) { + tests := []struct { + bytes []byte + err error + }{ + { + bytes: nil, + err: errInvalidNumHashes, + }, + { + bytes: NewMaliciousFilter(0, 1).Marshal(), + err: errTooFewHashes, + }, + { + bytes: NewMaliciousFilter(17, 1).Marshal(), + err: errTooManyHashes, + }, + { + bytes: NewMaliciousFilter(1, 0).Marshal(), + err: errTooFewEntries, + }, + { + bytes: []byte{ + 0x01, // num hashes = 1 + }, + err: errTooFewEntries, + }, + } + for _, test := range tests { + t.Run(test.err.Error(), func(t *testing.T) { + _, err := Parse(test.bytes) + require.ErrorIs(t, err, test.err) + }) + } +} + +func BenchmarkParse(b *testing.B) { + f, err := New(OptimalParameters(10_000, .01)) + require.NoError(b, err) + bytes := f.Marshal() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = Parse(bytes) + } +} + +func BenchmarkContains(b *testing.B) { + f := NewMaliciousFilter(maxHashes, 1) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + f.Contains(1) + } +} + +func FuzzParseThenMarshal(f *testing.F) { + f.Fuzz(func(t *testing.T, bytes []byte) { + f, err := Parse(bytes) + if err != nil { + return + } + + marshalledBytes := marshal(f.hashSeeds, f.entries) + require.Equal(t, bytes, marshalledBytes) + }) +} + +func FuzzMarshalThenParse(f *testing.F) { + f.Fuzz(func(t *testing.T, numHashes int, entries []byte) { + require := require.New(t) + + hashSeeds, err := newHashSeeds(numHashes) + if err != nil { + return + } + if len(entries) < minEntries { + return + } + + marshalledBytes := marshal(hashSeeds, entries) + rf, err := Parse(marshalledBytes) + require.NoError(err) + require.Equal(hashSeeds, rf.hashSeeds) + require.Equal(entries, rf.entries) + }) +} diff --git a/utils/buffer/bounded_nonblocking_queue.go b/utils/buffer/bounded_nonblocking_queue.go index 0b5d5f945d52..f8b0030e9687 100644 --- a/utils/buffer/bounded_nonblocking_queue.go +++ b/utils/buffer/bounded_nonblocking_queue.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package buffer diff --git a/utils/buffer/bounded_nonblocking_queue_test.go b/utils/buffer/bounded_nonblocking_queue_test.go index e6a6fdac3e49..323ea92589be 100644 --- a/utils/buffer/bounded_nonblocking_queue_test.go +++ b/utils/buffer/bounded_nonblocking_queue_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package buffer diff --git a/utils/buffer/unbounded_blocking_deque.go b/utils/buffer/unbounded_blocking_deque.go index 078d8d908ee8..a6c7fb66d6e1 100644 --- a/utils/buffer/unbounded_blocking_deque.go +++ b/utils/buffer/unbounded_blocking_deque.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package buffer diff --git a/utils/buffer/unbounded_blocking_deque_test.go b/utils/buffer/unbounded_blocking_deque_test.go index 054d3a2e6bed..1f22db9916b9 100644 --- a/utils/buffer/unbounded_blocking_deque_test.go +++ b/utils/buffer/unbounded_blocking_deque_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package buffer diff --git a/utils/buffer/unbounded_deque.go b/utils/buffer/unbounded_deque.go index 336f0869c907..873f33f14817 100644 --- a/utils/buffer/unbounded_deque.go +++ b/utils/buffer/unbounded_deque.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package buffer diff --git a/utils/buffer/unbounded_deque_test.go b/utils/buffer/unbounded_deque_test.go index 5b759da1c0e9..dfdac4a53412 100644 --- a/utils/buffer/unbounded_deque_test.go +++ b/utils/buffer/unbounded_deque_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package buffer diff --git a/utils/bytes.go b/utils/bytes.go index c025c4915c9e..a32f353cf75e 100644 --- a/utils/bytes.go +++ b/utils/bytes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils diff --git a/utils/cb58/cb58.go b/utils/cb58/cb58.go index 4d9cbd6f7449..27d8265cd2f9 100644 --- a/utils/cb58/cb58.go +++ b/utils/cb58/cb58.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cb58 diff --git a/utils/cb58/cb58_test.go b/utils/cb58/cb58_test.go index 858c0b8783ba..9d28c6f90fa4 100644 --- a/utils/cb58/cb58_test.go +++ b/utils/cb58/cb58_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package cb58 diff --git a/utils/compression/compressor.go b/utils/compression/compressor.go index f0848357a882..c8624f9baf84 100644 --- a/utils/compression/compressor.go +++ b/utils/compression/compressor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package compression diff --git a/utils/compression/compressor_test.go b/utils/compression/compressor_test.go index 0467c2c1b234..f4f024e550b9 100644 --- a/utils/compression/compressor_test.go +++ b/utils/compression/compressor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package compression diff --git a/utils/compression/gzip_compressor.go b/utils/compression/gzip_compressor.go index a17c46f6d6a3..da0b941a47a1 100644 --- a/utils/compression/gzip_compressor.go +++ b/utils/compression/gzip_compressor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package compression @@ -21,6 +21,7 @@ var ( ErrMsgTooLarge = errors.New("msg too large to be compressed") ) +// TODO: Remove once v1.11.x is out. type gzipCompressor struct { maxSize int64 gzipWriterPool sync.Pool diff --git a/utils/compression/no_compressor.go b/utils/compression/no_compressor.go index 1eb4237d9766..3c444c71d993 100644 --- a/utils/compression/no_compressor.go +++ b/utils/compression/no_compressor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package compression diff --git a/utils/compression/no_compressor_test.go b/utils/compression/no_compressor_test.go index 95000667658c..3b99a101814d 100644 --- a/utils/compression/no_compressor_test.go +++ b/utils/compression/no_compressor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package compression diff --git a/utils/compression/type.go b/utils/compression/type.go index fd58a21f70fa..09b4d64c33ea 100644 --- a/utils/compression/type.go +++ b/utils/compression/type.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package compression @@ -14,7 +14,7 @@ type Type byte const ( TypeNone Type = iota + 1 - TypeGzip + TypeGzip // Remove once v1.11.x is out. TypeZstd ) diff --git a/utils/compression/type_test.go b/utils/compression/type_test.go index 13d6b313aa48..eacad3bad598 100644 --- a/utils/compression/type_test.go +++ b/utils/compression/type_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package compression diff --git a/utils/compression/zstd_compressor.go b/utils/compression/zstd_compressor.go index eafc1071845f..b374fa850ee6 100644 --- a/utils/compression/zstd_compressor.go +++ b/utils/compression/zstd_compressor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package compression diff --git a/utils/constants/acps.go b/utils/constants/acps.go new file mode 100644 index 000000000000..6774828cde31 --- /dev/null +++ b/utils/constants/acps.go @@ -0,0 +1,19 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package constants + +import "github.com/ava-labs/avalanchego/utils/set" + +// CurrentACPs is the set of ACPs that are currently, at the time of release, +// marked as implementable. +// +// See: https://github.com/avalanche-foundation/ACPs/tree/main#readme +var CurrentACPs = set.Of[uint32]( + 23, // https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/23-p-chain-native-transfers.md + 24, // https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/24-shanghai-eips.md + 25, // https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/25-vm-application-errors.md + 30, // https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/30-avalanche-warp-x-evm.md + 31, // https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/31-enable-subnet-ownership-transfer.md + 41, // https://github.com/avalanche-foundation/ACPs/blob/main/ACPs/41-remove-pending-stakers.md +) diff --git a/utils/constants/aliases.go b/utils/constants/aliases.go index dd94bd363925..dd8388246d39 100644 --- a/utils/constants/aliases.go +++ b/utils/constants/aliases.go @@ -1,12 +1,7 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package constants -const ( - // ChainAliasPrefix denotes a prefix for an alias that belongs to a blockchain ID. - ChainAliasPrefix string = "bc" - - // VMAliasPrefix denotes a prefix for an alias that belongs to a VM ID. - VMAliasPrefix string = "vm" -) +// ChainAliasPrefix denotes a prefix for an alias that belongs to a blockchain ID. +const ChainAliasPrefix string = "bc" diff --git a/utils/constants/application.go b/utils/constants/application.go index 117b85d9eb9e..4e59fd3777fb 100644 --- a/utils/constants/application.go +++ b/utils/constants/application.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package constants diff --git a/utils/constants/memory.go b/utils/constants/memory.go index c8740ceba6f7..cca6ee7af0f9 100644 --- a/utils/constants/memory.go +++ b/utils/constants/memory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package constants diff --git a/utils/constants/network_ids.go b/utils/constants/network_ids.go index fe2819d376ee..d00472a39f32 100644 --- a/utils/constants/network_ids.go +++ b/utils/constants/network_ids.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package constants diff --git a/utils/constants/network_ids_test.go b/utils/constants/network_ids_test.go index 69557096efd8..a368a2d36532 100644 --- a/utils/constants/network_ids_test.go +++ b/utils/constants/network_ids_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package constants diff --git a/utils/constants/networking.go b/utils/constants/networking.go index a9417eac37c9..7a4ea89b8241 100644 --- a/utils/constants/networking.go +++ b/utils/constants/networking.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package constants @@ -32,6 +32,8 @@ const ( DefaultNetworkPeerListNonValidatorGossipSize = 0 DefaultNetworkPeerListPeersGossipSize = 10 DefaultNetworkPeerListGossipFreq = time.Minute + DefaultNetworkPeerListPullGossipFreq = 2 * time.Second + DefaultNetworkPeerListBloomResetFreq = time.Minute // Inbound Connection Throttling DefaultInboundConnUpgradeThrottlerCooldown = 10 * time.Second diff --git a/utils/constants/vm_ids.go b/utils/constants/vm_ids.go index 4fb887c425f2..9fda498f1f31 100644 --- a/utils/constants/vm_ids.go +++ b/utils/constants/vm_ids.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package constants diff --git a/utils/context.go b/utils/context.go index 9ff300186881..453c45e948a4 100644 --- a/utils/context.go +++ b/utils/context.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils diff --git a/utils/crypto/bls/bls_benchmark_test.go b/utils/crypto/bls/bls_benchmark_test.go index cd3568005764..b9648b43c04e 100644 --- a/utils/crypto/bls/bls_benchmark_test.go +++ b/utils/crypto/bls/bls_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bls diff --git a/utils/crypto/bls/bls_test.go b/utils/crypto/bls/bls_test.go index f3bb05004376..e8a4a45bb97d 100644 --- a/utils/crypto/bls/bls_test.go +++ b/utils/crypto/bls/bls_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bls diff --git a/utils/crypto/bls/public.go b/utils/crypto/bls/public.go index 8d8237f83d5e..2c3cca7a0181 100644 --- a/utils/crypto/bls/public.go +++ b/utils/crypto/bls/public.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bls diff --git a/utils/crypto/bls/public_test.go b/utils/crypto/bls/public_test.go index 9cd886400b2d..4465b014cff4 100644 --- a/utils/crypto/bls/public_test.go +++ b/utils/crypto/bls/public_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bls diff --git a/utils/crypto/bls/secret.go b/utils/crypto/bls/secret.go index 3f385624520c..049938bdaf8e 100644 --- a/utils/crypto/bls/secret.go +++ b/utils/crypto/bls/secret.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bls diff --git a/utils/crypto/bls/secret_test.go b/utils/crypto/bls/secret_test.go index c01540ac4f98..d3d46e1aa737 100644 --- a/utils/crypto/bls/secret_test.go +++ b/utils/crypto/bls/secret_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bls diff --git a/utils/crypto/bls/signature.go b/utils/crypto/bls/signature.go index cafba33c48e6..0d0d029b796e 100644 --- a/utils/crypto/bls/signature.go +++ b/utils/crypto/bls/signature.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bls diff --git a/utils/crypto/bls/signature_test.go b/utils/crypto/bls/signature_test.go index caf613fc18df..3d43282c487a 100644 --- a/utils/crypto/bls/signature_test.go +++ b/utils/crypto/bls/signature_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package bls diff --git a/utils/crypto/keychain/keychain.go b/utils/crypto/keychain/keychain.go index 5899bb40382a..47d39b59f07c 100644 --- a/utils/crypto/keychain/keychain.go +++ b/utils/crypto/keychain/keychain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keychain diff --git a/utils/crypto/keychain/keychain_test.go b/utils/crypto/keychain/keychain_test.go index 73aa476122db..1d1dd86b6055 100644 --- a/utils/crypto/keychain/keychain_test.go +++ b/utils/crypto/keychain/keychain_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keychain diff --git a/utils/crypto/keychain/ledger.go b/utils/crypto/keychain/ledger.go index d709ed19c939..955eb4480e24 100644 --- a/utils/crypto/keychain/ledger.go +++ b/utils/crypto/keychain/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keychain diff --git a/utils/crypto/keychain/mock_ledger.go b/utils/crypto/keychain/mock_ledger.go index 8f270bfbc032..b082631c416e 100644 --- a/utils/crypto/keychain/mock_ledger.go +++ b/utils/crypto/keychain/mock_ledger.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/utils/crypto/keychain (interfaces: Ledger) +// +// Generated by this command: +// +// mockgen -package=keychain -destination=utils/crypto/keychain/mock_ledger.go github.com/ava-labs/avalanchego/utils/crypto/keychain Ledger +// // Package keychain is a generated GoMock package. package keychain @@ -48,7 +50,7 @@ func (m *MockLedger) Address(arg0 string, arg1 uint32) (ids.ShortID, error) { } // Address indicates an expected call of Address. -func (mr *MockLedgerMockRecorder) Address(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLedgerMockRecorder) Address(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*MockLedger)(nil).Address), arg0, arg1) } @@ -63,7 +65,7 @@ func (m *MockLedger) Addresses(arg0 []uint32) ([]ids.ShortID, error) { } // Addresses indicates an expected call of Addresses. -func (mr *MockLedgerMockRecorder) Addresses(arg0 interface{}) *gomock.Call { +func (mr *MockLedgerMockRecorder) Addresses(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addresses", reflect.TypeOf((*MockLedger)(nil).Addresses), arg0) } @@ -92,7 +94,7 @@ func (m *MockLedger) Sign(arg0 []byte, arg1 []uint32) ([][]byte, error) { } // Sign indicates an expected call of Sign. -func (mr *MockLedgerMockRecorder) Sign(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLedgerMockRecorder) Sign(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*MockLedger)(nil).Sign), arg0, arg1) } @@ -107,7 +109,7 @@ func (m *MockLedger) SignHash(arg0 []byte, arg1 []uint32) ([][]byte, error) { } // SignHash indicates an expected call of SignHash. -func (mr *MockLedgerMockRecorder) SignHash(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockLedgerMockRecorder) SignHash(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignHash", reflect.TypeOf((*MockLedger)(nil).SignHash), arg0, arg1) } diff --git a/utils/crypto/ledger/ledger.go b/utils/crypto/ledger/ledger.go index 5f8c34fe5715..70f6d4f07b84 100644 --- a/utils/crypto/ledger/ledger.go +++ b/utils/crypto/ledger/ledger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ledger diff --git a/utils/crypto/ledger/ledger_test.go b/utils/crypto/ledger/ledger_test.go index 118dc8758d1b..160b26365f84 100644 --- a/utils/crypto/ledger/ledger_test.go +++ b/utils/crypto/ledger/ledger_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ledger diff --git a/utils/crypto/secp256k1/rfc6979_test.go b/utils/crypto/secp256k1/rfc6979_test.go index 5d9ee8b4f033..7efc019a3429 100644 --- a/utils/crypto/secp256k1/rfc6979_test.go +++ b/utils/crypto/secp256k1/rfc6979_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1 diff --git a/utils/crypto/secp256k1/secp256k1.go b/utils/crypto/secp256k1/secp256k1.go index 022cce2861a0..93ef887bf71d 100644 --- a/utils/crypto/secp256k1/secp256k1.go +++ b/utils/crypto/secp256k1/secp256k1.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1 @@ -220,7 +220,7 @@ func (k *PrivateKey) String() string { } func (k *PrivateKey) MarshalJSON() ([]byte, error) { - return []byte("\"" + k.String() + "\""), nil + return []byte(`"` + k.String() + `"`), nil } func (k *PrivateKey) MarshalText() ([]byte, error) { diff --git a/utils/crypto/secp256k1/secp256k1_benchmark_test.go b/utils/crypto/secp256k1/secp256k1_benchmark_test.go index 1d55f38f7d86..ca4f98e38fb5 100644 --- a/utils/crypto/secp256k1/secp256k1_benchmark_test.go +++ b/utils/crypto/secp256k1/secp256k1_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1 diff --git a/utils/crypto/secp256k1/secp256k1_test.go b/utils/crypto/secp256k1/secp256k1_test.go index a2074dff5229..39a915b9f9b3 100644 --- a/utils/crypto/secp256k1/secp256k1_test.go +++ b/utils/crypto/secp256k1/secp256k1_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1 diff --git a/utils/crypto/secp256k1/test_keys.go b/utils/crypto/secp256k1/test_keys.go index 3122f2617ddf..4ceb567469b2 100644 --- a/utils/crypto/secp256k1/test_keys.go +++ b/utils/crypto/secp256k1/test_keys.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1 diff --git a/utils/dynamicip/ifconfig_resolver.go b/utils/dynamicip/ifconfig_resolver.go index 0bbabcb58612..36c8d5adf04c 100644 --- a/utils/dynamicip/ifconfig_resolver.go +++ b/utils/dynamicip/ifconfig_resolver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package dynamicip diff --git a/utils/dynamicip/no_updater.go b/utils/dynamicip/no_updater.go index 5c9e38bd54a1..e3e7c6155bd0 100644 --- a/utils/dynamicip/no_updater.go +++ b/utils/dynamicip/no_updater.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package dynamicip diff --git a/utils/dynamicip/opendns_resolver.go b/utils/dynamicip/opendns_resolver.go index 3bda76c46404..5c39c95535fc 100644 --- a/utils/dynamicip/opendns_resolver.go +++ b/utils/dynamicip/opendns_resolver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package dynamicip diff --git a/utils/dynamicip/resolver.go b/utils/dynamicip/resolver.go index b3a341cd2121..45ad3778bc01 100644 --- a/utils/dynamicip/resolver.go +++ b/utils/dynamicip/resolver.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package dynamicip diff --git a/utils/dynamicip/resolver_test.go b/utils/dynamicip/resolver_test.go index e5e53d40f9f3..6af72a98a50a 100644 --- a/utils/dynamicip/resolver_test.go +++ b/utils/dynamicip/resolver_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package dynamicip diff --git a/utils/dynamicip/updater.go b/utils/dynamicip/updater.go index 87c99e6db0c5..9a59c9fd25e0 100644 --- a/utils/dynamicip/updater.go +++ b/utils/dynamicip/updater.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package dynamicip diff --git a/utils/dynamicip/updater_test.go b/utils/dynamicip/updater_test.go index 98ce26b4a189..66c9a21c4c6a 100644 --- a/utils/dynamicip/updater_test.go +++ b/utils/dynamicip/updater_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package dynamicip diff --git a/utils/error.go b/utils/error.go index b58c60cd001a..0a6a9f323e03 100644 --- a/utils/error.go +++ b/utils/error.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils diff --git a/utils/filesystem/io.go b/utils/filesystem/io.go index 939e635a2ca9..28a0c4aa1e32 100644 --- a/utils/filesystem/io.go +++ b/utils/filesystem/io.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package filesystem diff --git a/utils/filesystem/mock_file.go b/utils/filesystem/mock_file.go index 92b219393765..7b133025621b 100644 --- a/utils/filesystem/mock_file.go +++ b/utils/filesystem/mock_file.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package filesystem diff --git a/utils/filesystem/mock_io.go b/utils/filesystem/mock_io.go index 9ec15c548e39..06b27dd18dd0 100644 --- a/utils/filesystem/mock_io.go +++ b/utils/filesystem/mock_io.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/utils/filesystem (interfaces: Reader) +// +// Generated by this command: +// +// mockgen -package=filesystem -destination=utils/filesystem/mock_io.go github.com/ava-labs/avalanchego/utils/filesystem Reader +// // Package filesystem is a generated GoMock package. package filesystem @@ -47,7 +49,7 @@ func (m *MockReader) ReadDir(arg0 string) ([]fs.DirEntry, error) { } // ReadDir indicates an expected call of ReadDir. -func (mr *MockReaderMockRecorder) ReadDir(arg0 interface{}) *gomock.Call { +func (mr *MockReaderMockRecorder) ReadDir(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadDir", reflect.TypeOf((*MockReader)(nil).ReadDir), arg0) } diff --git a/utils/filesystem/rename.go b/utils/filesystem/rename.go index 3ab7c147d355..578c46fb6c9e 100644 --- a/utils/filesystem/rename.go +++ b/utils/filesystem/rename.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package filesystem diff --git a/utils/filesystem/rename_test.go b/utils/filesystem/rename_test.go index 305a65092727..53c8a503bcf6 100644 --- a/utils/filesystem/rename_test.go +++ b/utils/filesystem/rename_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package filesystem diff --git a/utils/formatting/address/address.go b/utils/formatting/address/address.go index 321fe692bf57..97d4e0552969 100644 --- a/utils/formatting/address/address.go +++ b/utils/formatting/address/address.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package address diff --git a/utils/formatting/address/converter.go b/utils/formatting/address/converter.go index 8a5812a5bb2c..f043ab6a3489 100644 --- a/utils/formatting/address/converter.go +++ b/utils/formatting/address/converter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package address diff --git a/utils/formatting/encoding.go b/utils/formatting/encoding.go index 20ab4df30157..742800fee86e 100644 --- a/utils/formatting/encoding.go +++ b/utils/formatting/encoding.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package formatting @@ -69,7 +69,7 @@ func (enc Encoding) MarshalJSON() ([]byte, error) { if !enc.valid() { return nil, errInvalidEncoding } - return []byte("\"" + enc.String() + "\""), nil + return []byte(`"` + enc.String() + `"`), nil } func (enc *Encoding) UnmarshalJSON(b []byte) error { diff --git a/utils/formatting/encoding_benchmark_test.go b/utils/formatting/encoding_benchmark_test.go index 598ed39310ca..879933418e3e 100644 --- a/utils/formatting/encoding_benchmark_test.go +++ b/utils/formatting/encoding_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package formatting diff --git a/utils/formatting/encoding_test.go b/utils/formatting/encoding_test.go index 29f6c1d5df39..6623e325e9cf 100644 --- a/utils/formatting/encoding_test.go +++ b/utils/formatting/encoding_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package formatting diff --git a/utils/formatting/int_format.go b/utils/formatting/int_format.go index 6cd8c870a43d..7c26655f2aba 100644 --- a/utils/formatting/int_format.go +++ b/utils/formatting/int_format.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package formatting diff --git a/utils/formatting/int_format_test.go b/utils/formatting/int_format_test.go index febf23bca4a2..aa5dce1dc8db 100644 --- a/utils/formatting/int_format_test.go +++ b/utils/formatting/int_format_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package formatting diff --git a/utils/formatting/prefixed_stringer.go b/utils/formatting/prefixed_stringer.go index 15fe720398a8..3c82cddd88a7 100644 --- a/utils/formatting/prefixed_stringer.go +++ b/utils/formatting/prefixed_stringer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package formatting diff --git a/utils/hashing/consistent/hashable.go b/utils/hashing/consistent/hashable.go index df4a08d0ba44..a51ce4dfc817 100644 --- a/utils/hashing/consistent/hashable.go +++ b/utils/hashing/consistent/hashable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package consistent diff --git a/utils/hashing/consistent/ring.go b/utils/hashing/consistent/ring.go index 1ade42ff359f..c99dd276047f 100644 --- a/utils/hashing/consistent/ring.go +++ b/utils/hashing/consistent/ring.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package consistent diff --git a/utils/hashing/consistent/ring_test.go b/utils/hashing/consistent/ring_test.go index ec9e69098b1a..a53e166657b6 100644 --- a/utils/hashing/consistent/ring_test.go +++ b/utils/hashing/consistent/ring_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package consistent @@ -181,7 +181,7 @@ func TestGetMapsToClockwiseNode(t *testing.T) { ring, hasher := setupTest(t, 1) // setup expected calls - calls := make([]*gomock.Call, len(test.ringNodes)+1) + calls := make([]any, len(test.ringNodes)+1) for i, key := range test.ringNodes { calls[i] = hasher.EXPECT().Hash(getHashKey(key.ConsistentHashKey(), 0)).Return(key.hash).Times(1) diff --git a/utils/hashing/hasher.go b/utils/hashing/hasher.go index 7519dfbb69ec..be74c160d00f 100644 --- a/utils/hashing/hasher.go +++ b/utils/hashing/hasher.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package hashing diff --git a/utils/hashing/hashing.go b/utils/hashing/hashing.go index f2c79e235a64..0d09fd457247 100644 --- a/utils/hashing/hashing.go +++ b/utils/hashing/hashing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package hashing diff --git a/utils/hashing/mock_hasher.go b/utils/hashing/mock_hasher.go index 26b4130c5d72..c2d5ea4b3918 100644 --- a/utils/hashing/mock_hasher.go +++ b/utils/hashing/mock_hasher.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/utils/hashing (interfaces: Hasher) +// +// Generated by this command: +// +// mockgen -package=hashing -destination=utils/hashing/mock_hasher.go github.com/ava-labs/avalanchego/utils/hashing Hasher +// // Package hashing is a generated GoMock package. package hashing @@ -45,7 +47,7 @@ func (m *MockHasher) Hash(arg0 []byte) uint64 { } // Hash indicates an expected call of Hash. -func (mr *MockHasherMockRecorder) Hash(arg0 interface{}) *gomock.Call { +func (mr *MockHasherMockRecorder) Hash(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockHasher)(nil).Hash), arg0) } diff --git a/utils/heap/map.go b/utils/heap/map.go index dbe06c06446e..1162e95fd15f 100644 --- a/utils/heap/map.go +++ b/utils/heap/map.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package heap diff --git a/utils/heap/map_test.go b/utils/heap/map_test.go index cc774a5a50df..64e3e4e29132 100644 --- a/utils/heap/map_test.go +++ b/utils/heap/map_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package heap diff --git a/utils/heap/queue.go b/utils/heap/queue.go index fc3bebaa0b8d..62687635f93e 100644 --- a/utils/heap/queue.go +++ b/utils/heap/queue.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package heap diff --git a/utils/heap/queue_test.go b/utils/heap/queue_test.go index e7481eddbbe3..66e3417178bd 100644 --- a/utils/heap/queue_test.go +++ b/utils/heap/queue_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package heap diff --git a/utils/heap/set.go b/utils/heap/set.go index 15fab421b278..e1865f1e64f7 100644 --- a/utils/heap/set.go +++ b/utils/heap/set.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package heap diff --git a/utils/heap/set_test.go b/utils/heap/set_test.go index fd93f996d5ff..d475226118ee 100644 --- a/utils/heap/set_test.go +++ b/utils/heap/set_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package heap diff --git a/utils/ips/claimed_ip_port.go b/utils/ips/claimed_ip_port.go index 4ba4a6085e79..2ef6c0a71087 100644 --- a/utils/ips/claimed_ip_port.go +++ b/utils/ips/claimed_ip_port.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ips @@ -6,16 +6,14 @@ package ips import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/staking" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/utils/wrappers" ) -// Can't import these from wrappers package due to circular import. const ( - intLen = 4 - longLen = 8 - ipLen = 18 - idLen = 32 // Certificate length, signature length, IP, timestamp, tx ID - baseIPCertDescLen = 2*intLen + ipLen + longLen + idLen + baseIPCertDescLen = 2*wrappers.IntLen + IPPortLen + wrappers.LongLen + ids.IDLen + preimageLen = ids.IDLen + wrappers.LongLen ) // A self contained proof that a peer is claiming ownership of an IPPort at a @@ -32,12 +30,36 @@ type ClaimedIPPort struct { // actually claimed by the peer in question, and not by a malicious peer // trying to get us to dial bogus IPPorts. Signature []byte - // The txID that added this peer into the validator set - TxID ids.ID + // NodeID derived from the peer certificate. + NodeID ids.NodeID + // GossipID derived from the nodeID and timestamp. + GossipID ids.ID } -// Returns the length of the byte representation of this ClaimedIPPort. -func (i *ClaimedIPPort) BytesLen() int { - // See wrappers.PackPeerTrackInfo. +func NewClaimedIPPort( + cert *staking.Certificate, + ipPort IPPort, + timestamp uint64, + signature []byte, +) *ClaimedIPPort { + ip := &ClaimedIPPort{ + Cert: cert, + IPPort: ipPort, + Timestamp: timestamp, + Signature: signature, + NodeID: ids.NodeIDFromCert(cert), + } + + packer := wrappers.Packer{ + Bytes: make([]byte, preimageLen), + } + packer.PackFixedBytes(ip.NodeID[:]) + packer.PackLong(timestamp) + ip.GossipID = hashing.ComputeHash256Array(packer.Bytes) + return ip +} + +// Returns the approximate size of the binary representation of this ClaimedIPPort. +func (i *ClaimedIPPort) Size() int { return baseIPCertDescLen + len(i.Cert.Raw) + len(i.Signature) } diff --git a/utils/ips/dynamic_ip_port.go b/utils/ips/dynamic_ip_port.go index 3f30dc0a24b5..0b83ab5924f1 100644 --- a/utils/ips/dynamic_ip_port.go +++ b/utils/ips/dynamic_ip_port.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ips diff --git a/utils/ips/ip_port.go b/utils/ips/ip_port.go index 3ca5bfe176d4..a60e6300ed49 100644 --- a/utils/ips/ip_port.go +++ b/utils/ips/ip_port.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ips @@ -12,7 +12,10 @@ import ( "github.com/ava-labs/avalanchego/utils/wrappers" ) -const nullStr = "null" +const ( + IPPortLen = 16 + wrappers.ShortLen + nullStr = "null" +) var ( errMissingQuotes = errors.New("first and last characters should be quotes") diff --git a/utils/ips/ip_test.go b/utils/ips/ip_test.go index 30a72017e6da..903f26a2d070 100644 --- a/utils/ips/ip_test.go +++ b/utils/ips/ip_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ips diff --git a/utils/ips/lookup.go b/utils/ips/lookup.go index 8ae3de470ff0..cdf9176f9568 100644 --- a/utils/ips/lookup.go +++ b/utils/ips/lookup.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ips diff --git a/utils/ips/lookup_test.go b/utils/ips/lookup_test.go index 52c0e5eda860..9fecccc54593 100644 --- a/utils/ips/lookup_test.go +++ b/utils/ips/lookup_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ips diff --git a/utils/json/codec.go b/utils/json/codec.go index 5871d67fd793..0bf51dcee2c2 100644 --- a/utils/json/codec.go +++ b/utils/json/codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package json diff --git a/utils/json/float32.go b/utils/json/float32.go index a20ee2bde525..ca35a760e3be 100644 --- a/utils/json/float32.go +++ b/utils/json/float32.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package json @@ -8,7 +8,7 @@ import "strconv" type Float32 float32 func (f Float32) MarshalJSON() ([]byte, error) { - return []byte("\"" + strconv.FormatFloat(float64(f), byte('f'), 4, 32) + "\""), nil + return []byte(`"` + strconv.FormatFloat(float64(f), byte('f'), 4, 32) + `"`), nil } func (f *Float32) UnmarshalJSON(b []byte) error { diff --git a/utils/json/float32_test.go b/utils/json/float32_test.go index 3d336927ced5..519ca7f4d561 100644 --- a/utils/json/float32_test.go +++ b/utils/json/float32_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package json diff --git a/utils/json/float64.go b/utils/json/float64.go index 4d31459ecf19..80fb8ae738da 100644 --- a/utils/json/float64.go +++ b/utils/json/float64.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package json @@ -8,7 +8,7 @@ import "strconv" type Float64 float64 func (f Float64) MarshalJSON() ([]byte, error) { - return []byte("\"" + strconv.FormatFloat(float64(f), byte('f'), 4, 64) + "\""), nil + return []byte(`"` + strconv.FormatFloat(float64(f), byte('f'), 4, 64) + `"`), nil } func (f *Float64) UnmarshalJSON(b []byte) error { diff --git a/utils/json/uint16.go b/utils/json/uint16.go index c2c8b6da378a..03e0f133d1a9 100644 --- a/utils/json/uint16.go +++ b/utils/json/uint16.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package json @@ -8,7 +8,7 @@ import "strconv" type Uint16 uint16 func (u Uint16) MarshalJSON() ([]byte, error) { - return []byte("\"" + strconv.FormatUint(uint64(u), 10) + "\""), nil + return []byte(`"` + strconv.FormatUint(uint64(u), 10) + `"`), nil } func (u *Uint16) UnmarshalJSON(b []byte) error { diff --git a/utils/json/uint32.go b/utils/json/uint32.go index 0bd0f28544d1..bae5b8857496 100644 --- a/utils/json/uint32.go +++ b/utils/json/uint32.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package json @@ -8,7 +8,7 @@ import "strconv" type Uint32 uint32 func (u Uint32) MarshalJSON() ([]byte, error) { - return []byte("\"" + strconv.FormatUint(uint64(u), 10) + "\""), nil + return []byte(`"` + strconv.FormatUint(uint64(u), 10) + `"`), nil } func (u *Uint32) UnmarshalJSON(b []byte) error { diff --git a/utils/json/uint64.go b/utils/json/uint64.go index ba3189039d67..60bc99887439 100644 --- a/utils/json/uint64.go +++ b/utils/json/uint64.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package json @@ -8,7 +8,7 @@ import "strconv" type Uint64 uint64 func (u Uint64) MarshalJSON() ([]byte, error) { - return []byte("\"" + strconv.FormatUint(uint64(u), 10) + "\""), nil + return []byte(`"` + strconv.FormatUint(uint64(u), 10) + `"`), nil } func (u *Uint64) UnmarshalJSON(b []byte) error { diff --git a/utils/json/uint8.go b/utils/json/uint8.go index c43f2786daf6..da2ca5270a83 100644 --- a/utils/json/uint8.go +++ b/utils/json/uint8.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package json @@ -8,7 +8,7 @@ import "strconv" type Uint8 uint8 func (u Uint8) MarshalJSON() ([]byte, error) { - return []byte("\"" + strconv.FormatUint(uint64(u), 10) + "\""), nil + return []byte(`"` + strconv.FormatUint(uint64(u), 10) + `"`), nil } func (u *Uint8) UnmarshalJSON(b []byte) error { diff --git a/utils/linkedhashmap/iterator.go b/utils/linkedhashmap/iterator.go index 306e41e872fd..a2869aac2a54 100644 --- a/utils/linkedhashmap/iterator.go +++ b/utils/linkedhashmap/iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package linkedhashmap diff --git a/utils/linkedhashmap/linkedhashmap.go b/utils/linkedhashmap/linkedhashmap.go index fa5a123a0942..9ae5b83ad7ae 100644 --- a/utils/linkedhashmap/linkedhashmap.go +++ b/utils/linkedhashmap/linkedhashmap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package linkedhashmap diff --git a/utils/linkedhashmap/linkedhashmap_test.go b/utils/linkedhashmap/linkedhashmap_test.go index 0c95c30b24a8..372bd24baa4c 100644 --- a/utils/linkedhashmap/linkedhashmap_test.go +++ b/utils/linkedhashmap/linkedhashmap_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package linkedhashmap diff --git a/utils/logging/color.go b/utils/logging/color.go index 323d1d1373f5..8fb7a8b6a29d 100644 --- a/utils/logging/color.go +++ b/utils/logging/color.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging diff --git a/utils/logging/config.go b/utils/logging/config.go index baeb666d753f..06d7f8ca92c4 100644 --- a/utils/logging/config.go +++ b/utils/logging/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging diff --git a/utils/logging/factory.go b/utils/logging/factory.go index b3426257956f..e6bb90272282 100644 --- a/utils/logging/factory.go +++ b/utils/logging/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging diff --git a/utils/logging/format.go b/utils/logging/format.go index 5ea9de28f087..53313c3dad8f 100644 --- a/utils/logging/format.go +++ b/utils/logging/format.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging diff --git a/utils/logging/level.go b/utils/logging/level.go index 173c606c44e2..7c1696b3202f 100644 --- a/utils/logging/level.go +++ b/utils/logging/level.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging diff --git a/utils/logging/log.go b/utils/logging/log.go index a5e6c9d79a05..b9fc8f796ce7 100644 --- a/utils/logging/log.go +++ b/utils/logging/log.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging diff --git a/utils/logging/log_test.go b/utils/logging/log_test.go index c968747ba726..cd7396b6ac6a 100644 --- a/utils/logging/log_test.go +++ b/utils/logging/log_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging diff --git a/utils/logging/logger.go b/utils/logging/logger.go index 50d035e02fa8..2ca95bff104c 100644 --- a/utils/logging/logger.go +++ b/utils/logging/logger.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging diff --git a/utils/logging/mock_logger.go b/utils/logging/mock_logger.go deleted file mode 100644 index ba1079d30a09..000000000000 --- a/utils/logging/mock_logger.go +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/utils/logging (interfaces: Logger) - -// Package logging is a generated GoMock package. -package logging - -import ( - reflect "reflect" - - gomock "go.uber.org/mock/gomock" - zapcore "go.uber.org/zap/zapcore" -) - -// MockLogger is a mock of Logger interface. -type MockLogger struct { - ctrl *gomock.Controller - recorder *MockLoggerMockRecorder -} - -// MockLoggerMockRecorder is the mock recorder for MockLogger. -type MockLoggerMockRecorder struct { - mock *MockLogger -} - -// NewMockLogger creates a new mock instance. -func NewMockLogger(ctrl *gomock.Controller) *MockLogger { - mock := &MockLogger{ctrl: ctrl} - mock.recorder = &MockLoggerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockLogger) EXPECT() *MockLoggerMockRecorder { - return m.recorder -} - -// Debug mocks base method. -func (m *MockLogger) Debug(arg0 string, arg1 ...zapcore.Field) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Debug", varargs...) -} - -// Debug indicates an expected call of Debug. -func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), varargs...) -} - -// Enabled mocks base method. -func (m *MockLogger) Enabled(arg0 Level) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Enabled", arg0) - ret0, _ := ret[0].(bool) - return ret0 -} - -// Enabled indicates an expected call of Enabled. -func (mr *MockLoggerMockRecorder) Enabled(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Enabled", reflect.TypeOf((*MockLogger)(nil).Enabled), arg0) -} - -// Error mocks base method. -func (m *MockLogger) Error(arg0 string, arg1 ...zapcore.Field) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Error", varargs...) -} - -// Error indicates an expected call of Error. -func (mr *MockLoggerMockRecorder) Error(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), varargs...) -} - -// Fatal mocks base method. -func (m *MockLogger) Fatal(arg0 string, arg1 ...zapcore.Field) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Fatal", varargs...) -} - -// Fatal indicates an expected call of Fatal. -func (mr *MockLoggerMockRecorder) Fatal(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatal", reflect.TypeOf((*MockLogger)(nil).Fatal), varargs...) -} - -// Info mocks base method. -func (m *MockLogger) Info(arg0 string, arg1 ...zapcore.Field) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Info", varargs...) -} - -// Info indicates an expected call of Info. -func (mr *MockLoggerMockRecorder) Info(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), varargs...) -} - -// RecoverAndExit mocks base method. -func (m *MockLogger) RecoverAndExit(arg0, arg1 func()) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "RecoverAndExit", arg0, arg1) -} - -// RecoverAndExit indicates an expected call of RecoverAndExit. -func (mr *MockLoggerMockRecorder) RecoverAndExit(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecoverAndExit", reflect.TypeOf((*MockLogger)(nil).RecoverAndExit), arg0, arg1) -} - -// RecoverAndPanic mocks base method. -func (m *MockLogger) RecoverAndPanic(arg0 func()) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "RecoverAndPanic", arg0) -} - -// RecoverAndPanic indicates an expected call of RecoverAndPanic. -func (mr *MockLoggerMockRecorder) RecoverAndPanic(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecoverAndPanic", reflect.TypeOf((*MockLogger)(nil).RecoverAndPanic), arg0) -} - -// SetLevel mocks base method. -func (m *MockLogger) SetLevel(arg0 Level) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetLevel", arg0) -} - -// SetLevel indicates an expected call of SetLevel. -func (mr *MockLoggerMockRecorder) SetLevel(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLevel", reflect.TypeOf((*MockLogger)(nil).SetLevel), arg0) -} - -// Stop mocks base method. -func (m *MockLogger) Stop() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "Stop") -} - -// Stop indicates an expected call of Stop. -func (mr *MockLoggerMockRecorder) Stop() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockLogger)(nil).Stop)) -} - -// StopOnPanic mocks base method. -func (m *MockLogger) StopOnPanic() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "StopOnPanic") -} - -// StopOnPanic indicates an expected call of StopOnPanic. -func (mr *MockLoggerMockRecorder) StopOnPanic() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopOnPanic", reflect.TypeOf((*MockLogger)(nil).StopOnPanic)) -} - -// Trace mocks base method. -func (m *MockLogger) Trace(arg0 string, arg1 ...zapcore.Field) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Trace", varargs...) -} - -// Trace indicates an expected call of Trace. -func (mr *MockLoggerMockRecorder) Trace(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trace", reflect.TypeOf((*MockLogger)(nil).Trace), varargs...) -} - -// Verbo mocks base method. -func (m *MockLogger) Verbo(arg0 string, arg1 ...zapcore.Field) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Verbo", varargs...) -} - -// Verbo indicates an expected call of Verbo. -func (mr *MockLoggerMockRecorder) Verbo(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verbo", reflect.TypeOf((*MockLogger)(nil).Verbo), varargs...) -} - -// Warn mocks base method. -func (m *MockLogger) Warn(arg0 string, arg1 ...zapcore.Field) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - m.ctrl.Call(m, "Warn", varargs...) -} - -// Warn indicates an expected call of Warn. -func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), varargs...) -} - -// Write mocks base method. -func (m *MockLogger) Write(arg0 []byte) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Write", arg0) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Write indicates an expected call of Write. -func (mr *MockLoggerMockRecorder) Write(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockLogger)(nil).Write), arg0) -} diff --git a/utils/logging/sanitize.go b/utils/logging/sanitize.go index 05b24ff96be2..18fc4021d965 100644 --- a/utils/logging/sanitize.go +++ b/utils/logging/sanitize.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging @@ -12,7 +12,7 @@ import ( type sanitizedString string func (s sanitizedString) String() string { - return strings.ReplaceAll(string(s), "\n", "\\n") + return strings.ReplaceAll(string(s), "\n", `\n`) } // UserString constructs a field with the given key and the value stripped of @@ -29,7 +29,7 @@ func (s sanitizedStrings) String() string { if i != 0 { _, _ = strs.WriteString(", ") } - _, _ = strs.WriteString(strings.ReplaceAll(str, "\n", "\\n")) + _, _ = strs.WriteString(strings.ReplaceAll(str, "\n", `\n`)) } return strs.String() } diff --git a/utils/logging/test_log.go b/utils/logging/test_log.go index 7df7df276a15..a8a85dc78e07 100644 --- a/utils/logging/test_log.go +++ b/utils/logging/test_log.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package logging diff --git a/utils/math/averager.go b/utils/math/averager.go index 14147d7ef54f..8573fbc80bb7 100644 --- a/utils/math/averager.go +++ b/utils/math/averager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package math diff --git a/utils/math/averager_heap.go b/utils/math/averager_heap.go index 070593f0eeb8..57d046786a99 100644 --- a/utils/math/averager_heap.go +++ b/utils/math/averager_heap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package math diff --git a/utils/math/averager_heap_test.go b/utils/math/averager_heap_test.go index 0586eb77947e..94f160a3a388 100644 --- a/utils/math/averager_heap_test.go +++ b/utils/math/averager_heap_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package math diff --git a/utils/math/continuous_averager.go b/utils/math/continuous_averager.go index e60832f23a58..7bc892576b9f 100644 --- a/utils/math/continuous_averager.go +++ b/utils/math/continuous_averager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package math diff --git a/utils/math/continuous_averager_benchmark_test.go b/utils/math/continuous_averager_benchmark_test.go index 7a8d30a3736c..3ebee526997d 100644 --- a/utils/math/continuous_averager_benchmark_test.go +++ b/utils/math/continuous_averager_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package math diff --git a/utils/math/continuous_averager_test.go b/utils/math/continuous_averager_test.go index 16f0f6913b90..c169f3903066 100644 --- a/utils/math/continuous_averager_test.go +++ b/utils/math/continuous_averager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package math diff --git a/utils/math/meter/continuous_meter.go b/utils/math/meter/continuous_meter.go index 4bd3f000524e..378248a15027 100644 --- a/utils/math/meter/continuous_meter.go +++ b/utils/math/meter/continuous_meter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package meter diff --git a/utils/math/meter/factory.go b/utils/math/meter/factory.go index 3fe1c3bb04b4..a4d3722e8f3c 100644 --- a/utils/math/meter/factory.go +++ b/utils/math/meter/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package meter diff --git a/utils/math/meter/meter.go b/utils/math/meter/meter.go index 07c03e37017a..e9ec6782b8c6 100644 --- a/utils/math/meter/meter.go +++ b/utils/math/meter/meter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package meter diff --git a/utils/math/meter/meter_benchmark_test.go b/utils/math/meter/meter_benchmark_test.go index 65f3dcfa56e0..80ed1ad9573c 100644 --- a/utils/math/meter/meter_benchmark_test.go +++ b/utils/math/meter/meter_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package meter diff --git a/utils/math/meter/meter_test.go b/utils/math/meter/meter_test.go index 9bffb77d9d1e..cbe1bd806b61 100644 --- a/utils/math/meter/meter_test.go +++ b/utils/math/meter/meter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package meter diff --git a/utils/math/safe_math.go b/utils/math/safe_math.go index 834547589806..8397327414a7 100644 --- a/utils/math/safe_math.go +++ b/utils/math/safe_math.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package math diff --git a/utils/math/safe_math_test.go b/utils/math/safe_math_test.go index b4ef771eb45e..fc89d2f7f639 100644 --- a/utils/math/safe_math_test.go +++ b/utils/math/safe_math_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package math diff --git a/utils/math/sync_averager.go b/utils/math/sync_averager.go index cbe8ba107f7a..92210ab4ca46 100644 --- a/utils/math/sync_averager.go +++ b/utils/math/sync_averager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package math diff --git a/utils/maybe/maybe.go b/utils/maybe/maybe.go index b4dfc7f9a452..fd50b41534c7 100644 --- a/utils/maybe/maybe.go +++ b/utils/maybe/maybe.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package maybe diff --git a/utils/maybe/maybe_test.go b/utils/maybe/maybe_test.go index 60b599000d0a..93d5fc32e85a 100644 --- a/utils/maybe/maybe_test.go +++ b/utils/maybe/maybe_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package maybe diff --git a/utils/metric/api_interceptor.go b/utils/metric/api_interceptor.go index ab8e4fd8d70c..67ae1a936742 100644 --- a/utils/metric/api_interceptor.go +++ b/utils/metric/api_interceptor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metric diff --git a/utils/metric/averager.go b/utils/metric/averager.go index da7f0aa5bd48..0f461a567ee7 100644 --- a/utils/metric/averager.go +++ b/utils/metric/averager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metric diff --git a/utils/password/hash.go b/utils/password/hash.go index 19c6f731f414..fc304bd50118 100644 --- a/utils/password/hash.go +++ b/utils/password/hash.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package password diff --git a/utils/password/hash_test.go b/utils/password/hash_test.go index c2d87a9744c4..07f56d0304f3 100644 --- a/utils/password/hash_test.go +++ b/utils/password/hash_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package password diff --git a/utils/password/password.go b/utils/password/password.go index 1efe3ac8a4f9..fa4d240eed2e 100644 --- a/utils/password/password.go +++ b/utils/password/password.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package password diff --git a/utils/password/password_test.go b/utils/password/password_test.go index 0b445fbf8545..2c4f534c857a 100644 --- a/utils/password/password_test.go +++ b/utils/password/password_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package password diff --git a/utils/perms/chmod.go b/utils/perms/chmod.go index 5b4ff4a3b02c..a5a8710b97b8 100644 --- a/utils/perms/chmod.go +++ b/utils/perms/chmod.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package perms diff --git a/utils/perms/create.go b/utils/perms/create.go index 8d91baea0683..123637e13443 100644 --- a/utils/perms/create.go +++ b/utils/perms/create.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package perms diff --git a/utils/perms/perms.go b/utils/perms/perms.go index e89dcc949984..0bb633d900f7 100644 --- a/utils/perms/perms.go +++ b/utils/perms/perms.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package perms diff --git a/utils/perms/write_file.go b/utils/perms/write_file.go index 4671c8bc66fe..f716459ab678 100644 --- a/utils/perms/write_file.go +++ b/utils/perms/write_file.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package perms diff --git a/utils/profiler/continuous.go b/utils/profiler/continuous.go index 548e88779b22..1f698395fd25 100644 --- a/utils/profiler/continuous.go +++ b/utils/profiler/continuous.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package profiler @@ -37,7 +37,7 @@ type continuousProfiler struct { func NewContinuous(dir string, freq time.Duration, maxNumFiles int) ContinuousProfiler { return &continuousProfiler{ - profiler: new(dir), + profiler: newProfiler(dir), freq: freq, maxNumFiles: maxNumFiles, closer: make(chan struct{}), diff --git a/utils/profiler/profiler.go b/utils/profiler/profiler.go index c35606e7df91..00a540cc9766 100644 --- a/utils/profiler/profiler.go +++ b/utils/profiler/profiler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package profiler @@ -23,6 +23,8 @@ const ( ) var ( + _ Profiler = (*profiler)(nil) + errCPUProfilerRunning = errors.New("cpu profiler already running") errCPUProfilerNotRunning = errors.New("cpu profiler doesn't exist") ) @@ -53,10 +55,10 @@ type profiler struct { } func New(dir string) Profiler { - return new(dir) + return newProfiler(dir) } -func new(dir string) *profiler { +func newProfiler(dir string) *profiler { return &profiler{ dir: dir, cpuProfileName: filepath.Join(dir, cpuProfileFile), diff --git a/utils/profiler/profiler_test.go b/utils/profiler/profiler_test.go index 2d7d864aca62..17ae695e38c2 100644 --- a/utils/profiler/profiler_test.go +++ b/utils/profiler/profiler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package profiler diff --git a/utils/resource/metrics.go b/utils/resource/metrics.go index e20458c42fb1..3ce87ade258c 100644 --- a/utils/resource/metrics.go +++ b/utils/resource/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package resource diff --git a/utils/resource/mock_user.go b/utils/resource/mock_user.go index 64b156ac1c1f..d333f2c58e4b 100644 --- a/utils/resource/mock_user.go +++ b/utils/resource/mock_user.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/utils/resource (interfaces: User) +// +// Generated by this command: +// +// mockgen -package=resource -destination=utils/resource/mock_user.go github.com/ava-labs/avalanchego/utils/resource User +// // Package resource is a generated GoMock package. package resource diff --git a/utils/resource/no_usage.go b/utils/resource/no_usage.go index 8a10d11ced2a..baa42437fc11 100644 --- a/utils/resource/no_usage.go +++ b/utils/resource/no_usage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package resource diff --git a/utils/resource/usage.go b/utils/resource/usage.go index d5a06d990c85..1eedbee04c14 100644 --- a/utils/resource/usage.go +++ b/utils/resource/usage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package resource diff --git a/utils/resource/usage_test.go b/utils/resource/usage_test.go index 5c1df7814f6a..b0ee74ec07a1 100644 --- a/utils/resource/usage_test.go +++ b/utils/resource/usage_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package resource diff --git a/utils/rpc/json.go b/utils/rpc/json.go index 72ddb3aac8a7..9b87661ea529 100644 --- a/utils/rpc/json.go +++ b/utils/rpc/json.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpc diff --git a/utils/rpc/options.go b/utils/rpc/options.go index ce79bc25920e..79c32c72b152 100644 --- a/utils/rpc/options.go +++ b/utils/rpc/options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpc diff --git a/utils/rpc/requester.go b/utils/rpc/requester.go index 49f3ffa0ef6d..6f2e312f66da 100644 --- a/utils/rpc/requester.go +++ b/utils/rpc/requester.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpc diff --git a/utils/sampler/rand.go b/utils/sampler/rand.go index 7ec14e6b275a..ce62d4a90ffc 100644 --- a/utils/sampler/rand.go +++ b/utils/sampler/rand.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -21,26 +21,15 @@ func newRNG() *rng { return &rng{rng: source} } -func Seed(seed int64) { - globalRNG.Seed(seed) -} - -type source interface { - Seed(uint64) - Uint64() uint64 -} - type rng struct { lock sync.Mutex - rng source + rng Source } -// Seed uses the provided seed value to initialize the generator to a -// deterministic state. -func (r *rng) Seed(seed int64) { - r.lock.Lock() - r.rng.Seed(uint64(seed)) - r.lock.Unlock() +type Source interface { + // Uint64 returns a random number in [0, MaxUint64] and advances the + // generator's state. + Uint64() uint64 } // Uint64Inclusive returns a pseudo-random number in [0,n]. diff --git a/utils/sampler/rand_test.go b/utils/sampler/rand_test.go index 362093a695ac..febffa60a4ec 100644 --- a/utils/sampler/rand_test.go +++ b/utils/sampler/rand_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/require" "github.com/thepudds/fzgen/fuzzer" + + "gonum.org/v1/gonum/mathext/prng" ) type testSource struct { @@ -208,3 +210,21 @@ func FuzzRNG(f *testing.F) { require.Len(stdSource.nums, len(source.nums)) }) } + +func BenchmarkSeed32(b *testing.B) { + source := prng.NewMT19937() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + source.Seed(0) + } +} + +func BenchmarkSeed64(b *testing.B) { + source := prng.NewMT19937_64() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + source.Seed(0) + } +} diff --git a/utils/sampler/uniform.go b/utils/sampler/uniform.go index 65c97f40b1a8..5ae9a21d8822 100644 --- a/utils/sampler/uniform.go +++ b/utils/sampler/uniform.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -11,14 +11,22 @@ type Uniform interface { // negative the implementation may panic. Sample(length int) ([]uint64, error) - Seed(int64) - ClearSeed() - Reset() Next() (uint64, error) } // NewUniform returns a new sampler func NewUniform() Uniform { - return &uniformReplacer{} + return &uniformReplacer{ + rng: globalRNG, + } +} + +// NewDeterministicUniform returns a new sampler +func NewDeterministicUniform(source Source) Uniform { + return &uniformReplacer{ + rng: &rng{ + rng: source, + }, + } } diff --git a/utils/sampler/uniform_benchmark_test.go b/utils/sampler/uniform_benchmark_test.go index 51d180b33db9..915fe45cc749 100644 --- a/utils/sampler/uniform_benchmark_test.go +++ b/utils/sampler/uniform_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler diff --git a/utils/sampler/uniform_best.go b/utils/sampler/uniform_best.go index 9ce1ed7f7187..21f7870d5bdc 100644 --- a/utils/sampler/uniform_best.go +++ b/utils/sampler/uniform_best.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -29,8 +29,12 @@ type uniformBest struct { func NewBestUniform(expectedSampleSize int) Uniform { return &uniformBest{ samplers: []Uniform{ - &uniformReplacer{}, - &uniformResample{}, + &uniformReplacer{ + rng: globalRNG, + }, + &uniformResample{ + rng: globalRNG, + }, }, maxSampleSize: expectedSampleSize, benchmarkIterations: 100, diff --git a/utils/sampler/uniform_replacer.go b/utils/sampler/uniform_replacer.go index 9ac1811c4b47..98d3e5acbe0d 100644 --- a/utils/sampler/uniform_replacer.go +++ b/utils/sampler/uniform_replacer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -27,15 +27,12 @@ func (m defaultMap) get(key uint64, defaultVal uint64) uint64 { // Sampling is performed in O(count) time and O(count) space. type uniformReplacer struct { rng *rng - seededRNG *rng length uint64 drawn defaultMap drawsCount uint64 } func (s *uniformReplacer) Initialize(length uint64) { - s.rng = globalRNG - s.seededRNG = newRNG() s.length = length s.drawn = make(defaultMap) s.drawsCount = 0 @@ -55,15 +52,6 @@ func (s *uniformReplacer) Sample(count int) ([]uint64, error) { return results, nil } -func (s *uniformReplacer) Seed(seed int64) { - s.rng = s.seededRNG - s.rng.Seed(seed) -} - -func (s *uniformReplacer) ClearSeed() { - s.rng = globalRNG -} - func (s *uniformReplacer) Reset() { maps.Clear(s.drawn) s.drawsCount = 0 diff --git a/utils/sampler/uniform_resample.go b/utils/sampler/uniform_resample.go index 8f09e95f777c..1e48d51fa421 100644 --- a/utils/sampler/uniform_resample.go +++ b/utils/sampler/uniform_resample.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -15,15 +15,12 @@ import "golang.org/x/exp/maps" // // Sampling is performed in O(count) time and O(count) space. type uniformResample struct { - rng *rng - seededRNG *rng - length uint64 - drawn map[uint64]struct{} + rng *rng + length uint64 + drawn map[uint64]struct{} } func (s *uniformResample) Initialize(length uint64) { - s.rng = globalRNG - s.seededRNG = newRNG() s.length = length s.drawn = make(map[uint64]struct{}) } @@ -42,15 +39,6 @@ func (s *uniformResample) Sample(count int) ([]uint64, error) { return results, nil } -func (s *uniformResample) Seed(seed int64) { - s.rng = s.seededRNG - s.rng.Seed(seed) -} - -func (s *uniformResample) ClearSeed() { - s.rng = globalRNG -} - func (s *uniformResample) Reset() { maps.Clear(s.drawn) } diff --git a/utils/sampler/uniform_test.go b/utils/sampler/uniform_test.go index e5b00af31c26..451a26625424 100644 --- a/utils/sampler/uniform_test.go +++ b/utils/sampler/uniform_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -19,12 +19,16 @@ var ( sampler Uniform }{ { - name: "replacer", - sampler: &uniformReplacer{}, + name: "replacer", + sampler: &uniformReplacer{ + rng: globalRNG, + }, }, { - name: "resampler", - sampler: &uniformResample{}, + name: "resampler", + sampler: &uniformResample{ + rng: globalRNG, + }, }, { name: "best", @@ -156,53 +160,3 @@ func UniformLazilySample(t *testing.T, s Uniform) { s.Reset() } } - -func TestSeeding(t *testing.T) { - require := require.New(t) - - s1 := NewBestUniform(30) - s2 := NewBestUniform(30) - - s1.Initialize(50) - s2.Initialize(50) - - s1.Seed(0) - - s1.Reset() - s1Val, err := s1.Next() - require.NoError(err) - - s2.Seed(1) - s2.Reset() - - s1.Seed(0) - v, err := s2.Next() - require.NoError(err) - require.NotEqual(s1Val, v) - - s1.ClearSeed() - - _, err = s1.Next() - require.NoError(err) -} - -func TestSeedingProducesTheSame(t *testing.T) { - require := require.New(t) - - s := NewBestUniform(30) - - s.Initialize(50) - - s.Seed(0) - s.Reset() - - val0, err := s.Next() - require.NoError(err) - - s.Seed(0) - s.Reset() - - val1, err := s.Next() - require.NoError(err) - require.Equal(val0, val1) -} diff --git a/utils/sampler/weighted.go b/utils/sampler/weighted.go index 69212d4351c2..2296da08e97a 100644 --- a/utils/sampler/weighted.go +++ b/utils/sampler/weighted.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler diff --git a/utils/sampler/weighted_array.go b/utils/sampler/weighted_array.go index 0db1dda17af9..e13788422b85 100644 --- a/utils/sampler/weighted_array.go +++ b/utils/sampler/weighted_array.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -19,8 +19,8 @@ type weightedArrayElement struct { } // Note that this sorts in order of decreasing weight. -func (e weightedArrayElement) Less(other weightedArrayElement) bool { - return e.cumulativeWeight > other.cumulativeWeight +func (e weightedArrayElement) Compare(other weightedArrayElement) int { + return utils.Compare(other.cumulativeWeight, e.cumulativeWeight) } // Sampling is performed by executing a modified binary search over the provided diff --git a/utils/sampler/weighted_array_test.go b/utils/sampler/weighted_array_test.go index e10583633436..866a0c7d2f2c 100644 --- a/utils/sampler/weighted_array_test.go +++ b/utils/sampler/weighted_array_test.go @@ -1,27 +1,42 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler import ( + "fmt" "testing" "github.com/stretchr/testify/require" ) -func TestWeightedArrayElementLess(t *testing.T) { - require := require.New(t) - - var elt1, elt2 weightedArrayElement - require.False(elt1.Less(elt2)) - require.False(elt2.Less(elt1)) - - elt1 = weightedArrayElement{ - cumulativeWeight: 1, +func TestWeightedArrayElementCompare(t *testing.T) { + tests := []struct { + a weightedArrayElement + b weightedArrayElement + expected int + }{ + { + a: weightedArrayElement{}, + b: weightedArrayElement{}, + expected: 0, + }, + { + a: weightedArrayElement{ + cumulativeWeight: 1, + }, + b: weightedArrayElement{ + cumulativeWeight: 2, + }, + expected: 1, + }, } - elt2 = weightedArrayElement{ - cumulativeWeight: 2, + for _, test := range tests { + t.Run(fmt.Sprintf("%d_%d_%d", test.a.cumulativeWeight, test.b.cumulativeWeight, test.expected), func(t *testing.T) { + require := require.New(t) + + require.Equal(test.expected, test.a.Compare(test.b)) + require.Equal(-test.expected, test.b.Compare(test.a)) + }) } - require.False(elt1.Less(elt2)) - require.True(elt2.Less(elt1)) } diff --git a/utils/sampler/weighted_benchmark_test.go b/utils/sampler/weighted_benchmark_test.go index 22c1a5f7def7..897e001935a1 100644 --- a/utils/sampler/weighted_benchmark_test.go +++ b/utils/sampler/weighted_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler diff --git a/utils/sampler/weighted_best.go b/utils/sampler/weighted_best.go index 276a9b475a9d..59bf60019144 100644 --- a/utils/sampler/weighted_best.go +++ b/utils/sampler/weighted_best.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler diff --git a/utils/sampler/weighted_heap.go b/utils/sampler/weighted_heap.go index 4b7fb84df482..866c23893fc2 100644 --- a/utils/sampler/weighted_heap.go +++ b/utils/sampler/weighted_heap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -19,17 +19,16 @@ type weightedHeapElement struct { index int } -func (e weightedHeapElement) Less(other weightedHeapElement) bool { +// Compare the elements. Weight is in decreasing order. Index is in increasing +// order. +func (e weightedHeapElement) Compare(other weightedHeapElement) int { // By accounting for the initial index of the weights, this results in a // stable sort. We do this rather than using `sort.Stable` because of the // reported change in performance of the sort used. - if e.weight > other.weight { - return true + if weightCmp := utils.Compare(other.weight, e.weight); weightCmp != 0 { + return weightCmp } - if e.weight < other.weight { - return false - } - return e.index < other.index + return utils.Compare(e.index, other.index) } // Sampling is performed by executing a search over a tree of elements in the diff --git a/utils/sampler/weighted_heap_test.go b/utils/sampler/weighted_heap_test.go index 3187c14fa10a..03aa94dfa2f9 100644 --- a/utils/sampler/weighted_heap_test.go +++ b/utils/sampler/weighted_heap_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -23,57 +23,44 @@ func TestWeightedHeapInitialize(t *testing.T) { } } -func TestWeightedHeapElementLess(t *testing.T) { +func TestWeightedHeapElementCompare(t *testing.T) { type test struct { name string elt1 weightedHeapElement elt2 weightedHeapElement - expected bool + expected int } tests := []test{ { name: "all same", elt1: weightedHeapElement{}, elt2: weightedHeapElement{}, - expected: false, + expected: 0, }, { - name: "first lower weight", + name: "lower weight", elt1: weightedHeapElement{}, elt2: weightedHeapElement{ weight: 1, }, - expected: false, + expected: 1, }, { - name: "first higher weight", - elt1: weightedHeapElement{ - weight: 1, - }, - elt2: weightedHeapElement{}, - expected: true, - }, - { - name: "first higher index", + name: "higher index", elt1: weightedHeapElement{ index: 1, }, elt2: weightedHeapElement{}, - expected: false, - }, - { - name: "second higher index", - elt1: weightedHeapElement{}, - elt2: weightedHeapElement{ - index: 1, - }, - expected: true, + expected: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.expected, tt.elt1.Less(tt.elt2)) + require := require.New(t) + + require.Equal(tt.expected, tt.elt1.Compare(tt.elt2)) + require.Equal(-tt.expected, tt.elt2.Compare(tt.elt1)) }) } } diff --git a/utils/sampler/weighted_linear.go b/utils/sampler/weighted_linear.go index a5d0e3b16711..496613aea6dc 100644 --- a/utils/sampler/weighted_linear.go +++ b/utils/sampler/weighted_linear.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -19,8 +19,8 @@ type weightedLinearElement struct { } // Note that this sorts in order of decreasing cumulative weight. -func (e weightedLinearElement) Less(other weightedLinearElement) bool { - return e.cumulativeWeight > other.cumulativeWeight +func (e weightedLinearElement) Compare(other weightedLinearElement) int { + return utils.Compare(other.cumulativeWeight, e.cumulativeWeight) } // Sampling is performed by executing a linear search over the provided elements diff --git a/utils/sampler/weighted_linear_test.go b/utils/sampler/weighted_linear_test.go index b34035017b4a..dd86679de627 100644 --- a/utils/sampler/weighted_linear_test.go +++ b/utils/sampler/weighted_linear_test.go @@ -1,27 +1,42 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler import ( + "fmt" "testing" "github.com/stretchr/testify/require" ) -func TestWeightedLinearElementLess(t *testing.T) { - require := require.New(t) - - var elt1, elt2 weightedLinearElement - require.False(elt1.Less(elt2)) - require.False(elt2.Less(elt1)) - - elt1 = weightedLinearElement{ - cumulativeWeight: 1, +func TestWeightedLinearElementCompare(t *testing.T) { + tests := []struct { + a weightedLinearElement + b weightedLinearElement + expected int + }{ + { + a: weightedLinearElement{}, + b: weightedLinearElement{}, + expected: 0, + }, + { + a: weightedLinearElement{ + cumulativeWeight: 1, + }, + b: weightedLinearElement{ + cumulativeWeight: 2, + }, + expected: 1, + }, } - elt2 = weightedLinearElement{ - cumulativeWeight: 2, + for _, test := range tests { + t.Run(fmt.Sprintf("%d_%d_%d", test.a.cumulativeWeight, test.b.cumulativeWeight, test.expected), func(t *testing.T) { + require := require.New(t) + + require.Equal(test.expected, test.a.Compare(test.b)) + require.Equal(-test.expected, test.b.Compare(test.a)) + }) } - require.False(elt1.Less(elt2)) - require.True(elt2.Less(elt1)) } diff --git a/utils/sampler/weighted_test.go b/utils/sampler/weighted_test.go index d67d86251c69..ea08230d175a 100644 --- a/utils/sampler/weighted_test.go +++ b/utils/sampler/weighted_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler diff --git a/utils/sampler/weighted_uniform.go b/utils/sampler/weighted_uniform.go index bff76ead34e4..22dbb6b5ebd5 100644 --- a/utils/sampler/weighted_uniform.go +++ b/utils/sampler/weighted_uniform.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler diff --git a/utils/sampler/weighted_without_replacement.go b/utils/sampler/weighted_without_replacement.go index a6039a65d82c..a6b619928742 100644 --- a/utils/sampler/weighted_without_replacement.go +++ b/utils/sampler/weighted_without_replacement.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -9,15 +9,12 @@ package sampler type WeightedWithoutReplacement interface { Initialize(weights []uint64) error Sample(count int) ([]int, error) - - Seed(int64) - ClearSeed() } // NewWeightedWithoutReplacement returns a new sampler -func NewDeterministicWeightedWithoutReplacement() WeightedWithoutReplacement { +func NewDeterministicWeightedWithoutReplacement(source Source) WeightedWithoutReplacement { return &weightedWithoutReplacementGeneric{ - u: NewUniform(), + u: NewDeterministicUniform(source), w: NewDeterministicWeighted(), } } diff --git a/utils/sampler/weighted_without_replacement_benchmark_test.go b/utils/sampler/weighted_without_replacement_benchmark_test.go index 03459a5e757b..58becf9d2311 100644 --- a/utils/sampler/weighted_without_replacement_benchmark_test.go +++ b/utils/sampler/weighted_without_replacement_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler diff --git a/utils/sampler/weighted_without_replacement_generic.go b/utils/sampler/weighted_without_replacement_generic.go index 17bd8a648185..c45d64d0b2b0 100644 --- a/utils/sampler/weighted_without_replacement_generic.go +++ b/utils/sampler/weighted_without_replacement_generic.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -41,11 +41,3 @@ func (s *weightedWithoutReplacementGeneric) Sample(count int) ([]int, error) { } return indices, nil } - -func (s *weightedWithoutReplacementGeneric) Seed(seed int64) { - s.u.Seed(seed) -} - -func (s *weightedWithoutReplacementGeneric) ClearSeed() { - s.u.ClearSeed() -} diff --git a/utils/sampler/weighted_without_replacement_test.go b/utils/sampler/weighted_without_replacement_test.go index fe2dadaeb5c6..48952d5cac14 100644 --- a/utils/sampler/weighted_without_replacement_test.go +++ b/utils/sampler/weighted_without_replacement_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sampler @@ -23,7 +23,9 @@ var ( { name: "generic with replacer and best", sampler: &weightedWithoutReplacementGeneric{ - u: &uniformReplacer{}, + u: &uniformReplacer{ + rng: globalRNG, + }, w: &weightedBest{ samplers: []Weighted{ &weightedArray{}, diff --git a/utils/set/bits.go b/utils/set/bits.go index 344c8dff6781..a6e74fb6c18e 100644 --- a/utils/set/bits.go +++ b/utils/set/bits.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package set diff --git a/utils/set/bits_64.go b/utils/set/bits_64.go index eed00afdedc6..d67c405ac922 100644 --- a/utils/set/bits_64.go +++ b/utils/set/bits_64.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package set diff --git a/utils/set/bits_64_test.go b/utils/set/bits_64_test.go index 87374b4f297a..b31bf9979730 100644 --- a/utils/set/bits_64_test.go +++ b/utils/set/bits_64_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package set diff --git a/utils/set/bits_test.go b/utils/set/bits_test.go index 5541395a5aa4..12efb343acd6 100644 --- a/utils/set/bits_test.go +++ b/utils/set/bits_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package set diff --git a/utils/set/sampleable_set.go b/utils/set/sampleable_set.go index 0d22f40fbf8a..becd228fac43 100644 --- a/utils/set/sampleable_set.go +++ b/utils/set/sampleable_set.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package set @@ -176,7 +176,7 @@ func (s *SampleableSet[_]) MarshalJSON() ([]byte, error) { } } // Sort for determinism - utils.SortBytes(elementBytes) + slices.SortFunc(elementBytes, bytes.Compare) // Build the JSON var ( diff --git a/utils/set/sampleable_set_test.go b/utils/set/sampleable_set_test.go index 5f19c13db9fe..31bd07712db5 100644 --- a/utils/set/sampleable_set_test.go +++ b/utils/set/sampleable_set_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package set diff --git a/utils/set/set.go b/utils/set/set.go index fd6525b6b127..84cb5d46cd95 100644 --- a/utils/set/set.go +++ b/utils/set/set.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package set @@ -9,6 +9,7 @@ import ( stdjson "encoding/json" "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/json" @@ -161,7 +162,7 @@ func (s Set[_]) MarshalJSON() ([]byte, error) { i++ } // Sort for determinism - utils.SortBytes(eltBytes) + slices.SortFunc(eltBytes, bytes.Compare) // Build the JSON var ( diff --git a/utils/set/set_benchmark_test.go b/utils/set/set_benchmark_test.go index c762b72cba01..300b8c8c0a71 100644 --- a/utils/set/set_benchmark_test.go +++ b/utils/set/set_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package set diff --git a/utils/set/set_test.go b/utils/set/set_test.go index 4e0a3d1fa3ed..3b0a7e1822f8 100644 --- a/utils/set/set_test.go +++ b/utils/set/set_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package set diff --git a/utils/setmap/setmap.go b/utils/setmap/setmap.go new file mode 100644 index 000000000000..a2924894dac5 --- /dev/null +++ b/utils/setmap/setmap.go @@ -0,0 +1,138 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package setmap + +import ( + "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/utils/set" +) + +type Entry[K any, V comparable] struct { + Key K + Set set.Set[V] +} + +// SetMap is a map to a set where all sets are non-overlapping. +type SetMap[K, V comparable] struct { + keyToSet map[K]set.Set[V] + valueToKey map[V]K +} + +// New creates a new empty setmap. +func New[K, V comparable]() *SetMap[K, V] { + return &SetMap[K, V]{ + keyToSet: make(map[K]set.Set[V]), + valueToKey: make(map[V]K), + } +} + +// Put the new entry into the map. Removes and returns: +// * The existing entry for [key]. +// * Existing entries where the set overlaps with the [set]. +func (m *SetMap[K, V]) Put(key K, set set.Set[V]) []Entry[K, V] { + removed := m.DeleteOverlapping(set) + if removedSet, ok := m.DeleteKey(key); ok { + removed = append(removed, Entry[K, V]{ + Key: key, + Set: removedSet, + }) + } + + m.keyToSet[key] = set + for val := range set { + m.valueToKey[val] = key + } + return removed +} + +// GetKey that maps to the provided value. +func (m *SetMap[K, V]) GetKey(val V) (K, bool) { + key, ok := m.valueToKey[val] + return key, ok +} + +// GetSet that is mapped to by the provided key. +func (m *SetMap[K, V]) GetSet(key K) (set.Set[V], bool) { + val, ok := m.keyToSet[key] + return val, ok +} + +// HasKey returns true if [key] is in the map. +func (m *SetMap[K, _]) HasKey(key K) bool { + _, ok := m.keyToSet[key] + return ok +} + +// HasValue returns true if [val] is in a set in the map. +func (m *SetMap[_, V]) HasValue(val V) bool { + _, ok := m.valueToKey[val] + return ok +} + +// HasOverlap returns true if [set] overlaps with any of the sets in the map. +func (m *SetMap[_, V]) HasOverlap(set set.Set[V]) bool { + if set.Len() < len(m.valueToKey) { + for val := range set { + if _, ok := m.valueToKey[val]; ok { + return true + } + } + } else { + for val := range m.valueToKey { + if set.Contains(val) { + return true + } + } + } + return false +} + +// DeleteKey removes [key] from the map and returns the set it mapped to. +func (m *SetMap[K, V]) DeleteKey(key K) (set.Set[V], bool) { + set, ok := m.keyToSet[key] + if !ok { + return nil, false + } + + delete(m.keyToSet, key) + for val := range set { + delete(m.valueToKey, val) + } + return set, true +} + +// DeleteValue removes and returns the entry that contained [val]. +func (m *SetMap[K, V]) DeleteValue(val V) (K, set.Set[V], bool) { + key, ok := m.valueToKey[val] + if !ok { + return utils.Zero[K](), nil, false + } + set, _ := m.DeleteKey(key) + return key, set, true +} + +// DeleteOverlapping removes and returns all the entries where the set overlaps +// with [set]. +func (m *SetMap[K, V]) DeleteOverlapping(set set.Set[V]) []Entry[K, V] { + var removed []Entry[K, V] + for val := range set { + if k, removedSet, ok := m.DeleteValue(val); ok { + removed = append(removed, Entry[K, V]{ + Key: k, + Set: removedSet, + }) + } + } + return removed +} + +// Len return the number of sets in the map. +func (m *SetMap[K, V]) Len() int { + return len(m.keyToSet) +} + +// LenValues return the total number of values across all sets in the map. +func (m *SetMap[K, V]) LenValues() int { + return len(m.valueToKey) +} diff --git a/utils/setmap/setmap_test.go b/utils/setmap/setmap_test.go new file mode 100644 index 000000000000..f3e70985451f --- /dev/null +++ b/utils/setmap/setmap_test.go @@ -0,0 +1,450 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package setmap + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/utils/set" +) + +func TestSetMapPut(t *testing.T) { + tests := []struct { + name string + state *SetMap[int, int] + key int + value set.Set[int] + expectedRemoved []Entry[int, int] + expectedState *SetMap[int, int] + }{ + { + name: "none removed", + state: New[int, int](), + key: 1, + value: set.Of(2), + expectedRemoved: nil, + expectedState: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(2), + }, + valueToKey: map[int]int{ + 2: 1, + }, + }, + }, + { + name: "key removed", + state: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(2), + }, + valueToKey: map[int]int{ + 2: 1, + }, + }, + key: 1, + value: set.Of(3), + expectedRemoved: []Entry[int, int]{ + { + Key: 1, + Set: set.Of(2), + }, + }, + expectedState: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(3), + }, + valueToKey: map[int]int{ + 3: 1, + }, + }, + }, + { + name: "value removed", + state: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(2), + }, + valueToKey: map[int]int{ + 2: 1, + }, + }, + key: 3, + value: set.Of(2), + expectedRemoved: []Entry[int, int]{ + { + Key: 1, + Set: set.Of(2), + }, + }, + expectedState: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 3: set.Of(2), + }, + valueToKey: map[int]int{ + 2: 3, + }, + }, + }, + { + name: "key and value removed", + state: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(2), + 3: set.Of(4), + }, + valueToKey: map[int]int{ + 2: 1, + 4: 3, + }, + }, + key: 1, + value: set.Of(4), + expectedRemoved: []Entry[int, int]{ + { + Key: 1, + Set: set.Of(2), + }, + { + Key: 3, + Set: set.Of(4), + }, + }, + expectedState: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(4), + }, + valueToKey: map[int]int{ + 4: 1, + }, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + removed := test.state.Put(test.key, test.value) + require.ElementsMatch(test.expectedRemoved, removed) + require.Equal(test.expectedState, test.state) + }) + } +} + +func TestSetMapHasValueAndGetKeyAndSetOverlaps(t *testing.T) { + m := New[int, int]() + require.Empty(t, m.Put(1, set.Of(2))) + + tests := []struct { + name string + value int + expectedKey int + expectedExists bool + }{ + { + name: "fetch unknown", + value: 3, + expectedKey: 0, + expectedExists: false, + }, + { + name: "fetch known value", + value: 2, + expectedKey: 1, + expectedExists: true, + }, + { + name: "fetch known key", + value: 1, + expectedKey: 0, + expectedExists: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + exists := m.HasValue(test.value) + require.Equal(test.expectedExists, exists) + + key, exists := m.GetKey(test.value) + require.Equal(test.expectedKey, key) + require.Equal(test.expectedExists, exists) + }) + } +} + +func TestSetMapHasOverlap(t *testing.T) { + m := New[int, int]() + require.Empty(t, m.Put(1, set.Of(2))) + require.Empty(t, m.Put(2, set.Of(3, 4))) + + tests := []struct { + name string + set set.Set[int] + expectedOverlaps bool + }{ + { + name: "small fetch unknown", + set: set.Of(5), + expectedOverlaps: false, + }, + { + name: "large fetch unknown", + set: set.Of(5, 6, 7, 8), + expectedOverlaps: false, + }, + { + name: "small fetch known", + set: set.Of(3), + expectedOverlaps: true, + }, + { + name: "large fetch known", + set: set.Of(3, 5, 6, 7, 8), + expectedOverlaps: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + overlaps := m.HasOverlap(test.set) + require.Equal(t, test.expectedOverlaps, overlaps) + }) + } +} + +func TestSetMapHasKeyAndGetSet(t *testing.T) { + m := New[int, int]() + require.Empty(t, m.Put(1, set.Of(2))) + + tests := []struct { + name string + key int + expectedValue set.Set[int] + expectedExists bool + }{ + { + name: "fetch unknown", + key: 3, + expectedValue: nil, + expectedExists: false, + }, + { + name: "fetch known key", + key: 1, + expectedValue: set.Of(2), + expectedExists: true, + }, + { + name: "fetch known value", + key: 2, + expectedValue: nil, + expectedExists: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + exists := m.HasKey(test.key) + require.Equal(test.expectedExists, exists) + + value, exists := m.GetSet(test.key) + require.Equal(test.expectedValue, value) + require.Equal(test.expectedExists, exists) + }) + } +} + +func TestSetMapDeleteKey(t *testing.T) { + tests := []struct { + name string + state *SetMap[int, int] + key int + expectedValue set.Set[int] + expectedRemoved bool + expectedState *SetMap[int, int] + }{ + { + name: "none removed", + state: New[int, int](), + key: 1, + expectedValue: nil, + expectedRemoved: false, + expectedState: New[int, int](), + }, + { + name: "key removed", + state: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(2), + }, + valueToKey: map[int]int{ + 2: 1, + }, + }, + key: 1, + expectedValue: set.Of(2), + expectedRemoved: true, + expectedState: New[int, int](), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + value, removed := test.state.DeleteKey(test.key) + require.Equal(test.expectedValue, value) + require.Equal(test.expectedRemoved, removed) + require.Equal(test.expectedState, test.state) + }) + } +} + +func TestSetMapDeleteValue(t *testing.T) { + tests := []struct { + name string + state *SetMap[int, int] + value int + expectedKey int + expectedSet set.Set[int] + expectedRemoved bool + expectedState *SetMap[int, int] + }{ + { + name: "none removed", + state: New[int, int](), + value: 1, + expectedKey: 0, + expectedSet: nil, + expectedRemoved: false, + expectedState: New[int, int](), + }, + { + name: "key removed", + state: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(2), + }, + valueToKey: map[int]int{ + 2: 1, + }, + }, + value: 2, + expectedKey: 1, + expectedSet: set.Of(2), + expectedRemoved: true, + expectedState: New[int, int](), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + key, set, removed := test.state.DeleteValue(test.value) + require.Equal(test.expectedKey, key) + require.Equal(test.expectedSet, set) + require.Equal(test.expectedRemoved, removed) + require.Equal(test.expectedState, test.state) + }) + } +} + +func TestSetMapDeleteOverlapping(t *testing.T) { + tests := []struct { + name string + state *SetMap[int, int] + set set.Set[int] + expectedRemoved []Entry[int, int] + expectedState *SetMap[int, int] + }{ + { + name: "none removed", + state: New[int, int](), + set: set.Of(1), + expectedRemoved: nil, + expectedState: New[int, int](), + }, + { + name: "key removed", + state: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(2), + }, + valueToKey: map[int]int{ + 2: 1, + }, + }, + set: set.Of(2), + expectedRemoved: []Entry[int, int]{ + { + Key: 1, + Set: set.Of(2), + }, + }, + expectedState: New[int, int](), + }, + { + name: "multiple keys removed", + state: &SetMap[int, int]{ + keyToSet: map[int]set.Set[int]{ + 1: set.Of(2, 3), + 2: set.Of(4), + }, + valueToKey: map[int]int{ + 2: 1, + 3: 1, + 4: 2, + }, + }, + set: set.Of(2, 4), + expectedRemoved: []Entry[int, int]{ + { + Key: 1, + Set: set.Of(2, 3), + }, + { + Key: 2, + Set: set.Of(4), + }, + }, + expectedState: New[int, int](), + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + removed := test.state.DeleteOverlapping(test.set) + require.ElementsMatch(test.expectedRemoved, removed) + require.Equal(test.expectedState, test.state) + }) + } +} + +func TestSetMapLen(t *testing.T) { + require := require.New(t) + + m := New[int, int]() + require.Zero(m.Len()) + require.Zero(m.LenValues()) + + m.Put(1, set.Of(2)) + require.Equal(1, m.Len()) + require.Equal(1, m.LenValues()) + + m.Put(2, set.Of(3, 4)) + require.Equal(2, m.Len()) + require.Equal(3, m.LenValues()) + + m.Put(1, set.Of(4, 5)) + require.Equal(1, m.Len()) + require.Equal(2, m.LenValues()) + + m.DeleteKey(1) + require.Zero(m.Len()) + require.Zero(m.LenValues()) +} diff --git a/utils/sorting.go b/utils/sorting.go index 74f24abeb69f..070375811ee3 100644 --- a/utils/sorting.go +++ b/utils/sorting.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils @@ -12,32 +12,23 @@ import ( "github.com/ava-labs/avalanchego/utils/hashing" ) -// TODO can we handle sorting where the Less function relies on a codec? +// TODO can we handle sorting where the Compare function relies on a codec? type Sortable[T any] interface { - Less(T) bool + Compare(T) int } // Sorts the elements of [s]. func Sort[T Sortable[T]](s []T) { - slices.SortFunc(s, T.Less) + slices.SortFunc(s, T.Compare) } // Sorts the elements of [s] based on their hashes. func SortByHash[T ~[]byte](s []T) { - slices.SortFunc(s, func(i, j T) bool { + slices.SortFunc(s, func(i, j T) int { iHash := hashing.ComputeHash256(i) jHash := hashing.ComputeHash256(j) - return bytes.Compare(iHash, jHash) == -1 - }) -} - -// Sorts a 2D byte slice. -// Each byte slice is not sorted internally; the byte slices are sorted relative -// to one another. -func SortBytes[T ~[]byte](s []T) { - slices.SortFunc(s, func(i, j T) bool { - return bytes.Compare(i, j) == -1 + return bytes.Compare(iHash, jHash) }) } @@ -54,7 +45,7 @@ func IsSortedBytes[T ~[]byte](s []T) bool { // Returns true iff the elements in [s] are unique and sorted. func IsSortedAndUnique[T Sortable[T]](s []T) bool { for i := 0; i < len(s)-1; i++ { - if !s[i].Less(s[i+1]) { + if s[i].Compare(s[i+1]) >= 0 { return false } } @@ -87,3 +78,21 @@ func IsSortedAndUniqueByHash[T ~[]byte](s []T) bool { } return true } + +// Compare returns +// +// -1 if x is less than y, +// 0 if x equals y, +// 1 if x is greater than y. +// +// TODO: Remove after updating to go1.21. +func Compare[T constraints.Ordered](x, y T) int { + switch { + case x < y: + return -1 + case x > y: + return 1 + default: + return 0 + } +} diff --git a/utils/sorting_test.go b/utils/sorting_test.go index 464959dd9588..acab335034ed 100644 --- a/utils/sorting_test.go +++ b/utils/sorting_test.go @@ -1,12 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils import ( - "math/rand" "testing" - "time" "github.com/stretchr/testify/require" ) @@ -15,8 +13,8 @@ var _ Sortable[sortable] = sortable(0) type sortable int -func (s sortable) Less(other sortable) bool { - return s < other +func (s sortable) Compare(other sortable) int { + return Compare(s, other) } func TestSortSliceSortable(t *testing.T) { @@ -59,23 +57,6 @@ func TestSortSliceSortable(t *testing.T) { require.Equal([]sortable{1, 2, 3}, s) } -func TestSortBytesIsSortedBytes(t *testing.T) { - require := require.New(t) - - seed := time.Now().UnixNano() - t.Log("Seed: ", seed) - rand := rand.New(rand.NewSource(seed)) //#nosec G404 - - slices := make([][]byte, 1024) - for j := 0; j < len(slices); j++ { - slices[j] = make([]byte, 32) - _, _ = rand.Read(slices[j]) - } - require.False(IsSortedBytes(slices)) - SortBytes(slices) - require.True(IsSortedBytes(slices)) -} - func TestIsSortedAndUniqueSortable(t *testing.T) { require := require.New(t) diff --git a/utils/stacktrace.go b/utils/stacktrace.go index d68ee4ea69cf..d0e0de56dc9d 100644 --- a/utils/stacktrace.go +++ b/utils/stacktrace.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils diff --git a/utils/storage/storage_common.go b/utils/storage/storage_common.go index cf1fbd3b895f..6fa5692cb86a 100644 --- a/utils/storage/storage_common.go +++ b/utils/storage/storage_common.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package storage diff --git a/utils/storage/storage_unix.go b/utils/storage/storage_unix.go index ae75ed4e833f..247bc2448f36 100644 --- a/utils/storage/storage_unix.go +++ b/utils/storage/storage_unix.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build !windows diff --git a/utils/storage/storage_windows.go b/utils/storage/storage_windows.go index e9242e2d2f1e..2514717c8bea 100644 --- a/utils/storage/storage_windows.go +++ b/utils/storage/storage_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build windows diff --git a/utils/timer/adaptive_timeout_manager.go b/utils/timer/adaptive_timeout_manager.go index a6d00654c064..493769018ba2 100644 --- a/utils/timer/adaptive_timeout_manager.go +++ b/utils/timer/adaptive_timeout_manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timer diff --git a/utils/timer/adaptive_timeout_manager_test.go b/utils/timer/adaptive_timeout_manager_test.go index ec9964bd5a7f..40b4186011f4 100644 --- a/utils/timer/adaptive_timeout_manager_test.go +++ b/utils/timer/adaptive_timeout_manager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timer diff --git a/utils/timer/eta.go b/utils/timer/eta.go index 490574864087..6af353fd1883 100644 --- a/utils/timer/eta.go +++ b/utils/timer/eta.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timer diff --git a/utils/timer/meter.go b/utils/timer/meter.go index c78376e1c62f..e0459e92ee2f 100644 --- a/utils/timer/meter.go +++ b/utils/timer/meter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timer diff --git a/utils/timer/mockable/clock.go b/utils/timer/mockable/clock.go index 23857a22efac..753da957ec97 100644 --- a/utils/timer/mockable/clock.go +++ b/utils/timer/mockable/clock.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package mockable diff --git a/utils/timer/mockable/clock_test.go b/utils/timer/mockable/clock_test.go index eee8922e9e58..a15e71efdcfb 100644 --- a/utils/timer/mockable/clock_test.go +++ b/utils/timer/mockable/clock_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package mockable diff --git a/utils/timer/staged_timer.go b/utils/timer/staged_timer.go deleted file mode 100644 index eec885ee63d9..000000000000 --- a/utils/timer/staged_timer.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package timer - -import "time" - -// NewStagedTimer returns a timer that will execute [f] -// when a timeout occurs and execute an additional timeout after -// the returned duration if [f] returns true and some duration. -// -// Deprecated: NewStagedTimer exists for historical compatibility -// and should not be used. -func NewStagedTimer(f func() (time.Duration, bool)) *Timer { - t := NewTimer(nil) - t.handler = func() { - delay, repeat := f() - if repeat { - t.SetTimeoutIn(delay) - } - } - return t -} diff --git a/utils/timer/staged_timer_test.go b/utils/timer/staged_timer_test.go deleted file mode 100644 index bd83ef206078..000000000000 --- a/utils/timer/staged_timer_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package timer - -import ( - "sync" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestSingleStagedTimer(t *testing.T) { - wg := sync.WaitGroup{} - wg.Add(1) - ticks := 1 - i := 0 - timer := NewStagedTimer(func() (time.Duration, bool) { - defer wg.Done() - i++ - return 0, false - }) - go timer.Dispatch() - - timer.SetTimeoutIn(time.Millisecond) - wg.Wait() - require.Equal(t, i, ticks) -} - -func TestMultiStageTimer(t *testing.T) { - wg := sync.WaitGroup{} - ticks := 3 - wg.Add(ticks) - - i := 0 - timer := NewStagedTimer(func() (time.Duration, bool) { - defer wg.Done() - i++ - return time.Millisecond, i < ticks - }) - go timer.Dispatch() - - timer.SetTimeoutIn(time.Millisecond) - wg.Wait() - require.Equal(t, i, ticks) -} diff --git a/utils/timer/timer.go b/utils/timer/timer.go index 1b5914fefc76..b4d3453477d9 100644 --- a/utils/timer/timer.go +++ b/utils/timer/timer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timer diff --git a/utils/timer/timer_test.go b/utils/timer/timer_test.go index 228b19f28d32..83994f963bf3 100644 --- a/utils/timer/timer_test.go +++ b/utils/timer/timer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package timer diff --git a/utils/ulimit/ulimit_bsd.go b/utils/ulimit/ulimit_bsd.go index 191b788286d2..bb4c5e150e6c 100644 --- a/utils/ulimit/ulimit_bsd.go +++ b/utils/ulimit/ulimit_bsd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build freebsd @@ -10,6 +10,8 @@ import ( "fmt" "syscall" + "go.uber.org/zap" + "github.com/ava-labs/avalanchego/utils/logging" ) diff --git a/utils/ulimit/ulimit_darwin.go b/utils/ulimit/ulimit_darwin.go index 9eaab72bd0f6..224d8faf056e 100644 --- a/utils/ulimit/ulimit_darwin.go +++ b/utils/ulimit/ulimit_darwin.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build darwin diff --git a/utils/ulimit/ulimit_unix.go b/utils/ulimit/ulimit_unix.go index 898b361cef92..8b23ab701b53 100644 --- a/utils/ulimit/ulimit_unix.go +++ b/utils/ulimit/ulimit_unix.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build linux || netbsd || openbsd diff --git a/utils/ulimit/ulimit_windows.go b/utils/ulimit/ulimit_windows.go index 7646d6f10d1f..82a88273735b 100644 --- a/utils/ulimit/ulimit_windows.go +++ b/utils/ulimit/ulimit_windows.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build windows diff --git a/utils/units/avax.go b/utils/units/avax.go index 341fd8bea8ad..bbd664928b33 100644 --- a/utils/units/avax.go +++ b/utils/units/avax.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package units diff --git a/utils/units/bytes.go b/utils/units/bytes.go index 93678e957a46..42d2526ae257 100644 --- a/utils/units/bytes.go +++ b/utils/units/bytes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package units diff --git a/utils/window/window.go b/utils/window/window.go index 245941467983..86dba5b717df 100644 --- a/utils/window/window.go +++ b/utils/window/window.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package window diff --git a/utils/window/window_test.go b/utils/window/window_test.go index d8cf20f870bb..332d20b3b329 100644 --- a/utils/window/window_test.go +++ b/utils/window/window_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package window diff --git a/utils/wrappers/closers.go b/utils/wrappers/closers.go index d366e928cba0..b16e4baa2831 100644 --- a/utils/wrappers/closers.go +++ b/utils/wrappers/closers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package wrappers diff --git a/utils/wrappers/errors.go b/utils/wrappers/errors.go index 641734da16c0..d887ffb4d20a 100644 --- a/utils/wrappers/errors.go +++ b/utils/wrappers/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package wrappers diff --git a/utils/wrappers/packing.go b/utils/wrappers/packing.go index 891f80e49ab2..96d6b9999c0c 100644 --- a/utils/wrappers/packing.go +++ b/utils/wrappers/packing.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package wrappers diff --git a/utils/wrappers/packing_test.go b/utils/wrappers/packing_test.go index 2372e1ed3cfb..bb3e7fe61d38 100644 --- a/utils/wrappers/packing_test.go +++ b/utils/wrappers/packing_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package wrappers diff --git a/utils/zero.go b/utils/zero.go index 9a9173cf3403..c691ed2e653c 100644 --- a/utils/zero.go +++ b/utils/zero.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils diff --git a/version/application.go b/version/application.go index bc545e54fa4b..2be9d838a89e 100644 --- a/version/application.go +++ b/version/application.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version @@ -6,9 +6,11 @@ package version import ( "errors" "fmt" - "sync/atomic" + "sync" ) +const LegacyAppName = "avalanche" + var ( errDifferentMajor = errors.New("different major version") @@ -16,29 +18,30 @@ var ( ) type Application struct { - Major int `json:"major" yaml:"major"` - Minor int `json:"minor" yaml:"minor"` - Patch int `json:"patch" yaml:"patch"` + Name string `json:"name" yaml:"name"` + Major int `json:"major" yaml:"major"` + Minor int `json:"minor" yaml:"minor"` + Patch int `json:"patch" yaml:"patch"` - str atomic.Value + makeStrOnce sync.Once + str string } // The only difference here between Application and Semantic is that Application -// prepends "avalanche/" rather than "v". +// prepends the client name rather than "v". func (a *Application) String() string { - strIntf := a.str.Load() - if strIntf != nil { - return strIntf.(string) - } + a.makeStrOnce.Do(a.initString) + return a.str +} - str := fmt.Sprintf( - "avalanche/%d.%d.%d", +func (a *Application) initString() { + a.str = fmt.Sprintf( + "%s/%d.%d.%d", + a.Name, a.Major, a.Minor, a.Patch, ) - a.str.Store(str) - return str } func (a *Application) Compatible(o *Application) error { diff --git a/version/application_test.go b/version/application_test.go index 0423e91918e5..deade1816e22 100644 --- a/version/application_test.go +++ b/version/application_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version @@ -14,6 +14,7 @@ func TestNewDefaultApplication(t *testing.T) { require := require.New(t) v := &Application{ + Name: LegacyAppName, Major: 1, Minor: 2, Patch: 3, @@ -33,11 +34,13 @@ func TestComparingVersions(t *testing.T) { }{ { myVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 3, }, peerVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 3, @@ -47,11 +50,13 @@ func TestComparingVersions(t *testing.T) { }, { myVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 4, }, peerVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 3, @@ -61,11 +66,13 @@ func TestComparingVersions(t *testing.T) { }, { myVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 3, }, peerVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 4, @@ -75,11 +82,13 @@ func TestComparingVersions(t *testing.T) { }, { myVersion: &Application{ + Name: Client, Major: 1, Minor: 3, Patch: 3, }, peerVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 3, @@ -89,11 +98,13 @@ func TestComparingVersions(t *testing.T) { }, { myVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 3, }, peerVersion: &Application{ + Name: Client, Major: 1, Minor: 3, Patch: 3, @@ -103,11 +114,13 @@ func TestComparingVersions(t *testing.T) { }, { myVersion: &Application{ + Name: Client, Major: 2, Minor: 2, Patch: 3, }, peerVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 3, @@ -117,11 +130,13 @@ func TestComparingVersions(t *testing.T) { }, { myVersion: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 3, }, peerVersion: &Application{ + Name: Client, Major: 2, Minor: 2, Patch: 3, diff --git a/version/compatibility.go b/version/compatibility.go index 6c3cae20778e..1cde189954a8 100644 --- a/version/compatibility.go +++ b/version/compatibility.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version diff --git a/version/compatibility.json b/version/compatibility.json index d34dfb1a5a28..e975f92c3698 100644 --- a/version/compatibility.json +++ b/version/compatibility.json @@ -1,4 +1,7 @@ { + "31": [ + "v1.10.18" + ], "30": [ "v1.10.15", "v1.10.16", diff --git a/version/compatibility_test.go b/version/compatibility_test.go index a95a85af55dd..09d3cdcb3336 100644 --- a/version/compatibility_test.go +++ b/version/compatibility_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version @@ -13,17 +13,20 @@ import ( func TestCompatibility(t *testing.T) { v := &Application{ + Name: Client, Major: 1, Minor: 4, Patch: 3, } minCompatable := &Application{ + Name: Client, Major: 1, Minor: 4, Patch: 0, } minCompatableTime := time.Unix(9000, 0) prevMinCompatable := &Application{ + Name: Client, Major: 1, Minor: 3, Patch: 0, @@ -44,6 +47,7 @@ func TestCompatibility(t *testing.T) { }{ { peer: &Application{ + Name: LegacyAppName, Major: 1, Minor: 5, Patch: 0, @@ -52,6 +56,16 @@ func TestCompatibility(t *testing.T) { }, { peer: &Application{ + Name: Client, + Major: 1, + Minor: 5, + Patch: 0, + }, + time: minCompatableTime, + }, + { + peer: &Application{ + Name: Client, Major: 1, Minor: 3, Patch: 5, @@ -60,6 +74,7 @@ func TestCompatibility(t *testing.T) { }, { peer: &Application{ + Name: Client, Major: 0, Minor: 1, Patch: 0, @@ -69,6 +84,7 @@ func TestCompatibility(t *testing.T) { }, { peer: &Application{ + Name: Client, Major: 1, Minor: 3, Patch: 5, @@ -78,6 +94,7 @@ func TestCompatibility(t *testing.T) { }, { peer: &Application{ + Name: Client, Major: 1, Minor: 2, Patch: 5, @@ -87,6 +104,7 @@ func TestCompatibility(t *testing.T) { }, { peer: &Application{ + Name: Client, Major: 1, Minor: 1, Patch: 5, diff --git a/version/constants.go b/version/constants.go index f7c27f641395..55af70a4d89f 100644 --- a/version/constants.go +++ b/version/constants.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version @@ -13,28 +13,35 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" ) -// RPCChainVMProtocol should be bumped anytime changes are made which require -// the plugin vm to upgrade to latest avalanchego release to be compatible. -const RPCChainVMProtocol uint = 30 +const ( + Client = "avalanchego" + // RPCChainVMProtocol should be bumped anytime changes are made which + // require the plugin vm to upgrade to latest avalanchego release to be + // compatible. + RPCChainVMProtocol uint = 31 +) // These are globals that describe network upgrades and node versions var ( Current = &Semantic{ Major: 1, Minor: 10, - Patch: 17, + Patch: 18, } CurrentApp = &Application{ + Name: Client, Major: Current.Major, Minor: Current.Minor, Patch: Current.Patch, } MinimumCompatibleVersion = &Application{ + Name: Client, Major: 1, Minor: 10, Patch: 0, } PrevMinimumCompatibleVersion = &Application{ + Name: Client, Major: 1, Minor: 9, Patch: 0, diff --git a/version/constants_test.go b/version/constants_test.go index 5e409dd91767..aea7ef493510 100644 --- a/version/constants_test.go +++ b/version/constants_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version diff --git a/version/parser.go b/version/parser.go index debb636a92cb..abc150450099 100644 --- a/version/parser.go +++ b/version/parser.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version @@ -34,18 +34,21 @@ func Parse(s string) (*Semantic, error) { }, nil } -func ParseApplication(s string) (*Application, error) { - if !strings.HasPrefix(s, "avalanche/") { +// TODO: Remove after v1.11.x is activated +func ParseLegacyApplication(s string) (*Application, error) { + prefix := fmt.Sprintf("%s/", LegacyAppName) + if !strings.HasPrefix(s, prefix) { return nil, fmt.Errorf("%w: %q", errMissingApplicationPrefix, s) } - s = s[10:] + s = s[len(prefix):] major, minor, patch, err := parseVersions(s) if err != nil { return nil, err } return &Application{ + Name: Client, // Convert the legacy name to the current client name Major: major, Minor: minor, Patch: patch, diff --git a/version/parser_test.go b/version/parser_test.go index 3bc8b5e30589..42adb764c9c2 100644 --- a/version/parser_test.go +++ b/version/parser_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version @@ -65,12 +65,13 @@ func TestParse(t *testing.T) { } } -func TestParseApplication(t *testing.T) { - v, err := ParseApplication("avalanche/1.2.3") +func TestParseLegacyApplication(t *testing.T) { + v, err := ParseLegacyApplication("avalanche/1.2.3") require.NoError(t, err) require.NotNil(t, v) - require.Equal(t, "avalanche/1.2.3", v.String()) + require.Equal(t, "avalanchego/1.2.3", v.String()) + require.Equal(t, "avalanchego", v.Name) require.Equal(t, 1, v.Major) require.Equal(t, 2, v.Minor) require.Equal(t, 3, v.Patch) @@ -85,6 +86,10 @@ func TestParseApplication(t *testing.T) { version: "", expectedErr: errMissingApplicationPrefix, }, + { + version: "avalanchego/v1.2.3", + expectedErr: errMissingApplicationPrefix, + }, { version: "avalanche/", expectedErr: errMissingVersions, @@ -108,7 +113,7 @@ func TestParseApplication(t *testing.T) { } for _, test := range tests { t.Run(test.version, func(t *testing.T) { - _, err := ParseApplication(test.version) + _, err := ParseLegacyApplication(test.version) require.ErrorIs(t, err, test.expectedErr) }) } diff --git a/version/string.go b/version/string.go index 8d83a51d4d05..9abe555bcebb 100644 --- a/version/string.go +++ b/version/string.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version diff --git a/version/version.go b/version/version.go index 81acdc42e77b..b8fe119b370c 100644 --- a/version/version.go +++ b/version/version.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version @@ -28,7 +28,7 @@ type Semantic struct { } // The only difference here between Semantic and Application is that Semantic -// prepends "v" rather than "avalanche/". +// prepends "v" rather than the client name. func (s *Semantic) String() string { strIntf := s.str.Load() if strIntf != nil { diff --git a/version/version_test.go b/version/version_test.go index d66c1212958c..69c494c88650 100644 --- a/version/version_test.go +++ b/version/version_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version diff --git a/vms/avm/block/block.go b/vms/avm/block/block.go index 331c310274e1..376062c0387e 100644 --- a/vms/avm/block/block.go +++ b/vms/avm/block/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/avm/block/block_test.go b/vms/avm/block/block_test.go index 568eef5f5851..6100f1d6d987 100644 --- a/vms/avm/block/block_test.go +++ b/vms/avm/block/block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -28,9 +28,12 @@ var ( func TestInvalidBlock(t *testing.T) { require := require.New(t) - parser, err := NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) _, err = parser.ParseBlock(nil) @@ -41,9 +44,12 @@ func TestStandardBlocks(t *testing.T) { // check standard block can be built and parsed require := require.New(t) - parser, err := NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) blkTimestamp := time.Now() diff --git a/vms/avm/block/builder/builder.go b/vms/avm/block/builder/builder.go index a3129d797808..80e734812a6c 100644 --- a/vms/avm/block/builder/builder.go +++ b/vms/avm/block/builder/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package builder @@ -93,15 +93,19 @@ func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { remainingSize = targetBlockSize ) for { - tx := b.mempool.Peek(remainingSize) - if tx == nil { + tx, exists := b.mempool.Peek() + // Invariant: [mempool.MaxTxSize] < [targetBlockSize]. This guarantees + // that we will only stop building a block once there are no + // transactions in the mempool or the block is at least + // [targetBlockSize - mempool.MaxTxSize] bytes full. + if !exists || len(tx.Bytes()) > remainingSize { break } - b.mempool.Remove([]*txs.Tx{tx}) + b.mempool.Remove(tx) // Invariant: [tx] has already been syntactically verified. - txDiff, err := wrapState(stateDiff) + txDiff, err := state.NewDiffOn(stateDiff) if err != nil { return nil, err } @@ -166,17 +170,3 @@ func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { return b.manager.NewBlock(statelessBlk), nil } - -type stateGetter struct { - state state.Chain -} - -func (s stateGetter) GetState(ids.ID) (state.Chain, bool) { - return s.state, true -} - -func wrapState(parentState state.Chain) (state.Diff, error) { - return state.NewDiff(ids.Empty, stateGetter{ - state: parentState, - }) -} diff --git a/vms/avm/block/builder/builder_test.go b/vms/avm/block/builder/builder_test.go index 7faeddbe71e6..185c93260eca 100644 --- a/vms/avm/block/builder/builder_test.go +++ b/vms/avm/block/builder/builder_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package builder @@ -134,11 +134,11 @@ func TestBuilderBuildBlock(t *testing.T) { tx := &txs.Tx{Unsigned: unsignedTx} mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Peek(gomock.Any()).Return(tx) + mempool.EXPECT().Peek().Return(tx, true) mempool.EXPECT().Remove([]*txs.Tx{tx}) mempool.EXPECT().MarkDropped(tx.ID(), errTest) // Second loop iteration - mempool.EXPECT().Peek(gomock.Any()).Return(nil) + mempool.EXPECT().Peek().Return(nil, false) mempool.EXPECT().RequestBuildBlock() return New( @@ -179,11 +179,11 @@ func TestBuilderBuildBlock(t *testing.T) { tx := &txs.Tx{Unsigned: unsignedTx} mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Peek(gomock.Any()).Return(tx) + mempool.EXPECT().Peek().Return(tx, true) mempool.EXPECT().Remove([]*txs.Tx{tx}) mempool.EXPECT().MarkDropped(tx.ID(), errTest) // Second loop iteration - mempool.EXPECT().Peek(gomock.Any()).Return(nil) + mempool.EXPECT().Peek().Return(nil, false) mempool.EXPECT().RequestBuildBlock() return New( @@ -225,11 +225,11 @@ func TestBuilderBuildBlock(t *testing.T) { tx := &txs.Tx{Unsigned: unsignedTx} mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Peek(gomock.Any()).Return(tx) + mempool.EXPECT().Peek().Return(tx, true) mempool.EXPECT().Remove([]*txs.Tx{tx}) mempool.EXPECT().MarkDropped(tx.ID(), errTest) // Second loop iteration - mempool.EXPECT().Peek(gomock.Any()).Return(nil) + mempool.EXPECT().Peek().Return(nil, false) mempool.EXPECT().RequestBuildBlock() return New( @@ -309,14 +309,14 @@ func TestBuilderBuildBlock(t *testing.T) { ) mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Peek(targetBlockSize).Return(tx1) + mempool.EXPECT().Peek().Return(tx1, true) mempool.EXPECT().Remove([]*txs.Tx{tx1}) // Second loop iteration - mempool.EXPECT().Peek(targetBlockSize - len(tx1Bytes)).Return(tx2) + mempool.EXPECT().Peek().Return(tx2, true) mempool.EXPECT().Remove([]*txs.Tx{tx2}) mempool.EXPECT().MarkDropped(tx2.ID(), blkexecutor.ErrConflictingBlockTxs) // Third loop iteration - mempool.EXPECT().Peek(targetBlockSize - len(tx1Bytes)).Return(nil) + mempool.EXPECT().Peek().Return(nil, false) mempool.EXPECT().RequestBuildBlock() // To marshal the tx/block @@ -385,10 +385,10 @@ func TestBuilderBuildBlock(t *testing.T) { tx := &txs.Tx{Unsigned: unsignedTx} mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Peek(gomock.Any()).Return(tx) + mempool.EXPECT().Peek().Return(tx, true) mempool.EXPECT().Remove([]*txs.Tx{tx}) // Second loop iteration - mempool.EXPECT().Peek(gomock.Any()).Return(nil) + mempool.EXPECT().Peek().Return(nil, false) mempool.EXPECT().RequestBuildBlock() // To marshal the tx/block @@ -459,10 +459,10 @@ func TestBuilderBuildBlock(t *testing.T) { tx := &txs.Tx{Unsigned: unsignedTx} mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Peek(gomock.Any()).Return(tx) + mempool.EXPECT().Peek().Return(tx, true) mempool.EXPECT().Remove([]*txs.Tx{tx}) // Second loop iteration - mempool.EXPECT().Peek(gomock.Any()).Return(nil) + mempool.EXPECT().Peek().Return(nil, false) mempool.EXPECT().RequestBuildBlock() // To marshal the tx/block @@ -510,11 +510,16 @@ func TestBlockBuilderAddLocalTx(t *testing.T) { tx := transactions[0] txID := tx.ID() require.NoError(mempool.Add(tx)) - require.True(mempool.Has(txID)) - parser, err := block.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + _, ok := mempool.Get(txID) + require.True(ok) + + parser, err := block.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) backend := &txexecutor.Backend{ diff --git a/vms/avm/block/executor/block.go b/vms/avm/block/executor/block.go index 5e643ad4ecc0..8663f27ba123 100644 --- a/vms/avm/block/executor/block.go +++ b/vms/avm/block/executor/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -200,7 +200,7 @@ func (b *Block) Verify(context.Context) error { stateDiff.AddBlock(b.Block) b.manager.blkIDToState[blkID] = blockState - b.manager.mempool.Remove(txs) + b.manager.mempool.Remove(txs...) return nil } @@ -220,7 +220,7 @@ func (b *Block) Accept(context.Context) error { } b.manager.lastAccepted = blkID - b.manager.mempool.Remove(txs) + b.manager.mempool.Remove(txs...) blkState, ok := b.manager.blkIDToState[blkID] if !ok { @@ -290,6 +290,10 @@ func (b *Block) Reject(context.Context) error { } } + // If we added transactions to the mempool, we should be willing to build a + // block. + b.manager.mempool.RequestBuildBlock() + b.rejected = true return nil } diff --git a/vms/avm/block/executor/block_test.go b/vms/avm/block/executor/block_test.go index da965884ae58..0b6738822c6e 100644 --- a/vms/avm/block/executor/block_test.go +++ b/vms/avm/block/executor/block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -857,28 +857,27 @@ func TestBlockReject(t *testing.T) { mempool := mempool.NewMockMempool(ctrl) mempool.EXPECT().Add(validTx).Return(nil) // Only add the one that passes verification + mempool.EXPECT().RequestBuildBlock() - preferredID := ids.GenerateTestID() - mockPreferredState := state.NewMockDiff(ctrl) - mockPreferredState.EXPECT().GetLastAccepted().Return(ids.GenerateTestID()).AnyTimes() - mockPreferredState.EXPECT().GetTimestamp().Return(time.Now()).AnyTimes() + lastAcceptedID := ids.GenerateTestID() + mockState := state.NewMockState(ctrl) + mockState.EXPECT().GetLastAccepted().Return(lastAcceptedID).AnyTimes() + mockState.EXPECT().GetTimestamp().Return(time.Now()).AnyTimes() return &Block{ Block: mockBlock, manager: &manager{ - preferred: preferredID, - mempool: mempool, - metrics: metrics.NewMockMetrics(ctrl), + lastAccepted: lastAcceptedID, + mempool: mempool, + metrics: metrics.NewMockMetrics(ctrl), backend: &executor.Backend{ Bootstrapped: true, Ctx: &snow.Context{ Log: logging.NoLog{}, }, }, + state: mockState, blkIDToState: map[ids.ID]*blockState{ - preferredID: { - onAcceptState: mockPreferredState, - }, blockID: {}, }, }, @@ -916,28 +915,27 @@ func TestBlockReject(t *testing.T) { mempool := mempool.NewMockMempool(ctrl) mempool.EXPECT().Add(tx1).Return(nil) mempool.EXPECT().Add(tx2).Return(nil) + mempool.EXPECT().RequestBuildBlock() - preferredID := ids.GenerateTestID() - mockPreferredState := state.NewMockDiff(ctrl) - mockPreferredState.EXPECT().GetLastAccepted().Return(ids.GenerateTestID()).AnyTimes() - mockPreferredState.EXPECT().GetTimestamp().Return(time.Now()).AnyTimes() + lastAcceptedID := ids.GenerateTestID() + mockState := state.NewMockState(ctrl) + mockState.EXPECT().GetLastAccepted().Return(lastAcceptedID).AnyTimes() + mockState.EXPECT().GetTimestamp().Return(time.Now()).AnyTimes() return &Block{ Block: mockBlock, manager: &manager{ - preferred: preferredID, - mempool: mempool, - metrics: metrics.NewMockMetrics(ctrl), + lastAccepted: lastAcceptedID, + mempool: mempool, + metrics: metrics.NewMockMetrics(ctrl), backend: &executor.Backend{ Bootstrapped: true, Ctx: &snow.Context{ Log: logging.NoLog{}, }, }, + state: mockState, blkIDToState: map[ids.ID]*blockState{ - preferredID: { - onAcceptState: mockPreferredState, - }, blockID: {}, }, }, diff --git a/vms/avm/block/executor/manager.go b/vms/avm/block/executor/manager.go index 48eea701bbd9..9822743b7fd3 100644 --- a/vms/avm/block/executor/manager.go +++ b/vms/avm/block/executor/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -43,8 +43,8 @@ type Manager interface { // preferred state. This should *not* be used to verify transactions in a block. VerifyTx(tx *txs.Tx) error - // VerifyUniqueInputs verifies that the inputs are not duplicated in the - // provided blk or any of its ancestors pinned in memory. + // VerifyUniqueInputs returns nil iff no blocks in the inclusive + // ancestry of [blkID] consume an input in [inputs]. VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error } @@ -155,7 +155,7 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { return err } - stateDiff, err := state.NewDiff(m.preferred, m) + stateDiff, err := state.NewDiff(m.lastAccepted, m) if err != nil { return err } @@ -174,12 +174,7 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { State: stateDiff, Tx: tx, } - err = tx.Unsigned.Visit(executor) - if err != nil { - return err - } - - return m.VerifyUniqueInputs(m.preferred, executor.Inputs) + return tx.Unsigned.Visit(executor) } func (m *manager) VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error { diff --git a/vms/avm/block/executor/manager_test.go b/vms/avm/block/executor/manager_test.go index 904154bf7030..012428d582e4 100644 --- a/vms/avm/block/executor/manager_test.go +++ b/vms/avm/block/executor/manager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -116,7 +116,6 @@ func TestManagerVerifyTx(t *testing.T) { expectedErr error } - inputID := ids.GenerateTestID() tests := []test{ { name: "not bootstrapped", @@ -161,11 +160,11 @@ func TestManagerVerifyTx(t *testing.T) { } }, managerF: func(ctrl *gomock.Controller) *manager { - preferred := ids.GenerateTestID() + lastAcceptedID := ids.GenerateTestID() // These values don't matter for this test state := state.NewMockState(ctrl) - state.EXPECT().GetLastAccepted().Return(preferred) + state.EXPECT().GetLastAccepted().Return(lastAcceptedID) state.EXPECT().GetTimestamp().Return(time.Time{}) return &manager{ @@ -173,8 +172,7 @@ func TestManagerVerifyTx(t *testing.T) { Bootstrapped: true, }, state: state, - lastAccepted: preferred, - preferred: preferred, + lastAccepted: lastAcceptedID, } }, expectedErr: errTestSemanticVerifyFail, @@ -194,11 +192,11 @@ func TestManagerVerifyTx(t *testing.T) { } }, managerF: func(ctrl *gomock.Controller) *manager { - preferred := ids.GenerateTestID() + lastAcceptedID := ids.GenerateTestID() // These values don't matter for this test state := state.NewMockState(ctrl) - state.EXPECT().GetLastAccepted().Return(preferred) + state.EXPECT().GetLastAccepted().Return(lastAcceptedID) state.EXPECT().GetTimestamp().Return(time.Time{}) return &manager{ @@ -206,57 +204,10 @@ func TestManagerVerifyTx(t *testing.T) { Bootstrapped: true, }, state: state, - lastAccepted: preferred, - preferred: preferred, - } - }, - expectedErr: errTestExecutionFail, - }, - { - name: "non-unique inputs", - txF: func(ctrl *gomock.Controller) *txs.Tx { - unsigned := txs.NewMockUnsignedTx(ctrl) - // Syntactic verification passes - unsigned.EXPECT().Visit(gomock.Any()).Return(nil) - // Semantic verification passes - unsigned.EXPECT().Visit(gomock.Any()).Return(nil) - // Execution passes - unsigned.EXPECT().Visit(gomock.Any()).DoAndReturn(func(e *executor.Executor) error { - e.Inputs.Add(inputID) - return nil - }) - return &txs.Tx{ - Unsigned: unsigned, - } - }, - managerF: func(ctrl *gomock.Controller) *manager { - lastAcceptedID := ids.GenerateTestID() - - preferredID := ids.GenerateTestID() - preferred := block.NewMockBlock(ctrl) - preferred.EXPECT().Parent().Return(lastAcceptedID).AnyTimes() - - // These values don't matter for this test - diffState := state.NewMockDiff(ctrl) - diffState.EXPECT().GetLastAccepted().Return(preferredID) - diffState.EXPECT().GetTimestamp().Return(time.Time{}) - - return &manager{ - backend: &executor.Backend{ - Bootstrapped: true, - }, - blkIDToState: map[ids.ID]*blockState{ - preferredID: { - statelessBlock: preferred, - onAcceptState: diffState, - importedInputs: set.Of(inputID), - }, - }, lastAccepted: lastAcceptedID, - preferred: preferredID, } }, - expectedErr: ErrConflictingParentTxs, + expectedErr: errTestExecutionFail, }, { name: "happy path", @@ -273,11 +224,11 @@ func TestManagerVerifyTx(t *testing.T) { } }, managerF: func(ctrl *gomock.Controller) *manager { - preferred := ids.GenerateTestID() + lastAcceptedID := ids.GenerateTestID() // These values don't matter for this test state := state.NewMockState(ctrl) - state.EXPECT().GetLastAccepted().Return(preferred) + state.EXPECT().GetLastAccepted().Return(lastAcceptedID) state.EXPECT().GetTimestamp().Return(time.Time{}) return &manager{ @@ -285,8 +236,7 @@ func TestManagerVerifyTx(t *testing.T) { Bootstrapped: true, }, state: state, - lastAccepted: preferred, - preferred: preferred, + lastAccepted: lastAcceptedID, } }, expectedErr: nil, diff --git a/vms/avm/block/executor/mock_manager.go b/vms/avm/block/executor/mock_manager.go index 5e27089b19fa..a882ec519fba 100644 --- a/vms/avm/block/executor/mock_manager.go +++ b/vms/avm/block/executor/mock_manager.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/vms/avm/block/executor (interfaces: Manager) +// Source: vms/avm/block/executor/manager.go +// +// Generated by this command: +// +// mockgen -source=vms/avm/block/executor/manager.go -destination=vms/avm/block/executor/mock_manager.go -package=executor -exclude_interfaces= +// // Package executor is a generated GoMock package. package executor @@ -43,48 +45,48 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder { } // GetBlock mocks base method. -func (m *MockManager) GetBlock(arg0 ids.ID) (snowman.Block, error) { +func (m *MockManager) GetBlock(blkID ids.ID) (snowman.Block, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBlock", arg0) + ret := m.ctrl.Call(m, "GetBlock", blkID) ret0, _ := ret[0].(snowman.Block) ret1, _ := ret[1].(error) return ret0, ret1 } // GetBlock indicates an expected call of GetBlock. -func (mr *MockManagerMockRecorder) GetBlock(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetBlock(blkID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockManager)(nil).GetBlock), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockManager)(nil).GetBlock), blkID) } // GetState mocks base method. -func (m *MockManager) GetState(arg0 ids.ID) (state.Chain, bool) { +func (m *MockManager) GetState(blkID ids.ID) (state.Chain, bool) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetState", arg0) + ret := m.ctrl.Call(m, "GetState", blkID) ret0, _ := ret[0].(state.Chain) ret1, _ := ret[1].(bool) return ret0, ret1 } // GetState indicates an expected call of GetState. -func (mr *MockManagerMockRecorder) GetState(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetState(blkID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockManager)(nil).GetState), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockManager)(nil).GetState), blkID) } // GetStatelessBlock mocks base method. -func (m *MockManager) GetStatelessBlock(arg0 ids.ID) (block.Block, error) { +func (m *MockManager) GetStatelessBlock(blkID ids.ID) (block.Block, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetStatelessBlock", arg0) + ret := m.ctrl.Call(m, "GetStatelessBlock", blkID) ret0, _ := ret[0].(block.Block) ret1, _ := ret[1].(error) return ret0, ret1 } // GetStatelessBlock indicates an expected call of GetStatelessBlock. -func (mr *MockManagerMockRecorder) GetStatelessBlock(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetStatelessBlock(blkID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatelessBlock", reflect.TypeOf((*MockManager)(nil).GetStatelessBlock), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatelessBlock", reflect.TypeOf((*MockManager)(nil).GetStatelessBlock), blkID) } // LastAccepted mocks base method. @@ -110,7 +112,7 @@ func (m *MockManager) NewBlock(arg0 block.Block) snowman.Block { } // NewBlock indicates an expected call of NewBlock. -func (mr *MockManagerMockRecorder) NewBlock(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) NewBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBlock", reflect.TypeOf((*MockManager)(nil).NewBlock), arg0) } @@ -130,41 +132,41 @@ func (mr *MockManagerMockRecorder) Preferred() *gomock.Call { } // SetPreference mocks base method. -func (m *MockManager) SetPreference(arg0 ids.ID) { +func (m *MockManager) SetPreference(blkID ids.ID) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetPreference", arg0) + m.ctrl.Call(m, "SetPreference", blkID) } // SetPreference indicates an expected call of SetPreference. -func (mr *MockManagerMockRecorder) SetPreference(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) SetPreference(blkID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPreference", reflect.TypeOf((*MockManager)(nil).SetPreference), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPreference", reflect.TypeOf((*MockManager)(nil).SetPreference), blkID) } // VerifyTx mocks base method. -func (m *MockManager) VerifyTx(arg0 *txs.Tx) error { +func (m *MockManager) VerifyTx(tx *txs.Tx) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VerifyTx", arg0) + ret := m.ctrl.Call(m, "VerifyTx", tx) ret0, _ := ret[0].(error) return ret0 } // VerifyTx indicates an expected call of VerifyTx. -func (mr *MockManagerMockRecorder) VerifyTx(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) VerifyTx(tx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyTx", reflect.TypeOf((*MockManager)(nil).VerifyTx), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyTx", reflect.TypeOf((*MockManager)(nil).VerifyTx), tx) } // VerifyUniqueInputs mocks base method. -func (m *MockManager) VerifyUniqueInputs(arg0 ids.ID, arg1 set.Set[ids.ID]) error { +func (m *MockManager) VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VerifyUniqueInputs", arg0, arg1) + ret := m.ctrl.Call(m, "VerifyUniqueInputs", blkID, inputs) ret0, _ := ret[0].(error) return ret0 } // VerifyUniqueInputs indicates an expected call of VerifyUniqueInputs. -func (mr *MockManagerMockRecorder) VerifyUniqueInputs(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) VerifyUniqueInputs(blkID, inputs any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyUniqueInputs", reflect.TypeOf((*MockManager)(nil).VerifyUniqueInputs), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyUniqueInputs", reflect.TypeOf((*MockManager)(nil).VerifyUniqueInputs), blkID, inputs) } diff --git a/vms/avm/block/mock_block.go b/vms/avm/block/mock_block.go index 770dbae29b7d..bc332e88590c 100644 --- a/vms/avm/block/mock_block.go +++ b/vms/avm/block/mock_block.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/avm/block (interfaces: Block) +// +// Generated by this command: +// +// mockgen -package=block -destination=vms/avm/block/mock_block.go github.com/ava-labs/avalanchego/vms/avm/block Block +// // Package block is a generated GoMock package. package block @@ -90,7 +92,7 @@ func (m *MockBlock) InitCtx(arg0 *snow.Context) { } // InitCtx indicates an expected call of InitCtx. -func (mr *MockBlockMockRecorder) InitCtx(arg0 interface{}) *gomock.Call { +func (mr *MockBlockMockRecorder) InitCtx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*MockBlock)(nil).InitCtx), arg0) } @@ -160,7 +162,7 @@ func (m *MockBlock) initialize(arg0 []byte, arg1 codec.Manager) error { } // initialize indicates an expected call of initialize. -func (mr *MockBlockMockRecorder) initialize(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockBlockMockRecorder) initialize(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "initialize", reflect.TypeOf((*MockBlock)(nil).initialize), arg0, arg1) } diff --git a/vms/avm/block/parser.go b/vms/avm/block/parser.go index 230568149b6d..f0c359a513b0 100644 --- a/vms/avm/block/parser.go +++ b/vms/avm/block/parser.go @@ -1,11 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block import ( - "fmt" "reflect" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/utils" @@ -25,17 +25,14 @@ type Parser interface { ParseBlock(bytes []byte) (Block, error) ParseGenesisBlock(bytes []byte) (Block, error) - - InitializeBlock(block Block) error - InitializeGenesisBlock(block Block) error } type parser struct { txs.Parser } -func NewParser(fxs []fxs.Fx) (Parser, error) { - p, err := txs.NewParser(fxs) +func NewParser(durangoTime time.Time, fxs []fxs.Fx) (Parser, error) { + p, err := txs.NewParser(durangoTime, fxs) if err != nil { return nil, err } @@ -52,12 +49,13 @@ func NewParser(fxs []fxs.Fx) (Parser, error) { } func NewCustomParser( + durangoTime time.Time, typeToFxIndex map[reflect.Type]int, clock *mockable.Clock, log logging.Logger, fxs []fxs.Fx, ) (Parser, error) { - p, err := txs.NewCustomParser(typeToFxIndex, clock, log, fxs) + p, err := txs.NewCustomParser(durangoTime, typeToFxIndex, clock, log, fxs) if err != nil { return nil, err } @@ -88,21 +86,3 @@ func parse(cm codec.Manager, bytes []byte) (Block, error) { } return blk, blk.initialize(bytes, cm) } - -func (p *parser) InitializeBlock(block Block) error { - return initialize(block, p.Codec()) -} - -func (p *parser) InitializeGenesisBlock(block Block) error { - return initialize(block, p.GenesisCodec()) -} - -func initialize(blk Block, cm codec.Manager) error { - // We serialize this block as a pointer so that it can be deserialized into - // a Block - bytes, err := cm.Marshal(CodecVersion, &blk) - if err != nil { - return fmt.Errorf("couldn't marshal block: %w", err) - } - return blk.initialize(bytes, cm) -} diff --git a/vms/avm/block/standard_block.go b/vms/avm/block/standard_block.go index be6f1c7456cd..614c7bdc332c 100644 --- a/vms/avm/block/standard_block.go +++ b/vms/avm/block/standard_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -88,5 +88,16 @@ func NewStandardBlock( Time: uint64(timestamp.Unix()), Transactions: txs, } - return blk, initialize(blk, cm) + + // We serialize this block as a pointer so that it can be deserialized into + // a Block + var blkIntf Block = blk + bytes, err := cm.Marshal(CodecVersion, &blkIntf) + if err != nil { + return nil, fmt.Errorf("couldn't marshal block: %w", err) + } + + blk.BlockID = hashing.ComputeHash256Array(bytes) + blk.bytes = bytes + return blk, nil } diff --git a/vms/avm/client.go b/vms/avm/client.go index 8f9ea084c237..63df6543446e 100644 --- a/vms/avm/client.go +++ b/vms/avm/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/client_test.go b/vms/avm/client_test.go index e8013b15d115..28a2d874128a 100644 --- a/vms/avm/client_test.go +++ b/vms/avm/client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/config.go b/vms/avm/config.go new file mode 100644 index 000000000000..f7661bbefd18 --- /dev/null +++ b/vms/avm/config.go @@ -0,0 +1,34 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package avm + +import ( + "encoding/json" + + "github.com/ava-labs/avalanchego/vms/avm/network" +) + +var DefaultConfig = Config{ + Network: network.DefaultConfig, + IndexTransactions: false, + IndexAllowIncomplete: false, + ChecksumsEnabled: false, +} + +type Config struct { + Network network.Config `json:"network"` + IndexTransactions bool `json:"index-transactions"` + IndexAllowIncomplete bool `json:"index-allow-incomplete"` + ChecksumsEnabled bool `json:"checksums-enabled"` +} + +func ParseConfig(configBytes []byte) (Config, error) { + if len(configBytes) == 0 { + return DefaultConfig, nil + } + + config := DefaultConfig + err := json.Unmarshal(configBytes, &config) + return config, err +} diff --git a/vms/avm/config/config.go b/vms/avm/config/config.go index 045b4474ca67..df6e4f7de2ae 100644 --- a/vms/avm/config/config.go +++ b/vms/avm/config/config.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package config +import "time" + // Struct collecting all the foundational parameters of the AVM type Config struct { // Fee that is burned by every non-asset creating transaction @@ -10,4 +12,7 @@ type Config struct { // Fee that must be burned by every asset creating transaction CreateAssetTxFee uint64 + + // Time of the Durango network upgrade + DurangoTime time.Time } diff --git a/vms/avm/config_test.go b/vms/avm/config_test.go new file mode 100644 index 000000000000..27481d78b901 --- /dev/null +++ b/vms/avm/config_test.go @@ -0,0 +1,67 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package avm + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/vms/avm/network" +) + +func TestParseConfig(t *testing.T) { + tests := []struct { + name string + configBytes []byte + expectedConfig Config + }{ + { + name: "unspecified config", + configBytes: nil, + expectedConfig: DefaultConfig, + }, + { + name: "manually specified checksums enabled", + configBytes: []byte(`{"checksums-enabled":true}`), + expectedConfig: Config{ + Network: network.DefaultConfig, + IndexTransactions: DefaultConfig.IndexTransactions, + IndexAllowIncomplete: DefaultConfig.IndexAllowIncomplete, + ChecksumsEnabled: true, + }, + }, + { + name: "manually specified checksums enabled", + configBytes: []byte(`{"network":{"max-validator-set-staleness":1}}`), + expectedConfig: Config{ + Network: network.Config{ + MaxValidatorSetStaleness: time.Nanosecond, + TargetGossipSize: network.DefaultConfig.TargetGossipSize, + PullGossipPollSize: network.DefaultConfig.PullGossipPollSize, + PullGossipFrequency: network.DefaultConfig.PullGossipFrequency, + PullGossipThrottlingPeriod: network.DefaultConfig.PullGossipThrottlingPeriod, + PullGossipThrottlingLimit: network.DefaultConfig.PullGossipThrottlingLimit, + ExpectedBloomFilterElements: network.DefaultConfig.ExpectedBloomFilterElements, + ExpectedBloomFilterFalsePositiveProbability: network.DefaultConfig.ExpectedBloomFilterFalsePositiveProbability, + MaxBloomFilterFalsePositiveProbability: network.DefaultConfig.MaxBloomFilterFalsePositiveProbability, + LegacyPushGossipCacheSize: network.DefaultConfig.LegacyPushGossipCacheSize, + }, + IndexTransactions: DefaultConfig.IndexTransactions, + IndexAllowIncomplete: DefaultConfig.IndexAllowIncomplete, + ChecksumsEnabled: DefaultConfig.ChecksumsEnabled, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + config, err := ParseConfig(test.configBytes) + require.NoError(err) + require.Equal(test.expectedConfig, config) + }) + } +} diff --git a/vms/avm/environment_test.go b/vms/avm/environment_test.go index e1e9e29f630e..236c20875796 100644 --- a/vms/avm/environment_test.go +++ b/vms/avm/environment_test.go @@ -1,13 +1,13 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm import ( "context" - "errors" "math/rand" "testing" + "time" stdjson "encoding/json" @@ -20,8 +20,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/utils/cb58" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting" @@ -67,25 +66,16 @@ var ( }, } - chainID = ids.ID{5, 4, 3, 2, 1} assetID = ids.ID{1, 2, 3} - keys []*secp256k1.PrivateKey - addrs []ids.ShortID // addrs[i] corresponds to keys[i] - - errMissing = errors.New("missing") + keys = secp256k1.TestKeys()[:3] // TODO: Remove [:3] + addrs []ids.ShortID // addrs[i] corresponds to keys[i] ) func init() { - for _, key := range []string{ - "24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5", - "2MMvUMsxx6zsHSNXJdFD8yc5XkancvwyKPwpw4xUK3TCGDuNBY", - "cxb7KpGWhDMALTjNNSJ7UQkkomPesyWAPUaWRGdyeBNzR6f35", - } { - keyBytes, _ := cb58.Decode(key) - pk, _ := secp256k1.ToPrivateKey(keyBytes) - keys = append(keys, pk) - addrs = append(addrs, pk.PublicKey().Address()) + addrs = make([]ids.ShortID, len(keys)) + for i, key := range keys { + addrs[i] = key.Address() } } @@ -131,7 +121,8 @@ func setup(tb testing.TB, c *envConfig) *environment { } genesisBytes := buildGenesisTestWithArgs(tb, genesisArgs) - ctx := newContext(tb) + + ctx := snowtest.Context(tb, snowtest.XChainID) baseDB := memdb.New() m := atomic.NewMemory(prefixdb.New([]byte{0}, baseDB)) @@ -167,9 +158,8 @@ func setup(tb testing.TB, c *envConfig) *environment { Config: vmStaticConfig, } - vmDynamicConfig := Config{ - IndexTransactions: true, - } + vmDynamicConfig := DefaultConfig + vmDynamicConfig.IndexTransactions = true if c.vmDynamicConfig != nil { vmDynamicConfig = *c.vmDynamicConfig } @@ -232,40 +222,6 @@ func setup(tb testing.TB, c *envConfig) *environment { return env } -func newContext(tb testing.TB) *snow.Context { - require := require.New(tb) - - genesisBytes := buildGenesisTest(tb) - tx := getCreateTxFromGenesisTest(tb, genesisBytes, "AVAX") - - ctx := snow.DefaultContextTest() - ctx.NetworkID = constants.UnitTestID - ctx.ChainID = chainID - ctx.AVAXAssetID = tx.ID() - ctx.XChainID = ids.Empty.Prefix(0) - ctx.CChainID = ids.Empty.Prefix(1) - aliaser := ctx.BCLookup.(ids.Aliaser) - - require.NoError(aliaser.Alias(chainID, "X")) - require.NoError(aliaser.Alias(chainID, chainID.String())) - require.NoError(aliaser.Alias(constants.PlatformChainID, "P")) - require.NoError(aliaser.Alias(constants.PlatformChainID, constants.PlatformChainID.String())) - - ctx.ValidatorState = &validators.TestState{ - GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { - subnetID, ok := map[ids.ID]ids.ID{ - constants.PlatformChainID: ctx.SubnetID, - chainID: ctx.SubnetID, - }[chainID] - if !ok { - return ids.Empty, errMissing - } - return subnetID, nil - }, - } - return ctx -} - // Returns: // // 1. tx in genesis that creates asset @@ -273,9 +229,12 @@ func newContext(tb testing.TB) *snow.Context { func getCreateTxFromGenesisTest(tb testing.TB, genesisBytes []byte, assetName string) *txs.Tx { require := require.New(tb) - parser, err := txs.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) cm := parser.GenesisCodec() @@ -296,7 +255,7 @@ func getCreateTxFromGenesisTest(tb testing.TB, genesisBytes []byte, assetName st tx := &txs.Tx{ Unsigned: &assetTx.CreateAssetTx, } - require.NoError(parser.InitializeGenesisTx(tx)) + require.NoError(tx.Initialize(parser.GenesisCodec())) return tx } @@ -320,7 +279,7 @@ func buildGenesisTestWithArgs(tb testing.TB, args *BuildGenesisArgs) []byte { return b } -func newTx(tb testing.TB, genesisBytes []byte, vm *VM, assetName string) *txs.Tx { +func newTx(tb testing.TB, genesisBytes []byte, chainID ids.ID, parser txs.Parser, assetName string) *txs.Tx { require := require.New(tb) createTx := getCreateTxFromGenesisTest(tb, genesisBytes, assetName) @@ -345,14 +304,14 @@ func newTx(tb testing.TB, genesisBytes []byte, vm *VM, assetName string) *txs.Tx }}, }, }} - require.NoError(tx.SignSECP256K1Fx(vm.parser.Codec(), [][]*secp256k1.PrivateKey{{keys[0]}})) + require.NoError(tx.SignSECP256K1Fx(parser.Codec(), [][]*secp256k1.PrivateKey{{keys[0]}})) return tx } // Sample from a set of addresses and return them raw and formatted as strings. // The size of the sample is between 1 and len(addrs) // If len(addrs) == 0, returns nil -func sampleAddrs(tb testing.TB, vm *VM, addrs []ids.ShortID) ([]ids.ShortID, []string) { +func sampleAddrs(tb testing.TB, addressFormatter avax.AddressManager, addrs []ids.ShortID) ([]ids.ShortID, []string) { require := require.New(tb) sampledAddrs := []ids.ShortID{} @@ -366,7 +325,7 @@ func sampleAddrs(tb testing.TB, vm *VM, addrs []ids.ShortID) ([]ids.ShortID, []s require.NoError(err) for _, index := range indices { addr := addrs[index] - addrStr, err := vm.FormatLocalAddress(addr) + addrStr, err := addressFormatter.FormatLocalAddress(addr) require.NoError(err) sampledAddrs = append(sampledAddrs, addr) @@ -524,30 +483,31 @@ func makeCustomAssetGenesis(tb testing.TB) *BuildGenesisArgs { } } -// issueAndAccept expects the context lock to be held +// issueAndAccept expects the context lock not to be held func issueAndAccept( require *require.Assertions, vm *VM, issuer <-chan common.Message, tx *txs.Tx, ) { - txID, err := vm.IssueTx(tx.Bytes()) + txID, err := vm.issueTx(tx) require.NoError(err) require.Equal(tx.ID(), txID) buildAndAccept(require, vm, issuer, txID) } -// buildAndAccept expects the context lock to be held +// buildAndAccept expects the context lock not to be held func buildAndAccept( require *require.Assertions, vm *VM, issuer <-chan common.Message, txID ids.ID, ) { - vm.ctx.Lock.Unlock() require.Equal(common.PendingTxs, <-issuer) + vm.ctx.Lock.Lock() + defer vm.ctx.Lock.Unlock() blkIntf, err := vm.BuildBlock(context.Background()) require.NoError(err) diff --git a/vms/avm/factory.go b/vms/avm/factory.go index 1e2c6f68a10f..ee71cac0346f 100644 --- a/vms/avm/factory.go +++ b/vms/avm/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/fx_test.go b/vms/avm/fx_test.go index ee0cdbfd8157..7cea92cf3194 100644 --- a/vms/avm/fx_test.go +++ b/vms/avm/fx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/fxs/fx.go b/vms/avm/fxs/fx.go index 512a3bf0da31..2749ee4500a3 100644 --- a/vms/avm/fxs/fx.go +++ b/vms/avm/fxs/fx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package fxs diff --git a/vms/avm/genesis.go b/vms/avm/genesis.go index 506d2465d691..b2d6e7409152 100644 --- a/vms/avm/genesis.go +++ b/vms/avm/genesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -19,6 +19,6 @@ type GenesisAsset struct { txs.CreateAssetTx `serialize:"true"` } -func (g *GenesisAsset) Less(other *GenesisAsset) bool { - return g.Alias < other.Alias +func (g *GenesisAsset) Compare(other *GenesisAsset) int { + return utils.Compare(g.Alias, other.Alias) } diff --git a/vms/avm/genesis_test.go b/vms/avm/genesis_test.go index 10c7aac40295..2e5da96fc63e 100644 --- a/vms/avm/genesis_test.go +++ b/vms/avm/genesis_test.go @@ -1,27 +1,42 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm import ( + "fmt" "testing" "github.com/stretchr/testify/require" ) -func TestGenesisAssetLess(t *testing.T) { - require := require.New(t) - - var g1, g2 GenesisAsset - require.False(g1.Less(&g2)) - require.False(g2.Less(&g1)) - - g1 = GenesisAsset{ - Alias: "a", +func TestGenesisAssetCompare(t *testing.T) { + tests := []struct { + a *GenesisAsset + b *GenesisAsset + expected int + }{ + { + a: &GenesisAsset{}, + b: &GenesisAsset{}, + expected: 0, + }, + { + a: &GenesisAsset{ + Alias: "a", + }, + b: &GenesisAsset{ + Alias: "aa", + }, + expected: -1, + }, } - g2 = GenesisAsset{ - Alias: "aa", + for _, test := range tests { + t.Run(fmt.Sprintf("%s_%s_%d", test.a.Alias, test.b.Alias, test.expected), func(t *testing.T) { + require := require.New(t) + + require.Equal(test.expected, test.a.Compare(test.b)) + require.Equal(-test.expected, test.b.Compare(test.a)) + }) } - require.True(g1.Less(&g2)) - require.False(g2.Less(&g1)) } diff --git a/vms/avm/health.go b/vms/avm/health.go index 725418b1ec8f..6cb2e14b0776 100644 --- a/vms/avm/health.go +++ b/vms/avm/health.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/index_test.go b/vms/avm/index_test.go index 74b306c16c0f..03a2fd863c6a 100644 --- a/vms/avm/index_test.go +++ b/vms/avm/index_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -52,12 +52,15 @@ func TestIndexTransaction_Ordered(t *testing.T) { env.vm.state.AddUTXO(utxo) // make transaction - tx := buildTX(utxoID, txAssetID, addr) + tx := buildTX(env.vm.ctx.XChainID, utxoID, txAssetID, addr) require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) - // issue transaction + env.vm.ctx.Lock.Unlock() + issueAndAccept(require, env.vm, env.issuer, tx) + env.vm.ctx.Lock.Lock() + txs = append(txs, tx) } @@ -93,12 +96,16 @@ func TestIndexTransaction_MultipleTransactions(t *testing.T) { env.vm.state.AddUTXO(utxo) // make transaction - tx := buildTX(utxoID, txAssetID, addr) + tx := buildTX(env.vm.ctx.XChainID, utxoID, txAssetID, addr) require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + env.vm.ctx.Lock.Unlock() + // issue transaction issueAndAccept(require, env.vm, env.issuer, tx) + env.vm.ctx.Lock.Lock() + addressTxMap[addr] = tx } @@ -142,12 +149,15 @@ func TestIndexTransaction_MultipleAddresses(t *testing.T) { env.vm.state.AddUTXO(utxo) // make transaction - tx := buildTX(utxoID, txAssetID, addrs...) + tx := buildTX(env.vm.ctx.XChainID, utxoID, txAssetID, addrs...) require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) - // issue transaction + env.vm.ctx.Lock.Unlock() + issueAndAccept(require, env.vm, env.issuer, tx) + env.vm.ctx.Lock.Lock() + assertIndexedTX(t, env.vm.db, 0, addr, txAssetID.ID, tx.ID()) assertLatestIdx(t, env.vm.db, addr, txAssetID.ID, 1) } @@ -258,7 +268,7 @@ func buildUTXO(utxoID avax.UTXOID, txAssetID avax.Asset, addr ids.ShortID) *avax } } -func buildTX(utxoID avax.UTXOID, txAssetID avax.Asset, address ...ids.ShortID) *txs.Tx { +func buildTX(chainID ids.ID, utxoID avax.UTXOID, txAssetID avax.Asset, address ...ids.ShortID) *txs.Tx { return &txs.Tx{Unsigned: &txs.BaseTx{ BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, diff --git a/vms/avm/metrics/metrics.go b/vms/avm/metrics/metrics.go index 59c4e159a901..9e4053e1fcc6 100644 --- a/vms/avm/metrics/metrics.go +++ b/vms/avm/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/vms/avm/metrics/mock_metrics.go b/vms/avm/metrics/mock_metrics.go index b83a065e3fa0..2ae4a0786bf0 100644 --- a/vms/avm/metrics/mock_metrics.go +++ b/vms/avm/metrics/mock_metrics.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/avm/metrics (interfaces: Metrics) +// +// Generated by this command: +// +// mockgen -package=metrics -destination=vms/avm/metrics/mock_metrics.go github.com/ava-labs/avalanchego/vms/avm/metrics Metrics +// // Package metrics is a generated GoMock package. package metrics @@ -47,7 +49,7 @@ func (m *MockMetrics) AfterRequest(arg0 *rpc.RequestInfo) { } // AfterRequest indicates an expected call of AfterRequest. -func (mr *MockMetricsMockRecorder) AfterRequest(arg0 interface{}) *gomock.Call { +func (mr *MockMetricsMockRecorder) AfterRequest(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AfterRequest", reflect.TypeOf((*MockMetrics)(nil).AfterRequest), arg0) } @@ -97,7 +99,7 @@ func (m *MockMetrics) InterceptRequest(arg0 *rpc.RequestInfo) *http.Request { } // InterceptRequest indicates an expected call of InterceptRequest. -func (mr *MockMetricsMockRecorder) InterceptRequest(arg0 interface{}) *gomock.Call { +func (mr *MockMetricsMockRecorder) InterceptRequest(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InterceptRequest", reflect.TypeOf((*MockMetrics)(nil).InterceptRequest), arg0) } @@ -111,7 +113,7 @@ func (m *MockMetrics) MarkBlockAccepted(arg0 block.Block) error { } // MarkBlockAccepted indicates an expected call of MarkBlockAccepted. -func (mr *MockMetricsMockRecorder) MarkBlockAccepted(arg0 interface{}) *gomock.Call { +func (mr *MockMetricsMockRecorder) MarkBlockAccepted(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkBlockAccepted", reflect.TypeOf((*MockMetrics)(nil).MarkBlockAccepted), arg0) } @@ -125,7 +127,7 @@ func (m *MockMetrics) MarkTxAccepted(arg0 *txs.Tx) error { } // MarkTxAccepted indicates an expected call of MarkTxAccepted. -func (mr *MockMetricsMockRecorder) MarkTxAccepted(arg0 interface{}) *gomock.Call { +func (mr *MockMetricsMockRecorder) MarkTxAccepted(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkTxAccepted", reflect.TypeOf((*MockMetrics)(nil).MarkTxAccepted), arg0) } diff --git a/vms/avm/metrics/tx_metrics.go b/vms/avm/metrics/tx_metrics.go index 217eeb18a346..3ae3e8cdea85 100644 --- a/vms/avm/metrics/tx_metrics.go +++ b/vms/avm/metrics/tx_metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/vms/avm/network/atomic.go b/vms/avm/network/atomic.go index c6b011dab1cf..0774ed36603e 100644 --- a/vms/avm/network/atomic.go +++ b/vms/avm/network/atomic.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -51,12 +51,14 @@ func (a *atomic) CrossChainAppRequestFailed( ctx context.Context, chainID ids.ID, requestID uint32, + appErr *common.AppError, ) error { h := a.handler.Get() return h.CrossChainAppRequestFailed( ctx, chainID, requestID, + appErr, ) } @@ -96,12 +98,14 @@ func (a *atomic) AppRequestFailed( ctx context.Context, nodeID ids.NodeID, requestID uint32, + appErr *common.AppError, ) error { h := a.handler.Get() return h.AppRequestFailed( ctx, nodeID, requestID, + appErr, ) } diff --git a/vms/avm/network/config.go b/vms/avm/network/config.go new file mode 100644 index 000000000000..8536504d8383 --- /dev/null +++ b/vms/avm/network/config.go @@ -0,0 +1,66 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "time" + + "github.com/ava-labs/avalanchego/utils/units" +) + +var DefaultConfig = Config{ + MaxValidatorSetStaleness: time.Minute, + TargetGossipSize: 20 * units.KiB, + PullGossipPollSize: 1, + PullGossipFrequency: 1500 * time.Millisecond, + PullGossipThrottlingPeriod: 10 * time.Second, + PullGossipThrottlingLimit: 2, + ExpectedBloomFilterElements: 8 * 1024, + ExpectedBloomFilterFalsePositiveProbability: .01, + MaxBloomFilterFalsePositiveProbability: .05, + LegacyPushGossipCacheSize: 512, +} + +type Config struct { + // MaxValidatorSetStaleness limits how old of a validator set the network + // will use for peer sampling and rate limiting. + MaxValidatorSetStaleness time.Duration `json:"max-validator-set-staleness"` + // TargetGossipSize is the number of bytes that will be attempted to be + // sent when pushing transactions and when responded to transaction pull + // requests. + TargetGossipSize int `json:"target-gossip-size"` + // PullGossipPollSize is the number of validators to sample when performing + // a round of pull gossip. + PullGossipPollSize int `json:"pull-gossip-poll-size"` + // PullGossipFrequency is how frequently rounds of pull gossip are + // performed. + PullGossipFrequency time.Duration `json:"pull-gossip-frequency"` + // PullGossipThrottlingPeriod is how large of a window the throttler should + // use. + PullGossipThrottlingPeriod time.Duration `json:"pull-gossip-throttling-period"` + // PullGossipThrottlingLimit is the number of pull querys that are allowed + // by a validator in every throttling window. + PullGossipThrottlingLimit int `json:"pull-gossip-throttling-limit"` + // ExpectedBloomFilterElements is the number of elements to expect when + // creating a new bloom filter. The larger this number is, the larger the + // bloom filter will be. + ExpectedBloomFilterElements int `json:"expected-bloom-filter-elements"` + // ExpectedBloomFilterFalsePositiveProbability is the expected probability + // of a false positive after having inserted ExpectedBloomFilterElements + // into a bloom filter. The smaller this number is, the larger the bloom + // filter will be. + ExpectedBloomFilterFalsePositiveProbability float64 `json:"expected-bloom-filter-false-positive-probability"` + // MaxBloomFilterFalsePositiveProbability is used to determine when the + // bloom filter should be refreshed. Once the expected probability of a + // false positive exceeds this value, the bloom filter will be regenerated. + // The smaller this number is, the more frequently that the bloom filter + // will be regenerated. + MaxBloomFilterFalsePositiveProbability float64 `json:"max-bloom-filter-false-positive-probability"` + // LegacyPushGossipCacheSize tracks the most recently received transactions + // and ensures to only gossip them once. + // + // Deprecated: The legacy push gossip mechanism is deprecated in favor of + // the p2p SDK's push gossip mechanism. + LegacyPushGossipCacheSize int `json:"legacy-push-gossip-cache-size"` +} diff --git a/vms/avm/network/gossip.go b/vms/avm/network/gossip.go new file mode 100644 index 000000000000..adfe9aa6831f --- /dev/null +++ b/vms/avm/network/gossip.go @@ -0,0 +1,158 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/gossip" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/avm/txs" + "github.com/ava-labs/avalanchego/vms/avm/txs/mempool" +) + +var ( + _ p2p.Handler = (*txGossipHandler)(nil) + _ gossip.Set[*txs.Tx] = (*gossipMempool)(nil) + _ gossip.Marshaller[*txs.Tx] = (*txParser)(nil) +) + +// bloomChurnMultiplier is the number used to multiply the size of the mempool +// to determine how large of a bloom filter to create. +const bloomChurnMultiplier = 3 + +// txGossipHandler is the handler called when serving gossip messages +type txGossipHandler struct { + p2p.NoOpHandler + appGossipHandler p2p.Handler + appRequestHandler p2p.Handler +} + +func (t txGossipHandler) AppGossip( + ctx context.Context, + nodeID ids.NodeID, + gossipBytes []byte, +) { + t.appGossipHandler.AppGossip(ctx, nodeID, gossipBytes) +} + +func (t txGossipHandler) AppRequest( + ctx context.Context, + nodeID ids.NodeID, + deadline time.Time, + requestBytes []byte, +) ([]byte, error) { + return t.appRequestHandler.AppRequest(ctx, nodeID, deadline, requestBytes) +} + +type txParser struct { + parser txs.Parser +} + +func (*txParser) MarshalGossip(tx *txs.Tx) ([]byte, error) { + return tx.Bytes(), nil +} + +func (g *txParser) UnmarshalGossip(bytes []byte) (*txs.Tx, error) { + return g.parser.ParseTx(bytes) +} + +func newGossipMempool( + mempool mempool.Mempool, + log logging.Logger, + txVerifier TxVerifier, + parser txs.Parser, + minTargetElements int, + targetFalsePositiveProbability, + resetFalsePositiveProbability float64, +) (*gossipMempool, error) { + bloom, err := gossip.NewBloomFilter(minTargetElements, targetFalsePositiveProbability, resetFalsePositiveProbability) + return &gossipMempool{ + Mempool: mempool, + log: log, + txVerifier: txVerifier, + parser: parser, + bloom: bloom, + }, err +} + +type gossipMempool struct { + mempool.Mempool + log logging.Logger + txVerifier TxVerifier + parser txs.Parser + + lock sync.RWMutex + bloom *gossip.BloomFilter +} + +// Add is called by the p2p SDK when handling transactions that were pushed to +// us and when handling transactions that were pulled from a peer. If this +// returns a nil error while handling push gossip, the p2p SDK will queue the +// transaction to push gossip as well. +func (g *gossipMempool) Add(tx *txs.Tx) error { + txID := tx.ID() + if _, ok := g.Mempool.Get(txID); ok { + return fmt.Errorf("attempted to issue %w: %s ", mempool.ErrDuplicateTx, txID) + } + + if reason := g.Mempool.GetDropReason(txID); reason != nil { + // If the tx is being dropped - just ignore it + // + // TODO: Should we allow re-verification of the transaction even if it + // failed previously? + return reason + } + + // Verify the tx at the currently preferred state + if err := g.txVerifier.VerifyTx(tx); err != nil { + g.Mempool.MarkDropped(txID, err) + return err + } + + return g.AddVerified(tx) +} + +func (g *gossipMempool) AddVerified(tx *txs.Tx) error { + if err := g.Mempool.Add(tx); err != nil { + g.Mempool.MarkDropped(tx.ID(), err) + return err + } + + g.lock.Lock() + defer g.lock.Unlock() + + g.bloom.Add(tx) + reset, err := gossip.ResetBloomFilterIfNeeded(g.bloom, g.Mempool.Len()*bloomChurnMultiplier) + if err != nil { + return err + } + + if reset { + g.log.Debug("resetting bloom filter") + g.Mempool.Iterate(func(tx *txs.Tx) bool { + g.bloom.Add(tx) + return true + }) + } + + g.Mempool.RequestBuildBlock() + return nil +} + +func (g *gossipMempool) Iterate(f func(*txs.Tx) bool) { + g.Mempool.Iterate(f) +} + +func (g *gossipMempool) GetFilter() (bloom []byte, salt []byte) { + g.lock.RLock() + defer g.lock.RUnlock() + + return g.bloom.Marshal() +} diff --git a/vms/avm/network/gossip_test.go b/vms/avm/network/gossip_test.go new file mode 100644 index 000000000000..8f9df5ac7c86 --- /dev/null +++ b/vms/avm/network/gossip_test.go @@ -0,0 +1,132 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/avm/fxs" + "github.com/ava-labs/avalanchego/vms/avm/txs" + "github.com/ava-labs/avalanchego/vms/avm/txs/mempool" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" +) + +var _ TxVerifier = (*testVerifier)(nil) + +type testVerifier struct { + err error +} + +func (v testVerifier) VerifyTx(*txs.Tx) error { + return v.err +} + +func TestMarshaller(t *testing.T) { + require := require.New(t) + + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) + require.NoError(err) + + marhsaller := txParser{ + parser: parser, + } + + want := &txs.Tx{Unsigned: &txs.BaseTx{}} + require.NoError(want.Initialize(parser.Codec())) + + bytes, err := marhsaller.MarshalGossip(want) + require.NoError(err) + + got, err := marhsaller.UnmarshalGossip(bytes) + require.NoError(err) + require.Equal(want.GossipID(), got.GossipID()) +} + +func TestGossipMempoolAdd(t *testing.T) { + require := require.New(t) + + metrics := prometheus.NewRegistry() + toEngine := make(chan common.Message, 1) + + baseMempool, err := mempool.New("", metrics, toEngine) + require.NoError(err) + + parser, err := txs.NewParser(time.Time{}, nil) + require.NoError(err) + + mempool, err := newGossipMempool( + baseMempool, + logging.NoLog{}, + testVerifier{}, + parser, + DefaultConfig.ExpectedBloomFilterElements, + DefaultConfig.ExpectedBloomFilterFalsePositiveProbability, + DefaultConfig.MaxBloomFilterFalsePositiveProbability, + ) + require.NoError(err) + + tx := &txs.Tx{ + Unsigned: &txs.BaseTx{ + BaseTx: avax.BaseTx{ + Ins: []*avax.TransferableInput{}, + }, + }, + TxID: ids.GenerateTestID(), + } + + require.NoError(mempool.Add(tx)) + require.True(mempool.bloom.Has(tx)) +} + +func TestGossipMempoolAddVerified(t *testing.T) { + require := require.New(t) + + metrics := prometheus.NewRegistry() + toEngine := make(chan common.Message, 1) + + baseMempool, err := mempool.New("", metrics, toEngine) + require.NoError(err) + + parser, err := txs.NewParser(time.Time{}, nil) + require.NoError(err) + + mempool, err := newGossipMempool( + baseMempool, + logging.NoLog{}, + testVerifier{ + err: errTest, // We shouldn't be attempting to verify the tx in this flow + }, + parser, + DefaultConfig.ExpectedBloomFilterElements, + DefaultConfig.ExpectedBloomFilterFalsePositiveProbability, + DefaultConfig.MaxBloomFilterFalsePositiveProbability, + ) + require.NoError(err) + + tx := &txs.Tx{ + Unsigned: &txs.BaseTx{ + BaseTx: avax.BaseTx{ + Ins: []*avax.TransferableInput{}, + }, + }, + TxID: ids.GenerateTestID(), + } + + require.NoError(mempool.AddVerified(tx)) + require.True(mempool.bloom.Has(tx)) +} diff --git a/vms/avm/network/network.go b/vms/avm/network/network.go index a1d3337ddb23..fbeed92a95a1 100644 --- a/vms/avm/network/network.go +++ b/vms/avm/network/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -6,43 +6,41 @@ package network import ( "context" "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/vms/avm/block/executor" + "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/avm/txs/mempool" "github.com/ava-labs/avalanchego/vms/components/message" ) -// We allow [recentTxsCacheSize] to be fairly large because we only store hashes -// in the cache, not entire transactions. -const recentTxsCacheSize = 512 +const txGossipHandlerID = 0 -var _ Network = (*network)(nil) +var ( + _ common.AppHandler = (*Network)(nil) + _ validators.Connector = (*Network)(nil) +) -type Network interface { - common.AppHandler +type Network struct { + *p2p.Network - // IssueTx verifies the transaction at the currently preferred state, adds - // it to the mempool, and gossips it to the network. - // - // Invariant: Assumes the context lock is held. - IssueTx(context.Context, *txs.Tx) error -} - -type network struct { - // We embed a noop handler for all unhandled messages - common.AppHandler + txPushGossiper gossip.Accumulator[*txs.Tx] + txPullGossiper gossip.Gossiper + txPullGossipFrequency time.Duration ctx *snow.Context parser txs.Parser - manager executor.Manager - mempool mempool.Mempool + mempool *gossipMempool appSender common.AppSender // gossip related attributes @@ -53,26 +51,127 @@ type network struct { func New( ctx *snow.Context, parser txs.Parser, - manager executor.Manager, + txVerifier TxVerifier, mempool mempool.Mempool, appSender common.AppSender, -) Network { - return &network{ - AppHandler: common.NewNoOpAppHandler(ctx.Log), + registerer prometheus.Registerer, + config Config, +) (*Network, error) { + p2pNetwork, err := p2p.NewNetwork(ctx.Log, appSender, registerer, "p2p") + if err != nil { + return nil, err + } + + marshaller := &txParser{ + parser: parser, + } + validators := p2p.NewValidators( + p2pNetwork.Peers, + ctx.Log, + ctx.SubnetID, + ctx.ValidatorState, + config.MaxValidatorSetStaleness, + ) + txGossipClient := p2pNetwork.NewClient( + txGossipHandlerID, + p2p.WithValidatorSampling(validators), + ) + txGossipMetrics, err := gossip.NewMetrics(registerer, "tx") + if err != nil { + return nil, err + } - ctx: ctx, - parser: parser, - manager: manager, - mempool: mempool, - appSender: appSender, + txPushGossiper := gossip.NewPushGossiper[*txs.Tx]( + marshaller, + txGossipClient, + txGossipMetrics, + config.TargetGossipSize, + ) + + gossipMempool, err := newGossipMempool( + mempool, + ctx.Log, + txVerifier, + parser, + config.ExpectedBloomFilterElements, + config.ExpectedBloomFilterFalsePositiveProbability, + config.MaxBloomFilterFalsePositiveProbability, + ) + if err != nil { + return nil, err + } + + var txPullGossiper gossip.Gossiper + txPullGossiper = gossip.NewPullGossiper[*txs.Tx]( + ctx.Log, + marshaller, + gossipMempool, + txGossipClient, + txGossipMetrics, + config.PullGossipPollSize, + ) + + // Gossip requests are only served if a node is a validator + txPullGossiper = gossip.ValidatorGossiper{ + Gossiper: txPullGossiper, + NodeID: ctx.NodeID, + Validators: validators, + } + + handler := gossip.NewHandler[*txs.Tx]( + ctx.Log, + marshaller, + txPushGossiper, + gossipMempool, + txGossipMetrics, + config.TargetGossipSize, + ) + + validatorHandler := p2p.NewValidatorHandler( + p2p.NewThrottlerHandler( + handler, + p2p.NewSlidingWindowThrottler( + config.PullGossipThrottlingPeriod, + config.PullGossipThrottlingLimit, + ), + ctx.Log, + ), + validators, + ctx.Log, + ) + + // We allow pushing txs between all peers, but only serve gossip requests + // from validators + txGossipHandler := txGossipHandler{ + appGossipHandler: handler, + appRequestHandler: validatorHandler, + } + + if err := p2pNetwork.AddHandler(txGossipHandlerID, txGossipHandler); err != nil { + return nil, err + } + + return &Network{ + Network: p2pNetwork, + txPushGossiper: txPushGossiper, + txPullGossiper: txPullGossiper, + txPullGossipFrequency: config.PullGossipFrequency, + ctx: ctx, + parser: parser, + mempool: gossipMempool, + appSender: appSender, recentTxs: &cache.LRU[ids.ID, struct{}]{ - Size: recentTxsCacheSize, + Size: config.LegacyPushGossipCacheSize, }, - } + }, nil } -func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, msgBytes []byte) error { +func (n *Network) Gossip(ctx context.Context) { + gossip.Every(ctx, n.ctx.Log, n.txPullGossiper, n.txPullGossipFrequency) +} + +func (n *Network) AppGossip(ctx context.Context, nodeID ids.NodeID, msgBytes []byte) error { n.ctx.Log.Debug("called AppGossip message handler", zap.Stringer("nodeID", nodeID), zap.Int("messageLen", len(msgBytes)), @@ -80,10 +179,11 @@ func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, msgBytes []b msgIntf, err := message.Parse(msgBytes) if err != nil { - n.ctx.Log.Debug("dropping AppGossip message", + n.ctx.Log.Debug("forwarding AppGossip message to SDK network", zap.String("reason", "failed to parse message"), ) - return nil + + return n.Network.AppGossip(ctx, nodeID, msgBytes) } msg, ok := msgIntf.(*message.Tx) @@ -103,83 +203,76 @@ func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, msgBytes []b ) return nil } - txID := tx.ID() - // We need to grab the context lock here to avoid racy behavior with - // transaction verification + mempool modifications. - // - // Invariant: tx should not be referenced again without the context lock - // held to avoid any data races. - n.ctx.Lock.Lock() - err = n.issueTx(tx) - n.ctx.Lock.Unlock() - if err == nil { - n.gossipTx(ctx, txID, msgBytes) + if err := n.mempool.Add(tx); err == nil { + txID := tx.ID() + n.txPushGossiper.Add(tx) + if err := n.txPushGossiper.Gossip(ctx); err != nil { + n.ctx.Log.Error("failed to gossip tx", + zap.Stringer("txID", tx.ID()), + zap.Error(err), + ) + } + n.gossipTxMessage(ctx, txID, msgBytes) } return nil } -func (n *network) IssueTx(ctx context.Context, tx *txs.Tx) error { - if err := n.issueTx(tx); err != nil { +// IssueTx attempts to add a tx to the mempool, after verifying it. If the tx is +// added to the mempool, it will attempt to push gossip the tx to random peers +// in the network using both the legacy and p2p SDK. +// +// If the tx is already in the mempool, mempool.ErrDuplicateTx will be +// returned. +// If the tx is not added to the mempool, an error will be returned. +func (n *Network) IssueTx(ctx context.Context, tx *txs.Tx) error { + if err := n.mempool.Add(tx); err != nil { return err } + return n.gossipTx(ctx, tx) +} - txBytes := tx.Bytes() - msg := &message.Tx{ - Tx: txBytes, - } - msgBytes, err := message.Build(msg) - if err != nil { +// IssueVerifiedTx attempts to add a tx to the mempool, without first verifying +// it. If the tx is added to the mempool, it will attempt to push gossip the tx +// to random peers in the network using both the legacy and p2p SDK. +// +// If the tx is already in the mempool, mempool.ErrDuplicateTx will be +// returned. +// If the tx is not added to the mempool, an error will be returned. +func (n *Network) IssueVerifiedTx(ctx context.Context, tx *txs.Tx) error { + if err := n.mempool.AddVerified(tx); err != nil { return err } - - txID := tx.ID() - n.gossipTx(ctx, txID, msgBytes) - return nil + return n.gossipTx(ctx, tx) } -// returns nil if the tx is in the mempool -func (n *network) issueTx(tx *txs.Tx) error { - txID := tx.ID() - if n.mempool.Has(txID) { - // The tx is already in the mempool - return nil - } - - if reason := n.mempool.GetDropReason(txID); reason != nil { - // If the tx is being dropped - just ignore it - // - // TODO: Should we allow re-verification of the transaction even if it - // failed previously? - return reason - } - - // Verify the tx at the currently preferred state - if err := n.manager.VerifyTx(tx); err != nil { - n.ctx.Log.Debug("tx failed verification", - zap.Stringer("txID", txID), +// gossipTx pushes the tx to peers using both the legacy and p2p SDK. +func (n *Network) gossipTx(ctx context.Context, tx *txs.Tx) error { + n.txPushGossiper.Add(tx) + if err := n.txPushGossiper.Gossip(ctx); err != nil { + n.ctx.Log.Error("failed to gossip tx", + zap.Stringer("txID", tx.ID()), zap.Error(err), ) - - n.mempool.MarkDropped(txID, err) - return err } - if err := n.mempool.Add(tx); err != nil { - n.ctx.Log.Debug("tx failed to be added to the mempool", - zap.Stringer("txID", txID), - zap.Error(err), - ) - - n.mempool.MarkDropped(txID, err) + txBytes := tx.Bytes() + msg := &message.Tx{ + Tx: txBytes, + } + msgBytes, err := message.Build(msg) + if err != nil { return err } - n.mempool.RequestBuildBlock() + txID := tx.ID() + n.gossipTxMessage(ctx, txID, msgBytes) return nil } -func (n *network) gossipTx(ctx context.Context, txID ids.ID, msgBytes []byte) { +// gossipTxMessage pushes the tx message to peers using the legacy format. +// If the tx was recently gossiped, this function does nothing. +func (n *Network) gossipTxMessage(ctx context.Context, txID ids.ID, msgBytes []byte) { n.recentTxsLock.Lock() _, has := n.recentTxs.Get(txID) n.recentTxs.Put(txID, struct{}{}) diff --git a/vms/avm/network/network_test.go b/vms/avm/network/network_test.go index 6d779eadcc28..da66dff9b2ac 100644 --- a/vms/avm/network/network_test.go +++ b/vms/avm/network/network_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -7,6 +7,9 @@ import ( "context" "errors" "testing" + "time" + + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" @@ -27,7 +30,22 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) -var errTest = errors.New("test error") +var ( + testConfig = Config{ + MaxValidatorSetStaleness: time.Second, + TargetGossipSize: 1, + PullGossipPollSize: 1, + PullGossipFrequency: time.Second, + PullGossipThrottlingPeriod: time.Second, + PullGossipThrottlingLimit: 1, + ExpectedBloomFilterElements: 10, + ExpectedBloomFilterFalsePositiveProbability: .1, + MaxBloomFilterFalsePositiveProbability: .5, + LegacyPushGossipCacheSize: 512, + } + + errTest = errors.New("test error") +) func TestNetworkAppGossip(t *testing.T) { testTx := &txs.Tx{ @@ -41,37 +59,31 @@ func TestNetworkAppGossip(t *testing.T) { }, } - parser, err := txs.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(t, err) - require.NoError(t, parser.InitializeTx(testTx)) + require.NoError(t, testTx.Initialize(parser.Codec())) type test struct { - name string - msgBytesFunc func() []byte - mempoolFunc func(*gomock.Controller) mempool.Mempool - appSenderFunc func(*gomock.Controller) common.AppSender + name string + msgBytesFunc func() []byte + mempoolFunc func(*gomock.Controller) mempool.Mempool + txVerifierFunc func(*gomock.Controller) TxVerifier + appSenderFunc func(*gomock.Controller) common.AppSender } tests := []test{ { - // Shouldn't attempt to issue or gossip the tx name: "invalid message bytes", msgBytesFunc: func() []byte { return []byte{0x00} }, - mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { - // Unused in this test - return nil - }, - appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Unused in this test - return nil - }, }, { - // Shouldn't attempt to issue or gossip the tx name: "invalid tx bytes", msgBytesFunc: func() []byte { msg := message.Tx{ @@ -81,18 +93,42 @@ func TestNetworkAppGossip(t *testing.T) { require.NoError(t, err) return msgBytes }, + }, + { + name: "tx already in mempool", + msgBytesFunc: func() []byte { + msg := message.Tx{ + Tx: testTx.Bytes(), + } + msgBytes, err := message.Build(&msg) + require.NoError(t, err) + return msgBytes + }, mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { - // Unused in this test - return mempool.NewMockMempool(ctrl) + mempool := mempool.NewMockMempool(ctrl) + mempool.EXPECT().Get(gomock.Any()).Return(testTx, true) + return mempool }, - appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Unused in this test - return common.NewMockSender(ctrl) + }, + { + name: "tx previously dropped", + msgBytesFunc: func() []byte { + msg := message.Tx{ + Tx: testTx.Bytes(), + } + msgBytes, err := message.Build(&msg) + require.NoError(t, err) + return msgBytes + }, + mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { + mempool := mempool.NewMockMempool(ctrl) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) + mempool.EXPECT().GetDropReason(gomock.Any()).Return(errTest) + return mempool }, }, { - // Issue returns nil because mempool has tx. We should gossip the tx. - name: "issuance succeeds", + name: "transaction invalid", msgBytesFunc: func() []byte { msg := message.Tx{ Tx: testTx.Bytes(), @@ -103,18 +139,19 @@ func TestNetworkAppGossip(t *testing.T) { }, mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(true) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) + mempool.EXPECT().GetDropReason(gomock.Any()).Return(nil) + mempool.EXPECT().MarkDropped(gomock.Any(), gomock.Any()) return mempool }, - appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - appSender := common.NewMockSender(ctrl) - appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()) - return appSender + txVerifierFunc: func(ctrl *gomock.Controller) TxVerifier { + txVerifier := executor.NewMockManager(ctrl) + txVerifier.EXPECT().VerifyTx(gomock.Any()).Return(errTest) + return txVerifier }, }, { - // Issue returns error because tx was dropped. We shouldn't gossip the tx. - name: "issuance fails", + name: "happy path", msgBytesFunc: func() []byte { msg := message.Tx{ Tx: testTx.Bytes(), @@ -125,13 +162,22 @@ func TestNetworkAppGossip(t *testing.T) { }, mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) - mempool.EXPECT().GetDropReason(gomock.Any()).Return(errTest) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) + mempool.EXPECT().GetDropReason(gomock.Any()).Return(nil) + mempool.EXPECT().Add(gomock.Any()).Return(nil) + mempool.EXPECT().Len().Return(0) + mempool.EXPECT().RequestBuildBlock() return mempool }, + txVerifierFunc: func(ctrl *gomock.Controller) TxVerifier { + txVerifier := executor.NewMockManager(ctrl) + txVerifier.EXPECT().VerifyTx(gomock.Any()).Return(nil) + return txVerifier + }, appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Unused in this test - return common.NewMockSender(ctrl) + appSender := common.NewMockSender(ctrl) + appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil).Times(2) + return appSender }, }, } @@ -141,22 +187,49 @@ func TestNetworkAppGossip(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - parser, err := txs.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - &nftfx.Fx{}, - &propertyfx.Fx{}, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + &nftfx.Fx{}, + &propertyfx.Fx{}, + }, + ) require.NoError(err) - n := New( + mempoolFunc := func(ctrl *gomock.Controller) mempool.Mempool { + return mempool.NewMockMempool(ctrl) + } + if tt.mempoolFunc != nil { + mempoolFunc = tt.mempoolFunc + } + + txVerifierFunc := func(ctrl *gomock.Controller) TxVerifier { + return executor.NewMockManager(ctrl) + } + if tt.txVerifierFunc != nil { + txVerifierFunc = tt.txVerifierFunc + } + + appSenderFunc := func(ctrl *gomock.Controller) common.AppSender { + return common.NewMockSender(ctrl) + } + if tt.appSenderFunc != nil { + appSenderFunc = tt.appSenderFunc + } + + n, err := New( &snow.Context{ Log: logging.NoLog{}, }, parser, - executor.NewMockManager(ctrl), // Manager is unused in this test - tt.mempoolFunc(ctrl), - tt.appSenderFunc(ctrl), + txVerifierFunc(ctrl), + mempoolFunc(ctrl), + appSenderFunc(ctrl), + prometheus.NewRegistry(), + testConfig, ) + require.NoError(err) require.NoError(n.AppGossip(context.Background(), ids.GenerateTestNodeID(), tt.msgBytesFunc())) }) } @@ -164,11 +237,11 @@ func TestNetworkAppGossip(t *testing.T) { func TestNetworkIssueTx(t *testing.T) { type test struct { - name string - mempoolFunc func(*gomock.Controller) mempool.Mempool - managerFunc func(*gomock.Controller) executor.Manager - appSenderFunc func(*gomock.Controller) common.AppSender - expectedErr error + name string + mempoolFunc func(*gomock.Controller) mempool.Mempool + txVerifierFunc func(*gomock.Controller) TxVerifier + appSenderFunc func(*gomock.Controller) common.AppSender + expectedErr error } tests := []test{ @@ -176,56 +249,34 @@ func TestNetworkIssueTx(t *testing.T) { name: "mempool has transaction", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(true) + mempool.EXPECT().Get(gomock.Any()).Return(nil, true) return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - // Unused in this test - return executor.NewMockManager(ctrl) - }, - appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Should gossip the tx - appSender := common.NewMockSender(ctrl) - appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil) - return appSender - }, - expectedErr: nil, + expectedErr: mempool.ErrDuplicateTx, }, { name: "transaction marked as dropped in mempool", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) mempool.EXPECT().GetDropReason(gomock.Any()).Return(errTest) return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - // Unused in this test - return executor.NewMockManager(ctrl) - }, - appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Shouldn't gossip the tx - return common.NewMockSender(ctrl) - }, expectedErr: errTest, }, { name: "transaction invalid", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) mempool.EXPECT().GetDropReason(gomock.Any()).Return(nil) mempool.EXPECT().MarkDropped(gomock.Any(), gomock.Any()) return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - manager := executor.NewMockManager(ctrl) - manager.EXPECT().VerifyTx(gomock.Any()).Return(errTest) - return manager - }, - appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Shouldn't gossip the tx - return common.NewMockSender(ctrl) + txVerifierFunc: func(ctrl *gomock.Controller) TxVerifier { + txVerifier := executor.NewMockManager(ctrl) + txVerifier.EXPECT().VerifyTx(gomock.Any()).Return(errTest) + return txVerifier }, expectedErr: errTest, }, @@ -233,20 +284,16 @@ func TestNetworkIssueTx(t *testing.T) { name: "can't add transaction to mempool", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) mempool.EXPECT().GetDropReason(gomock.Any()).Return(nil) mempool.EXPECT().Add(gomock.Any()).Return(errTest) mempool.EXPECT().MarkDropped(gomock.Any(), gomock.Any()) return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - manager := executor.NewMockManager(ctrl) - manager.EXPECT().VerifyTx(gomock.Any()).Return(nil) - return manager - }, - appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Shouldn't gossip the tx - return common.NewMockSender(ctrl) + txVerifierFunc: func(ctrl *gomock.Controller) TxVerifier { + txVerifier := executor.NewMockManager(ctrl) + txVerifier.EXPECT().VerifyTx(gomock.Any()).Return(nil) + return txVerifier }, expectedErr: errTest, }, @@ -254,21 +301,21 @@ func TestNetworkIssueTx(t *testing.T) { name: "happy path", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) mempool.EXPECT().GetDropReason(gomock.Any()).Return(nil) mempool.EXPECT().Add(gomock.Any()).Return(nil) + mempool.EXPECT().Len().Return(0) mempool.EXPECT().RequestBuildBlock() return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - manager := executor.NewMockManager(ctrl) - manager.EXPECT().VerifyTx(gomock.Any()).Return(nil) - return manager + txVerifierFunc: func(ctrl *gomock.Controller) TxVerifier { + txVerifier := executor.NewMockManager(ctrl) + txVerifier.EXPECT().VerifyTx(gomock.Any()).Return(nil) + return txVerifier }, appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Should gossip the tx appSender := common.NewMockSender(ctrl) - appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil) + appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil).Times(2) return appSender }, expectedErr: nil, @@ -280,40 +327,154 @@ func TestNetworkIssueTx(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - parser, err := txs.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - &nftfx.Fx{}, - &propertyfx.Fx{}, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + &nftfx.Fx{}, + &propertyfx.Fx{}, + }, + ) require.NoError(err) - n := New( + mempoolFunc := func(ctrl *gomock.Controller) mempool.Mempool { + return mempool.NewMockMempool(ctrl) + } + if tt.mempoolFunc != nil { + mempoolFunc = tt.mempoolFunc + } + + txVerifierFunc := func(ctrl *gomock.Controller) TxVerifier { + return executor.NewMockManager(ctrl) + } + if tt.txVerifierFunc != nil { + txVerifierFunc = tt.txVerifierFunc + } + + appSenderFunc := func(ctrl *gomock.Controller) common.AppSender { + return common.NewMockSender(ctrl) + } + if tt.appSenderFunc != nil { + appSenderFunc = tt.appSenderFunc + } + + n, err := New( &snow.Context{ Log: logging.NoLog{}, }, parser, - tt.managerFunc(ctrl), - tt.mempoolFunc(ctrl), - tt.appSenderFunc(ctrl), + txVerifierFunc(ctrl), + mempoolFunc(ctrl), + appSenderFunc(ctrl), + prometheus.NewRegistry(), + testConfig, ) + require.NoError(err) err = n.IssueTx(context.Background(), &txs.Tx{}) require.ErrorIs(err, tt.expectedErr) }) } } +func TestNetworkIssueVerifiedTx(t *testing.T) { + type test struct { + name string + mempoolFunc func(*gomock.Controller) mempool.Mempool + appSenderFunc func(*gomock.Controller) common.AppSender + expectedErr error + } + + tests := []test{ + { + name: "can't add transaction to mempool", + mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { + mempool := mempool.NewMockMempool(ctrl) + mempool.EXPECT().Add(gomock.Any()).Return(errTest) + mempool.EXPECT().MarkDropped(gomock.Any(), gomock.Any()) + return mempool + }, + expectedErr: errTest, + }, + { + name: "happy path", + mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { + mempool := mempool.NewMockMempool(ctrl) + mempool.EXPECT().Add(gomock.Any()).Return(nil) + mempool.EXPECT().Len().Return(0) + mempool.EXPECT().RequestBuildBlock() + return mempool + }, + appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { + appSender := common.NewMockSender(ctrl) + appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil).Times(2) + return appSender + }, + expectedErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + &nftfx.Fx{}, + &propertyfx.Fx{}, + }, + ) + require.NoError(err) + + mempoolFunc := func(ctrl *gomock.Controller) mempool.Mempool { + return mempool.NewMockMempool(ctrl) + } + if tt.mempoolFunc != nil { + mempoolFunc = tt.mempoolFunc + } + + appSenderFunc := func(ctrl *gomock.Controller) common.AppSender { + return common.NewMockSender(ctrl) + } + if tt.appSenderFunc != nil { + appSenderFunc = tt.appSenderFunc + } + + n, err := New( + &snow.Context{ + Log: logging.NoLog{}, + }, + parser, + executor.NewMockManager(ctrl), // Should never verify a tx + mempoolFunc(ctrl), + appSenderFunc(ctrl), + prometheus.NewRegistry(), + testConfig, + ) + require.NoError(err) + err = n.IssueVerifiedTx(context.Background(), &txs.Tx{}) + require.ErrorIs(err, tt.expectedErr) + }) + } +} + func TestNetworkGossipTx(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - parser, err := txs.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) appSender := common.NewMockSender(ctrl) - nIntf := New( + n, err := New( &snow.Context{ Log: logging.NoLog{}, }, @@ -321,19 +482,20 @@ func TestNetworkGossipTx(t *testing.T) { executor.NewMockManager(ctrl), mempool.NewMockMempool(ctrl), appSender, + prometheus.NewRegistry(), + testConfig, ) - require.IsType(&network{}, nIntf) - n := nIntf.(*network) + require.NoError(err) // Case: Tx was recently gossiped txID := ids.GenerateTestID() n.recentTxs.Put(txID, struct{}{}) - n.gossipTx(context.Background(), txID, []byte{}) + n.gossipTxMessage(context.Background(), txID, []byte{}) // Didn't make a call to SendAppGossip // Case: Tx was not recently gossiped msgBytes := []byte{1, 2, 3} appSender.EXPECT().SendAppGossip(gomock.Any(), msgBytes).Return(nil) - n.gossipTx(context.Background(), ids.GenerateTestID(), msgBytes) + n.gossipTxMessage(context.Background(), ids.GenerateTestID(), msgBytes) // Did make a call to SendAppGossip } diff --git a/vms/avm/network/tx_verifier.go b/vms/avm/network/tx_verifier.go new file mode 100644 index 000000000000..09f869283448 --- /dev/null +++ b/vms/avm/network/tx_verifier.go @@ -0,0 +1,36 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "sync" + + "github.com/ava-labs/avalanchego/vms/avm/txs" +) + +var _ TxVerifier = (*LockedTxVerifier)(nil) + +type TxVerifier interface { + // VerifyTx verifies that the transaction should be issued into the mempool. + VerifyTx(tx *txs.Tx) error +} + +type LockedTxVerifier struct { + lock sync.Locker + txVerifier TxVerifier +} + +func (l *LockedTxVerifier) VerifyTx(tx *txs.Tx) error { + l.lock.Lock() + defer l.lock.Unlock() + + return l.txVerifier.VerifyTx(tx) +} + +func NewLockedTxVerifier(lock sync.Locker, txVerifier TxVerifier) *LockedTxVerifier { + return &LockedTxVerifier{ + lock: lock, + txVerifier: txVerifier, + } +} diff --git a/vms/avm/pubsub_filterer.go b/vms/avm/pubsub_filterer.go index 242970347901..caf0ba348393 100644 --- a/vms/avm/pubsub_filterer.go +++ b/vms/avm/pubsub_filterer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/pubsub_filterer_test.go b/vms/avm/pubsub_filterer_test.go index 95f4fc3cd229..0059b2218e39 100644 --- a/vms/avm/pubsub_filterer_test.go +++ b/vms/avm/pubsub_filterer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/service.go b/vms/avm/service.go index f0ce5aef5daa..85545546b014 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -206,16 +206,16 @@ func (s *Service) IssueTx(_ *http.Request, args *api.FormattedTx, reply *api.JSO return fmt.Errorf("problem decoding transaction: %w", err) } - s.vm.ctx.Lock.Lock() - defer s.vm.ctx.Lock.Unlock() - - txID, err := s.vm.IssueTx(txBytes) + tx, err := s.vm.parser.ParseTx(txBytes) if err != nil { + s.vm.ctx.Log.Debug("failed to parse tx", + zap.Error(err), + ) return err } - reply.TxID = txID - return nil + reply.TxID, err = s.vm.issueTx(tx) + return err } // GetTxStatusReply defines the GetTxStatus replies returned from the API @@ -710,14 +710,30 @@ func (s *Service) CreateAsset(_ *http.Request, args *CreateAssetArgs, reply *Ass zap.Int("numMinters", len(args.MinterSets)), ) + tx, changeAddr, err := s.buildCreateAssetTx(args) + if err != nil { + return err + } + + assetID, err := s.vm.issueTx(tx) + if err != nil { + return fmt.Errorf("problem issuing transaction: %w", err) + } + + reply.AssetID = assetID + reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) + return err +} + +func (s *Service) buildCreateAssetTx(args *CreateAssetArgs) (*txs.Tx, ids.ShortID, error) { if len(args.InitialHolders) == 0 && len(args.MinterSets) == 0 { - return errNoHoldersOrMinters + return nil, ids.ShortEmpty, errNoHoldersOrMinters } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -726,16 +742,16 @@ func (s *Service) CreateAsset(_ *http.Request, args *CreateAssetArgs, reply *Ass // Get the UTXOs/keys for the from addresses utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the change address. if len(kc.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr) if err != nil { - return err + return nil, ids.ShortEmpty, err } amountsSpent, ins, keys, err := s.vm.Spend( @@ -746,7 +762,7 @@ func (s *Service) CreateAsset(_ *http.Request, args *CreateAssetArgs, reply *Ass }, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } outs := []*avax.TransferableOutput{} @@ -771,7 +787,7 @@ func (s *Service) CreateAsset(_ *http.Request, args *CreateAssetArgs, reply *Ass for _, holder := range args.InitialHolders { addr, err := avax.ParseServiceAddress(s.vm, holder.Address) if err != nil { - return err + return nil, ids.ShortEmpty, err } initialState.Outs = append(initialState.Outs, &secp256k1fx.TransferOutput{ Amt: uint64(holder.Amount), @@ -790,15 +806,17 @@ func (s *Service) CreateAsset(_ *http.Request, args *CreateAssetArgs, reply *Ass } minterAddrsSet, err := avax.ParseServiceAddresses(s.vm, owner.Minters) if err != nil { - return err + return nil, ids.ShortEmpty, err } minter.Addrs = minterAddrsSet.List() utils.Sort(minter.Addrs) initialState.Outs = append(initialState.Outs, minter) } - initialState.Sort(s.vm.parser.Codec()) - tx := txs.Tx{Unsigned: &txs.CreateAssetTx{ + codec := s.vm.parser.Codec() + initialState.Sort(codec) + + tx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: s.vm.ctx.NetworkID, BlockchainID: s.vm.ctx.ChainID, @@ -810,18 +828,7 @@ func (s *Service) CreateAsset(_ *http.Request, args *CreateAssetArgs, reply *Ass Denomination: args.Denomination, States: []*txs.InitialState{initialState}, }} - if err := tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys); err != nil { - return err - } - - assetID, err := s.vm.IssueTx(tx.Bytes()) - if err != nil { - return fmt.Errorf("problem issuing transaction: %w", err) - } - - reply.AssetID = assetID - reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) - return err + return tx, changeAddr, tx.SignSECP256K1Fx(codec, keys) } // CreateFixedCapAsset returns ID of the newly created asset @@ -868,14 +875,30 @@ func (s *Service) CreateNFTAsset(_ *http.Request, args *CreateNFTAssetArgs, repl zap.Int("numMinters", len(args.MinterSets)), ) + tx, changeAddr, err := s.buildCreateNFTAsset(args) + if err != nil { + return err + } + + assetID, err := s.vm.issueTx(tx) + if err != nil { + return fmt.Errorf("problem issuing transaction: %w", err) + } + + reply.AssetID = assetID + reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) + return err +} + +func (s *Service) buildCreateNFTAsset(args *CreateNFTAssetArgs) (*txs.Tx, ids.ShortID, error) { if len(args.MinterSets) == 0 { - return errNoMinters + return nil, ids.ShortEmpty, errNoMinters } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -884,16 +907,16 @@ func (s *Service) CreateNFTAsset(_ *http.Request, args *CreateNFTAssetArgs, repl // Get the UTXOs/keys for the from addresses utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the change address. if len(kc.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr) if err != nil { - return err + return nil, ids.ShortEmpty, err } amountsSpent, ins, keys, err := s.vm.Spend( @@ -904,7 +927,7 @@ func (s *Service) CreateNFTAsset(_ *http.Request, args *CreateNFTAssetArgs, repl }, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } outs := []*avax.TransferableOutput{} @@ -935,15 +958,17 @@ func (s *Service) CreateNFTAsset(_ *http.Request, args *CreateNFTAssetArgs, repl } minterAddrsSet, err := avax.ParseServiceAddresses(s.vm, owner.Minters) if err != nil { - return err + return nil, ids.ShortEmpty, err } minter.Addrs = minterAddrsSet.List() utils.Sort(minter.Addrs) initialState.Outs = append(initialState.Outs, minter) } - initialState.Sort(s.vm.parser.Codec()) - tx := txs.Tx{Unsigned: &txs.CreateAssetTx{ + codec := s.vm.parser.Codec() + initialState.Sort(codec) + + tx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: s.vm.ctx.NetworkID, BlockchainID: s.vm.ctx.ChainID, @@ -955,18 +980,7 @@ func (s *Service) CreateNFTAsset(_ *http.Request, args *CreateNFTAssetArgs, repl Denomination: 0, // NFTs are non-fungible States: []*txs.InitialState{initialState}, }} - if err := tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys); err != nil { - return err - } - - assetID, err := s.vm.IssueTx(tx.Bytes()) - if err != nil { - return fmt.Errorf("problem issuing transaction: %w", err) - } - - reply.AssetID = assetID - reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) - return err + return tx, changeAddr, tx.SignSECP256K1Fx(codec, keys) } // CreateAddress creates an address for the user [args.Username] @@ -1181,18 +1195,34 @@ func (s *Service) SendMultiple(_ *http.Request, args *SendMultipleArgs, reply *a logging.UserString("username", args.Username), ) + tx, changeAddr, err := s.buildSendMultiple(args) + if err != nil { + return err + } + + txID, err := s.vm.issueTx(tx) + if err != nil { + return fmt.Errorf("problem issuing transaction: %w", err) + } + + reply.TxID = txID + reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) + return err +} + +func (s *Service) buildSendMultiple(args *SendMultipleArgs) (*txs.Tx, ids.ShortID, error) { // Validate the memo field memoBytes := []byte(args.Memo) if l := len(memoBytes); l > avax.MaxMemoSize { - return fmt.Errorf("max memo length is %d but provided memo field is length %d", avax.MaxMemoSize, l) + return nil, ids.ShortEmpty, fmt.Errorf("max memo length is %d but provided memo field is length %d", avax.MaxMemoSize, l) } else if len(args.Outputs) == 0 { - return errNoOutputs + return nil, ids.ShortEmpty, errNoOutputs } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1201,16 +1231,16 @@ func (s *Service) SendMultiple(_ *http.Request, args *SendMultipleArgs, reply *a // Load user's UTXOs/keys utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the change address. if len(kc.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Calculate required input amounts and create the desired outputs @@ -1222,27 +1252,27 @@ func (s *Service) SendMultiple(_ *http.Request, args *SendMultipleArgs, reply *a outs := []*avax.TransferableOutput{} for _, output := range args.Outputs { if output.Amount == 0 { - return errZeroAmount + return nil, ids.ShortEmpty, errZeroAmount } assetID, ok := assetIDs[output.AssetID] // Asset ID of next output if !ok { assetID, err = s.vm.lookupAssetID(output.AssetID) if err != nil { - return fmt.Errorf("couldn't find asset %s", output.AssetID) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't find asset %s", output.AssetID) } assetIDs[output.AssetID] = assetID } currentAmount := amounts[assetID] newAmount, err := safemath.Add64(currentAmount, uint64(output.Amount)) if err != nil { - return fmt.Errorf("problem calculating required spend amount: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("problem calculating required spend amount: %w", err) } amounts[assetID] = newAmount // Parse the to address to, err := avax.ParseServiceAddress(s.vm, output.To) if err != nil { - return fmt.Errorf("problem parsing to address %q: %w", output.To, err) + return nil, ids.ShortEmpty, fmt.Errorf("problem parsing to address %q: %w", output.To, err) } // Create the Output @@ -1266,7 +1296,7 @@ func (s *Service) SendMultiple(_ *http.Request, args *SendMultipleArgs, reply *a amountWithFee, err := safemath.Add64(amounts[s.vm.feeAssetID], s.vm.TxFee) if err != nil { - return fmt.Errorf("problem calculating required spend amount: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("problem calculating required spend amount: %w", err) } amountsWithFee[s.vm.feeAssetID] = amountWithFee @@ -1276,7 +1306,7 @@ func (s *Service) SendMultiple(_ *http.Request, args *SendMultipleArgs, reply *a amountsWithFee, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Add the required change outputs @@ -1297,27 +1327,18 @@ func (s *Service) SendMultiple(_ *http.Request, args *SendMultipleArgs, reply *a }) } } - avax.SortTransferableOutputs(outs, s.vm.parser.Codec()) - tx := txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ + codec := s.vm.parser.Codec() + avax.SortTransferableOutputs(outs, codec) + + tx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: s.vm.ctx.NetworkID, BlockchainID: s.vm.ctx.ChainID, Outs: outs, Ins: ins, Memo: memoBytes, }}} - if err := tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys); err != nil { - return err - } - - txID, err := s.vm.IssueTx(tx.Bytes()) - if err != nil { - return fmt.Errorf("problem issuing transaction: %w", err) - } - - reply.TxID = txID - reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) - return err + return tx, changeAddr, tx.SignSECP256K1Fx(codec, keys) } // MintArgs are arguments for passing into Mint requests @@ -1336,24 +1357,46 @@ func (s *Service) Mint(_ *http.Request, args *MintArgs, reply *api.JSONTxIDChang logging.UserString("username", args.Username), ) + tx, changeAddr, err := s.buildMint(args) + if err != nil { + return err + } + + txID, err := s.vm.issueTx(tx) + if err != nil { + return fmt.Errorf("problem issuing transaction: %w", err) + } + + reply.TxID = txID + reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) + return err +} + +func (s *Service) buildMint(args *MintArgs) (*txs.Tx, ids.ShortID, error) { + s.vm.ctx.Log.Warn("deprecated API called", + zap.String("service", "avm"), + zap.String("method", "mint"), + logging.UserString("username", args.Username), + ) + if args.Amount == 0 { - return errInvalidMintAmount + return nil, ids.ShortEmpty, errInvalidMintAmount } assetID, err := s.vm.lookupAssetID(args.AssetID) if err != nil { - return err + return nil, ids.ShortEmpty, err } to, err := avax.ParseServiceAddress(s.vm, args.To) if err != nil { - return fmt.Errorf("problem parsing to address %q: %w", args.To, err) + return nil, ids.ShortEmpty, fmt.Errorf("problem parsing to address %q: %w", args.To, err) } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1362,16 +1405,16 @@ func (s *Service) Mint(_ *http.Request, args *MintArgs, reply *api.JSONTxIDChang // Get the UTXOs/keys for the from addresses feeUTXOs, feeKc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the change address. if len(feeKc.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr, err := s.vm.selectChangeAddr(feeKc.Keys[0].PublicKey().Address(), args.ChangeAddr) if err != nil { - return err + return nil, ids.ShortEmpty, err } amountsSpent, ins, keys, err := s.vm.Spend( @@ -1382,7 +1425,7 @@ func (s *Service) Mint(_ *http.Request, args *MintArgs, reply *api.JSONTxIDChang }, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } outs := []*avax.TransferableOutput{} @@ -1403,7 +1446,7 @@ func (s *Service) Mint(_ *http.Request, args *MintArgs, reply *api.JSONTxIDChang // Get all UTXOs/keys for the user utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, nil) if err != nil { - return err + return nil, ids.ShortEmpty, err } ops, opKeys, err := s.vm.Mint( @@ -1415,11 +1458,11 @@ func (s *Service) Mint(_ *http.Request, args *MintArgs, reply *api.JSONTxIDChang to, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } keys = append(keys, opKeys...) - tx := txs.Tx{Unsigned: &txs.OperationTx{ + tx := &txs.Tx{Unsigned: &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: s.vm.ctx.NetworkID, BlockchainID: s.vm.ctx.ChainID, @@ -1428,18 +1471,7 @@ func (s *Service) Mint(_ *http.Request, args *MintArgs, reply *api.JSONTxIDChang }}, Ops: ops, }} - if err := tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys); err != nil { - return err - } - - txID, err := s.vm.IssueTx(tx.Bytes()) - if err != nil { - return fmt.Errorf("problem issuing transaction: %w", err) - } - - reply.TxID = txID - reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) - return err + return tx, changeAddr, tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys) } // SendNFTArgs are arguments for passing into SendNFT requests @@ -1458,22 +1490,38 @@ func (s *Service) SendNFT(_ *http.Request, args *SendNFTArgs, reply *api.JSONTxI logging.UserString("username", args.Username), ) + tx, changeAddr, err := s.buildSendNFT(args) + if err != nil { + return err + } + + txID, err := s.vm.issueTx(tx) + if err != nil { + return fmt.Errorf("problem issuing transaction: %w", err) + } + + reply.TxID = txID + reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) + return err +} + +func (s *Service) buildSendNFT(args *SendNFTArgs) (*txs.Tx, ids.ShortID, error) { // Parse the asset ID assetID, err := s.vm.lookupAssetID(args.AssetID) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the to address to, err := avax.ParseServiceAddress(s.vm, args.To) if err != nil { - return fmt.Errorf("problem parsing to address %q: %w", args.To, err) + return nil, ids.ShortEmpty, fmt.Errorf("problem parsing to address %q: %w", args.To, err) } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1482,16 +1530,16 @@ func (s *Service) SendNFT(_ *http.Request, args *SendNFTArgs, reply *api.JSONTxI // Get the UTXOs/keys for the from addresses utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the change address. if len(kc.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr) if err != nil { - return err + return nil, ids.ShortEmpty, err } amountsSpent, ins, secpKeys, err := s.vm.Spend( @@ -1502,7 +1550,7 @@ func (s *Service) SendNFT(_ *http.Request, args *SendNFTArgs, reply *api.JSONTxI }, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } outs := []*avax.TransferableOutput{} @@ -1528,10 +1576,10 @@ func (s *Service) SendNFT(_ *http.Request, args *SendNFTArgs, reply *api.JSONTxI to, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } - tx := txs.Tx{Unsigned: &txs.OperationTx{ + tx := &txs.Tx{Unsigned: &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: s.vm.ctx.NetworkID, BlockchainID: s.vm.ctx.ChainID, @@ -1540,21 +1588,12 @@ func (s *Service) SendNFT(_ *http.Request, args *SendNFTArgs, reply *api.JSONTxI }}, Ops: ops, }} - if err := tx.SignSECP256K1Fx(s.vm.parser.Codec(), secpKeys); err != nil { - return err - } - if err := tx.SignNFTFx(s.vm.parser.Codec(), nftKeys); err != nil { - return err - } - txID, err := s.vm.IssueTx(tx.Bytes()) - if err != nil { - return fmt.Errorf("problem issuing transaction: %w", err) + codec := s.vm.parser.Codec() + if err := tx.SignSECP256K1Fx(codec, secpKeys); err != nil { + return nil, ids.ShortEmpty, err } - - reply.TxID = txID - reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) - return err + return tx, changeAddr, tx.SignNFTFx(codec, nftKeys) } // MintNFTArgs are arguments for passing into MintNFT requests @@ -1574,25 +1613,41 @@ func (s *Service) MintNFT(_ *http.Request, args *MintNFTArgs, reply *api.JSONTxI logging.UserString("username", args.Username), ) - assetID, err := s.vm.lookupAssetID(args.AssetID) + tx, changeAddr, err := s.buildMintNFT(args) if err != nil { return err } + txID, err := s.vm.issueTx(tx) + if err != nil { + return fmt.Errorf("problem issuing transaction: %w", err) + } + + reply.TxID = txID + reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) + return err +} + +func (s *Service) buildMintNFT(args *MintNFTArgs) (*txs.Tx, ids.ShortID, error) { + assetID, err := s.vm.lookupAssetID(args.AssetID) + if err != nil { + return nil, ids.ShortEmpty, err + } + to, err := avax.ParseServiceAddress(s.vm, args.To) if err != nil { - return fmt.Errorf("problem parsing to address %q: %w", args.To, err) + return nil, ids.ShortEmpty, fmt.Errorf("problem parsing to address %q: %w", args.To, err) } payloadBytes, err := formatting.Decode(args.Encoding, args.Payload) if err != nil { - return fmt.Errorf("problem decoding payload bytes: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("problem decoding payload bytes: %w", err) } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1601,16 +1656,16 @@ func (s *Service) MintNFT(_ *http.Request, args *MintNFTArgs, reply *api.JSONTxI // Get the UTXOs/keys for the from addresses feeUTXOs, feeKc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the change address. if len(feeKc.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr, err := s.vm.selectChangeAddr(feeKc.Keys[0].PublicKey().Address(), args.ChangeAddr) if err != nil { - return err + return nil, ids.ShortEmpty, err } amountsSpent, ins, secpKeys, err := s.vm.Spend( @@ -1621,7 +1676,7 @@ func (s *Service) MintNFT(_ *http.Request, args *MintNFTArgs, reply *api.JSONTxI }, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } outs := []*avax.TransferableOutput{} @@ -1642,7 +1697,7 @@ func (s *Service) MintNFT(_ *http.Request, args *MintNFTArgs, reply *api.JSONTxI // Get all UTXOs/keys utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, nil) if err != nil { - return err + return nil, ids.ShortEmpty, err } ops, nftKeys, err := s.vm.MintNFT( @@ -1653,10 +1708,10 @@ func (s *Service) MintNFT(_ *http.Request, args *MintNFTArgs, reply *api.JSONTxI to, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } - tx := txs.Tx{Unsigned: &txs.OperationTx{ + tx := &txs.Tx{Unsigned: &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: s.vm.ctx.NetworkID, BlockchainID: s.vm.ctx.ChainID, @@ -1665,21 +1720,12 @@ func (s *Service) MintNFT(_ *http.Request, args *MintNFTArgs, reply *api.JSONTxI }}, Ops: ops, }} - if err := tx.SignSECP256K1Fx(s.vm.parser.Codec(), secpKeys); err != nil { - return err - } - if err := tx.SignNFTFx(s.vm.parser.Codec(), nftKeys); err != nil { - return err - } - txID, err := s.vm.IssueTx(tx.Bytes()) - if err != nil { - return fmt.Errorf("problem issuing transaction: %w", err) + codec := s.vm.parser.Codec() + if err := tx.SignSECP256K1Fx(codec, secpKeys); err != nil { + return nil, ids.ShortEmpty, err } - - reply.TxID = txID - reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) - return err + return tx, changeAddr, tx.SignNFTFx(codec, nftKeys) } // ImportArgs are arguments for passing into Import requests @@ -1704,14 +1750,29 @@ func (s *Service) Import(_ *http.Request, args *ImportArgs, reply *api.JSONTxID) logging.UserString("username", args.Username), ) + tx, err := s.buildImport(args) + if err != nil { + return err + } + + txID, err := s.vm.issueTx(tx) + if err != nil { + return fmt.Errorf("problem issuing transaction: %w", err) + } + + reply.TxID = txID + return nil +} + +func (s *Service) buildImport(args *ImportArgs) (*txs.Tx, error) { chainID, err := s.vm.ctx.BCLookup.Lookup(args.SourceChain) if err != nil { - return fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err) + return nil, fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err) } to, err := avax.ParseServiceAddress(s.vm, args.To) if err != nil { - return fmt.Errorf("problem parsing to address %q: %w", args.To, err) + return nil, fmt.Errorf("problem parsing to address %q: %w", args.To, err) } s.vm.ctx.Lock.Lock() @@ -1719,17 +1780,17 @@ func (s *Service) Import(_ *http.Request, args *ImportArgs, reply *api.JSONTxID) utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, nil) if err != nil { - return err + return nil, err } atomicUTXOs, _, _, err := s.vm.GetAtomicUTXOs(chainID, kc.Addrs, ids.ShortEmpty, ids.Empty, int(maxPageSize)) if err != nil { - return fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err) + return nil, fmt.Errorf("problem retrieving user's atomic UTXOs: %w", err) } amountsSpent, importInputs, importKeys, err := s.vm.SpendAll(atomicUTXOs, kc) if err != nil { - return err + return nil, err } ins := []*avax.TransferableInput{} @@ -1745,12 +1806,12 @@ func (s *Service) Import(_ *http.Request, args *ImportArgs, reply *api.JSONTxID) }, ) if err != nil { - return err + return nil, err } for asset, amount := range localAmountsSpent { newAmount, err := safemath.Add64(amountsSpent[asset], amount) if err != nil { - return fmt.Errorf("problem calculating required spend amount: %w", err) + return nil, fmt.Errorf("problem calculating required spend amount: %w", err) } amountsSpent[asset] = newAmount } @@ -1780,7 +1841,7 @@ func (s *Service) Import(_ *http.Request, args *ImportArgs, reply *api.JSONTxID) } avax.SortTransferableOutputs(outs, s.vm.parser.Codec()) - tx := txs.Tx{Unsigned: &txs.ImportTx{ + tx := &txs.Tx{Unsigned: &txs.ImportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: s.vm.ctx.NetworkID, BlockchainID: s.vm.ctx.ChainID, @@ -1790,17 +1851,7 @@ func (s *Service) Import(_ *http.Request, args *ImportArgs, reply *api.JSONTxID) SourceChain: chainID, ImportedIns: importInputs, }} - if err := tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys); err != nil { - return err - } - - txID, err := s.vm.IssueTx(tx.Bytes()) - if err != nil { - return fmt.Errorf("problem issuing transaction: %w", err) - } - - reply.TxID = txID - return nil + return tx, tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys) } // ExportArgs are arguments for passing into ExportAVA requests @@ -1830,10 +1881,26 @@ func (s *Service) Export(_ *http.Request, args *ExportArgs, reply *api.JSONTxIDC logging.UserString("username", args.Username), ) + tx, changeAddr, err := s.buildExport(args) + if err != nil { + return err + } + + txID, err := s.vm.issueTx(tx) + if err != nil { + return fmt.Errorf("problem issuing transaction: %w", err) + } + + reply.TxID = txID + reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) + return err +} + +func (s *Service) buildExport(args *ExportArgs) (*txs.Tx, ids.ShortID, error) { // Parse the asset ID assetID, err := s.vm.lookupAssetID(args.AssetID) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Get the chainID and parse the to address @@ -1841,22 +1908,22 @@ func (s *Service) Export(_ *http.Request, args *ExportArgs, reply *api.JSONTxIDC if err != nil { chainID, err = s.vm.ctx.BCLookup.Lookup(args.TargetChain) if err != nil { - return err + return nil, ids.ShortEmpty, err } to, err = ids.ShortFromString(args.To) if err != nil { - return err + return nil, ids.ShortEmpty, err } } if args.Amount == 0 { - return errZeroAmount + return nil, ids.ShortEmpty, errZeroAmount } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.vm, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1865,23 +1932,23 @@ func (s *Service) Export(_ *http.Request, args *ExportArgs, reply *api.JSONTxIDC // Get the UTXOs/keys for the from addresses utxos, kc, err := s.vm.LoadUser(args.Username, args.Password, fromAddrs) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the change address. if len(kc.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr, err := s.vm.selectChangeAddr(kc.Keys[0].PublicKey().Address(), args.ChangeAddr) if err != nil { - return err + return nil, ids.ShortEmpty, err } amounts := map[ids.ID]uint64{} if assetID == s.vm.feeAssetID { amountWithFee, err := safemath.Add64(uint64(args.Amount), s.vm.TxFee) if err != nil { - return fmt.Errorf("problem calculating required spend amount: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("problem calculating required spend amount: %w", err) } amounts[s.vm.feeAssetID] = amountWithFee } else { @@ -1891,7 +1958,7 @@ func (s *Service) Export(_ *http.Request, args *ExportArgs, reply *api.JSONTxIDC amountsSpent, ins, keys, err := s.vm.Spend(utxos, kc, amounts) if err != nil { - return err + return nil, ids.ShortEmpty, err } exportOuts := []*avax.TransferableOutput{{ @@ -1923,9 +1990,11 @@ func (s *Service) Export(_ *http.Request, args *ExportArgs, reply *api.JSONTxIDC }) } } - avax.SortTransferableOutputs(outs, s.vm.parser.Codec()) - tx := txs.Tx{Unsigned: &txs.ExportTx{ + codec := s.vm.parser.Codec() + avax.SortTransferableOutputs(outs, codec) + + tx := &txs.Tx{Unsigned: &txs.ExportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: s.vm.ctx.NetworkID, BlockchainID: s.vm.ctx.ChainID, @@ -1935,16 +2004,5 @@ func (s *Service) Export(_ *http.Request, args *ExportArgs, reply *api.JSONTxIDC DestinationChain: chainID, ExportedOuts: exportOuts, }} - if err := tx.SignSECP256K1Fx(s.vm.parser.Codec(), keys); err != nil { - return err - } - - txID, err := s.vm.IssueTx(tx.Bytes()) - if err != nil { - return fmt.Errorf("problem issuing transaction: %w", err) - } - - reply.TxID = txID - reply.ChangeAddr, err = s.vm.FormatLocalAddress(changeAddr) - return err + return tx, changeAddr, tx.SignSECP256K1Fx(codec, keys) } diff --git a/vms/avm/service_test.go b/vms/avm/service_test.go index 19cacb158d13..c659b8e9de9e 100644 --- a/vms/avm/service_test.go +++ b/vms/avm/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -6,6 +6,7 @@ package avm import ( "context" "fmt" + "strings" "testing" "time" @@ -63,7 +64,7 @@ func TestServiceIssueTx(t *testing.T) { err := env.service.IssueTx(nil, txArgs, txReply) require.ErrorIs(err, codec.ErrCantUnpackVersion) - tx := newTx(t, env.genesisBytes, env.vm, "AVAX") + tx := newTx(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.parser, "AVAX") txArgs.Tx, err = formatting.Encode(formatting.Hex, tx.Bytes()) require.NoError(err) txArgs.Encoding = formatting.Hex @@ -89,7 +90,7 @@ func TestServiceGetTxStatus(t *testing.T) { err := env.service.GetTxStatus(nil, statusArgs, statusReply) require.ErrorIs(err, errNilTxID) - newTx := newAvaxBaseTxWithOutputs(t, env.genesisBytes, env.vm) + newTx := newAvaxBaseTxWithOutputs(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.TxFee, env.vm.parser) txID := newTx.ID() statusArgs = &api.JSONTxID{ @@ -99,12 +100,8 @@ func TestServiceGetTxStatus(t *testing.T) { require.NoError(env.service.GetTxStatus(nil, statusArgs, statusReply)) require.Equal(choices.Unknown, statusReply.Status) - env.vm.ctx.Lock.Lock() - issueAndAccept(require, env.vm, env.issuer, newTx) - env.vm.ctx.Lock.Unlock() - statusReply = &GetTxStatusReply{} require.NoError(env.service.GetTxStatus(nil, statusArgs, statusReply)) require.Equal(choices.Accepted, statusReply.Status) @@ -539,17 +536,16 @@ func TestServiceGetTxJSON_BaseTx(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{}) + env.vm.ctx.Lock.Unlock() defer func() { env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() - newTx := newAvaxBaseTxWithOutputs(t, env.genesisBytes, env.vm) + newTx := newAvaxBaseTxWithOutputs(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.TxFee, env.vm.parser) issueAndAccept(require, env.vm, env.issuer, newTx) - env.vm.ctx.Lock.Unlock() - reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ TxID: newTx.ID(), @@ -557,27 +553,82 @@ func TestServiceGetTxJSON_BaseTx(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonString := string(reply.Tx) - require.Contains(jsonString, `"memo":"0x0102030405060708"`) - require.Contains(jsonString, `"inputs":[{"txID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","outputIndex":2,"assetID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","input":{"amount":50000,"signatureIndices":[0]}}]`) - require.Contains(jsonString, `"outputs":[{"assetID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","output":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"amount":49000,"locktime":0,"threshold":1}}]`) + + replyTxBytes, err := stdjson.MarshalIndent(reply.Tx, "", "\t") + require.NoError(err) + + expectedReplyTxString := `{ + "unsignedTx": { + "networkID": 10, + "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", + "outputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 49000, + "locktime": 0, + "threshold": 1 + } + } + ], + "inputs": [ + { + "txID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "outputIndex": 2, + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 50000, + "signatureIndices": [ + 0 + ] + } + } + ], + "memo": "0x0102030405060708" + }, + "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + } + ], + "id": "PLACEHOLDER_TX_ID" +}` + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", newTx.ID().String(), 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", newTx.Unsigned.(*txs.BaseTx).BlockchainID.String(), 1) + + sigStr, err := formatting.Encode(formatting.HexNC, newTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:]) + require.NoError(err) + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1) + + require.Equal(expectedReplyTxString, string(replyTxBytes)) } func TestServiceGetTxJSON_ExportTx(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{}) + env.vm.ctx.Lock.Unlock() defer func() { env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() - newTx := newAvaxExportTxWithOutputs(t, env.genesisBytes, env.vm) + newTx := newAvaxExportTxWithOutputs(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.TxFee, env.vm.parser) issueAndAccept(require, env.vm, env.issuer, newTx) - env.vm.ctx.Lock.Unlock() - reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ TxID: newTx.ID(), @@ -585,9 +636,67 @@ func TestServiceGetTxJSON_ExportTx(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonString := string(reply.Tx) - require.Contains(jsonString, `"inputs":[{"txID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","outputIndex":2,"assetID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","input":{"amount":50000,"signatureIndices":[0]}}]`) - require.Contains(jsonString, `"exportedOutputs":[{"assetID":"2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ","fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","output":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"amount":49000,"locktime":0,"threshold":1}}]}`) + replyTxBytes, err := stdjson.MarshalIndent(reply.Tx, "", "\t") + require.NoError(err) + + expectedReplyTxString := `{ + "unsignedTx": { + "networkID": 10, + "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", + "outputs": null, + "inputs": [ + { + "txID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "outputIndex": 2, + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "input": { + "amount": 50000, + "signatureIndices": [ + 0 + ] + } + } + ], + "memo": "0x", + "destinationChain": "11111111111111111111111111111111LpoYY", + "exportedOutputs": [ + { + "assetID": "2XGxUr7VF7j1iwUp2aiGe4b6Ue2yyNghNS1SuNTNmZ77dPpXFZ", + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "output": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 49000, + "locktime": 0, + "threshold": 1 + } + } + ] + }, + "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + } + ], + "id": "PLACEHOLDER_TX_ID" +}` + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", newTx.ID().String(), 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", newTx.Unsigned.(*txs.ExportTx).BlockchainID.String(), 1) + + sigStr, err := formatting.Encode(formatting.HexNC, newTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:]) + require.NoError(err) + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1) + + require.Equal(expectedReplyTxString, string(replyTxBytes)) } func TestServiceGetTxJSON_CreateAssetTx(t *testing.T) { @@ -600,17 +709,16 @@ func TestServiceGetTxJSON_CreateAssetTx(t *testing.T) { Fx: &propertyfx.Fx{}, }}, }) + env.vm.ctx.Lock.Unlock() defer func() { env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm) + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - env.vm.ctx.Lock.Unlock() - reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ TxID: createAssetTx.ID(), @@ -618,11 +726,93 @@ func TestServiceGetTxJSON_CreateAssetTx(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonString := string(reply.Tx) - // contains the address in the right format - require.Contains(jsonString, `"outputs":[{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"groupID":1,"locktime":0,"threshold":1},{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"groupID":2,"locktime":0,"threshold":1}]}`) - require.Contains(jsonString, `"initialStates":[{"fxIndex":0,"fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","outputs":[{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"locktime":0,"threshold":1},{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"locktime":0,"threshold":1}]},{"fxIndex":1,"fxID":"qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT","outputs":[{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"groupID":1,"locktime":0,"threshold":1},{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"groupID":2,"locktime":0,"threshold":1}]},{"fxIndex":2,"fxID":"rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy","outputs":[{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"locktime":0,"threshold":1},{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"locktime":0,"threshold":1}]}]},"credentials":[],"id":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS"}`) + replyTxBytes, err := stdjson.MarshalIndent(reply.Tx, "", "\t") + require.NoError(err) + + expectedReplyTxString := `{ + "unsignedTx": { + "networkID": 10, + "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", + "outputs": null, + "inputs": null, + "memo": "0x", + "name": "Team Rocket", + "symbol": "TR", + "denomination": 0, + "initialStates": [ + { + "fxIndex": 0, + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "outputs": [ + { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + }, + { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + } + ] + }, + { + "fxIndex": 1, + "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", + "outputs": [ + { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "groupID": 1, + "locktime": 0, + "threshold": 1 + }, + { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "groupID": 2, + "locktime": 0, + "threshold": 1 + } + ] + }, + { + "fxIndex": 2, + "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", + "outputs": [ + { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + }, + { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + } + ] + } + ] + }, + "credentials": null, + "id": "PLACEHOLDER_TX_ID" +}` + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", createAssetTx.ID().String(), 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", createAssetTx.Unsigned.(*txs.CreateAssetTx).BlockchainID.String(), 1) + + require.Equal(expectedReplyTxString, string(replyTxBytes)) } func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { @@ -635,6 +825,7 @@ func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { Fx: &propertyfx.Fx{}, }}, }) + env.vm.ctx.Lock.Unlock() defer func() { env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) @@ -642,15 +833,13 @@ func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm) + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintNFTTx := buildOperationTxWithOp(buildNFTxMintOp(createAssetTx, key, 2, 1)) + mintNFTTx := buildOperationTxWithOp(env.vm.ctx.ChainID, buildNFTxMintOp(createAssetTx, key, 2, 1)) require.NoError(mintNFTTx.SignNFTFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) issueAndAccept(require, env.vm, env.issuer, mintNFTTx) - env.vm.ctx.Lock.Unlock() - reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ TxID: mintNFTTx.ID(), @@ -658,15 +847,71 @@ func TestServiceGetTxJSON_OperationTxWithNftxMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonString := string(reply.Tx) - // assert memo and payload are in hex - require.Contains(jsonString, `"memo":"0x"`) - require.Contains(jsonString, `"payload":"0x68656c6c6f"`) - // contains the address in the right format - require.Contains(jsonString, `"outputs":[{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"]`) - // contains the fxID - require.Contains(jsonString, `"operations":[{"assetID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","inputIDs":[{"txID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","outputIndex":2}],"fxID":"qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT"`) - require.Contains(jsonString, `"credentials":[{"fxID":"qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT","credential":{"signatures":["0x571f18cfdb254263ab6b987f742409bd5403eafe08b4dbc297c5cd8d1c85eb8812e4541e11d3dc692cd14b5f4bccc1835ec001df6d8935ce881caf97017c2a4801"]}}]`) + + replyTxBytes, err := stdjson.MarshalIndent(reply.Tx, "", "\t") + require.NoError(err) + + expectedReplyTxString := `{ + "unsignedTx": { + "networkID": 10, + "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", + "outputs": null, + "inputs": null, + "memo": "0x", + "operations": [ + { + "assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "inputIDs": [ + { + "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "outputIndex": 2 + } + ], + "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", + "operation": { + "mintInput": { + "signatureIndices": [ + 0 + ] + }, + "groupID": 1, + "payload": "0x68656c6c6f", + "outputs": [ + { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + } + ] + } + } + ] + }, + "credentials": [ + { + "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + } + ], + "id": "PLACEHOLDER_TX_ID" +}` + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 2) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintNFTTx.ID().String(), 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintNFTTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) + + sigStr, err := formatting.Encode(formatting.HexNC, mintNFTTx.Creds[0].Credential.(*nftfx.Credential).Sigs[0][:]) + require.NoError(err) + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1) + + require.Equal(expectedReplyTxString, string(replyTxBytes)) } func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { @@ -679,6 +924,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { Fx: &propertyfx.Fx{}, }}, }) + env.vm.ctx.Lock.Unlock() defer func() { env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) @@ -686,18 +932,16 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm) + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) issueAndAccept(require, env.vm, env.issuer, createAssetTx) mintOp1 := buildNFTxMintOp(createAssetTx, key, 2, 1) mintOp2 := buildNFTxMintOp(createAssetTx, key, 3, 2) - mintNFTTx := buildOperationTxWithOp(mintOp1, mintOp2) + mintNFTTx := buildOperationTxWithOp(env.vm.ctx.ChainID, mintOp1, mintOp2) require.NoError(mintNFTTx.SignNFTFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}, {key}})) issueAndAccept(require, env.vm, env.issuer, mintNFTTx) - env.vm.ctx.Lock.Unlock() - reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ TxID: mintNFTTx.ID(), @@ -705,14 +949,107 @@ func TestServiceGetTxJSON_OperationTxWithMultipleNftxMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonString := string(reply.Tx) - // contains the address in the right format - require.Contains(jsonString, `"outputs":[{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"]`) + replyTxBytes, err := stdjson.MarshalIndent(reply.Tx, "", "\t") + require.NoError(err) + + expectedReplyTxString := `{ + "unsignedTx": { + "networkID": 10, + "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", + "outputs": null, + "inputs": null, + "memo": "0x", + "operations": [ + { + "assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "inputIDs": [ + { + "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "outputIndex": 2 + } + ], + "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", + "operation": { + "mintInput": { + "signatureIndices": [ + 0 + ] + }, + "groupID": 1, + "payload": "0x68656c6c6f", + "outputs": [ + { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + } + ] + } + }, + { + "assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "inputIDs": [ + { + "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "outputIndex": 3 + } + ], + "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", + "operation": { + "mintInput": { + "signatureIndices": [ + 0 + ] + }, + "groupID": 2, + "payload": "0x68656c6c6f", + "outputs": [ + { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + } + ] + } + } + ] + }, + "credentials": [ + { + "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + }, + { + "fxID": "qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + } + ], + "id": "PLACEHOLDER_TX_ID" +}` + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 4) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintNFTTx.ID().String(), 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintNFTTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) + + sigStr, err := formatting.Encode(formatting.HexNC, mintNFTTx.Creds[0].Credential.(*nftfx.Credential).Sigs[0][:]) + require.NoError(err) + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2) - // contains the fxID - require.Contains(jsonString, `"operations":[{"assetID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","inputIDs":[{"txID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","outputIndex":2}],"fxID":"qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT"`) - require.Contains(jsonString, `"credentials":[{"fxID":"qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT","credential":{"signatures":["0x2400cf2cf978697b3484d5340609b524eb9dfa401e5b2bd5d1bc6cee2a6b1ae41926550f00ae0651c312c35e225cb3f39b506d96c5170fb38a820dcfed11ccd801"]}},{"fxID":"qd2U4HDWUvMrVUeTcCHp6xH3Qpnn1XbU5MDdnBoiifFqvgXwT","credential":{"signatures":["0x2400cf2cf978697b3484d5340609b524eb9dfa401e5b2bd5d1bc6cee2a6b1ae41926550f00ae0651c312c35e225cb3f39b506d96c5170fb38a820dcfed11ccd801"]}}]`) + require.Equal(expectedReplyTxString, string(replyTxBytes)) } func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { @@ -725,6 +1062,7 @@ func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { Fx: &propertyfx.Fx{}, }}, }) + env.vm.ctx.Lock.Unlock() defer func() { env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) @@ -732,15 +1070,13 @@ func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm) + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintSecpOpTx := buildOperationTxWithOp(buildSecpMintOp(createAssetTx, key, 0)) + mintSecpOpTx := buildOperationTxWithOp(env.vm.ctx.ChainID, buildSecpMintOp(createAssetTx, key, 0)) require.NoError(mintSecpOpTx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) issueAndAccept(require, env.vm, env.issuer, mintSecpOpTx) - env.vm.ctx.Lock.Unlock() - reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ TxID: mintSecpOpTx.ID(), @@ -748,17 +1084,75 @@ func TestServiceGetTxJSON_OperationTxWithSecpMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonString := string(reply.Tx) - // ensure memo is in hex - require.Contains(jsonString, `"memo":"0x"`) - // contains the address in the right format - require.Contains(jsonString, `"mintOutput":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"]`) - require.Contains(jsonString, `"transferOutput":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"amount":1,"locktime":0,"threshold":1}}}]}`) + replyTxBytes, err := stdjson.MarshalIndent(reply.Tx, "", "\t") + require.NoError(err) - // contains the fxID - require.Contains(jsonString, `"operations":[{"assetID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","inputIDs":[{"txID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","outputIndex":0}],"fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ"`) - require.Contains(jsonString, `"credentials":[{"fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","credential":{"signatures":["0x6d7406d5e1bdb1d80de542e276e2d162b0497d0df1170bec72b14d40e84ecf7929cb571211d60149404413a9342fdfa0a2b5d07b48e6f3eaea1e2f9f183b480500"]}}]`) + expectedReplyTxString := `{ + "unsignedTx": { + "networkID": 10, + "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", + "outputs": null, + "inputs": null, + "memo": "0x", + "operations": [ + { + "assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "inputIDs": [ + { + "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "outputIndex": 0 + } + ], + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "operation": { + "mintInput": { + "signatureIndices": [ + 0 + ] + }, + "mintOutput": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + }, + "transferOutput": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 1, + "locktime": 0, + "threshold": 1 + } + } + } + ] + }, + "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + } + ], + "id": "PLACEHOLDER_TX_ID" +}` + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 2) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintSecpOpTx.ID().String(), 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintSecpOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) + + sigStr, err := formatting.Encode(formatting.HexNC, mintSecpOpTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:]) + require.NoError(err) + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1) + + require.Equal(expectedReplyTxString, string(replyTxBytes)) } func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { @@ -771,6 +1165,7 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { Fx: &propertyfx.Fx{}, }}, }) + env.vm.ctx.Lock.Unlock() defer func() { env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) @@ -778,18 +1173,16 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm) + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) issueAndAccept(require, env.vm, env.issuer, createAssetTx) op1 := buildSecpMintOp(createAssetTx, key, 0) op2 := buildSecpMintOp(createAssetTx, key, 1) - mintSecpOpTx := buildOperationTxWithOp(op1, op2) + mintSecpOpTx := buildOperationTxWithOp(env.vm.ctx.ChainID, op1, op2) require.NoError(mintSecpOpTx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}, {key}})) issueAndAccept(require, env.vm, env.issuer, mintSecpOpTx) - env.vm.ctx.Lock.Unlock() - reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ TxID: mintSecpOpTx.ID(), @@ -797,15 +1190,115 @@ func TestServiceGetTxJSON_OperationTxWithMultipleSecpMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonString := string(reply.Tx) - // contains the address in the right format - require.Contains(jsonString, `"mintOutput":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"]`) - require.Contains(jsonString, `"transferOutput":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"],"amount":1,"locktime":0,"threshold":1}}}`) + replyTxBytes, err := stdjson.MarshalIndent(reply.Tx, "", "\t") + require.NoError(err) + + expectedReplyTxString := `{ + "unsignedTx": { + "networkID": 10, + "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", + "outputs": null, + "inputs": null, + "memo": "0x", + "operations": [ + { + "assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "inputIDs": [ + { + "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "outputIndex": 0 + } + ], + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "operation": { + "mintInput": { + "signatureIndices": [ + 0 + ] + }, + "mintOutput": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + }, + "transferOutput": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 1, + "locktime": 0, + "threshold": 1 + } + } + }, + { + "assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "inputIDs": [ + { + "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "outputIndex": 1 + } + ], + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "operation": { + "mintInput": { + "signatureIndices": [ + 0 + ] + }, + "mintOutput": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + }, + "transferOutput": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "amount": 1, + "locktime": 0, + "threshold": 1 + } + } + } + ] + }, + "credentials": [ + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + }, + { + "fxID": "spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + } + ], + "id": "PLACEHOLDER_TX_ID" +}` + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 4) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintSecpOpTx.ID().String(), 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintSecpOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) + + sigStr, err := formatting.Encode(formatting.HexNC, mintSecpOpTx.Creds[0].Credential.(*secp256k1fx.Credential).Sigs[0][:]) + require.NoError(err) - // contains the fxID - require.Contains(jsonString, `"assetID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","inputIDs":[{"txID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","outputIndex":1}],"fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ"`) - require.Contains(jsonString, `"credentials":[{"fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","credential":{"signatures":["0xcc650f48341601c348d8634e8d207e07ea7b4ee4fbdeed3055fa1f1e4f4e27556d25056447a3bd5d949e5f1cbb0155bb20216ac3a4055356e3c82dca74323e7401"]}},{"fxID":"spdxUxVJQbX85MGxMHbKw1sHxMnSqJ3QBzDyDYEP3h6TLuxqQ","credential":{"signatures":["0xcc650f48341601c348d8634e8d207e07ea7b4ee4fbdeed3055fa1f1e4f4e27556d25056447a3bd5d949e5f1cbb0155bb20216ac3a4055356e3c82dca74323e7401"]}}]`) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2) + + require.Equal(expectedReplyTxString, string(replyTxBytes)) } func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { @@ -818,6 +1311,7 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { Fx: &propertyfx.Fx{}, }}, }) + env.vm.ctx.Lock.Unlock() defer func() { env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) @@ -825,15 +1319,13 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm) + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) issueAndAccept(require, env.vm, env.issuer, createAssetTx) - mintPropertyFxOpTx := buildOperationTxWithOp(buildPropertyFxMintOp(createAssetTx, key, 4)) + mintPropertyFxOpTx := buildOperationTxWithOp(env.vm.ctx.ChainID, buildPropertyFxMintOp(createAssetTx, key, 4)) require.NoError(mintPropertyFxOpTx.SignPropertyFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) issueAndAccept(require, env.vm, env.issuer, mintPropertyFxOpTx) - env.vm.ctx.Lock.Unlock() - reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ TxID: mintPropertyFxOpTx.ID(), @@ -841,16 +1333,72 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOp(t *testing.T) { }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonString := string(reply.Tx) - // ensure memo is in hex - require.Contains(jsonString, `"memo":"0x"`) - // contains the address in the right format - require.Contains(jsonString, `"mintOutput":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"]`) + replyTxBytes, err := stdjson.MarshalIndent(reply.Tx, "", "\t") + require.NoError(err) + + expectedReplyTxString := `{ + "unsignedTx": { + "networkID": 10, + "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", + "outputs": null, + "inputs": null, + "memo": "0x", + "operations": [ + { + "assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "inputIDs": [ + { + "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "outputIndex": 4 + } + ], + "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", + "operation": { + "mintInput": { + "signatureIndices": [ + 0 + ] + }, + "mintOutput": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + }, + "ownedOutput": { + "addresses": [], + "locktime": 0, + "threshold": 0 + } + } + } + ] + }, + "credentials": [ + { + "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + } + ], + "id": "PLACEHOLDER_TX_ID" +}` + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 2) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintPropertyFxOpTx.ID().String(), 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintPropertyFxOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) - // contains the fxID - require.Contains(jsonString, `"assetID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","inputIDs":[{"txID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","outputIndex":4}],"fxID":"rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy"`) - require.Contains(jsonString, `"credentials":[{"fxID":"rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy","credential":{"signatures":["0xa3a00a03d3f1551ff696d6c0abdde73ae7002cd6dcce1c37d720de3b7ed80757411c9698cd9681a0fa55ca685904ca87056a3b8abc858a8ac08f45483b32a80201"]}}]`) + sigStr, err := formatting.Encode(formatting.HexNC, mintPropertyFxOpTx.Creds[0].Credential.(*propertyfx.Credential).Sigs[0][:]) + require.NoError(err) + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 1) + + require.Equal(expectedReplyTxString, string(replyTxBytes)) } func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) { @@ -863,6 +1411,7 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) Fx: &propertyfx.Fx{}, }}, }) + env.vm.ctx.Lock.Unlock() defer func() { env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) @@ -870,18 +1419,16 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) }() key := keys[0] - createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm) + createAssetTx := newAvaxCreateAssetTxWithOutputs(t, env.vm.ctx.ChainID, env.vm.parser) issueAndAccept(require, env.vm, env.issuer, createAssetTx) op1 := buildPropertyFxMintOp(createAssetTx, key, 4) op2 := buildPropertyFxMintOp(createAssetTx, key, 5) - mintPropertyFxOpTx := buildOperationTxWithOp(op1, op2) + mintPropertyFxOpTx := buildOperationTxWithOp(env.vm.ctx.ChainID, op1, op2) require.NoError(mintPropertyFxOpTx.SignPropertyFx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}, {key}})) issueAndAccept(require, env.vm, env.issuer, mintPropertyFxOpTx) - env.vm.ctx.Lock.Unlock() - reply := api.GetTxReply{} require.NoError(env.service.GetTx(nil, &api.GetTxArgs{ TxID: mintPropertyFxOpTx.ID(), @@ -889,40 +1436,135 @@ func TestServiceGetTxJSON_OperationTxWithPropertyFxMintOpMultiple(t *testing.T) }, &reply)) require.Equal(reply.Encoding, formatting.JSON) - jsonString := string(reply.Tx) - // contains the address in the right format - require.Contains(jsonString, `"mintOutput":{"addresses":["X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e"]`) + replyTxBytes, err := stdjson.MarshalIndent(reply.Tx, "", "\t") + require.NoError(err) + + expectedReplyTxString := `{ + "unsignedTx": { + "networkID": 10, + "blockchainID": "PLACEHOLDER_BLOCKCHAIN_ID", + "outputs": null, + "inputs": null, + "memo": "0x", + "operations": [ + { + "assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "inputIDs": [ + { + "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "outputIndex": 4 + } + ], + "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", + "operation": { + "mintInput": { + "signatureIndices": [ + 0 + ] + }, + "mintOutput": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + }, + "ownedOutput": { + "addresses": [], + "locktime": 0, + "threshold": 0 + } + } + }, + { + "assetID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "inputIDs": [ + { + "txID": "PLACEHOLDER_CREATE_ASSET_TX_ID", + "outputIndex": 5 + } + ], + "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", + "operation": { + "mintInput": { + "signatureIndices": [ + 0 + ] + }, + "mintOutput": { + "addresses": [ + "X-testing1lnk637g0edwnqc2tn8tel39652fswa3xk4r65e" + ], + "locktime": 0, + "threshold": 1 + }, + "ownedOutput": { + "addresses": [], + "locktime": 0, + "threshold": 0 + } + } + } + ] + }, + "credentials": [ + { + "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + }, + { + "fxID": "rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy", + "credential": { + "signatures": [ + "PLACEHOLDER_SIGNATURE" + ] + } + } + ], + "id": "PLACEHOLDER_TX_ID" +}` + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_CREATE_ASSET_TX_ID", createAssetTx.ID().String(), 4) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_TX_ID", mintPropertyFxOpTx.ID().String(), 1) + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_BLOCKCHAIN_ID", mintPropertyFxOpTx.Unsigned.(*txs.OperationTx).BlockchainID.String(), 1) - // contains the fxID - require.Contains(jsonString, `"operations":[{"assetID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","inputIDs":[{"txID":"2MDgrsBHMRsEPa4D4NA1Bo1pjkVLUK173S3dd9BgT2nCJNiDuS","outputIndex":4}],"fxID":"rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy"`) - require.Contains(jsonString, `"credentials":[{"fxID":"rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy","credential":{"signatures":["0x25b7ca14df108d4a32877bda4f10d84eda6d653c620f4c8d124265bdcf0ac91f45712b58b33f4b62a19698325a3c89adff214b77f772d9f311742860039abb5601"]}},{"fxID":"rXJsCSEYXg2TehWxCEEGj6JU2PWKTkd6cBdNLjoe2SpsKD9cy","credential":{"signatures":["0x25b7ca14df108d4a32877bda4f10d84eda6d653c620f4c8d124265bdcf0ac91f45712b58b33f4b62a19698325a3c89adff214b77f772d9f311742860039abb5601"]}}]`) + sigStr, err := formatting.Encode(formatting.HexNC, mintPropertyFxOpTx.Creds[0].Credential.(*propertyfx.Credential).Sigs[0][:]) + require.NoError(err) + + expectedReplyTxString = strings.Replace(expectedReplyTxString, "PLACEHOLDER_SIGNATURE", sigStr, 2) + + require.Equal(expectedReplyTxString, string(replyTxBytes)) } -func newAvaxBaseTxWithOutputs(t *testing.T, genesisBytes []byte, vm *VM) *txs.Tx { +func newAvaxBaseTxWithOutputs(t *testing.T, genesisBytes []byte, chainID ids.ID, fee uint64, parser txs.Parser) *txs.Tx { avaxTx := getCreateTxFromGenesisTest(t, genesisBytes, "AVAX") key := keys[0] - tx := buildBaseTx(avaxTx, vm, key) - require.NoError(t, tx.SignSECP256K1Fx(vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + tx := buildBaseTx(avaxTx, chainID, fee, key) + require.NoError(t, tx.SignSECP256K1Fx(parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) return tx } -func newAvaxExportTxWithOutputs(t *testing.T, genesisBytes []byte, vm *VM) *txs.Tx { +func newAvaxExportTxWithOutputs(t *testing.T, genesisBytes []byte, chainID ids.ID, fee uint64, parser txs.Parser) *txs.Tx { avaxTx := getCreateTxFromGenesisTest(t, genesisBytes, "AVAX") key := keys[0] - tx := buildExportTx(avaxTx, vm, key) - require.NoError(t, tx.SignSECP256K1Fx(vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) + tx := buildExportTx(avaxTx, chainID, fee, key) + require.NoError(t, tx.SignSECP256K1Fx(parser.Codec(), [][]*secp256k1.PrivateKey{{key}})) return tx } -func newAvaxCreateAssetTxWithOutputs(t *testing.T, vm *VM) *txs.Tx { +func newAvaxCreateAssetTxWithOutputs(t *testing.T, chainID ids.ID, parser txs.Parser) *txs.Tx { key := keys[0] - tx := buildCreateAssetTx(key) - require.NoError(t, vm.parser.InitializeTx(tx)) + tx := buildCreateAssetTx(chainID, key) + require.NoError(t, tx.Initialize(parser.Codec())) return tx } -func buildBaseTx(avaxTx *txs.Tx, vm *VM, key *secp256k1.PrivateKey) *txs.Tx { +func buildBaseTx(avaxTx *txs.Tx, chainID ids.ID, fee uint64, key *secp256k1.PrivateKey) *txs.Tx { return &txs.Tx{Unsigned: &txs.BaseTx{ BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, @@ -946,7 +1588,7 @@ func buildBaseTx(avaxTx *txs.Tx, vm *VM, key *secp256k1.PrivateKey) *txs.Tx { Outs: []*avax.TransferableOutput{{ Asset: avax.Asset{ID: avaxTx.ID()}, Out: &secp256k1fx.TransferOutput{ - Amt: startBalance - vm.TxFee, + Amt: startBalance - fee, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{key.PublicKey().Address()}, @@ -957,7 +1599,7 @@ func buildBaseTx(avaxTx *txs.Tx, vm *VM, key *secp256k1.PrivateKey) *txs.Tx { }} } -func buildExportTx(avaxTx *txs.Tx, vm *VM, key *secp256k1.PrivateKey) *txs.Tx { +func buildExportTx(avaxTx *txs.Tx, chainID ids.ID, fee uint64, key *secp256k1.PrivateKey) *txs.Tx { return &txs.Tx{Unsigned: &txs.ExportTx{ BaseTx: txs.BaseTx{ BaseTx: avax.BaseTx{ @@ -980,7 +1622,7 @@ func buildExportTx(avaxTx *txs.Tx, vm *VM, key *secp256k1.PrivateKey) *txs.Tx { ExportedOuts: []*avax.TransferableOutput{{ Asset: avax.Asset{ID: avaxTx.ID()}, Out: &secp256k1fx.TransferOutput{ - Amt: startBalance - vm.TxFee, + Amt: startBalance - fee, OutputOwners: secp256k1fx.OutputOwners{ Threshold: 1, Addrs: []ids.ShortID{key.PublicKey().Address()}, @@ -990,7 +1632,7 @@ func buildExportTx(avaxTx *txs.Tx, vm *VM, key *secp256k1.PrivateKey) *txs.Tx { }} } -func buildCreateAssetTx(key *secp256k1.PrivateKey) *txs.Tx { +func buildCreateAssetTx(chainID ids.ID, key *secp256k1.PrivateKey) *txs.Tx { return &txs.Tx{Unsigned: &txs.CreateAssetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, @@ -1129,7 +1771,7 @@ func buildSecpMintOp(createAssetTx *txs.Tx, key *secp256k1.PrivateKey, outputInd } } -func buildOperationTxWithOp(op ...*txs.Operation) *txs.Tx { +func buildOperationTxWithOp(chainID ids.ID, op ...*txs.Operation) *txs.Tx { return &txs.Tx{Unsigned: &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, @@ -1501,7 +2143,7 @@ func TestCreateFixedCapAsset(t *testing.T) { changeAddrStr, err := env.vm.FormatLocalAddress(testChangeAddr) require.NoError(err) - _, fromAddrsStr := sampleAddrs(t, env.vm, addrs) + _, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs) require.NoError(env.service.CreateFixedCapAsset(nil, &CreateAssetArgs{ JSONSpendHeader: api.JSONSpendHeader{ @@ -1549,7 +2191,7 @@ func TestCreateVariableCapAsset(t *testing.T) { reply := AssetIDChangeAddr{} minterAddrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address()) require.NoError(err) - _, fromAddrsStr := sampleAddrs(t, env.vm, addrs) + _, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs) changeAddrStr := fromAddrsStr[0] require.NoError(env.service.CreateVariableCapAsset(nil, &CreateAssetArgs{ @@ -1574,12 +2216,8 @@ func TestCreateVariableCapAsset(t *testing.T) { }, &reply)) require.Equal(changeAddrStr, reply.ChangeAddr) - env.vm.ctx.Lock.Lock() - buildAndAccept(require, env.vm, env.issuer, reply.AssetID) - env.vm.ctx.Lock.Unlock() - createdAssetID := reply.AssetID.String() // Test minting of the created variable cap asset mintArgs := &MintArgs{ @@ -1598,12 +2236,8 @@ func TestCreateVariableCapAsset(t *testing.T) { require.NoError(env.service.Mint(nil, mintArgs, mintReply)) require.Equal(changeAddrStr, mintReply.ChangeAddr) - env.vm.ctx.Lock.Lock() - buildAndAccept(require, env.vm, env.issuer, mintReply.TxID) - env.vm.ctx.Lock.Unlock() - sendArgs := &SendArgs{ JSONSpendHeader: api.JSONSpendHeader{ UserPass: api.UserPass{ @@ -1647,7 +2281,7 @@ func TestNFTWorkflow(t *testing.T) { env.vm.ctx.Lock.Unlock() }() - fromAddrs, fromAddrsStr := sampleAddrs(t, env.vm, addrs) + fromAddrs, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs) // Test minting of the created variable cap asset addrStr, err := env.vm.FormatLocalAddress(keys[0].PublicKey().Address()) @@ -1677,12 +2311,8 @@ func TestNFTWorkflow(t *testing.T) { require.NoError(env.service.CreateNFTAsset(nil, createArgs, createReply)) require.Equal(fromAddrsStr[0], createReply.ChangeAddr) - env.vm.ctx.Lock.Lock() - buildAndAccept(require, env.vm, env.issuer, createReply.AssetID) - env.vm.ctx.Lock.Unlock() - // Key: Address // Value: AVAX balance balances := map[ids.ShortID]uint64{} @@ -1732,13 +2362,9 @@ func TestNFTWorkflow(t *testing.T) { require.NoError(env.service.MintNFT(nil, mintArgs, mintReply)) require.Equal(fromAddrsStr[0], createReply.ChangeAddr) - env.vm.ctx.Lock.Lock() - // Accept the transaction so that we can send the newly minted NFT buildAndAccept(require, env.vm, env.issuer, mintReply.TxID) - env.vm.ctx.Lock.Unlock() - sendArgs := &SendNFTArgs{ JSONSpendHeader: api.JSONSpendHeader{ UserPass: api.UserPass{ @@ -1866,6 +2492,7 @@ func TestSend(t *testing.T) { env.vm.ctx.Lock.Unlock() defer func() { + env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() @@ -1877,7 +2504,7 @@ func TestSend(t *testing.T) { require.NoError(err) changeAddrStr, err := env.vm.FormatLocalAddress(testChangeAddr) require.NoError(err) - _, fromAddrsStr := sampleAddrs(t, env.vm, addrs) + _, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs) args := &SendArgs{ JSONSpendHeader: api.JSONSpendHeader{ @@ -1898,8 +2525,6 @@ func TestSend(t *testing.T) { require.NoError(env.service.Send(nil, args, reply)) require.Equal(changeAddrStr, reply.ChangeAddr) - env.vm.ctx.Lock.Lock() - buildAndAccept(require, env.vm, env.issuer, reply.TxID) } @@ -1919,6 +2544,7 @@ func TestSendMultiple(t *testing.T) { env.vm.ctx.Lock.Unlock() defer func() { + env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() @@ -1930,7 +2556,7 @@ func TestSendMultiple(t *testing.T) { require.NoError(err) changeAddrStr, err := env.vm.FormatLocalAddress(testChangeAddr) require.NoError(err) - _, fromAddrsStr := sampleAddrs(t, env.vm, addrs) + _, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs) args := &SendMultipleArgs{ JSONSpendHeader: api.JSONSpendHeader{ @@ -1958,8 +2584,6 @@ func TestSendMultiple(t *testing.T) { require.NoError(env.service.SendMultiple(nil, args, reply)) require.Equal(changeAddrStr, reply.ChangeAddr) - env.vm.ctx.Lock.Lock() - buildAndAccept(require, env.vm, env.issuer, reply.TxID) }) } diff --git a/vms/avm/state/diff.go b/vms/avm/state/diff.go index 1d53fa37da3e..83e9c5e0d590 100644 --- a/vms/avm/state/diff.go +++ b/vms/avm/state/diff.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -16,7 +16,8 @@ import ( ) var ( - _ Diff = (*diff)(nil) + _ Diff = (*diff)(nil) + _ Versions = stateGetter{} ErrMissingParentState = errors.New("missing parent state") ) @@ -61,6 +62,20 @@ func NewDiff( }, nil } +type stateGetter struct { + state Chain +} + +func (s stateGetter) GetState(ids.ID) (Chain, bool) { + return s.state, true +} + +func NewDiffOn(parentState Chain) (Diff, error) { + return NewDiff(ids.Empty, stateGetter{ + state: parentState, + }) +} + func (d *diff) GetUTXO(utxoID ids.ID) (*avax.UTXO, error) { if utxo, modified := d.modifiedUTXOs[utxoID]; modified { if utxo == nil { diff --git a/vms/avm/state/mock_state.go b/vms/avm/state/mock_state.go index 3bb615283ce6..cb5138c90369 100644 --- a/vms/avm/state/mock_state.go +++ b/vms/avm/state/mock_state.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/avm/state (interfaces: Chain,State,Diff) +// +// Generated by this command: +// +// mockgen -package=state -destination=vms/avm/state/mock_state.go github.com/ava-labs/avalanchego/vms/avm/state Chain,State,Diff +// // Package state is a generated GoMock package. package state @@ -51,7 +53,7 @@ func (m *MockChain) AddBlock(arg0 block.Block) { } // AddBlock indicates an expected call of AddBlock. -func (mr *MockChainMockRecorder) AddBlock(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) AddBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBlock", reflect.TypeOf((*MockChain)(nil).AddBlock), arg0) } @@ -63,7 +65,7 @@ func (m *MockChain) AddTx(arg0 *txs.Tx) { } // AddTx indicates an expected call of AddTx. -func (mr *MockChainMockRecorder) AddTx(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) AddTx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTx", reflect.TypeOf((*MockChain)(nil).AddTx), arg0) } @@ -75,7 +77,7 @@ func (m *MockChain) AddUTXO(arg0 *avax.UTXO) { } // AddUTXO indicates an expected call of AddUTXO. -func (mr *MockChainMockRecorder) AddUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) AddUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUTXO", reflect.TypeOf((*MockChain)(nil).AddUTXO), arg0) } @@ -87,7 +89,7 @@ func (m *MockChain) DeleteUTXO(arg0 ids.ID) { } // DeleteUTXO indicates an expected call of DeleteUTXO. -func (mr *MockChainMockRecorder) DeleteUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) DeleteUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUTXO", reflect.TypeOf((*MockChain)(nil).DeleteUTXO), arg0) } @@ -102,7 +104,7 @@ func (m *MockChain) GetBlock(arg0 ids.ID) (block.Block, error) { } // GetBlock indicates an expected call of GetBlock. -func (mr *MockChainMockRecorder) GetBlock(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockChain)(nil).GetBlock), arg0) } @@ -117,7 +119,7 @@ func (m *MockChain) GetBlockIDAtHeight(arg0 uint64) (ids.ID, error) { } // GetBlockIDAtHeight indicates an expected call of GetBlockIDAtHeight. -func (mr *MockChainMockRecorder) GetBlockIDAtHeight(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetBlockIDAtHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockIDAtHeight", reflect.TypeOf((*MockChain)(nil).GetBlockIDAtHeight), arg0) } @@ -160,7 +162,7 @@ func (m *MockChain) GetTx(arg0 ids.ID) (*txs.Tx, error) { } // GetTx indicates an expected call of GetTx. -func (mr *MockChainMockRecorder) GetTx(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetTx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTx", reflect.TypeOf((*MockChain)(nil).GetTx), arg0) } @@ -175,7 +177,7 @@ func (m *MockChain) GetUTXO(arg0 ids.ID) (*avax.UTXO, error) { } // GetUTXO indicates an expected call of GetUTXO. -func (mr *MockChainMockRecorder) GetUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUTXO", reflect.TypeOf((*MockChain)(nil).GetUTXO), arg0) } @@ -187,7 +189,7 @@ func (m *MockChain) SetLastAccepted(arg0 ids.ID) { } // SetLastAccepted indicates an expected call of SetLastAccepted. -func (mr *MockChainMockRecorder) SetLastAccepted(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) SetLastAccepted(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastAccepted", reflect.TypeOf((*MockChain)(nil).SetLastAccepted), arg0) } @@ -199,7 +201,7 @@ func (m *MockChain) SetTimestamp(arg0 time.Time) { } // SetTimestamp indicates an expected call of SetTimestamp. -func (mr *MockChainMockRecorder) SetTimestamp(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) SetTimestamp(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimestamp", reflect.TypeOf((*MockChain)(nil).SetTimestamp), arg0) } @@ -246,7 +248,7 @@ func (m *MockState) AddBlock(arg0 block.Block) { } // AddBlock indicates an expected call of AddBlock. -func (mr *MockStateMockRecorder) AddBlock(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBlock", reflect.TypeOf((*MockState)(nil).AddBlock), arg0) } @@ -258,7 +260,7 @@ func (m *MockState) AddTx(arg0 *txs.Tx) { } // AddTx indicates an expected call of AddTx. -func (mr *MockStateMockRecorder) AddTx(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddTx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTx", reflect.TypeOf((*MockState)(nil).AddTx), arg0) } @@ -270,7 +272,7 @@ func (m *MockState) AddUTXO(arg0 *avax.UTXO) { } // AddUTXO indicates an expected call of AddUTXO. -func (mr *MockStateMockRecorder) AddUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUTXO", reflect.TypeOf((*MockState)(nil).AddUTXO), arg0) } @@ -340,7 +342,7 @@ func (m *MockState) DeleteUTXO(arg0 ids.ID) { } // DeleteUTXO indicates an expected call of DeleteUTXO. -func (mr *MockStateMockRecorder) DeleteUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) DeleteUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUTXO", reflect.TypeOf((*MockState)(nil).DeleteUTXO), arg0) } @@ -355,7 +357,7 @@ func (m *MockState) GetBlock(arg0 ids.ID) (block.Block, error) { } // GetBlock indicates an expected call of GetBlock. -func (mr *MockStateMockRecorder) GetBlock(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockState)(nil).GetBlock), arg0) } @@ -370,7 +372,7 @@ func (m *MockState) GetBlockIDAtHeight(arg0 uint64) (ids.ID, error) { } // GetBlockIDAtHeight indicates an expected call of GetBlockIDAtHeight. -func (mr *MockStateMockRecorder) GetBlockIDAtHeight(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetBlockIDAtHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockIDAtHeight", reflect.TypeOf((*MockState)(nil).GetBlockIDAtHeight), arg0) } @@ -413,7 +415,7 @@ func (m *MockState) GetTx(arg0 ids.ID) (*txs.Tx, error) { } // GetTx indicates an expected call of GetTx. -func (mr *MockStateMockRecorder) GetTx(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetTx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTx", reflect.TypeOf((*MockState)(nil).GetTx), arg0) } @@ -428,7 +430,7 @@ func (m *MockState) GetUTXO(arg0 ids.ID) (*avax.UTXO, error) { } // GetUTXO indicates an expected call of GetUTXO. -func (mr *MockStateMockRecorder) GetUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUTXO", reflect.TypeOf((*MockState)(nil).GetUTXO), arg0) } @@ -442,7 +444,7 @@ func (m *MockState) InitializeChainState(arg0 ids.ID, arg1 time.Time) error { } // InitializeChainState indicates an expected call of InitializeChainState. -func (mr *MockStateMockRecorder) InitializeChainState(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) InitializeChainState(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitializeChainState", reflect.TypeOf((*MockState)(nil).InitializeChainState), arg0, arg1) } @@ -471,7 +473,7 @@ func (m *MockState) Prune(arg0 sync.Locker, arg1 logging.Logger) error { } // Prune indicates an expected call of Prune. -func (mr *MockStateMockRecorder) Prune(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) Prune(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prune", reflect.TypeOf((*MockState)(nil).Prune), arg0, arg1) } @@ -497,7 +499,7 @@ func (m *MockState) SetLastAccepted(arg0 ids.ID) { } // SetLastAccepted indicates an expected call of SetLastAccepted. -func (mr *MockStateMockRecorder) SetLastAccepted(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetLastAccepted(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastAccepted", reflect.TypeOf((*MockState)(nil).SetLastAccepted), arg0) } @@ -509,7 +511,7 @@ func (m *MockState) SetTimestamp(arg0 time.Time) { } // SetTimestamp indicates an expected call of SetTimestamp. -func (mr *MockStateMockRecorder) SetTimestamp(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetTimestamp(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimestamp", reflect.TypeOf((*MockState)(nil).SetTimestamp), arg0) } @@ -524,7 +526,7 @@ func (m *MockState) UTXOIDs(arg0 []byte, arg1 ids.ID, arg2 int) ([]ids.ID, error } // UTXOIDs indicates an expected call of UTXOIDs. -func (mr *MockStateMockRecorder) UTXOIDs(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) UTXOIDs(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UTXOIDs", reflect.TypeOf((*MockState)(nil).UTXOIDs), arg0, arg1, arg2) } @@ -559,7 +561,7 @@ func (m *MockDiff) AddBlock(arg0 block.Block) { } // AddBlock indicates an expected call of AddBlock. -func (mr *MockDiffMockRecorder) AddBlock(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) AddBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBlock", reflect.TypeOf((*MockDiff)(nil).AddBlock), arg0) } @@ -571,7 +573,7 @@ func (m *MockDiff) AddTx(arg0 *txs.Tx) { } // AddTx indicates an expected call of AddTx. -func (mr *MockDiffMockRecorder) AddTx(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) AddTx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTx", reflect.TypeOf((*MockDiff)(nil).AddTx), arg0) } @@ -583,7 +585,7 @@ func (m *MockDiff) AddUTXO(arg0 *avax.UTXO) { } // AddUTXO indicates an expected call of AddUTXO. -func (mr *MockDiffMockRecorder) AddUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) AddUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUTXO", reflect.TypeOf((*MockDiff)(nil).AddUTXO), arg0) } @@ -595,7 +597,7 @@ func (m *MockDiff) Apply(arg0 Chain) { } // Apply indicates an expected call of Apply. -func (mr *MockDiffMockRecorder) Apply(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) Apply(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockDiff)(nil).Apply), arg0) } @@ -607,7 +609,7 @@ func (m *MockDiff) DeleteUTXO(arg0 ids.ID) { } // DeleteUTXO indicates an expected call of DeleteUTXO. -func (mr *MockDiffMockRecorder) DeleteUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) DeleteUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUTXO", reflect.TypeOf((*MockDiff)(nil).DeleteUTXO), arg0) } @@ -622,7 +624,7 @@ func (m *MockDiff) GetBlock(arg0 ids.ID) (block.Block, error) { } // GetBlock indicates an expected call of GetBlock. -func (mr *MockDiffMockRecorder) GetBlock(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockDiff)(nil).GetBlock), arg0) } @@ -637,7 +639,7 @@ func (m *MockDiff) GetBlockIDAtHeight(arg0 uint64) (ids.ID, error) { } // GetBlockIDAtHeight indicates an expected call of GetBlockIDAtHeight. -func (mr *MockDiffMockRecorder) GetBlockIDAtHeight(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetBlockIDAtHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockIDAtHeight", reflect.TypeOf((*MockDiff)(nil).GetBlockIDAtHeight), arg0) } @@ -680,7 +682,7 @@ func (m *MockDiff) GetTx(arg0 ids.ID) (*txs.Tx, error) { } // GetTx indicates an expected call of GetTx. -func (mr *MockDiffMockRecorder) GetTx(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetTx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTx", reflect.TypeOf((*MockDiff)(nil).GetTx), arg0) } @@ -695,7 +697,7 @@ func (m *MockDiff) GetUTXO(arg0 ids.ID) (*avax.UTXO, error) { } // GetUTXO indicates an expected call of GetUTXO. -func (mr *MockDiffMockRecorder) GetUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUTXO", reflect.TypeOf((*MockDiff)(nil).GetUTXO), arg0) } @@ -707,7 +709,7 @@ func (m *MockDiff) SetLastAccepted(arg0 ids.ID) { } // SetLastAccepted indicates an expected call of SetLastAccepted. -func (mr *MockDiffMockRecorder) SetLastAccepted(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) SetLastAccepted(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastAccepted", reflect.TypeOf((*MockDiff)(nil).SetLastAccepted), arg0) } @@ -719,7 +721,7 @@ func (m *MockDiff) SetTimestamp(arg0 time.Time) { } // SetTimestamp indicates an expected call of SetTimestamp. -func (mr *MockDiffMockRecorder) SetTimestamp(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) SetTimestamp(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimestamp", reflect.TypeOf((*MockDiff)(nil).SetTimestamp), arg0) } diff --git a/vms/avm/state/state.go b/vms/avm/state/state.go index e290f093aa22..e85907d77a50 100644 --- a/vms/avm/state/state.go +++ b/vms/avm/state/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/avm/state/state_test.go b/vms/avm/state/state_test.go index c97836aee794..758e55ffd7cd 100644 --- a/vms/avm/state/state_test.go +++ b/vms/avm/state/state_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -38,9 +38,12 @@ var ( func init() { var err error - parser, err = block.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err = block.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) if err != nil { panic(err) } @@ -61,7 +64,7 @@ func init() { populatedTx = &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ BlockchainID: ids.GenerateTestID(), }}} - err = parser.InitializeTx(populatedTx) + err = populatedTx.Initialize(parser.Codec()) if err != nil { panic(err) } @@ -197,7 +200,7 @@ func ChainTxTest(t *testing.T, c Chain) { tx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ BlockchainID: ids.GenerateTestID(), }}} - require.NoError(parser.InitializeTx(tx)) + require.NoError(tx.Initialize(parser.Codec())) txID := tx.ID() _, err = c.GetTx(txID) diff --git a/vms/avm/state/versions.go b/vms/avm/state/versions.go index da84182bb683..6afb0fe8e5f2 100644 --- a/vms/avm/state/versions.go +++ b/vms/avm/state/versions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/avm/state_test.go b/vms/avm/state_test.go index 976ce1c60840..b17604b2ba62 100644 --- a/vms/avm/state_test.go +++ b/vms/avm/state_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -51,7 +51,7 @@ func TestSetsAndGets(t *testing.T) { tx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, Ins: []*avax.TransferableInput{{ UTXOID: avax.UTXOID{ TxID: ids.Empty, diff --git a/vms/avm/static_client.go b/vms/avm/static_client.go deleted file mode 100644 index 78014785cba9..000000000000 --- a/vms/avm/static_client.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package avm - -import ( - "context" - - "github.com/ava-labs/avalanchego/utils/rpc" -) - -var _ StaticClient = (*staticClient)(nil) - -// StaticClient for interacting with the AVM static api -type StaticClient interface { - BuildGenesis(ctx context.Context, args *BuildGenesisArgs, options ...rpc.Option) (*BuildGenesisReply, error) -} - -// staticClient is an implementation of an AVM client for interacting with the -// avm static api -type staticClient struct { - requester rpc.EndpointRequester -} - -// NewClient returns an AVM client for interacting with the avm static api -func NewStaticClient(uri string) StaticClient { - return &staticClient{requester: rpc.NewEndpointRequester( - uri + "/ext/vm/avm", - )} -} - -func (c *staticClient) BuildGenesis(ctx context.Context, args *BuildGenesisArgs, options ...rpc.Option) (resp *BuildGenesisReply, err error) { - resp = &BuildGenesisReply{} - err = c.requester.SendRequest(ctx, "avm.buildGenesis", args, resp, options...) - return resp, err -} diff --git a/vms/avm/static_service.go b/vms/avm/static_service.go index 275546061354..8340bbb6a5df 100644 --- a/vms/avm/static_service.go +++ b/vms/avm/static_service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net/http" + "time" stdjson "encoding/json" @@ -77,11 +78,14 @@ type BuildGenesisReply struct { // BuildGenesis returns the UTXOs such that at least one address in [args.Addresses] is // referenced in the UTXO. func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, reply *BuildGenesisReply) error { - parser, err := txs.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - &nftfx.Fx{}, - &propertyfx.Fx{}, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + &nftfx.Fx{}, + &propertyfx.Fx{}, + }, + ) if err != nil { return err } diff --git a/vms/avm/static_service_test.go b/vms/avm/static_service_test.go deleted file mode 100644 index a18220e5de37..000000000000 --- a/vms/avm/static_service_test.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package avm - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/formatting" - "github.com/ava-labs/avalanchego/utils/formatting/address" - "github.com/ava-labs/avalanchego/utils/json" -) - -var addrStrArray = []string{ - "A9bTQjfYGBFK3JPRJqF2eh3JYL7cHocvy", - "6mxBGnjGDCKgkVe7yfrmvMA7xE7qCv3vv", - "6ncQ19Q2U4MamkCYzshhD8XFjfwAWFzTa", - "Jz9ayEDt7dx9hDx45aXALujWmL9ZUuqe7", -} - -func TestBuildGenesis(t *testing.T) { - require := require.New(t) - - ss := CreateStaticService() - addrMap := map[string]string{} - for _, addrStr := range addrStrArray { - addr, err := ids.ShortFromString(addrStr) - require.NoError(err) - addrMap[addrStr], err = address.FormatBech32(constants.UnitTestHRP, addr[:]) - require.NoError(err) - } - args := BuildGenesisArgs{ - Encoding: formatting.Hex, - GenesisData: map[string]AssetDefinition{ - "asset1": { - Name: "myFixedCapAsset", - Symbol: "MFCA", - Denomination: 8, - InitialState: map[string][]interface{}{ - "fixedCap": { - Holder{ - Amount: 100000, - Address: addrMap["A9bTQjfYGBFK3JPRJqF2eh3JYL7cHocvy"], - }, - Holder{ - Amount: 100000, - Address: addrMap["6mxBGnjGDCKgkVe7yfrmvMA7xE7qCv3vv"], - }, - Holder{ - Amount: json.Uint64(startBalance), - Address: addrMap["6ncQ19Q2U4MamkCYzshhD8XFjfwAWFzTa"], - }, - Holder{ - Amount: json.Uint64(startBalance), - Address: addrMap["Jz9ayEDt7dx9hDx45aXALujWmL9ZUuqe7"], - }, - }, - }, - }, - "asset2": { - Name: "myVarCapAsset", - Symbol: "MVCA", - InitialState: map[string][]interface{}{ - "variableCap": { - Owners{ - Threshold: 1, - Minters: []string{ - addrMap["A9bTQjfYGBFK3JPRJqF2eh3JYL7cHocvy"], - addrMap["6mxBGnjGDCKgkVe7yfrmvMA7xE7qCv3vv"], - }, - }, - Owners{ - Threshold: 2, - Minters: []string{ - addrMap["6ncQ19Q2U4MamkCYzshhD8XFjfwAWFzTa"], - addrMap["Jz9ayEDt7dx9hDx45aXALujWmL9ZUuqe7"], - }, - }, - }, - }, - }, - "asset3": { - Name: "myOtherVarCapAsset", - InitialState: map[string][]interface{}{ - "variableCap": { - Owners{ - Threshold: 1, - Minters: []string{ - addrMap["A9bTQjfYGBFK3JPRJqF2eh3JYL7cHocvy"], - }, - }, - }, - }, - }, - }, - } - reply := BuildGenesisReply{} - require.NoError(ss.BuildGenesis(nil, &args, &reply)) -} diff --git a/vms/avm/tx.go b/vms/avm/tx.go index 57afe6864d8e..13064a59a18f 100644 --- a/vms/avm/tx.go +++ b/vms/avm/tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/tx_init.go b/vms/avm/tx_init.go index 1a7d29ebeb40..00112bf6dd45 100644 --- a/vms/avm/tx_init.go +++ b/vms/avm/tx_init.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/txs/base_tx.go b/vms/avm/txs/base_tx.go index 617769d343d8..5cc0d222dce3 100644 --- a/vms/avm/txs/base_tx.go +++ b/vms/avm/txs/base_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/avm/txs/base_tx_test.go b/vms/avm/txs/base_tx_test.go index 6ec386a7ab8a..d7403e3420fc 100644 --- a/vms/avm/txs/base_tx_test.go +++ b/vms/avm/txs/base_tx_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -125,12 +126,15 @@ func TestBaseTxSerialization(t *testing.T) { Memo: []byte{0x00, 0x01, 0x02, 0x03}, }}} - parser, err := NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) - require.NoError(parser.InitializeTx(tx)) + require.NoError(tx.Initialize(parser.Codec())) require.Equal(tx.ID().String(), "zeqT8FTnRAxes7QQQYkaWhNkHavd9d6aCdH8TQu2Mx5KEydEz") result := tx.Bytes() diff --git a/vms/avm/txs/codec.go b/vms/avm/txs/codec.go index 777e4562a509..2a83f7497ae0 100644 --- a/vms/avm/txs/codec.go +++ b/vms/avm/txs/codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/avm/txs/create_asset_tx.go b/vms/avm/txs/create_asset_tx.go index 4a80d018e428..818bea5b9259 100644 --- a/vms/avm/txs/create_asset_tx.go +++ b/vms/avm/txs/create_asset_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/avm/txs/create_asset_tx_test.go b/vms/avm/txs/create_asset_tx_test.go index 08d0c46f4d54..9ef548eedea2 100644 --- a/vms/avm/txs/create_asset_tx_test.go +++ b/vms/avm/txs/create_asset_tx_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -193,12 +194,15 @@ func TestCreateAssetTxSerialization(t *testing.T) { }, }} - parser, err := NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) - require.NoError(parser.InitializeTx(tx)) + require.NoError(tx.Initialize(parser.Codec())) result := tx.Bytes() require.Equal(expected, result) @@ -362,11 +366,14 @@ func TestCreateAssetTxSerializationAgain(t *testing.T) { }) } - parser, err := NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) - require.NoError(parser.InitializeTx(tx)) + require.NoError(tx.Initialize(parser.Codec())) result := tx.Bytes() require.Equal(expected, result) diff --git a/vms/avm/txs/executor/backend.go b/vms/avm/txs/executor/backend.go index fbf4a756faec..fdb020423bdb 100644 --- a/vms/avm/txs/executor/backend.go +++ b/vms/avm/txs/executor/backend.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/avm/txs/executor/executor.go b/vms/avm/txs/executor/executor.go index 6a5991cade04..2e7db5659dae 100644 --- a/vms/avm/txs/executor/executor.go +++ b/vms/avm/txs/executor/executor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/avm/txs/executor/executor_test.go b/vms/avm/txs/executor/executor_test.go index 81d301ad6bae..66d210b40cb8 100644 --- a/vms/avm/txs/executor/executor_test.go +++ b/vms/avm/txs/executor/executor_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor import ( "testing" + "time" "github.com/prometheus/client_golang/prometheus" @@ -37,7 +38,10 @@ func TestBaseTxExecutor(t *testing.T) { require := require.New(t) secpFx := &secp256k1fx.Fx{} - parser, err := block.NewParser([]fxs.Fx{secpFx}) + parser, err := block.NewParser( + time.Time{}, + []fxs.Fx{secpFx}, + ) require.NoError(err) codec := parser.Codec() @@ -142,7 +146,10 @@ func TestCreateAssetTxExecutor(t *testing.T) { require := require.New(t) secpFx := &secp256k1fx.Fx{} - parser, err := block.NewParser([]fxs.Fx{secpFx}) + parser, err := block.NewParser( + time.Time{}, + []fxs.Fx{secpFx}, + ) require.NoError(err) codec := parser.Codec() @@ -285,7 +292,10 @@ func TestOperationTxExecutor(t *testing.T) { require := require.New(t) secpFx := &secp256k1fx.Fx{} - parser, err := block.NewParser([]fxs.Fx{secpFx}) + parser, err := block.NewParser( + time.Time{}, + []fxs.Fx{secpFx}, + ) require.NoError(err) codec := parser.Codec() diff --git a/vms/avm/txs/executor/semantic_verifier.go b/vms/avm/txs/executor/semantic_verifier.go index 1fd94fb0e131..946346bc0646 100644 --- a/vms/avm/txs/executor/semantic_verifier.go +++ b/vms/avm/txs/executor/semantic_verifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/avm/txs/executor/semantic_verifier_test.go b/vms/avm/txs/executor/semantic_verifier_test.go index 7c659f7ec227..6579e29784d5 100644 --- a/vms/avm/txs/executor/semantic_verifier_test.go +++ b/vms/avm/txs/executor/semantic_verifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -6,6 +6,7 @@ package executor import ( "reflect" "testing" + "time" "github.com/stretchr/testify/require" @@ -16,6 +17,7 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" @@ -30,11 +32,12 @@ import ( ) func TestSemanticVerifierBaseTx(t *testing.T) { - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) typeToFxIndex := make(map[reflect.Type]int) secpFx := &secp256k1fx.Fx{} parser, err := txs.NewCustomParser( + time.Time{}, typeToFxIndex, new(mockable.Clock), logging.NoWarn{}, @@ -387,16 +390,12 @@ func TestSemanticVerifierBaseTx(t *testing.T) { } func TestSemanticVerifierExportTx(t *testing.T) { - ctx := newContext(t) - ctrl := gomock.NewController(t) - - validatorState := validators.NewMockState(ctrl) - validatorState.EXPECT().GetSubnetID(gomock.Any(), ctx.CChainID).AnyTimes().Return(ctx.SubnetID, nil) - ctx.ValidatorState = validatorState + ctx := snowtest.Context(t, snowtest.XChainID) typeToFxIndex := make(map[reflect.Type]int) secpFx := &secp256k1fx.Fx{} parser, err := txs.NewCustomParser( + time.Time{}, typeToFxIndex, new(mockable.Clock), logging.NoWarn{}, @@ -756,7 +755,7 @@ func TestSemanticVerifierExportTxDifferentSubnet(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) validatorState := validators.NewMockState(ctrl) validatorState.EXPECT().GetSubnetID(gomock.Any(), ctx.CChainID).AnyTimes().Return(ids.GenerateTestID(), nil) @@ -765,6 +764,7 @@ func TestSemanticVerifierExportTxDifferentSubnet(t *testing.T) { typeToFxIndex := make(map[reflect.Type]int) secpFx := &secp256k1fx.Fx{} parser, err := txs.NewCustomParser( + time.Time{}, typeToFxIndex, new(mockable.Clock), logging.NoWarn{}, @@ -873,13 +873,7 @@ func TestSemanticVerifierExportTxDifferentSubnet(t *testing.T) { } func TestSemanticVerifierImportTx(t *testing.T) { - ctrl := gomock.NewController(t) - - ctx := newContext(t) - - validatorState := validators.NewMockState(ctrl) - validatorState.EXPECT().GetSubnetID(gomock.Any(), ctx.CChainID).AnyTimes().Return(ctx.SubnetID, nil) - ctx.ValidatorState = validatorState + ctx := snowtest.Context(t, snowtest.XChainID) m := atomic.NewMemory(prefixdb.New([]byte{0}, memdb.New())) ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) @@ -887,6 +881,7 @@ func TestSemanticVerifierImportTx(t *testing.T) { typeToFxIndex := make(map[reflect.Type]int) fx := &secp256k1fx.Fx{} parser, err := txs.NewCustomParser( + time.Time{}, typeToFxIndex, new(mockable.Clock), logging.NoWarn{}, diff --git a/vms/avm/txs/executor/syntactic_verifier.go b/vms/avm/txs/executor/syntactic_verifier.go index 7419b5738215..81a2f2a715f4 100644 --- a/vms/avm/txs/executor/syntactic_verifier.go +++ b/vms/avm/txs/executor/syntactic_verifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/avm/txs/executor/syntactic_verifier_test.go b/vms/avm/txs/executor/syntactic_verifier_test.go index 34f1b27ebbcc..108ac9e94a60 100644 --- a/vms/avm/txs/executor/syntactic_verifier_test.go +++ b/vms/avm/txs/executor/syntactic_verifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -7,11 +7,12 @@ import ( "math" "strings" "testing" + "time" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/vms/avm/config" @@ -32,30 +33,16 @@ var ( } ) -func newContext(t testing.TB) *snow.Context { - require := require.New(t) - - ctx := snow.DefaultContextTest() - ctx.NetworkID = constants.UnitTestID - ctx.ChainID = ids.GenerateTestID() - ctx.XChainID = ctx.ChainID - ctx.CChainID = ids.GenerateTestID() - - aliaser := ctx.BCLookup.(ids.Aliaser) - require.NoError(aliaser.Alias(ctx.XChainID, "X")) - require.NoError(aliaser.Alias(ctx.XChainID, ctx.XChainID.String())) - require.NoError(aliaser.Alias(constants.PlatformChainID, "P")) - require.NoError(aliaser.Alias(constants.PlatformChainID, constants.PlatformChainID.String())) - return ctx -} - func TestSyntacticVerifierBaseTx(t *testing.T) { - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) fx := &secp256k1fx.Fx{} - parser, err := txs.NewParser([]fxs.Fx{ - fx, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + fx, + }, + ) require.NoError(t, err) feeAssetID := ids.GenerateTestID() @@ -420,12 +407,15 @@ func TestSyntacticVerifierBaseTx(t *testing.T) { } func TestSyntacticVerifierCreateAssetTx(t *testing.T) { - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) fx := &secp256k1fx.Fx{} - parser, err := txs.NewParser([]fxs.Fx{ - fx, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + fx, + }, + ) require.NoError(t, err) feeAssetID := ids.GenerateTestID() @@ -1027,12 +1017,15 @@ func TestSyntacticVerifierCreateAssetTx(t *testing.T) { } func TestSyntacticVerifierOperationTx(t *testing.T) { - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) fx := &secp256k1fx.Fx{} - parser, err := txs.NewParser([]fxs.Fx{ - fx, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + fx, + }, + ) require.NoError(t, err) feeAssetID := ids.GenerateTestID() @@ -1514,12 +1507,15 @@ func TestSyntacticVerifierOperationTx(t *testing.T) { } func TestSyntacticVerifierImportTx(t *testing.T) { - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) fx := &secp256k1fx.Fx{} - parser, err := txs.NewParser([]fxs.Fx{ - fx, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + fx, + }, + ) require.NoError(t, err) feeAssetID := ids.GenerateTestID() @@ -1912,12 +1908,15 @@ func TestSyntacticVerifierImportTx(t *testing.T) { } func TestSyntacticVerifierExportTx(t *testing.T) { - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) fx := &secp256k1fx.Fx{} - parser, err := txs.NewParser([]fxs.Fx{ - fx, - }) + parser, err := txs.NewParser( + time.Time{}, + []fxs.Fx{ + fx, + }, + ) require.NoError(t, err) feeAssetID := ids.GenerateTestID() diff --git a/vms/avm/txs/export_tx.go b/vms/avm/txs/export_tx.go index aec13141497d..e0be45360693 100644 --- a/vms/avm/txs/export_tx.go +++ b/vms/avm/txs/export_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/avm/txs/export_tx_test.go b/vms/avm/txs/export_tx_test.go index a7c1ed16196f..1d3ce2dee276 100644 --- a/vms/avm/txs/export_tx_test.go +++ b/vms/avm/txs/export_tx_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -108,12 +109,15 @@ func TestExportTxSerialization(t *testing.T) { }, }} - parser, err := NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) - require.NoError(parser.InitializeTx(tx)) + require.NoError(tx.Initialize(parser.Codec())) require.Equal(tx.ID().String(), "2PKJE4TrKYpgynBFCpNPpV3GHK7d9QTgrL5mpYG6abHKDvNBG3") result := tx.Bytes() diff --git a/vms/avm/txs/import_tx.go b/vms/avm/txs/import_tx.go index c3066ccc5c40..5ef8929fc641 100644 --- a/vms/avm/txs/import_tx.go +++ b/vms/avm/txs/import_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/avm/txs/import_tx_test.go b/vms/avm/txs/import_tx_test.go index 4172a4047792..82dd0cfcd7b2 100644 --- a/vms/avm/txs/import_tx_test.go +++ b/vms/avm/txs/import_tx_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -108,12 +109,15 @@ func TestImportTxSerialization(t *testing.T) { }}, }} - parser, err := NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - }) + parser, err := NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + }, + ) require.NoError(err) - require.NoError(parser.InitializeTx(tx)) + require.NoError(tx.Initialize(parser.Codec())) require.Equal(tx.ID().String(), "9wdPb5rsThXYLX4WxkNeyYrNMfDE5cuWLgifSjxKiA2dCmgCZ") result := tx.Bytes() diff --git a/vms/avm/txs/initial_state.go b/vms/avm/txs/initial_state.go index a50c88c0a294..a093ead5c6b2 100644 --- a/vms/avm/txs/initial_state.go +++ b/vms/avm/txs/initial_state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -59,8 +59,8 @@ func (is *InitialState) Verify(c codec.Manager, numFxs int) error { return nil } -func (is *InitialState) Less(other *InitialState) bool { - return is.FxIndex < other.FxIndex +func (is *InitialState) Compare(other *InitialState) int { + return utils.Compare(is.FxIndex, other.FxIndex) } func (is *InitialState) Sort(c codec.Manager) { diff --git a/vms/avm/txs/initial_state_test.go b/vms/avm/txs/initial_state_test.go index f54f54b3b9ed..5f61deb3e7c6 100644 --- a/vms/avm/txs/initial_state_test.go +++ b/vms/avm/txs/initial_state_test.go @@ -1,11 +1,13 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs import ( "errors" + "fmt" "testing" + "time" "github.com/stretchr/testify/require" @@ -22,7 +24,7 @@ var errTest = errors.New("non-nil error") func TestInitialStateVerifySerialization(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) require.NoError(c.RegisterType(&secp256k1fx.TransferOutput{})) m := codec.NewDefaultManager() require.NoError(m.RegisterCodec(CodecVersion, c)) @@ -79,7 +81,7 @@ func TestInitialStateVerifySerialization(t *testing.T) { func TestInitialStateVerifyNil(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) m := codec.NewDefaultManager() require.NoError(m.RegisterCodec(CodecVersion, c)) numFxs := 1 @@ -92,7 +94,7 @@ func TestInitialStateVerifyNil(t *testing.T) { func TestInitialStateVerifyUnknownFxID(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) m := codec.NewDefaultManager() require.NoError(m.RegisterCodec(CodecVersion, c)) numFxs := 1 @@ -107,7 +109,7 @@ func TestInitialStateVerifyUnknownFxID(t *testing.T) { func TestInitialStateVerifyNilOutput(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) m := codec.NewDefaultManager() require.NoError(m.RegisterCodec(CodecVersion, c)) numFxs := 1 @@ -123,7 +125,7 @@ func TestInitialStateVerifyNilOutput(t *testing.T) { func TestInitialStateVerifyInvalidOutput(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) require.NoError(c.RegisterType(&avax.TestState{})) m := codec.NewDefaultManager() require.NoError(m.RegisterCodec(CodecVersion, c)) @@ -140,7 +142,7 @@ func TestInitialStateVerifyInvalidOutput(t *testing.T) { func TestInitialStateVerifyUnsortedOutputs(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) require.NoError(c.RegisterType(&avax.TestTransferable{})) m := codec.NewDefaultManager() require.NoError(m.RegisterCodec(CodecVersion, c)) @@ -159,14 +161,31 @@ func TestInitialStateVerifyUnsortedOutputs(t *testing.T) { require.NoError(is.Verify(m, numFxs)) } -func TestInitialStateLess(t *testing.T) { - require := require.New(t) - - var is1, is2 InitialState - require.False(is1.Less(&is2)) - require.False(is2.Less(&is1)) +func TestInitialStateCompare(t *testing.T) { + tests := []struct { + a *InitialState + b *InitialState + expected int + }{ + { + a: &InitialState{}, + b: &InitialState{}, + expected: 0, + }, + { + a: &InitialState{ + FxIndex: 1, + }, + b: &InitialState{}, + expected: 1, + }, + } + for _, test := range tests { + t.Run(fmt.Sprintf("%d_%d_%d", test.a.FxIndex, test.b.FxIndex, test.expected), func(t *testing.T) { + require := require.New(t) - is1.FxIndex = 1 - require.False(is1.Less(&is2)) - require.True(is2.Less(&is1)) + require.Equal(test.expected, test.a.Compare(test.b)) + require.Equal(-test.expected, test.b.Compare(test.a)) + }) + } } diff --git a/vms/avm/txs/mempool/mempool.go b/vms/avm/txs/mempool/mempool.go index 84a3583781ef..4ac275a21305 100644 --- a/vms/avm/txs/mempool/mempool.go +++ b/vms/avm/txs/mempool/mempool.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package mempool @@ -6,14 +6,16 @@ package mempool import ( "errors" "fmt" + "sync" "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/linkedhashmap" - "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/setmap" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/avm/txs" ) @@ -26,8 +28,6 @@ const ( // droppedTxIDsCacheSize is the maximum number of dropped txIDs to cache droppedTxIDsCacheSize = 64 - initialConsumedUTXOsSize = 512 - // maxMempoolSize is the maximum number of bytes allowed in the mempool maxMempoolSize = 64 * units.MiB ) @@ -35,21 +35,25 @@ const ( var ( _ Mempool = (*mempool)(nil) - errDuplicateTx = errors.New("duplicate tx") - errTxTooLarge = errors.New("tx too large") - errMempoolFull = errors.New("mempool is full") - errConflictsWithOtherTx = errors.New("tx conflicts with other tx") + ErrDuplicateTx = errors.New("duplicate tx") + ErrTxTooLarge = errors.New("tx too large") + ErrMempoolFull = errors.New("mempool is full") + ErrConflictsWithOtherTx = errors.New("tx conflicts with other tx") ) // Mempool contains transactions that have not yet been put into a block. type Mempool interface { Add(tx *txs.Tx) error - Has(txID ids.ID) bool - Get(txID ids.ID) *txs.Tx - Remove(txs []*txs.Tx) + Get(txID ids.ID) (*txs.Tx, bool) + // Remove [txs] and any conflicts of [txs] from the mempool. + Remove(txs ...*txs.Tx) + + // Peek returns the oldest tx in the mempool. + Peek() (tx *txs.Tx, exists bool) - // Peek returns the first tx in the mempool whose size is <= [maxTxSize]. - Peek(maxTxSize int) *txs.Tx + // Iterate over transactions from oldest to newest until the function + // returns false or there are no more transactions. + Iterate(f func(tx *txs.Tx) bool) // RequestBuildBlock notifies the consensus engine that a block should be // built if there is at least one transaction in the mempool. @@ -59,22 +63,22 @@ type Mempool interface { // unissued. This allows previously dropped txs to be possibly reissued. MarkDropped(txID ids.ID, reason error) GetDropReason(txID ids.ID) error + + // Len returns the number of txs in the mempool. + Len() int } type mempool struct { - bytesAvailableMetric prometheus.Gauge - bytesAvailable int - - unissuedTxs linkedhashmap.LinkedHashmap[ids.ID, *txs.Tx] - numTxs prometheus.Gauge + lock sync.RWMutex + unissuedTxs linkedhashmap.LinkedHashmap[ids.ID, *txs.Tx] + consumedUTXOs *setmap.SetMap[ids.ID, ids.ID] // TxID -> Consumed UTXOs + bytesAvailable int + droppedTxIDs *cache.LRU[ids.ID, error] // TxID -> Verification error toEngine chan<- common.Message - // Key: Tx ID - // Value: Verification error - droppedTxIDs *cache.LRU[ids.ID, error] - - consumedUTXOs set.Set[ids.ID] + numTxs prometheus.Gauge + bytesAvailableMetric prometheus.Gauge } func New( @@ -82,47 +86,46 @@ func New( registerer prometheus.Registerer, toEngine chan<- common.Message, ) (Mempool, error) { - bytesAvailableMetric := prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "bytes_available", - Help: "Number of bytes of space currently available in the mempool", - }) - if err := registerer.Register(bytesAvailableMetric); err != nil { - return nil, err + m := &mempool{ + unissuedTxs: linkedhashmap.New[ids.ID, *txs.Tx](), + consumedUTXOs: setmap.New[ids.ID, ids.ID](), + bytesAvailable: maxMempoolSize, + droppedTxIDs: &cache.LRU[ids.ID, error]{Size: droppedTxIDsCacheSize}, + toEngine: toEngine, + numTxs: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "count", + Help: "Number of transactions in the mempool", + }), + bytesAvailableMetric: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "bytes_available", + Help: "Number of bytes of space currently available in the mempool", + }), } + m.bytesAvailableMetric.Set(maxMempoolSize) - numTxsMetric := prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "count", - Help: "Number of transactions in the mempool", - }) - if err := registerer.Register(numTxsMetric); err != nil { - return nil, err - } - - bytesAvailableMetric.Set(maxMempoolSize) - return &mempool{ - bytesAvailableMetric: bytesAvailableMetric, - bytesAvailable: maxMempoolSize, - unissuedTxs: linkedhashmap.New[ids.ID, *txs.Tx](), - numTxs: numTxsMetric, - toEngine: toEngine, - droppedTxIDs: &cache.LRU[ids.ID, error]{Size: droppedTxIDsCacheSize}, - consumedUTXOs: set.NewSet[ids.ID](initialConsumedUTXOsSize), - }, nil + err := utils.Err( + registerer.Register(m.numTxs), + registerer.Register(m.bytesAvailableMetric), + ) + return m, err } func (m *mempool) Add(tx *txs.Tx) error { - // Note: a previously dropped tx can be re-added txID := tx.ID() - if m.Has(txID) { - return fmt.Errorf("%w: %s", errDuplicateTx, txID) + + m.lock.Lock() + defer m.lock.Unlock() + + if _, ok := m.unissuedTxs.Get(txID); ok { + return fmt.Errorf("%w: %s", ErrDuplicateTx, txID) } txSize := len(tx.Bytes()) if txSize > MaxTxSize { return fmt.Errorf("%w: %s size (%d) > max size (%d)", - errTxTooLarge, + ErrTxTooLarge, txID, txSize, MaxTxSize, @@ -130,7 +133,7 @@ func (m *mempool) Add(tx *txs.Tx) error { } if txSize > m.bytesAvailable { return fmt.Errorf("%w: %s size (%d) > available space (%d)", - errMempoolFull, + ErrMempoolFull, txID, txSize, m.bytesAvailable, @@ -138,8 +141,8 @@ func (m *mempool) Add(tx *txs.Tx) error { } inputs := tx.Unsigned.InputIDs() - if m.consumedUTXOs.Overlaps(inputs) { - return fmt.Errorf("%w: %s", errConflictsWithOtherTx, txID) + if m.consumedUTXOs.HasOverlap(inputs) { + return fmt.Errorf("%w: %s", ErrConflictsWithOtherTx, txID) } m.bytesAvailable -= txSize @@ -149,49 +152,58 @@ func (m *mempool) Add(tx *txs.Tx) error { m.numTxs.Inc() // Mark these UTXOs as consumed in the mempool - m.consumedUTXOs.Union(inputs) + m.consumedUTXOs.Put(txID, inputs) - // An explicitly added tx must not be marked as dropped. + // An added tx must not be marked as dropped. m.droppedTxIDs.Evict(txID) return nil } -func (m *mempool) Has(txID ids.ID) bool { - return m.Get(txID) != nil +func (m *mempool) Get(txID ids.ID) (*txs.Tx, bool) { + tx, ok := m.unissuedTxs.Get(txID) + return tx, ok } -func (m *mempool) Get(txID ids.ID) *txs.Tx { - tx, _ := m.unissuedTxs.Get(txID) - return tx -} +func (m *mempool) Remove(txs ...*txs.Tx) { + m.lock.Lock() + defer m.lock.Unlock() -func (m *mempool) Remove(txsToRemove []*txs.Tx) { - for _, tx := range txsToRemove { + for _, tx := range txs { txID := tx.ID() - if !m.unissuedTxs.Delete(txID) { + // If the transaction is in the mempool, remove it. + if _, ok := m.consumedUTXOs.DeleteKey(txID); ok { + m.unissuedTxs.Delete(txID) + m.bytesAvailable += len(tx.Bytes()) continue } - m.bytesAvailable += len(tx.Bytes()) - m.bytesAvailableMetric.Set(float64(m.bytesAvailable)) - - m.numTxs.Dec() - + // If the transaction isn't in the mempool, remove any conflicts it has. inputs := tx.Unsigned.InputIDs() - m.consumedUTXOs.Difference(inputs) + for _, removed := range m.consumedUTXOs.DeleteOverlapping(inputs) { + tx, _ := m.unissuedTxs.Get(removed.Key) + m.unissuedTxs.Delete(removed.Key) + m.bytesAvailable += len(tx.Bytes()) + } } + m.bytesAvailableMetric.Set(float64(m.bytesAvailable)) + m.numTxs.Set(float64(m.unissuedTxs.Len())) +} + +func (m *mempool) Peek() (*txs.Tx, bool) { + _, tx, exists := m.unissuedTxs.Oldest() + return tx, exists } -func (m *mempool) Peek(maxTxSize int) *txs.Tx { - txIter := m.unissuedTxs.NewIterator() - for txIter.Next() { - tx := txIter.Value() - txSize := len(tx.Bytes()) - if txSize <= maxTxSize { - return tx +func (m *mempool) Iterate(f func(*txs.Tx) bool) { + m.lock.RLock() + defer m.lock.RUnlock() + + it := m.unissuedTxs.NewIterator() + for it.Next() { + if !f(it.Value()) { + return } } - return nil } func (m *mempool) RequestBuildBlock() { @@ -206,6 +218,17 @@ func (m *mempool) RequestBuildBlock() { } func (m *mempool) MarkDropped(txID ids.ID, reason error) { + if errors.Is(reason, ErrMempoolFull) { + return + } + + m.lock.RLock() + defer m.lock.RUnlock() + + if _, ok := m.unissuedTxs.Get(txID); ok { + return + } + m.droppedTxIDs.Put(txID, reason) } @@ -213,3 +236,10 @@ func (m *mempool) GetDropReason(txID ids.ID) error { err, _ := m.droppedTxIDs.Get(txID) return err } + +func (m *mempool) Len() int { + m.lock.RLock() + defer m.lock.RUnlock() + + return m.unissuedTxs.Len() +} diff --git a/vms/avm/txs/mempool/mempool_test.go b/vms/avm/txs/mempool/mempool_test.go index 4e1396ac3d41..3a1a82484267 100644 --- a/vms/avm/txs/mempool/mempool_test.go +++ b/vms/avm/txs/mempool/mempool_test.go @@ -1,9 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package mempool import ( + "errors" "testing" "github.com/prometheus/client_golang/prometheus" @@ -13,160 +14,306 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/vms/avm/txs" "github.com/ava-labs/avalanchego/vms/components/avax" - "github.com/ava-labs/avalanchego/vms/components/verify" - "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) -var ( - keys = secp256k1.TestKeys() - chainID = ids.ID{5, 4, 3, 2, 1} - assetID = ids.ID{1, 2, 3} -) +func TestAdd(t *testing.T) { + tx0 := newTx(0, 32) + + tests := []struct { + name string + initialTxs []*txs.Tx + tx *txs.Tx + err error + dropReason error + }{ + { + name: "successfully add tx", + initialTxs: nil, + tx: tx0, + err: nil, + dropReason: nil, + }, + { + name: "attempt adding duplicate tx", + initialTxs: []*txs.Tx{tx0}, + tx: tx0, + err: ErrDuplicateTx, + dropReason: nil, + }, + { + name: "attempt adding too large tx", + initialTxs: nil, + tx: newTx(0, MaxTxSize+1), + err: ErrTxTooLarge, + dropReason: ErrTxTooLarge, + }, + { + name: "attempt adding tx when full", + initialTxs: newTxs(maxMempoolSize/MaxTxSize, MaxTxSize), + tx: newTx(maxMempoolSize/MaxTxSize, MaxTxSize), + err: ErrMempoolFull, + dropReason: nil, + }, + { + name: "attempt adding conflicting tx", + initialTxs: []*txs.Tx{tx0}, + tx: newTx(0, 32), + err: ErrConflictsWithOtherTx, + dropReason: ErrConflictsWithOtherTx, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + mempool, err := New( + "mempool", + prometheus.NewRegistry(), + nil, + ) + require.NoError(err) + + for _, tx := range test.initialTxs { + require.NoError(mempool.Add(tx)) + } -// shows that valid tx is not added to mempool if this would exceed its maximum -// size -func TestBlockBuilderMaxMempoolSizeHandling(t *testing.T) { + err = mempool.Add(test.tx) + require.ErrorIs(err, test.err) + + txID := test.tx.ID() + + if err != nil { + mempool.MarkDropped(txID, err) + } + + err = mempool.GetDropReason(txID) + require.ErrorIs(err, test.dropReason) + }) + } +} + +func TestGet(t *testing.T) { require := require.New(t) - registerer := prometheus.NewRegistry() - mempoolIntf, err := New("mempool", registerer, nil) + mempool, err := New( + "mempool", + prometheus.NewRegistry(), + nil, + ) require.NoError(err) - mempool := mempoolIntf.(*mempool) + tx := newTx(0, 32) + txID := tx.ID() - testTxs := createTestTxs(2) - tx := testTxs[0] + _, exists := mempool.Get(txID) + require.False(exists) - // shortcut to simulated almost filled mempool - mempool.bytesAvailable = len(tx.Bytes()) - 1 + require.NoError(mempool.Add(tx)) - err = mempool.Add(tx) - require.ErrorIs(err, errMempoolFull) + returned, exists := mempool.Get(txID) + require.True(exists) + require.Equal(tx, returned) - // shortcut to simulated almost filled mempool - mempool.bytesAvailable = len(tx.Bytes()) + mempool.Remove(tx) - require.NoError(mempool.Add(tx)) + _, exists = mempool.Get(txID) + require.False(exists) } -func TestTxsInMempool(t *testing.T) { +func TestPeek(t *testing.T) { require := require.New(t) - registerer := prometheus.NewRegistry() - toEngine := make(chan common.Message, 100) - mempool, err := New("mempool", registerer, toEngine) + mempool, err := New( + "mempool", + prometheus.NewRegistry(), + nil, + ) require.NoError(err) - testTxs := createTestTxs(2) + _, exists := mempool.Peek() + require.False(exists) - mempool.RequestBuildBlock() - select { - case <-toEngine: - require.FailNow("should not have sent message to engine") - default: - } + tx0 := newTx(0, 32) + tx1 := newTx(1, 32) - for _, tx := range testTxs { - txID := tx.ID() - // tx not already there - require.False(mempool.Has(txID)) + require.NoError(mempool.Add(tx0)) + require.NoError(mempool.Add(tx1)) - // we can insert - require.NoError(mempool.Add(tx)) + tx, exists := mempool.Peek() + require.True(exists) + require.Equal(tx, tx0) - // we can get it - require.True(mempool.Has(txID)) + mempool.Remove(tx0) - retrieved := mempool.Get(txID) - require.NotNil(retrieved) - require.Equal(tx, retrieved) + tx, exists = mempool.Peek() + require.True(exists) + require.Equal(tx, tx1) - // tx exists in mempool - require.True(mempool.Has(txID)) + mempool.Remove(tx0) - // once removed it cannot be there - mempool.Remove([]*txs.Tx{tx}) + tx, exists = mempool.Peek() + require.True(exists) + require.Equal(tx, tx1) - require.False(mempool.Has(txID)) - require.Nil(mempool.Get(txID)) + mempool.Remove(tx1) - // we can reinsert it again to grow the mempool - require.NoError(mempool.Add(tx)) + _, exists = mempool.Peek() + require.False(exists) +} + +func TestRemoveConflict(t *testing.T) { + require := require.New(t) + + mempool, err := New( + "mempool", + prometheus.NewRegistry(), + nil, + ) + require.NoError(err) + + tx := newTx(0, 32) + txConflict := newTx(0, 32) + + require.NoError(mempool.Add(tx)) + + returnedTx, exists := mempool.Peek() + require.True(exists) + require.Equal(returnedTx, tx) + + mempool.Remove(txConflict) + + _, exists = mempool.Peek() + require.False(exists) +} + +func TestIterate(t *testing.T) { + require := require.New(t) + + mempool, err := New( + "mempool", + prometheus.NewRegistry(), + nil, + ) + require.NoError(err) + + var ( + iteratedTxs []*txs.Tx + maxLen = 2 + ) + addTxs := func(tx *txs.Tx) bool { + iteratedTxs = append(iteratedTxs, tx) + return len(iteratedTxs) < maxLen } + mempool.Iterate(addTxs) + require.Empty(iteratedTxs) + + tx0 := newTx(0, 32) + require.NoError(mempool.Add(tx0)) + + mempool.Iterate(addTxs) + require.Equal([]*txs.Tx{tx0}, iteratedTxs) + + tx1 := newTx(1, 32) + require.NoError(mempool.Add(tx1)) + + iteratedTxs = nil + mempool.Iterate(addTxs) + require.Equal([]*txs.Tx{tx0, tx1}, iteratedTxs) + + tx2 := newTx(2, 32) + require.NoError(mempool.Add(tx2)) + + iteratedTxs = nil + mempool.Iterate(addTxs) + require.Equal([]*txs.Tx{tx0, tx1}, iteratedTxs) + + mempool.Remove(tx0, tx2) + + iteratedTxs = nil + mempool.Iterate(addTxs) + require.Equal([]*txs.Tx{tx1}, iteratedTxs) +} + +func TestRequestBuildBlock(t *testing.T) { + require := require.New(t) + + toEngine := make(chan common.Message, 1) + mempool, err := New( + "mempool", + prometheus.NewRegistry(), + toEngine, + ) + require.NoError(err) mempool.RequestBuildBlock() select { case <-toEngine: + require.FailNow("should not have sent message to engine") default: - require.FailNow("should have sent message to engine") } - mempool.Remove(testTxs) + tx := newTx(0, 32) + require.NoError(mempool.Add(tx)) mempool.RequestBuildBlock() + mempool.RequestBuildBlock() // Must not deadlock select { case <-toEngine: - require.FailNow("should not have sent message to engine") + default: + require.FailNow("should have sent message to engine") + } + select { + case <-toEngine: + require.FailNow("should have only sent one message to engine") default: } } -func createTestTxs(count int) []*txs.Tx { - testTxs := make([]*txs.Tx, 0, count) - addr := keys[0].PublicKey().Address() - for i := uint32(0); i < uint32(count); i++ { - tx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - NetworkID: constants.UnitTestID, - BlockchainID: chainID, - Ins: []*avax.TransferableInput{{ - UTXOID: avax.UTXOID{ - TxID: ids.ID{'t', 'x', 'I', 'D'}, - OutputIndex: i, - }, - Asset: avax.Asset{ID: assetID}, - In: &secp256k1fx.TransferInput{ - Amt: 54321, - Input: secp256k1fx.Input{ - SigIndices: []uint32{i}, - }, - }, - }}, - Outs: []*avax.TransferableOutput{{ - Asset: avax.Asset{ID: assetID}, - Out: &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{addr}, - }, - }, - }}, - }}, - Name: "NormalName", - Symbol: "TICK", - Denomination: byte(2), - States: []*txs.InitialState{ - { - FxIndex: 0, - Outs: []verify.State{ - &secp256k1fx.TransferOutput{ - Amt: 12345, - OutputOwners: secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{addr}, - }, - }, - }, - }, - }, - }} - tx.SetBytes(utils.RandomBytes(16), utils.RandomBytes(16)) - testTxs = append(testTxs, tx) +func TestDropped(t *testing.T) { + require := require.New(t) + + mempool, err := New( + "mempool", + prometheus.NewRegistry(), + nil, + ) + require.NoError(err) + + tx := newTx(0, 32) + txID := tx.ID() + testErr := errors.New("test") + + mempool.MarkDropped(txID, testErr) + + err = mempool.GetDropReason(txID) + require.ErrorIs(err, testErr) + + require.NoError(mempool.Add(tx)) + require.NoError(mempool.GetDropReason(txID)) + + mempool.MarkDropped(txID, testErr) + require.NoError(mempool.GetDropReason(txID)) +} + +func newTxs(num int, size int) []*txs.Tx { + txs := make([]*txs.Tx, num) + for i := range txs { + txs[i] = newTx(uint32(i), size) } - return testTxs + return txs +} + +func newTx(index uint32, size int) *txs.Tx { + tx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ + Ins: []*avax.TransferableInput{{ + UTXOID: avax.UTXOID{ + TxID: ids.ID{'t', 'x', 'I', 'D'}, + OutputIndex: index, + }, + }}, + }}} + tx.SetBytes(utils.RandomBytes(size), utils.RandomBytes(size)) + return tx } diff --git a/vms/avm/txs/mempool/mock_mempool.go b/vms/avm/txs/mempool/mock_mempool.go index e84f01e0875e..69860c38d5d3 100644 --- a/vms/avm/txs/mempool/mock_mempool.go +++ b/vms/avm/txs/mempool/mock_mempool.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/avm/txs/mempool (interfaces: Mempool) +// +// Generated by this command: +// +// mockgen -package=mempool -destination=vms/avm/txs/mempool/mock_mempool.go github.com/ava-labs/avalanchego/vms/avm/txs/mempool Mempool +// // Package mempool is a generated GoMock package. package mempool @@ -47,21 +49,22 @@ func (m *MockMempool) Add(arg0 *txs.Tx) error { } // Add indicates an expected call of Add. -func (mr *MockMempoolMockRecorder) Add(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) Add(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockMempool)(nil).Add), arg0) } // Get mocks base method. -func (m *MockMempool) Get(arg0 ids.ID) *txs.Tx { +func (m *MockMempool) Get(arg0 ids.ID) (*txs.Tx, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", arg0) ret0, _ := ret[0].(*txs.Tx) - return ret0 + ret1, _ := ret[1].(bool) + return ret0, ret1 } // Get indicates an expected call of Get. -func (mr *MockMempoolMockRecorder) Get(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) Get(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMempool)(nil).Get), arg0) } @@ -75,23 +78,35 @@ func (m *MockMempool) GetDropReason(arg0 ids.ID) error { } // GetDropReason indicates an expected call of GetDropReason. -func (mr *MockMempoolMockRecorder) GetDropReason(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) GetDropReason(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDropReason", reflect.TypeOf((*MockMempool)(nil).GetDropReason), arg0) } -// Has mocks base method. -func (m *MockMempool) Has(arg0 ids.ID) bool { +// Iterate mocks base method. +func (m *MockMempool) Iterate(arg0 func(*txs.Tx) bool) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Has", arg0) - ret0, _ := ret[0].(bool) + m.ctrl.Call(m, "Iterate", arg0) +} + +// Iterate indicates an expected call of Iterate. +func (mr *MockMempoolMockRecorder) Iterate(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockMempool)(nil).Iterate), arg0) +} + +// Len mocks base method. +func (m *MockMempool) Len() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Len") + ret0, _ := ret[0].(int) return ret0 } -// Has indicates an expected call of Has. -func (mr *MockMempoolMockRecorder) Has(arg0 interface{}) *gomock.Call { +// Len indicates an expected call of Len. +func (mr *MockMempoolMockRecorder) Len() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockMempool)(nil).Has), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockMempool)(nil).Len)) } // MarkDropped mocks base method. @@ -101,35 +116,40 @@ func (m *MockMempool) MarkDropped(arg0 ids.ID, arg1 error) { } // MarkDropped indicates an expected call of MarkDropped. -func (mr *MockMempoolMockRecorder) MarkDropped(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) MarkDropped(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkDropped", reflect.TypeOf((*MockMempool)(nil).MarkDropped), arg0, arg1) } // Peek mocks base method. -func (m *MockMempool) Peek(arg0 int) *txs.Tx { +func (m *MockMempool) Peek() (*txs.Tx, bool) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Peek", arg0) + ret := m.ctrl.Call(m, "Peek") ret0, _ := ret[0].(*txs.Tx) - return ret0 + ret1, _ := ret[1].(bool) + return ret0, ret1 } // Peek indicates an expected call of Peek. -func (mr *MockMempoolMockRecorder) Peek(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) Peek() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peek", reflect.TypeOf((*MockMempool)(nil).Peek), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peek", reflect.TypeOf((*MockMempool)(nil).Peek)) } // Remove mocks base method. -func (m *MockMempool) Remove(arg0 []*txs.Tx) { +func (m *MockMempool) Remove(arg0 ...*txs.Tx) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Remove", arg0) + varargs := []any{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Remove", varargs...) } // Remove indicates an expected call of Remove. -func (mr *MockMempoolMockRecorder) Remove(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) Remove(arg0 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockMempool)(nil).Remove), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockMempool)(nil).Remove), arg0...) } // RequestBuildBlock mocks base method. diff --git a/vms/avm/txs/mock_unsigned_tx.go b/vms/avm/txs/mock_unsigned_tx.go index c6504c7855a0..25bc9d501a16 100644 --- a/vms/avm/txs/mock_unsigned_tx.go +++ b/vms/avm/txs/mock_unsigned_tx.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/vms/avm/txs (interfaces: UnsignedTx) +// Source: vms/avm/txs/tx.go +// +// Generated by this command: +// +// mockgen -source=vms/avm/txs/tx.go -destination=vms/avm/txs/mock_unsigned_tx.go -package=txs -exclude_interfaces= +// // Package txs is a generated GoMock package. package txs @@ -55,15 +57,15 @@ func (mr *MockUnsignedTxMockRecorder) Bytes() *gomock.Call { } // InitCtx mocks base method. -func (m *MockUnsignedTx) InitCtx(arg0 *snow.Context) { +func (m *MockUnsignedTx) InitCtx(ctx *snow.Context) { m.ctrl.T.Helper() - m.ctrl.Call(m, "InitCtx", arg0) + m.ctrl.Call(m, "InitCtx", ctx) } // InitCtx indicates an expected call of InitCtx. -func (mr *MockUnsignedTxMockRecorder) InitCtx(arg0 interface{}) *gomock.Call { +func (mr *MockUnsignedTxMockRecorder) InitCtx(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*MockUnsignedTx)(nil).InitCtx), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*MockUnsignedTx)(nil).InitCtx), ctx) } // InputIDs mocks base method. @@ -109,27 +111,27 @@ func (mr *MockUnsignedTxMockRecorder) NumCredentials() *gomock.Call { } // SetBytes mocks base method. -func (m *MockUnsignedTx) SetBytes(arg0 []byte) { +func (m *MockUnsignedTx) SetBytes(unsignedBytes []byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetBytes", arg0) + m.ctrl.Call(m, "SetBytes", unsignedBytes) } // SetBytes indicates an expected call of SetBytes. -func (mr *MockUnsignedTxMockRecorder) SetBytes(arg0 interface{}) *gomock.Call { +func (mr *MockUnsignedTxMockRecorder) SetBytes(unsignedBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBytes", reflect.TypeOf((*MockUnsignedTx)(nil).SetBytes), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBytes", reflect.TypeOf((*MockUnsignedTx)(nil).SetBytes), unsignedBytes) } // Visit mocks base method. -func (m *MockUnsignedTx) Visit(arg0 Visitor) error { +func (m *MockUnsignedTx) Visit(visitor Visitor) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Visit", arg0) + ret := m.ctrl.Call(m, "Visit", visitor) ret0, _ := ret[0].(error) return ret0 } // Visit indicates an expected call of Visit. -func (mr *MockUnsignedTxMockRecorder) Visit(arg0 interface{}) *gomock.Call { +func (mr *MockUnsignedTxMockRecorder) Visit(visitor any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Visit", reflect.TypeOf((*MockUnsignedTx)(nil).Visit), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Visit", reflect.TypeOf((*MockUnsignedTx)(nil).Visit), visitor) } diff --git a/vms/avm/txs/operation.go b/vms/avm/txs/operation.go index 4b4cb27aa46b..d37b162955c6 100644 --- a/vms/avm/txs/operation.go +++ b/vms/avm/txs/operation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -48,16 +48,16 @@ type operationAndCodec struct { codec codec.Manager } -func (o *operationAndCodec) Less(other *operationAndCodec) bool { +func (o *operationAndCodec) Compare(other *operationAndCodec) int { oBytes, err := o.codec.Marshal(CodecVersion, o.op) if err != nil { - return false + return 0 } otherBytes, err := o.codec.Marshal(CodecVersion, other.op) if err != nil { - return false + return 0 } - return bytes.Compare(oBytes, otherBytes) == -1 + return bytes.Compare(oBytes, otherBytes) } func SortOperations(ops []*Operation, c codec.Manager) { diff --git a/vms/avm/txs/operation_test.go b/vms/avm/txs/operation_test.go index f0dc3ec3c742..3ca4676eb370 100644 --- a/vms/avm/txs/operation_test.go +++ b/vms/avm/txs/operation_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -79,7 +80,7 @@ func TestOperationVerify(t *testing.T) { func TestOperationSorting(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) require.NoError(c.RegisterType(&testOperable{})) m := codec.NewDefaultManager() diff --git a/vms/avm/txs/operation_tx.go b/vms/avm/txs/operation_tx.go index df143af30b36..8a1b261ca2dd 100644 --- a/vms/avm/txs/operation_tx.go +++ b/vms/avm/txs/operation_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/avm/txs/parser.go b/vms/avm/txs/parser.go index def42dfed501..979c71d8a7c8 100644 --- a/vms/avm/txs/parser.go +++ b/vms/avm/txs/parser.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -7,10 +7,10 @@ import ( "fmt" "math" "reflect" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" - "github.com/ava-labs/avalanchego/codec/reflectcodec" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/timer/mockable" @@ -31,9 +31,6 @@ type Parser interface { ParseTx(bytes []byte) (*Tx, error) ParseGenesisTx(bytes []byte) (*Tx, error) - - InitializeTx(tx *Tx) error - InitializeGenesisTx(tx *Tx) error } type parser struct { @@ -43,8 +40,9 @@ type parser struct { gc linearcodec.Codec } -func NewParser(fxs []fxs.Fx) (Parser, error) { +func NewParser(durangoTime time.Time, fxs []fxs.Fx) (Parser, error) { return NewCustomParser( + durangoTime, make(map[reflect.Type]int), &mockable.Clock{}, logging.NoLog{}, @@ -53,13 +51,14 @@ func NewParser(fxs []fxs.Fx) (Parser, error) { } func NewCustomParser( + durangoTime time.Time, typeToFxIndex map[reflect.Type]int, clock *mockable.Clock, log logging.Logger, fxs []fxs.Fx, ) (Parser, error) { - gc := linearcodec.New([]string{reflectcodec.DefaultTagName}, 1<<20) - c := linearcodec.NewDefault() + gc := linearcodec.NewDefault(time.Time{}) + c := linearcodec.NewDefault(durangoTime) gcm := codec.NewManager(math.MaxInt32) cm := codec.NewDefaultManager() @@ -130,14 +129,6 @@ func (p *parser) ParseGenesisTx(bytes []byte) (*Tx, error) { return parse(p.gcm, bytes) } -func (p *parser) InitializeTx(tx *Tx) error { - return initializeTx(p.cm, tx) -} - -func (p *parser) InitializeGenesisTx(tx *Tx) error { - return initializeTx(p.gcm, tx) -} - func parse(cm codec.Manager, signedBytes []byte) (*Tx, error) { tx := &Tx{} parsedVersion, err := cm.Unmarshal(signedBytes, tx) @@ -157,19 +148,3 @@ func parse(cm codec.Manager, signedBytes []byte) (*Tx, error) { tx.SetBytes(unsignedBytes, signedBytes) return tx, nil } - -func initializeTx(cm codec.Manager, tx *Tx) error { - signedBytes, err := cm.Marshal(CodecVersion, tx) - if err != nil { - return fmt.Errorf("problem creating transaction: %w", err) - } - - unsignedBytesLen, err := cm.Size(CodecVersion, &tx.Unsigned) - if err != nil { - return fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err) - } - - unsignedBytes := signedBytes[:unsignedBytesLen] - tx.SetBytes(unsignedBytes, signedBytes) - return nil -} diff --git a/vms/avm/txs/tx.go b/vms/avm/txs/tx.go index 6025828a5dcb..42e845b07b15 100644 --- a/vms/avm/txs/tx.go +++ b/vms/avm/txs/tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/hashing" @@ -19,6 +20,8 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) +var _ gossip.Gossipable = (*Tx)(nil) + type UnsignedTx interface { snow.ContextInitializable @@ -75,6 +78,11 @@ func (t *Tx) ID() ids.ID { return t.TxID } +// GossipID returns the unique ID that this tx should use for mempool gossip +func (t *Tx) GossipID() ids.ID { + return t.TxID +} + // Bytes returns the binary representation of this tx func (t *Tx) Bytes() []byte { return t.bytes diff --git a/vms/avm/txs/visitor.go b/vms/avm/txs/visitor.go index 8de00c1bf35c..31eccb67834d 100644 --- a/vms/avm/txs/visitor.go +++ b/vms/avm/txs/visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/avm/utxo/spender.go b/vms/avm/utxo/spender.go index ed57549da306..02ade0d92eae 100644 --- a/vms/avm/utxo/spender.go +++ b/vms/avm/utxo/spender.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utxo diff --git a/vms/avm/vm.go b/vms/avm/vm.go index 36049befd07c..2b3577082813 100644 --- a/vms/avm/vm.go +++ b/vms/avm/vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -9,8 +9,7 @@ import ( "fmt" "net/http" "reflect" - - stdjson "encoding/json" + "sync" "github.com/gorilla/rpc/v2" @@ -59,7 +58,6 @@ var ( errIncompatibleFx = errors.New("incompatible feature extension") errUnknownFx = errors.New("unknown feature extension") errGenesisAssetMustHaveState = errors.New("genesis asset must have non-empty state") - errBootstrapping = errors.New("chain is currently bootstrapping") _ vertex.LinearizableVMWithEngine = (*VM)(nil) ) @@ -84,6 +82,8 @@ type VM struct { registerer prometheus.Registerer + connectedPeers map[ids.NodeID]*version.Application + parser block.Parser pubsub *pubsub.Server @@ -114,18 +114,37 @@ type VM struct { txBackend *txexecutor.Backend + // Cancelled on shutdown + onShutdownCtx context.Context + // Call [onShutdownCtxCancel] to cancel [onShutdownCtx] during Shutdown() + onShutdownCtxCancel context.CancelFunc + awaitShutdown sync.WaitGroup + + networkConfig network.Config // These values are only initialized after the chain has been linearized. blockbuilder.Builder chainManager blockexecutor.Manager - network network.Network + network *network.Network } -func (*VM) Connected(context.Context, ids.NodeID, *version.Application) error { - return nil +func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error { + // If the chain isn't linearized yet, we must track the peers externally + // until the network is initialized. + if vm.network == nil { + vm.connectedPeers[nodeID] = version + return nil + } + return vm.network.Connected(ctx, nodeID, version) } -func (*VM) Disconnected(context.Context, ids.NodeID) error { - return nil +func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error { + // If the chain isn't linearized yet, we must track the peers externally + // until the network is initialized. + if vm.network == nil { + delete(vm.connectedPeers, nodeID) + return nil + } + return vm.network.Disconnected(ctx, nodeID) } /* @@ -134,12 +153,6 @@ func (*VM) Disconnected(context.Context, ids.NodeID) error { ****************************************************************************** */ -type Config struct { - IndexTransactions bool `json:"index-transactions"` - IndexAllowIncomplete bool `json:"index-allow-incomplete"` - ChecksumsEnabled bool `json:"checksums-enabled"` -} - func (vm *VM) Initialize( _ context.Context, ctx *snow.Context, @@ -154,15 +167,13 @@ func (vm *VM) Initialize( noopMessageHandler := common.NewNoOpAppHandler(ctx.Log) vm.Atomic = network.NewAtomic(noopMessageHandler) - avmConfig := Config{} - if len(configBytes) > 0 { - if err := stdjson.Unmarshal(configBytes, &avmConfig); err != nil { - return err - } - ctx.Log.Info("VM config initialized", - zap.Reflect("config", avmConfig), - ) + avmConfig, err := ParseConfig(configBytes) + if err != nil { + return err } + ctx.Log.Info("VM config initialized", + zap.Reflect("config", avmConfig), + ) registerer := prometheus.NewRegistry() if err := ctx.Metrics.Register(registerer); err != nil { @@ -170,8 +181,9 @@ func (vm *VM) Initialize( } vm.registerer = registerer + vm.connectedPeers = make(map[ids.NodeID]*version.Application) + // Initialize metrics as soon as possible - var err error vm.metrics, err = metrics.New("", registerer) if err != nil { return fmt.Errorf("failed to initialize metrics: %w", err) @@ -207,6 +219,7 @@ func (vm *VM) Initialize( vm.typeToFxIndex = map[reflect.Type]int{} vm.parser, err = block.NewCustomParser( + vm.DurangoTime, vm.typeToFxIndex, &vm.clock, ctx.Log, @@ -264,6 +277,8 @@ func (vm *VM) Initialize( Bootstrapped: false, } + vm.onShutdownCtx, vm.onShutdownCtxCancel = context.WithCancel(context.Background()) + vm.networkConfig = avmConfig.Network return vm.state.Commit() } @@ -306,6 +321,9 @@ func (vm *VM) Shutdown(context.Context) error { return nil } + vm.onShutdownCtxCancel() + vm.awaitShutdown.Wait() + return utils.Err( vm.state.Close(), vm.baseDB.Close(), @@ -344,17 +362,6 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { }, err } -func (*VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, error) { - server := rpc.NewServer() - codec := json.NewCodec() - server.RegisterCodec(codec, "application/json") - server.RegisterCodec(codec, "application/json;charset=UTF-8") - staticService := CreateStaticService() - return map[string]http.Handler{ - "": server, - }, server.RegisterService(staticService, "avm") -} - /* ****************************************************************************** ********************************** Chain VM ********************************** @@ -396,7 +403,7 @@ func (*VM) VerifyHeightIndex(context.Context) error { ****************************************************************************** */ -func (vm *VM) Linearize(_ context.Context, stopVertexID ids.ID, toEngine chan<- common.Message) error { +func (vm *VM) Linearize(ctx context.Context, stopVertexID ids.ID, toEngine chan<- common.Message) error { time := version.GetCortinaTime(vm.ctx.NetworkID) err := vm.state.InitializeChainState(stopVertexID, time) if err != nil { @@ -424,19 +431,44 @@ func (vm *VM) Linearize(_ context.Context, stopVertexID ids.ID, toEngine chan<- mempool, ) - vm.network = network.New( + // Invariant: The context lock is not held when calling network.IssueTx. + vm.network, err = network.New( vm.ctx, vm.parser, - vm.chainManager, + network.NewLockedTxVerifier( + &vm.ctx.Lock, + vm.chainManager, + ), mempool, vm.appSender, + vm.registerer, + vm.networkConfig, ) + if err != nil { + return fmt.Errorf("failed to initialize network: %w", err) + } + + // Notify the network of our current peers + for nodeID, version := range vm.connectedPeers { + if err := vm.network.Connected(ctx, nodeID, version); err != nil { + return err + } + } + vm.connectedPeers = nil // Note: It's important only to switch the networking stack after the full // chainVM has been initialized. Traffic will immediately start being // handled asynchronously. vm.Atomic.Set(vm.network) + vm.awaitShutdown.Add(1) + go func() { + defer vm.awaitShutdown.Done() + + // Invariant: Gossip must never grab the context lock. + vm.network.Gossip(vm.onShutdownCtx) + }() + go func() { err := vm.state.Prune(&vm.ctx.Lock, vm.ctx.Log) if err != nil { @@ -477,32 +509,21 @@ func (vm *VM) ParseTx(_ context.Context, bytes []byte) (snowstorm.Tx, error) { ****************************************************************************** */ -// IssueTx attempts to send a transaction to consensus. -// If onDecide is specified, the function will be called when the transaction is -// either accepted or rejected with the appropriate status. This function will -// go out of scope when the transaction is removed from memory. -func (vm *VM) IssueTx(b []byte) (ids.ID, error) { - if !vm.bootstrapped || vm.Builder == nil { - return ids.ID{}, errBootstrapping - } - - tx, err := vm.parser.ParseTx(b) - if err != nil { - vm.ctx.Log.Debug("failed to parse tx", - zap.Error(err), - ) - return ids.ID{}, err - } - - err = vm.network.IssueTx(context.TODO(), tx) - if err != nil { +// issueTx attempts to send a transaction to consensus. +// +// Invariant: The context lock is not held +// Invariant: This function is only called after Linearize has been called. +func (vm *VM) issueTx(tx *txs.Tx) (ids.ID, error) { + txID := tx.ID() + err := vm.network.IssueTx(context.TODO(), tx) + if err != nil && !errors.Is(err, mempool.ErrDuplicateTx) { vm.ctx.Log.Debug("failed to add tx to mempool", + zap.Stringer("txID", txID), zap.Error(err), ) - return ids.ID{}, err + return txID, err } - - return tx.ID(), nil + return txID, nil } /* @@ -534,7 +555,7 @@ func (vm *VM) initGenesis(genesisBytes []byte) error { tx := &txs.Tx{ Unsigned: &genesisTx.CreateAssetTx, } - if err := vm.parser.InitializeGenesisTx(tx); err != nil { + if err := tx.Initialize(genesisCodec); err != nil { return err } diff --git a/vms/avm/vm_benchmark_test.go b/vms/avm/vm_benchmark_test.go index ba6ca3cef957..713f809f7f5c 100644 --- a/vms/avm/vm_benchmark_test.go +++ b/vms/avm/vm_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -82,7 +82,7 @@ func GetAllUTXOsBenchmark(b *testing.B, utxoCount int) { TxID: ids.GenerateTestID(), OutputIndex: rand.Uint32(), }, - Asset: avax.Asset{ID: ids.ID{'y', 'e', 'e', 't'}}, + Asset: avax.Asset{ID: env.vm.ctx.AVAXAssetID}, Out: &secp256k1fx.TransferOutput{ Amt: 100000, OutputOwners: secp256k1fx.OutputOwners{ diff --git a/vms/avm/vm_regression_test.go b/vms/avm/vm_regression_test.go index 6c1dd1be1798..c6ac40df845d 100644 --- a/vms/avm/vm_regression_test.go +++ b/vms/avm/vm_regression_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -26,7 +26,9 @@ func TestVerifyFxUsage(t *testing.T) { env := setup(t, &envConfig{ vmStaticConfig: &config.Config{}, }) + env.vm.ctx.Lock.Unlock() defer func() { + env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() @@ -34,7 +36,7 @@ func TestVerifyFxUsage(t *testing.T) { createAssetTx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, }}, Name: "Team Rocket", Symbol: "TR", @@ -66,13 +68,13 @@ func TestVerifyFxUsage(t *testing.T) { }, }, }} - require.NoError(env.vm.parser.InitializeTx(createAssetTx)) + require.NoError(createAssetTx.Initialize(env.vm.parser.Codec())) issueAndAccept(require, env.vm, env.issuer, createAssetTx) mintNFTTx := &txs.Tx{Unsigned: &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, }}, Ops: []*txs.Operation{{ Asset: avax.Asset{ID: createAssetTx.ID()}, @@ -95,7 +97,7 @@ func TestVerifyFxUsage(t *testing.T) { spendTx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, Ins: []*avax.TransferableInput{{ UTXOID: avax.UTXOID{ TxID: createAssetTx.ID(), diff --git a/vms/avm/vm_test.go b/vms/avm/vm_test.go index 3b4dc9558807..d8aeaf3b8743 100644 --- a/vms/avm/vm_test.go +++ b/vms/avm/vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -16,6 +16,7 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/vms/avm/config" @@ -32,12 +33,9 @@ func TestInvalidGenesis(t *testing.T) { require := require.New(t) vm := &VM{} - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) ctx.Lock.Lock() - defer func() { - require.NoError(vm.Shutdown(context.Background())) - ctx.Lock.Unlock() - }() + defer ctx.Lock.Unlock() err := vm.Initialize( context.Background(), @@ -57,7 +55,7 @@ func TestInvalidFx(t *testing.T) { require := require.New(t) vm := &VM{} - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -85,7 +83,7 @@ func TestFxInitializationFailure(t *testing.T) { require := require.New(t) vm := &VM{} - ctx := newContext(t) + ctx := snowtest.Context(t, snowtest.XChainID) ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -118,12 +116,14 @@ func TestIssueTx(t *testing.T) { require := require.New(t) env := setup(t, &envConfig{}) + env.vm.ctx.Lock.Unlock() defer func() { + env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() - tx := newTx(t, env.genesisBytes, env.vm, "AVAX") + tx := newTx(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.parser, "AVAX") issueAndAccept(require, env.vm, env.issuer, tx) } @@ -134,7 +134,9 @@ func TestIssueNFT(t *testing.T) { env := setup(t, &envConfig{ vmStaticConfig: &config.Config{}, }) + env.vm.ctx.Lock.Unlock() defer func() { + env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() @@ -142,7 +144,7 @@ func TestIssueNFT(t *testing.T) { createAssetTx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, }}, Name: "Team Rocket", Symbol: "TR", @@ -167,13 +169,13 @@ func TestIssueNFT(t *testing.T) { }, }}, }} - require.NoError(env.vm.parser.InitializeTx(createAssetTx)) + require.NoError(createAssetTx.Initialize(env.vm.parser.Codec())) issueAndAccept(require, env.vm, env.issuer, createAssetTx) mintNFTTx := &txs.Tx{Unsigned: &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, }}, Ops: []*txs.Operation{{ Asset: avax.Asset{ID: createAssetTx.ID()}, @@ -198,7 +200,7 @@ func TestIssueNFT(t *testing.T) { Unsigned: &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, }}, Ops: []*txs.Operation{{ Asset: avax.Asset{ID: createAssetTx.ID()}, @@ -222,7 +224,7 @@ func TestIssueNFT(t *testing.T) { }, }, } - require.NoError(env.vm.parser.InitializeTx(transferNFTTx)) + require.NoError(transferNFTTx.Initialize(env.vm.parser.Codec())) issueAndAccept(require, env.vm, env.issuer, transferNFTTx) } @@ -237,7 +239,9 @@ func TestIssueProperty(t *testing.T) { Fx: &propertyfx.Fx{}, }}, }) + env.vm.ctx.Lock.Unlock() defer func() { + env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() @@ -245,7 +249,7 @@ func TestIssueProperty(t *testing.T) { createAssetTx := &txs.Tx{Unsigned: &txs.CreateAssetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, }}, Name: "Team Rocket", Symbol: "TR", @@ -262,13 +266,13 @@ func TestIssueProperty(t *testing.T) { }, }}, }} - require.NoError(env.vm.parser.InitializeTx(createAssetTx)) + require.NoError(createAssetTx.Initialize(env.vm.parser.Codec())) issueAndAccept(require, env.vm, env.issuer, createAssetTx) mintPropertyTx := &txs.Tx{Unsigned: &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, }}, Ops: []*txs.Operation{{ Asset: avax.Asset{ID: createAssetTx.ID()}, @@ -300,7 +304,7 @@ func TestIssueProperty(t *testing.T) { burnPropertyTx := &txs.Tx{Unsigned: &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, }}, Ops: []*txs.Operation{{ Asset: avax.Asset{ID: createAssetTx.ID()}, @@ -324,13 +328,15 @@ func TestIssueTxWithFeeAsset(t *testing.T) { env := setup(t, &envConfig{ isCustomFeeAsset: true, }) + env.vm.ctx.Lock.Unlock() defer func() { + env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() // send first asset - tx := newTx(t, env.genesisBytes, env.vm, feeAssetName) + tx := newTx(t, env.genesisBytes, env.vm.ctx.ChainID, env.vm.parser, feeAssetName) issueAndAccept(require, env.vm, env.issuer, tx) } @@ -340,7 +346,9 @@ func TestIssueTxWithAnotherAsset(t *testing.T) { env := setup(t, &envConfig{ isCustomFeeAsset: true, }) + env.vm.ctx.Lock.Unlock() defer func() { + env.vm.ctx.Lock.Lock() require.NoError(env.vm.Shutdown(context.Background())) env.vm.ctx.Lock.Unlock() }() @@ -352,7 +360,7 @@ func TestIssueTxWithAnotherAsset(t *testing.T) { tx := &txs.Tx{Unsigned: &txs.BaseTx{ BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, Ins: []*avax.TransferableInput{ // fee asset { @@ -435,7 +443,7 @@ func TestTxAcceptAfterParseTx(t *testing.T) { firstTx := &txs.Tx{Unsigned: &txs.BaseTx{ BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, Ins: []*avax.TransferableInput{{ UTXOID: avax.UTXOID{ TxID: env.genesisTx.ID(), @@ -468,7 +476,7 @@ func TestTxAcceptAfterParseTx(t *testing.T) { secondTx := &txs.Tx{Unsigned: &txs.BaseTx{ BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, Ins: []*avax.TransferableInput{{ UTXOID: avax.UTXOID{ TxID: firstTx.ID(), @@ -538,7 +546,7 @@ func TestIssueImportTx(t *testing.T) { tx := &txs.Tx{Unsigned: &txs.ImportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, Outs: []*avax.TransferableOutput{{ Asset: txAssetID, Out: &secp256k1fx.TransferOutput{ @@ -593,8 +601,12 @@ func TestIssueImportTx(t *testing.T) { }, })) + env.vm.ctx.Lock.Unlock() + issueAndAccept(require, env.vm, env.issuer, tx) + env.vm.ctx.Lock.Lock() + assertIndexedTX(t, env.vm.db, 0, key.PublicKey().Address(), txAssetID.AssetID(), tx.ID()) assertLatestIdx(t, env.vm.db, key.PublicKey().Address(), avaxID, 1) @@ -633,7 +645,7 @@ func TestForceAcceptImportTx(t *testing.T) { tx := &txs.Tx{Unsigned: &txs.ImportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, Outs: []*avax.TransferableOutput{{ Asset: txAssetID, Out: &secp256k1fx.TransferOutput{ @@ -698,7 +710,7 @@ func TestIssueExportTx(t *testing.T) { tx := &txs.Tx{Unsigned: &txs.ExportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, Ins: []*avax.TransferableInput{{ UTXOID: avax.UTXOID{ TxID: avaxID, @@ -738,8 +750,12 @@ func TestIssueExportTx(t *testing.T) { require.NoError(err) require.Empty(utxoBytes) + env.vm.ctx.Lock.Unlock() + issueAndAccept(require, env.vm, env.issuer, tx) + env.vm.ctx.Lock.Lock() + utxoBytes, _, _, err = peerSharedMemory.Indexed( env.vm.ctx.ChainID, [][]byte{ @@ -770,7 +786,7 @@ func TestClearForceAcceptedExportTx(t *testing.T) { tx := &txs.Tx{Unsigned: &txs.ExportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: constants.UnitTestID, - BlockchainID: chainID, + BlockchainID: env.vm.ctx.XChainID, Ins: []*avax.TransferableInput{{ UTXOID: avax.UTXOID{ TxID: avaxID, @@ -813,8 +829,12 @@ func TestClearForceAcceptedExportTx(t *testing.T) { _, err := peerSharedMemory.Get(env.vm.ctx.ChainID, [][]byte{utxoID[:]}) require.ErrorIs(err, database.ErrNotFound) + env.vm.ctx.Lock.Unlock() + issueAndAccept(require, env.vm, env.issuer, tx) + env.vm.ctx.Lock.Lock() + _, err = peerSharedMemory.Get(env.vm.ctx.ChainID, [][]byte{utxoID[:]}) require.ErrorIs(err, database.ErrNotFound) diff --git a/vms/avm/wallet_client.go b/vms/avm/wallet_client.go index c74918e6e401..69bdc06f9d74 100644 --- a/vms/avm/wallet_client.go +++ b/vms/avm/wallet_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm diff --git a/vms/avm/wallet_service.go b/vms/avm/wallet_service.go index 47aa3bbf88ef..f10c6eee780b 100644 --- a/vms/avm/wallet_service.go +++ b/vms/avm/wallet_service.go @@ -1,9 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm import ( + "context" "errors" "fmt" "net/http" @@ -19,6 +20,7 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/vms/avm/txs" + "github.com/ava-labs/avalanchego/vms/avm/txs/mempool" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) @@ -31,29 +33,29 @@ type WalletService struct { } func (w *WalletService) decided(txID ids.ID) { - if _, ok := w.pendingTxs.Get(txID); !ok { + if !w.pendingTxs.Delete(txID) { return } w.vm.ctx.Log.Info("tx decided over wallet API", zap.Stringer("txID", txID), ) - w.pendingTxs.Delete(txID) - for { txID, tx, ok := w.pendingTxs.Oldest() if !ok { return } - txBytes := tx.Bytes() - _, err := w.vm.IssueTx(txBytes) + err := w.vm.network.IssueVerifiedTx(context.TODO(), tx) if err == nil { w.vm.ctx.Log.Info("issued tx to mempool over wallet API", zap.Stringer("txID", txID), ) return } + if errors.Is(err, mempool.ErrDuplicateTx) { + return + } w.pendingTxs.Delete(txID) w.vm.ctx.Log.Warn("dropping tx issued over wallet API", @@ -63,12 +65,7 @@ func (w *WalletService) decided(txID ids.ID) { } } -func (w *WalletService) issue(txBytes []byte) (ids.ID, error) { - tx, err := w.vm.parser.ParseTx(txBytes) - if err != nil { - return ids.ID{}, err - } - +func (w *WalletService) issue(tx *txs.Tx) (ids.ID, error) { txID := tx.ID() w.vm.ctx.Log.Info("issuing tx over wallet API", zap.Stringer("txID", txID), @@ -82,14 +79,17 @@ func (w *WalletService) issue(txBytes []byte) (ids.ID, error) { } if w.pendingTxs.Len() == 0 { - _, err := w.vm.IssueTx(txBytes) - if err != nil { - return ids.ID{}, err + if err := w.vm.network.IssueVerifiedTx(context.TODO(), tx); err == nil { + w.vm.ctx.Log.Info("issued tx to mempool over wallet API", + zap.Stringer("txID", txID), + ) + } else if !errors.Is(err, mempool.ErrDuplicateTx) { + w.vm.ctx.Log.Warn("failed to issue tx over wallet API", + zap.Stringer("txID", txID), + zap.Error(err), + ) + return ids.Empty, err } - - w.vm.ctx.Log.Info("issued tx to mempool over wallet API", - zap.Stringer("txID", txID), - ) } else { w.vm.ctx.Log.Info("enqueueing tx over wallet API", zap.Stringer("txID", txID), @@ -142,10 +142,15 @@ func (w *WalletService) IssueTx(_ *http.Request, args *api.FormattedTx, reply *a return fmt.Errorf("problem decoding transaction: %w", err) } + tx, err := w.vm.parser.ParseTx(txBytes) + if err != nil { + return err + } + w.vm.ctx.Lock.Lock() defer w.vm.ctx.Lock.Unlock() - txID, err := w.issue(txBytes) + txID, err := w.issue(tx) reply.TxID = txID return err } @@ -291,7 +296,7 @@ func (w *WalletService) SendMultiple(_ *http.Request, args *SendMultipleArgs, re codec := w.vm.parser.Codec() avax.SortTransferableOutputs(outs, codec) - tx := txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ + tx := &txs.Tx{Unsigned: &txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: w.vm.ctx.NetworkID, BlockchainID: w.vm.ctx.ChainID, Outs: outs, @@ -302,7 +307,7 @@ func (w *WalletService) SendMultiple(_ *http.Request, args *SendMultipleArgs, re return err } - txID, err := w.issue(tx.Bytes()) + txID, err := w.issue(tx) if err != nil { return fmt.Errorf("problem issuing transaction: %w", err) } diff --git a/vms/avm/wallet_service_test.go b/vms/avm/wallet_service_test.go index 7a9232e00a6a..7ffdccdaaa20 100644 --- a/vms/avm/wallet_service_test.go +++ b/vms/avm/wallet_service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avm @@ -39,7 +39,7 @@ func TestWalletService_SendMultiple(t *testing.T) { require.NoError(err) changeAddrStr, err := env.vm.FormatLocalAddress(testChangeAddr) require.NoError(err) - _, fromAddrsStr := sampleAddrs(t, env.vm, addrs) + _, fromAddrsStr := sampleAddrs(t, env.vm.AddressManager, addrs) args := &SendMultipleArgs{ JSONSpendHeader: api.JSONSpendHeader{ @@ -67,10 +67,10 @@ func TestWalletService_SendMultiple(t *testing.T) { require.NoError(env.walletService.SendMultiple(nil, args, reply)) require.Equal(changeAddrStr, reply.ChangeAddr) - env.vm.ctx.Lock.Lock() - buildAndAccept(require, env.vm, env.issuer, reply.TxID) + env.vm.ctx.Lock.Lock() + _, err = env.vm.state.GetTx(reply.TxID) require.NoError(err) }) diff --git a/vms/components/avax/addresses.go b/vms/components/avax/addresses.go index 40929e22f895..a1567f75f4d6 100644 --- a/vms/components/avax/addresses.go +++ b/vms/components/avax/addresses.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/asset.go b/vms/components/avax/asset.go index 90a3eef6dff9..bc165b4d59ed 100644 --- a/vms/components/avax/asset.go +++ b/vms/components/avax/asset.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/asset_test.go b/vms/components/avax/asset_test.go index 68c371ae1b06..ad7628ce98b9 100644 --- a/vms/components/avax/asset_test.go +++ b/vms/components/avax/asset_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -28,7 +29,7 @@ func TestAssetVerifyEmpty(t *testing.T) { func TestAssetID(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) manager := codec.NewDefaultManager() require.NoError(manager.RegisterCodec(codecVersion, c)) diff --git a/vms/components/avax/atomic_utxos.go b/vms/components/avax/atomic_utxos.go index 20b22420f8cb..3ac9c166ea3c 100644 --- a/vms/components/avax/atomic_utxos.go +++ b/vms/components/avax/atomic_utxos.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/base_tx.go b/vms/components/avax/base_tx.go index 2bcafa24e497..10561ae0ad52 100644 --- a/vms/components/avax/base_tx.go +++ b/vms/components/avax/base_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/flow_checker.go b/vms/components/avax/flow_checker.go index b0ed8c86e551..e02aee717e3b 100644 --- a/vms/components/avax/flow_checker.go +++ b/vms/components/avax/flow_checker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/metadata.go b/vms/components/avax/metadata.go index f03389c46937..1630484131a8 100644 --- a/vms/components/avax/metadata.go +++ b/vms/components/avax/metadata.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/metadata_test.go b/vms/components/avax/metadata_test.go index 01dada2feda7..9569e3e3a465 100644 --- a/vms/components/avax/metadata_test.go +++ b/vms/components/avax/metadata_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/mock_transferable_in.go b/vms/components/avax/mock_transferable_in.go index cfd7e419dc26..b4db89933057 100644 --- a/vms/components/avax/mock_transferable_in.go +++ b/vms/components/avax/mock_transferable_in.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/components/avax (interfaces: TransferableIn) +// +// Generated by this command: +// +// mockgen -package=avax -destination=vms/components/avax/mock_transferable_in.go github.com/ava-labs/avalanchego/vms/components/avax TransferableIn +// // Package avax is a generated GoMock package. package avax @@ -73,7 +75,7 @@ func (m *MockTransferableIn) InitCtx(arg0 *snow.Context) { } // InitCtx indicates an expected call of InitCtx. -func (mr *MockTransferableInMockRecorder) InitCtx(arg0 interface{}) *gomock.Call { +func (mr *MockTransferableInMockRecorder) InitCtx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*MockTransferableIn)(nil).InitCtx), arg0) } diff --git a/vms/components/avax/mock_transferable_out.go b/vms/components/avax/mock_transferable_out.go index bf1836002214..b518b86302d4 100644 --- a/vms/components/avax/mock_transferable_out.go +++ b/vms/components/avax/mock_transferable_out.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/components/avax (interfaces: TransferableOut) +// +// Generated by this command: +// +// mockgen -package=avax -destination=vms/components/avax/mock_transferable_out.go github.com/ava-labs/avalanchego/vms/components/avax TransferableOut +// // Package avax is a generated GoMock package. package avax @@ -61,7 +63,7 @@ func (m *MockTransferableOut) InitCtx(arg0 *snow.Context) { } // InitCtx indicates an expected call of InitCtx. -func (mr *MockTransferableOutMockRecorder) InitCtx(arg0 interface{}) *gomock.Call { +func (mr *MockTransferableOutMockRecorder) InitCtx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*MockTransferableOut)(nil).InitCtx), arg0) } @@ -79,3 +81,15 @@ func (mr *MockTransferableOutMockRecorder) Verify() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockTransferableOut)(nil).Verify)) } + +// isState mocks base method. +func (m *MockTransferableOut) isState() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "isState") +} + +// isState indicates an expected call of isState. +func (mr *MockTransferableOutMockRecorder) isState() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "isState", reflect.TypeOf((*MockTransferableOut)(nil).isState)) +} diff --git a/vms/components/avax/state.go b/vms/components/avax/state.go index ab0d42ab080d..1a7616b2f729 100644 --- a/vms/components/avax/state.go +++ b/vms/components/avax/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/test_verifiable.go b/vms/components/avax/test_verifiable.go index 36ea7d57f8ff..8649b01eaa97 100644 --- a/vms/components/avax/test_verifiable.go +++ b/vms/components/avax/test_verifiable.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/transferables.go b/vms/components/avax/transferables.go index 3134ac68ff4a..18e3cf77542c 100644 --- a/vms/components/avax/transferables.go +++ b/vms/components/avax/transferables.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax @@ -161,8 +161,8 @@ func (in *TransferableInput) Verify() error { } } -func (in *TransferableInput) Less(other *TransferableInput) bool { - return in.UTXOID.Less(&other.UTXOID) +func (in *TransferableInput) Compare(other *TransferableInput) int { + return in.UTXOID.Compare(&other.UTXOID) } type innerSortTransferableInputsWithSigners struct { diff --git a/vms/components/avax/transferables_test.go b/vms/components/avax/transferables_test.go index f0bd6332f78d..755a0124eb7b 100644 --- a/vms/components/avax/transferables_test.go +++ b/vms/components/avax/transferables_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -42,7 +43,7 @@ func TestTransferableOutputVerify(t *testing.T) { func TestTransferableOutputSorting(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) require.NoError(c.RegisterType(&TestTransferable{})) manager := codec.NewDefaultManager() require.NoError(manager.RegisterCodec(codecVersion, c)) @@ -84,7 +85,7 @@ func TestTransferableOutputSorting(t *testing.T) { func TestTransferableOutputSerialization(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) require.NoError(c.RegisterType(&secp256k1fx.TransferOutput{})) manager := codec.NewDefaultManager() require.NoError(manager.RegisterCodec(codecVersion, c)) @@ -175,7 +176,7 @@ func TestTransferableInputVerify(t *testing.T) { func TestTransferableInputSorting(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) require.NoError(c.RegisterType(&TestTransferable{})) ins := []*TransferableInput{ @@ -232,7 +233,7 @@ func TestTransferableInputSorting(t *testing.T) { func TestTransferableInputSerialization(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) require.NoError(c.RegisterType(&secp256k1fx.TransferInput{})) manager := codec.NewDefaultManager() require.NoError(manager.RegisterCodec(codecVersion, c)) diff --git a/vms/components/avax/utxo.go b/vms/components/avax/utxo.go index afea68914b58..a11c94af80ba 100644 --- a/vms/components/avax/utxo.go +++ b/vms/components/avax/utxo.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/utxo_fetching.go b/vms/components/avax/utxo_fetching.go index 1852513165a4..c5170f1d3123 100644 --- a/vms/components/avax/utxo_fetching.go +++ b/vms/components/avax/utxo_fetching.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/utxo_fetching_test.go b/vms/components/avax/utxo_fetching_test.go index ad34ee9e25b1..e36545c19cb7 100644 --- a/vms/components/avax/utxo_fetching_test.go +++ b/vms/components/avax/utxo_fetching_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -39,7 +40,7 @@ func TestFetchUTXOs(t *testing.T) { }, } - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) manager := codec.NewDefaultManager() require.NoError(c.RegisterType(&secp256k1fx.TransferOutput{})) @@ -72,7 +73,7 @@ func TestGetPaginatedUTXOs(t *testing.T) { addr2 := ids.GenerateTestShortID() addrs := set.Of(addr0, addr1) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) manager := codec.NewDefaultManager() require.NoError(c.RegisterType(&secp256k1fx.TransferOutput{})) diff --git a/vms/components/avax/utxo_handler.go b/vms/components/avax/utxo_handler.go index 782d8592e448..c6e705affa2a 100644 --- a/vms/components/avax/utxo_handler.go +++ b/vms/components/avax/utxo_handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/utxo_id.go b/vms/components/avax/utxo_id.go index 26fe8b83fb98..fafc940444b5 100644 --- a/vms/components/avax/utxo_id.go +++ b/vms/components/avax/utxo_id.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax @@ -91,16 +91,11 @@ func (utxo *UTXOID) Verify() error { } } -func (utxo *UTXOID) Less(other *UTXOID) bool { +func (utxo *UTXOID) Compare(other *UTXOID) int { utxoID, utxoIndex := utxo.InputSource() otherID, otherIndex := other.InputSource() - - switch bytes.Compare(utxoID[:], otherID[:]) { - case -1: - return true - case 0: - return utxoIndex < otherIndex - default: - return false + if txIDComp := bytes.Compare(utxoID[:], otherID[:]); txIDComp != 0 { + return txIDComp } + return utils.Compare(utxoIndex, otherIndex) } diff --git a/vms/components/avax/utxo_id_test.go b/vms/components/avax/utxo_id_test.go index 5652fa1afa69..fed21d5ce986 100644 --- a/vms/components/avax/utxo_id_test.go +++ b/vms/components/avax/utxo_id_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax @@ -6,6 +6,7 @@ package avax import ( "math" "testing" + "time" "github.com/stretchr/testify/require" @@ -23,7 +24,7 @@ func TestUTXOIDVerifyNil(t *testing.T) { func TestUTXOID(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) manager := codec.NewDefaultManager() require.NoError(manager.RegisterCodec(codecVersion, c)) @@ -50,56 +51,43 @@ func TestUTXOID(t *testing.T) { require.Equal(utxoID.InputID(), newUTXOID.InputID()) } -func TestUTXOIDLess(t *testing.T) { +func TestUTXOIDCompare(t *testing.T) { type test struct { name string id1 UTXOID id2 UTXOID - expected bool + expected int } tests := []*test{ { name: "same", id1: UTXOID{}, id2: UTXOID{}, - expected: false, + expected: 0, }, { - name: "first id smaller", + name: "id smaller", id1: UTXOID{}, id2: UTXOID{ TxID: ids.ID{1}, }, - expected: true, + expected: -1, }, { - name: "first id larger", - id1: UTXOID{ - TxID: ids.ID{1}, - }, - id2: UTXOID{}, - expected: false, - }, - { - name: "first index smaller", + name: "index smaller", id1: UTXOID{}, id2: UTXOID{ OutputIndex: 1, }, - expected: true, - }, - { - name: "first index larger", - id1: UTXOID{ - OutputIndex: 1, - }, - id2: UTXOID{}, - expected: false, + expected: -1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.expected, tt.id1.Less(&tt.id2)) + require := require.New(t) + + require.Equal(tt.expected, tt.id1.Compare(&tt.id2)) + require.Equal(-tt.expected, tt.id2.Compare(&tt.id1)) }) } } diff --git a/vms/components/avax/utxo_state.go b/vms/components/avax/utxo_state.go index 5ff20b38dddd..9bc648a6e531 100644 --- a/vms/components/avax/utxo_state.go +++ b/vms/components/avax/utxo_state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax diff --git a/vms/components/avax/utxo_state_test.go b/vms/components/avax/utxo_state_test.go index e5fc8c695638..fa4c530e011a 100644 --- a/vms/components/avax/utxo_state_test.go +++ b/vms/components/avax/utxo_state_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -41,7 +42,7 @@ func TestUTXOState(t *testing.T) { } utxoID := utxo.InputID() - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) manager := codec.NewDefaultManager() require.NoError(c.RegisterType(&secp256k1fx.MintOutput{})) diff --git a/vms/components/avax/utxo_test.go b/vms/components/avax/utxo_test.go index 7561f85da2cc..a79c8fcb6cd6 100644 --- a/vms/components/avax/utxo_test.go +++ b/vms/components/avax/utxo_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package avax import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -32,7 +33,7 @@ func TestUTXOVerifyEmpty(t *testing.T) { func TestUTXOSerialize(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) manager := codec.NewDefaultManager() require.NoError(c.RegisterType(&secp256k1fx.MintOutput{})) diff --git a/vms/components/chain/block.go b/vms/components/chain/block.go index d03659ed16da..3966dd2005bd 100644 --- a/vms/components/chain/block.go +++ b/vms/components/chain/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chain diff --git a/vms/components/chain/state.go b/vms/components/chain/state.go index 6311e550ce6c..6ada30e73eee 100644 --- a/vms/components/chain/state.go +++ b/vms/components/chain/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chain diff --git a/vms/components/chain/state_test.go b/vms/components/chain/state_test.go index add7723e9fcf..c0583011bd75 100644 --- a/vms/components/chain/state_test.go +++ b/vms/components/chain/state_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chain diff --git a/vms/components/index/index.go b/vms/components/index/index.go index a1bced563979..18bdd7337b28 100644 --- a/vms/components/index/index.go +++ b/vms/components/index/index.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package index diff --git a/vms/components/index/metrics.go b/vms/components/index/metrics.go index a38006305548..8531de6982e3 100644 --- a/vms/components/index/metrics.go +++ b/vms/components/index/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package index diff --git a/vms/components/keystore/codec.go b/vms/components/keystore/codec.go index 5acb1725aa6e..15576b73e4ea 100644 --- a/vms/components/keystore/codec.go +++ b/vms/components/keystore/codec.go @@ -1,31 +1,28 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keystore import ( "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/utils" ) -const ( - // CodecVersion is the current default codec version - CodecVersion = 0 -) +const CodecVersion = 0 -// Codecs do serialization and deserialization var ( Codec codec.Manager LegacyCodec codec.Manager ) func init() { - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) Codec = codec.NewDefaultManager() - lc := linearcodec.NewCustomMaxLength(math.MaxUint32) + lc := linearcodec.NewDefault(time.Time{}) LegacyCodec = codec.NewManager(math.MaxInt32) err := utils.Err( diff --git a/vms/components/keystore/user.go b/vms/components/keystore/user.go index 561e2f52b819..20749e5b48bf 100644 --- a/vms/components/keystore/user.go +++ b/vms/components/keystore/user.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keystore diff --git a/vms/components/keystore/user_test.go b/vms/components/keystore/user_test.go index 9f94cf03b7c6..66e331c2f655 100644 --- a/vms/components/keystore/user_test.go +++ b/vms/components/keystore/user_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package keystore diff --git a/vms/components/message/codec.go b/vms/components/message/codec.go index 3a5eee5416ca..5614125b1cee 100644 --- a/vms/components/message/codec.go +++ b/vms/components/message/codec.go @@ -1,9 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message import ( + "time" + "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/utils" @@ -11,21 +13,20 @@ import ( ) const ( - codecVersion = 0 + CodecVersion = 0 + maxMessageSize = 512 * units.KiB - maxSliceLen = maxMessageSize ) -// Codec does serialization and deserialization -var c codec.Manager +var Codec codec.Manager func init() { - c = codec.NewManager(maxMessageSize) - lc := linearcodec.NewCustomMaxLength(maxSliceLen) + Codec = codec.NewManager(maxMessageSize) + lc := linearcodec.NewDefault(time.Time{}) err := utils.Err( lc.RegisterType(&Tx{}), - c.RegisterCodec(codecVersion, lc), + Codec.RegisterCodec(CodecVersion, lc), ) if err != nil { panic(err) diff --git a/vms/components/message/handler.go b/vms/components/message/handler.go index afe123518164..2af2f55a3f0c 100644 --- a/vms/components/message/handler.go +++ b/vms/components/message/handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message diff --git a/vms/components/message/handler_test.go b/vms/components/message/handler_test.go index 489c973b68e0..bc2342838efa 100644 --- a/vms/components/message/handler_test.go +++ b/vms/components/message/handler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message diff --git a/vms/components/message/message.go b/vms/components/message/message.go index 009cf67f4884..a33d4104430a 100644 --- a/vms/components/message/message.go +++ b/vms/components/message/message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message @@ -65,11 +65,11 @@ func Parse(bytes []byte) (Message, error) { // It must have been encoded with avalanchego's codec. // TODO remove else statement remove once all nodes support proto encoding. // i.e. when all nodes are on v1.11.0 or later. - version, err := c.Unmarshal(bytes, &msg) + version, err := Codec.Unmarshal(bytes, &msg) if err != nil { return nil, err } - if version != codecVersion { + if version != CodecVersion { return nil, ErrUnexpectedCodecVersion } } @@ -78,7 +78,7 @@ func Parse(bytes []byte) (Message, error) { } func Build(msg Message) ([]byte, error) { - bytes, err := c.Marshal(codecVersion, &msg) + bytes, err := Codec.Marshal(CodecVersion, &msg) msg.initialize(bytes) return bytes, err } diff --git a/vms/components/message/message_test.go b/vms/components/message/message_test.go index 38b5099e2b45..a4a12312cddb 100644 --- a/vms/components/message/message_test.go +++ b/vms/components/message/message_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message diff --git a/vms/components/message/tx.go b/vms/components/message/tx.go index 62fdb0dd54f8..4eced1818233 100644 --- a/vms/components/message/tx.go +++ b/vms/components/message/tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message diff --git a/vms/components/message/tx_test.go b/vms/components/message/tx_test.go index 3634abb4c71f..8c52828b7977 100644 --- a/vms/components/message/tx_test.go +++ b/vms/components/message/tx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package message diff --git a/vms/components/verify/mock_verifiable.go b/vms/components/verify/mock_verifiable.go index 915491142014..fe0e5770500c 100644 --- a/vms/components/verify/mock_verifiable.go +++ b/vms/components/verify/mock_verifiable.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/components/verify (interfaces: Verifiable) +// +// Generated by this command: +// +// mockgen -package=verify -destination=vms/components/verify/mock_verifiable.go github.com/ava-labs/avalanchego/vms/components/verify Verifiable +// // Package verify is a generated GoMock package. package verify diff --git a/vms/components/verify/subnet.go b/vms/components/verify/subnet.go index a1030164e145..ba4e65ee2bb3 100644 --- a/vms/components/verify/subnet.go +++ b/vms/components/verify/subnet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package verify diff --git a/vms/components/verify/subnet_test.go b/vms/components/verify/subnet_test.go index 1bfc6c490350..bcffab905ed0 100644 --- a/vms/components/verify/subnet_test.go +++ b/vms/components/verify/subnet_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package verify diff --git a/vms/components/verify/verification.go b/vms/components/verify/verification.go index 566facbdf865..b712b730e8e8 100644 --- a/vms/components/verify/verification.go +++ b/vms/components/verify/verification.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package verify diff --git a/vms/components/verify/verification_test.go b/vms/components/verify/verification_test.go index 408fc2e94736..57f3b856b3a8 100644 --- a/vms/components/verify/verification_test.go +++ b/vms/components/verify/verification_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package verify diff --git a/vms/example/xsvm/README.md b/vms/example/xsvm/README.md index 6f30f7b7e756..53548228a989 100644 --- a/vms/example/xsvm/README.md +++ b/vms/example/xsvm/README.md @@ -18,7 +18,7 @@ Avalanche is a network composed of multiple sub-networks (called [subnets][Subne ## Introduction -Just as [Coreth] powers the [C-Chain], XSVM can be used to power its own blockchain in an Avalanche [Subnet]. Instead of providing a place to execute Solidity smart contracts, however, XSVM enables asset transfers for assets originating on it's own chain or other XSVM chains on other subnets. +Just as [Coreth] powers the [C-Chain], XSVM can be used to power its own blockchain in an Avalanche [Subnet]. Instead of providing a place to execute Solidity smart contracts, however, XSVM enables asset transfers for assets originating on its own chain or other XSVM chains on other subnets. ## How it Works diff --git a/vms/example/xsvm/api/client.go b/vms/example/xsvm/api/client.go index 785b092faed1..d9a6a711950a 100644 --- a/vms/example/xsvm/api/client.go +++ b/vms/example/xsvm/api/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package api @@ -170,7 +170,7 @@ func (c *client) IssueTx( newTx *tx.Tx, options ...rpc.Option, ) (ids.ID, error) { - txBytes, err := tx.Codec.Marshal(tx.Version, newTx) + txBytes, err := tx.Codec.Marshal(tx.CodecVersion, newTx) if err != nil { return ids.Empty, err } diff --git a/vms/example/xsvm/api/server.go b/vms/example/xsvm/api/server.go index 733cf4f2cc44..dd2545e88dcf 100644 --- a/vms/example/xsvm/api/server.go +++ b/vms/example/xsvm/api/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package api diff --git a/vms/example/xsvm/block/block.go b/vms/example/xsvm/block/block.go index 2db314ea1a5a..ab6b41d77f2b 100644 --- a/vms/example/xsvm/block/block.go +++ b/vms/example/xsvm/block/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -26,7 +26,7 @@ func (b *Stateless) Time() time.Time { } func (b *Stateless) ID() (ids.ID, error) { - bytes, err := Codec.Marshal(Version, b) + bytes, err := Codec.Marshal(CodecVersion, b) return hashing.ComputeHash256Array(bytes), err } diff --git a/vms/example/xsvm/block/codec.go b/vms/example/xsvm/block/codec.go index 0ffbc98d58e7..b4e5c811e29f 100644 --- a/vms/example/xsvm/block/codec.go +++ b/vms/example/xsvm/block/codec.go @@ -1,11 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block import "github.com/ava-labs/avalanchego/vms/example/xsvm/tx" -// Version is the current default codec version -const Version = tx.Version +const CodecVersion = tx.CodecVersion var Codec = tx.Codec diff --git a/vms/example/xsvm/builder/builder.go b/vms/example/xsvm/builder/builder.go index 7135985d3707..231679f5df56 100644 --- a/vms/example/xsvm/builder/builder.go +++ b/vms/example/xsvm/builder/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package builder diff --git a/vms/example/xsvm/chain/block.go b/vms/example/xsvm/chain/block.go index 8eb660e23c12..8ab761d515f8 100644 --- a/vms/example/xsvm/chain/block.go +++ b/vms/example/xsvm/chain/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chain diff --git a/vms/example/xsvm/chain/chain.go b/vms/example/xsvm/chain/chain.go index ef6a59de2dbb..7fc60261d159 100644 --- a/vms/example/xsvm/chain/chain.go +++ b/vms/example/xsvm/chain/chain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chain @@ -80,7 +80,7 @@ func (c *chain) NewBlock(blk *xsblock.Stateless) (Block, error) { return blk, nil } - blkBytes, err := xsblock.Codec.Marshal(xsblock.Version, blk) + blkBytes, err := xsblock.Codec.Marshal(xsblock.CodecVersion, blk) if err != nil { return nil, err } diff --git a/vms/example/xsvm/cmd/account/cmd.go b/vms/example/xsvm/cmd/account/cmd.go index 3436c3fea1b5..cea0b7b6a227 100644 --- a/vms/example/xsvm/cmd/account/cmd.go +++ b/vms/example/xsvm/cmd/account/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package account diff --git a/vms/example/xsvm/cmd/account/flags.go b/vms/example/xsvm/cmd/account/flags.go index 17092bbe1a21..3a9588ab2c63 100644 --- a/vms/example/xsvm/cmd/account/flags.go +++ b/vms/example/xsvm/cmd/account/flags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package account diff --git a/vms/example/xsvm/cmd/chain/cmd.go b/vms/example/xsvm/cmd/chain/cmd.go index a87e6911b8a8..679bdea0b9f0 100644 --- a/vms/example/xsvm/cmd/chain/cmd.go +++ b/vms/example/xsvm/cmd/chain/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package chain diff --git a/vms/example/xsvm/cmd/chain/create/cmd.go b/vms/example/xsvm/cmd/chain/create/cmd.go index 1f00491a4ce6..984ff45df8b0 100644 --- a/vms/example/xsvm/cmd/chain/create/cmd.go +++ b/vms/example/xsvm/cmd/chain/create/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package create @@ -55,7 +55,7 @@ func createFunc(c *cobra.Command, args []string) error { // Get the P-chain wallet pWallet := wallet.P() - genesisBytes, err := genesis.Codec.Marshal(genesis.Version, &genesis.Genesis{ + genesisBytes, err := genesis.Codec.Marshal(genesis.CodecVersion, &genesis.Genesis{ Timestamp: 0, Allocations: []genesis.Allocation{ { diff --git a/vms/example/xsvm/cmd/chain/create/flags.go b/vms/example/xsvm/cmd/chain/create/flags.go index 80b1eefd67cf..d3e554659e49 100644 --- a/vms/example/xsvm/cmd/chain/create/flags.go +++ b/vms/example/xsvm/cmd/chain/create/flags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package create diff --git a/vms/example/xsvm/cmd/chain/genesis/cmd.go b/vms/example/xsvm/cmd/chain/genesis/cmd.go index ae18e1db85e3..be839fced200 100644 --- a/vms/example/xsvm/cmd/chain/genesis/cmd.go +++ b/vms/example/xsvm/cmd/chain/genesis/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis @@ -34,7 +34,7 @@ func genesisFunc(c *cobra.Command, args []string) error { return err } - genesisBytes, err := genesis.Codec.Marshal(genesis.Version, config.Genesis) + genesisBytes, err := genesis.Codec.Marshal(genesis.CodecVersion, config.Genesis) if err != nil { return err } diff --git a/vms/example/xsvm/cmd/chain/genesis/flags.go b/vms/example/xsvm/cmd/chain/genesis/flags.go index 5291327197d5..0bacf0edd29b 100644 --- a/vms/example/xsvm/cmd/chain/genesis/flags.go +++ b/vms/example/xsvm/cmd/chain/genesis/flags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/vms/example/xsvm/cmd/issue/cmd.go b/vms/example/xsvm/cmd/issue/cmd.go index e973efc31d5b..12c156d06628 100644 --- a/vms/example/xsvm/cmd/issue/cmd.go +++ b/vms/example/xsvm/cmd/issue/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package issue diff --git a/vms/example/xsvm/cmd/issue/export/cmd.go b/vms/example/xsvm/cmd/issue/export/cmd.go index c0a8cd11008c..efde479971cc 100644 --- a/vms/example/xsvm/cmd/issue/export/cmd.go +++ b/vms/example/xsvm/cmd/issue/export/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package export diff --git a/vms/example/xsvm/cmd/issue/export/flags.go b/vms/example/xsvm/cmd/issue/export/flags.go index f14c21ef8f6f..6d7f4e49a233 100644 --- a/vms/example/xsvm/cmd/issue/export/flags.go +++ b/vms/example/xsvm/cmd/issue/export/flags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package export diff --git a/vms/example/xsvm/cmd/issue/importtx/cmd.go b/vms/example/xsvm/cmd/issue/importtx/cmd.go index 2c8fbb7edb27..5bf104212ef6 100644 --- a/vms/example/xsvm/cmd/issue/importtx/cmd.go +++ b/vms/example/xsvm/cmd/issue/importtx/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package importtx diff --git a/vms/example/xsvm/cmd/issue/importtx/flags.go b/vms/example/xsvm/cmd/issue/importtx/flags.go index 486dfa1a05ea..15b968775fcc 100644 --- a/vms/example/xsvm/cmd/issue/importtx/flags.go +++ b/vms/example/xsvm/cmd/issue/importtx/flags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package importtx diff --git a/vms/example/xsvm/cmd/issue/transfer/cmd.go b/vms/example/xsvm/cmd/issue/transfer/cmd.go index 5dd15bc4ea26..86c47032a6c0 100644 --- a/vms/example/xsvm/cmd/issue/transfer/cmd.go +++ b/vms/example/xsvm/cmd/issue/transfer/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package transfer diff --git a/vms/example/xsvm/cmd/issue/transfer/flags.go b/vms/example/xsvm/cmd/issue/transfer/flags.go index 5d4667b47458..043c07243e54 100644 --- a/vms/example/xsvm/cmd/issue/transfer/flags.go +++ b/vms/example/xsvm/cmd/issue/transfer/flags.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package transfer diff --git a/vms/example/xsvm/cmd/run/cmd.go b/vms/example/xsvm/cmd/run/cmd.go index 9776b15eb3a7..eace7e85d7ed 100644 --- a/vms/example/xsvm/cmd/run/cmd.go +++ b/vms/example/xsvm/cmd/run/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package run diff --git a/vms/example/xsvm/cmd/version/cmd.go b/vms/example/xsvm/cmd/version/cmd.go index 0827a9e800b7..1c956c6a9b00 100644 --- a/vms/example/xsvm/cmd/version/cmd.go +++ b/vms/example/xsvm/cmd/version/cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package version diff --git a/vms/example/xsvm/cmd/xsvm/main.go b/vms/example/xsvm/cmd/xsvm/main.go index ac370a4a9df2..c6961a8c1741 100644 --- a/vms/example/xsvm/cmd/xsvm/main.go +++ b/vms/example/xsvm/cmd/xsvm/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/vms/example/xsvm/constants.go b/vms/example/xsvm/constants.go index e4b22b742450..eb2199211ef7 100644 --- a/vms/example/xsvm/constants.go +++ b/vms/example/xsvm/constants.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package xsvm diff --git a/vms/example/xsvm/execute/block.go b/vms/example/xsvm/execute/block.go index dc9de45af19e..b2938a58f5e8 100644 --- a/vms/example/xsvm/execute/block.go +++ b/vms/example/xsvm/execute/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package execute @@ -62,7 +62,7 @@ func Block( return err } - blkBytes, err := xsblock.Codec.Marshal(xsblock.Version, blk) + blkBytes, err := xsblock.Codec.Marshal(xsblock.CodecVersion, blk) if err != nil { return err } diff --git a/vms/example/xsvm/execute/expects_context.go b/vms/example/xsvm/execute/expects_context.go index 0109cadc3731..da21b520f314 100644 --- a/vms/example/xsvm/execute/expects_context.go +++ b/vms/example/xsvm/execute/expects_context.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package execute diff --git a/vms/example/xsvm/execute/genesis.go b/vms/example/xsvm/execute/genesis.go index 312a7bd0b73c..889432d38256 100644 --- a/vms/example/xsvm/execute/genesis.go +++ b/vms/example/xsvm/execute/genesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package execute @@ -36,7 +36,7 @@ func Genesis(db database.KeyValueReaderWriterDeleter, chainID ids.ID, g *genesis return err } - blkBytes, err := block.Codec.Marshal(block.Version, blk) + blkBytes, err := block.Codec.Marshal(block.CodecVersion, blk) if err != nil { return err } diff --git a/vms/example/xsvm/execute/tx.go b/vms/example/xsvm/execute/tx.go index 01bfc1fb7d6d..f3f6ad504de4 100644 --- a/vms/example/xsvm/execute/tx.go +++ b/vms/example/xsvm/execute/tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package execute diff --git a/vms/example/xsvm/factory.go b/vms/example/xsvm/factory.go index 0531a1bcc7d0..99d33b8290d1 100644 --- a/vms/example/xsvm/factory.go +++ b/vms/example/xsvm/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package xsvm diff --git a/vms/example/xsvm/genesis/codec.go b/vms/example/xsvm/genesis/codec.go index 6ef652864574..c0851cccf146 100644 --- a/vms/example/xsvm/genesis/codec.go +++ b/vms/example/xsvm/genesis/codec.go @@ -1,11 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis import "github.com/ava-labs/avalanchego/vms/example/xsvm/block" -// Version is the current default codec version -const Version = block.Version +const CodecVersion = block.CodecVersion var Codec = block.Codec diff --git a/vms/example/xsvm/genesis/genesis.go b/vms/example/xsvm/genesis/genesis.go index e8580ffef7f6..0fb420f3a4da 100644 --- a/vms/example/xsvm/genesis/genesis.go +++ b/vms/example/xsvm/genesis/genesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis @@ -26,7 +26,7 @@ func Parse(bytes []byte) (*Genesis, error) { } func Block(genesis *Genesis) (*block.Stateless, error) { - bytes, err := Codec.Marshal(Version, genesis) + bytes, err := Codec.Marshal(CodecVersion, genesis) if err != nil { return nil, err } diff --git a/vms/example/xsvm/genesis/genesis_test.go b/vms/example/xsvm/genesis/genesis_test.go index 511c5c9b0b8b..ba050d12f801 100644 --- a/vms/example/xsvm/genesis/genesis_test.go +++ b/vms/example/xsvm/genesis/genesis_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis @@ -26,7 +26,7 @@ func TestGenesis(t *testing.T) { {Address: id2, Balance: 3000000000}, }, } - bytes, err := Codec.Marshal(Version, genesis) + bytes, err := Codec.Marshal(CodecVersion, genesis) require.NoError(err) parsed, err := Parse(bytes) diff --git a/vms/example/xsvm/state/keys.go b/vms/example/xsvm/state/keys.go index 5e78f1b95a0e..e0df1e36cdc0 100644 --- a/vms/example/xsvm/state/keys.go +++ b/vms/example/xsvm/state/keys.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/example/xsvm/state/storage.go b/vms/example/xsvm/state/storage.go index c9b6bf1ae65d..48234e9678de 100644 --- a/vms/example/xsvm/state/storage.go +++ b/vms/example/xsvm/state/storage.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/example/xsvm/tx/codec.go b/vms/example/xsvm/tx/codec.go index c91a2165f1f6..f61c7bf18098 100644 --- a/vms/example/xsvm/tx/codec.go +++ b/vms/example/xsvm/tx/codec.go @@ -1,30 +1,30 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tx import ( "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/utils" ) -// Version is the current default codec version -const Version = 0 +const CodecVersion = 0 var Codec codec.Manager func init() { - c := linearcodec.NewCustomMaxLength(math.MaxInt32) + c := linearcodec.NewDefault(time.Time{}) Codec = codec.NewManager(math.MaxInt32) err := utils.Err( c.RegisterType(&Transfer{}), c.RegisterType(&Export{}), c.RegisterType(&Import{}), - Codec.RegisterCodec(Version, c), + Codec.RegisterCodec(CodecVersion, c), ) if err != nil { panic(err) diff --git a/vms/example/xsvm/tx/export.go b/vms/example/xsvm/tx/export.go index 1192952c4dca..d8de16a69e7e 100644 --- a/vms/example/xsvm/tx/export.go +++ b/vms/example/xsvm/tx/export.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tx diff --git a/vms/example/xsvm/tx/import.go b/vms/example/xsvm/tx/import.go index 8449c79c55b2..ff98b0a01aa2 100644 --- a/vms/example/xsvm/tx/import.go +++ b/vms/example/xsvm/tx/import.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tx diff --git a/vms/example/xsvm/tx/payload.go b/vms/example/xsvm/tx/payload.go index e93e5f560ac5..eecc2f082d11 100644 --- a/vms/example/xsvm/tx/payload.go +++ b/vms/example/xsvm/tx/payload.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tx @@ -34,7 +34,7 @@ func NewPayload( Amount: amount, To: to, } - bytes, err := Codec.Marshal(Version, p) + bytes, err := Codec.Marshal(CodecVersion, p) p.bytes = bytes return p, err } diff --git a/vms/example/xsvm/tx/transfer.go b/vms/example/xsvm/tx/transfer.go index c895b5e5cc51..a3d29c1432c9 100644 --- a/vms/example/xsvm/tx/transfer.go +++ b/vms/example/xsvm/tx/transfer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tx diff --git a/vms/example/xsvm/tx/tx.go b/vms/example/xsvm/tx/tx.go index fae58bae0806..8b05d5375b27 100644 --- a/vms/example/xsvm/tx/tx.go +++ b/vms/example/xsvm/tx/tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tx @@ -28,7 +28,7 @@ func Parse(bytes []byte) (*Tx, error) { } func Sign(utx Unsigned, key *secp256k1.PrivateKey) (*Tx, error) { - unsignedBytes, err := Codec.Marshal(Version, &utx) + unsignedBytes, err := Codec.Marshal(CodecVersion, &utx) if err != nil { return nil, err } @@ -46,12 +46,12 @@ func Sign(utx Unsigned, key *secp256k1.PrivateKey) (*Tx, error) { } func (tx *Tx) ID() (ids.ID, error) { - bytes, err := Codec.Marshal(Version, tx) + bytes, err := Codec.Marshal(CodecVersion, tx) return hashing.ComputeHash256Array(bytes), err } func (tx *Tx) SenderID() (ids.ShortID, error) { - unsignedBytes, err := Codec.Marshal(Version, &tx.Unsigned) + unsignedBytes, err := Codec.Marshal(CodecVersion, &tx.Unsigned) if err != nil { return ids.ShortEmpty, err } diff --git a/vms/example/xsvm/tx/unsigned.go b/vms/example/xsvm/tx/unsigned.go index 2c57f91e26fb..110611412b59 100644 --- a/vms/example/xsvm/tx/unsigned.go +++ b/vms/example/xsvm/tx/unsigned.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tx diff --git a/vms/example/xsvm/tx/visitor.go b/vms/example/xsvm/tx/visitor.go index b411e06f15b0..045b03247479 100644 --- a/vms/example/xsvm/tx/visitor.go +++ b/vms/example/xsvm/tx/visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tx diff --git a/vms/example/xsvm/vm.go b/vms/example/xsvm/vm.go index 3150fc59b481..f090ff468fe2 100644 --- a/vms/example/xsvm/vm.go +++ b/vms/example/xsvm/vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package xsvm @@ -112,10 +112,6 @@ func (*VM) Version(context.Context) (string, error) { return Version.String(), nil } -func (*VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, error) { - return nil, nil -} - func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { server := rpc.NewServer() server.RegisterCodec(json.NewCodec(), "application/json") diff --git a/vms/manager.go b/vms/manager.go index d1041d3a4693..f4ae49e39cd0 100644 --- a/vms/manager.go +++ b/vms/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package vms diff --git a/vms/metervm/batched_vm.go b/vms/metervm/batched_vm.go index dad17637a918..7b06f0989b89 100644 --- a/vms/metervm/batched_vm.go +++ b/vms/metervm/batched_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metervm diff --git a/vms/metervm/block.go b/vms/metervm/block.go index 17ffffd5f549..10d44d2c41b6 100644 --- a/vms/metervm/block.go +++ b/vms/metervm/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metervm diff --git a/vms/metervm/block_metrics.go b/vms/metervm/block_metrics.go index 094e41875aac..160d0eee50ad 100644 --- a/vms/metervm/block_metrics.go +++ b/vms/metervm/block_metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metervm diff --git a/vms/metervm/block_vm.go b/vms/metervm/block_vm.go index 8f9fee1e247d..73e949180a96 100644 --- a/vms/metervm/block_vm.go +++ b/vms/metervm/block_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metervm diff --git a/vms/metervm/build_block_with_context_vm.go b/vms/metervm/build_block_with_context_vm.go index 141d68e0ceac..012237ee3178 100644 --- a/vms/metervm/build_block_with_context_vm.go +++ b/vms/metervm/build_block_with_context_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metervm diff --git a/vms/metervm/metrics.go b/vms/metervm/metrics.go index eb2c2b40736b..f79c5e38e029 100644 --- a/vms/metervm/metrics.go +++ b/vms/metervm/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metervm diff --git a/vms/metervm/state_syncable_vm.go b/vms/metervm/state_syncable_vm.go index bcb27d682b08..42b5efa8a79e 100644 --- a/vms/metervm/state_syncable_vm.go +++ b/vms/metervm/state_syncable_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metervm diff --git a/vms/metervm/vertex_metrics.go b/vms/metervm/vertex_metrics.go index 2a4b0a506151..67caa50b610e 100644 --- a/vms/metervm/vertex_metrics.go +++ b/vms/metervm/vertex_metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metervm diff --git a/vms/metervm/vertex_vm.go b/vms/metervm/vertex_vm.go index 827bb535fcbd..8992b4863283 100644 --- a/vms/metervm/vertex_vm.go +++ b/vms/metervm/vertex_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metervm diff --git a/vms/mock_manager.go b/vms/mock_manager.go index 9726f085c513..cea232ba2a7d 100644 --- a/vms/mock_manager.go +++ b/vms/mock_manager.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms (interfaces: Factory,Manager) +// +// Generated by this command: +// +// mockgen -package=vms -destination=vms/mock_manager.go github.com/ava-labs/avalanchego/vms Factory,Manager +// // Package vms is a generated GoMock package. package vms @@ -40,16 +42,16 @@ func (m *MockFactory) EXPECT() *MockFactoryMockRecorder { } // New mocks base method. -func (m *MockFactory) New(arg0 logging.Logger) (interface{}, error) { +func (m *MockFactory) New(arg0 logging.Logger) (any, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "New", arg0) - ret0, _ := ret[0].(interface{}) + ret0, _ := ret[0].(any) ret1, _ := ret[1].(error) return ret0, ret1 } // New indicates an expected call of New. -func (mr *MockFactoryMockRecorder) New(arg0 interface{}) *gomock.Call { +func (mr *MockFactoryMockRecorder) New(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockFactory)(nil).New), arg0) } @@ -86,7 +88,7 @@ func (m *MockManager) Alias(arg0 ids.ID, arg1 string) error { } // Alias indicates an expected call of Alias. -func (mr *MockManagerMockRecorder) Alias(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) Alias(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Alias", reflect.TypeOf((*MockManager)(nil).Alias), arg0, arg1) } @@ -101,7 +103,7 @@ func (m *MockManager) Aliases(arg0 ids.ID) ([]string, error) { } // Aliases indicates an expected call of Aliases. -func (mr *MockManagerMockRecorder) Aliases(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) Aliases(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Aliases", reflect.TypeOf((*MockManager)(nil).Aliases), arg0) } @@ -116,7 +118,7 @@ func (m *MockManager) GetFactory(arg0 ids.ID) (Factory, error) { } // GetFactory indicates an expected call of GetFactory. -func (mr *MockManagerMockRecorder) GetFactory(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetFactory(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFactory", reflect.TypeOf((*MockManager)(nil).GetFactory), arg0) } @@ -146,7 +148,7 @@ func (m *MockManager) Lookup(arg0 string) (ids.ID, error) { } // Lookup indicates an expected call of Lookup. -func (mr *MockManagerMockRecorder) Lookup(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) Lookup(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lookup", reflect.TypeOf((*MockManager)(nil).Lookup), arg0) } @@ -161,7 +163,7 @@ func (m *MockManager) PrimaryAlias(arg0 ids.ID) (string, error) { } // PrimaryAlias indicates an expected call of PrimaryAlias. -func (mr *MockManagerMockRecorder) PrimaryAlias(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) PrimaryAlias(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrimaryAlias", reflect.TypeOf((*MockManager)(nil).PrimaryAlias), arg0) } @@ -175,7 +177,7 @@ func (m *MockManager) PrimaryAliasOrDefault(arg0 ids.ID) string { } // PrimaryAliasOrDefault indicates an expected call of PrimaryAliasOrDefault. -func (mr *MockManagerMockRecorder) PrimaryAliasOrDefault(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) PrimaryAliasOrDefault(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrimaryAliasOrDefault", reflect.TypeOf((*MockManager)(nil).PrimaryAliasOrDefault), arg0) } @@ -189,7 +191,7 @@ func (m *MockManager) RegisterFactory(arg0 context.Context, arg1 ids.ID, arg2 Fa } // RegisterFactory indicates an expected call of RegisterFactory. -func (mr *MockManagerMockRecorder) RegisterFactory(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) RegisterFactory(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterFactory", reflect.TypeOf((*MockManager)(nil).RegisterFactory), arg0, arg1, arg2) } @@ -201,7 +203,7 @@ func (m *MockManager) RemoveAliases(arg0 ids.ID) { } // RemoveAliases indicates an expected call of RemoveAliases. -func (mr *MockManagerMockRecorder) RemoveAliases(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) RemoveAliases(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveAliases", reflect.TypeOf((*MockManager)(nil).RemoveAliases), arg0) } diff --git a/vms/nftfx/credential.go b/vms/nftfx/credential.go index 64af78587282..a8970b854b1a 100644 --- a/vms/nftfx/credential.go +++ b/vms/nftfx/credential.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/credential_test.go b/vms/nftfx/credential_test.go index c1e83ccab00e..0f05af26a738 100644 --- a/vms/nftfx/credential_test.go +++ b/vms/nftfx/credential_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/factory.go b/vms/nftfx/factory.go index e52d629fe670..a111bc0ed7e2 100644 --- a/vms/nftfx/factory.go +++ b/vms/nftfx/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/factory_test.go b/vms/nftfx/factory_test.go index 10581c8db922..6b4fba9861ac 100644 --- a/vms/nftfx/factory_test.go +++ b/vms/nftfx/factory_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/fx.go b/vms/nftfx/fx.go index f56ffcc10e5d..66ea9460b56b 100644 --- a/vms/nftfx/fx.go +++ b/vms/nftfx/fx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/fx_test.go b/vms/nftfx/fx_test.go index d054d680c305..1ed3426f5b11 100644 --- a/vms/nftfx/fx_test.go +++ b/vms/nftfx/fx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx @@ -39,7 +39,7 @@ var ( func TestFxInitialize(t *testing.T) { vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } fx := Fx{} @@ -56,7 +56,7 @@ func TestFxVerifyMintOperation(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -92,7 +92,7 @@ func TestFxVerifyMintOperationWrongTx(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -126,7 +126,7 @@ func TestFxVerifyMintOperationWrongNumberUTXOs(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -157,7 +157,7 @@ func TestFxVerifyMintOperationWrongCredential(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -189,7 +189,7 @@ func TestFxVerifyMintOperationInvalidUTXO(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -220,7 +220,7 @@ func TestFxVerifyMintOperationFailingVerification(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -258,7 +258,7 @@ func TestFxVerifyMintOperationInvalidGroupID(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -296,7 +296,7 @@ func TestFxVerifyTransferOperation(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -346,7 +346,7 @@ func TestFxVerifyTransferOperationWrongUTXO(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -387,7 +387,7 @@ func TestFxVerifyTransferOperationFailedVerify(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -435,7 +435,7 @@ func TestFxVerifyTransferOperationWrongGroupID(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -486,7 +486,7 @@ func TestFxVerifyTransferOperationWrongBytes(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -537,7 +537,7 @@ func TestFxVerifyTransferOperationTooSoon(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -589,7 +589,7 @@ func TestFxVerifyOperationUnknownOperation(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -625,7 +625,7 @@ func TestFxVerifyTransfer(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) diff --git a/vms/nftfx/mint_operation.go b/vms/nftfx/mint_operation.go index 2d1c5bbb5d98..bb01ee52ed23 100644 --- a/vms/nftfx/mint_operation.go +++ b/vms/nftfx/mint_operation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/mint_operation_test.go b/vms/nftfx/mint_operation_test.go index 4dc1ce2a3eb6..ff397e9a07a3 100644 --- a/vms/nftfx/mint_operation_test.go +++ b/vms/nftfx/mint_operation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/mint_output.go b/vms/nftfx/mint_output.go index 4c2f53698307..e3a974379a16 100644 --- a/vms/nftfx/mint_output.go +++ b/vms/nftfx/mint_output.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/mint_output_test.go b/vms/nftfx/mint_output_test.go index 583f211f972f..9589fc17f75b 100644 --- a/vms/nftfx/mint_output_test.go +++ b/vms/nftfx/mint_output_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/transfer_operation.go b/vms/nftfx/transfer_operation.go index 010d43890f62..014cd900eca7 100644 --- a/vms/nftfx/transfer_operation.go +++ b/vms/nftfx/transfer_operation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/transfer_operation_test.go b/vms/nftfx/transfer_operation_test.go index ad896b14ee7d..b8892aec63bd 100644 --- a/vms/nftfx/transfer_operation_test.go +++ b/vms/nftfx/transfer_operation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/transfer_output.go b/vms/nftfx/transfer_output.go index c87d47984e39..e849e1c462e5 100644 --- a/vms/nftfx/transfer_output.go +++ b/vms/nftfx/transfer_output.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/nftfx/transfer_output_test.go b/vms/nftfx/transfer_output_test.go index 330723144106..0effa6c393a7 100644 --- a/vms/nftfx/transfer_output_test.go +++ b/vms/nftfx/transfer_output_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nftfx diff --git a/vms/platformvm/api/static_client.go b/vms/platformvm/api/static_client.go deleted file mode 100644 index 9dea36664592..000000000000 --- a/vms/platformvm/api/static_client.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package api - -import ( - "context" - - "github.com/ava-labs/avalanchego/utils/rpc" -) - -var _ StaticClient = (*staticClient)(nil) - -// StaticClient for interacting with the platformvm static api -type StaticClient interface { - BuildGenesis( - ctx context.Context, - args *BuildGenesisArgs, - options ...rpc.Option, - ) (*BuildGenesisReply, error) -} - -// staticClient is an implementation of a platformvm client for interacting with -// the platformvm static api -type staticClient struct { - requester rpc.EndpointRequester -} - -// NewClient returns a platformvm client for interacting with the platformvm static api -func NewStaticClient(uri string) StaticClient { - return &staticClient{requester: rpc.NewEndpointRequester( - uri + "/ext/vm/platform", - )} -} - -func (c *staticClient) BuildGenesis( - ctx context.Context, - args *BuildGenesisArgs, - options ...rpc.Option, -) (resp *BuildGenesisReply, err error) { - resp = &BuildGenesisReply{} - err = c.requester.SendRequest(ctx, "platform.buildGenesis", args, resp, options...) - return resp, err -} diff --git a/vms/platformvm/api/static_service.go b/vms/platformvm/api/static_service.go index ea1cfcb2d63f..bd6e32d250a1 100644 --- a/vms/platformvm/api/static_service.go +++ b/vms/platformvm/api/static_service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package api @@ -50,30 +50,25 @@ type UTXO struct { } // TODO can we define this on *UTXO? -func (utxo UTXO) Less(other UTXO) bool { - if utxo.Locktime < other.Locktime { - return true - } else if utxo.Locktime > other.Locktime { - return false +func (utxo UTXO) Compare(other UTXO) int { + if locktimeCmp := utils.Compare(utxo.Locktime, other.Locktime); locktimeCmp != 0 { + return locktimeCmp } - - if utxo.Amount < other.Amount { - return true - } else if utxo.Amount > other.Amount { - return false + if amountCmp := utils.Compare(utxo.Amount, other.Amount); amountCmp != 0 { + return amountCmp } utxoAddr, err := bech32ToID(utxo.Address) if err != nil { - return false + return 0 } otherAddr, err := bech32ToID(other.Address) if err != nil { - return false + return 0 } - return utxoAddr.Less(otherAddr) + return utxoAddr.Compare(otherAddr) } // TODO: Refactor APIStaker, APIValidators and merge them together for @@ -395,7 +390,7 @@ func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, repl } // Marshal genesis to bytes - bytes, err := genesis.Codec.Marshal(genesis.Version, g) + bytes, err := genesis.Codec.Marshal(genesis.CodecVersion, g) if err != nil { return fmt.Errorf("couldn't marshal genesis: %w", err) } diff --git a/vms/platformvm/api/static_service_test.go b/vms/platformvm/api/static_service_test.go index 8bcbf4e766db..a0e62fa9a31f 100644 --- a/vms/platformvm/api/static_service_test.go +++ b/vms/platformvm/api/static_service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package api @@ -237,7 +237,7 @@ func TestBuildGenesisReturnsSortedValidators(t *testing.T) { require.Len(validators, 3) } -func TestUTXOLess(t *testing.T) { +func TestUTXOCompare(t *testing.T) { var ( smallerAddr = ids.ShortID{} largerAddr = ids.ShortID{1} @@ -251,72 +251,49 @@ func TestUTXOLess(t *testing.T) { name string utxo1 UTXO utxo2 UTXO - expected bool + expected int } tests := []test{ { name: "both empty", utxo1: UTXO{}, utxo2: UTXO{}, - expected: false, + expected: 0, }, { - name: "first locktime smaller", + name: "locktime smaller", utxo1: UTXO{}, utxo2: UTXO{ Locktime: 1, }, - expected: true, + expected: -1, }, { - name: "first locktime larger", - utxo1: UTXO{ - Locktime: 1, - }, - utxo2: UTXO{}, - expected: false, - }, - { - name: "first amount smaller", + name: "amount smaller", utxo1: UTXO{}, utxo2: UTXO{ Amount: 1, }, - expected: true, + expected: -1, }, { - name: "first amount larger", - utxo1: UTXO{ - Amount: 1, - }, - utxo2: UTXO{}, - expected: false, - }, - { - name: "first address smaller", + name: "address smaller", utxo1: UTXO{ Address: smallerAddrStr, }, utxo2: UTXO{ Address: largerAddrStr, }, - expected: true, - }, - { - name: "first address larger", - utxo1: UTXO{ - Address: largerAddrStr, - }, - utxo2: UTXO{ - Address: smallerAddrStr, - }, - expected: false, + expected: -1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.expected, tt.utxo1.Less(tt.utxo2)) + require := require.New(t) + + require.Equal(tt.expected, tt.utxo1.Compare(tt.utxo2)) + require.Equal(-tt.expected, tt.utxo2.Compare(tt.utxo1)) }) } } diff --git a/vms/platformvm/block/abort_block.go b/vms/platformvm/block/abort_block.go index cb8efcef144b..ace8087fd385 100644 --- a/vms/platformvm/block/abort_block.go +++ b/vms/platformvm/block/abort_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -43,7 +43,7 @@ func NewBanffAbortBlock( }, }, } - return blk, initialize(blk) + return blk, initialize(blk, &blk.CommonBlock) } type ApricotAbortBlock struct { @@ -78,5 +78,5 @@ func NewApricotAbortBlock( Hght: height, }, } - return blk, initialize(blk) + return blk, initialize(blk, &blk.CommonBlock) } diff --git a/vms/platformvm/block/abort_block_test.go b/vms/platformvm/block/abort_block_test.go index 149067aab8e4..a6517cef0137 100644 --- a/vms/platformvm/block/abort_block_test.go +++ b/vms/platformvm/block/abort_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/platformvm/block/atomic_block.go b/vms/platformvm/block/atomic_block.go index dddddaa43685..35deda80c0b5 100644 --- a/vms/platformvm/block/atomic_block.go +++ b/vms/platformvm/block/atomic_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -52,5 +52,5 @@ func NewApricotAtomicBlock( }, Tx: tx, } - return blk, initialize(blk) + return blk, initialize(blk, &blk.CommonBlock) } diff --git a/vms/platformvm/block/atomic_block_test.go b/vms/platformvm/block/atomic_block_test.go index 7436a0c24153..d81310184f23 100644 --- a/vms/platformvm/block/atomic_block_test.go +++ b/vms/platformvm/block/atomic_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/platformvm/block/block.go b/vms/platformvm/block/block.go index 933667a83f68..30be125b8b43 100644 --- a/vms/platformvm/block/block.go +++ b/vms/platformvm/block/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -36,12 +36,14 @@ type BanffBlock interface { Timestamp() time.Time } -func initialize(blk Block) error { +func initialize(blk Block, commonBlk *CommonBlock) error { // We serialize this block as a pointer so that it can be deserialized into // a Block - bytes, err := Codec.Marshal(Version, &blk) + bytes, err := Codec.Marshal(CodecVersion, &blk) if err != nil { return fmt.Errorf("couldn't marshal block: %w", err) } - return blk.initialize(bytes) + + commonBlk.initialize(bytes) + return nil } diff --git a/vms/platformvm/block/builder/builder.go b/vms/platformvm/block/builder/builder.go index b8476e19b032..09e99ba9ac3e 100644 --- a/vms/platformvm/block/builder/builder.go +++ b/vms/platformvm/block/builder/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package builder @@ -7,17 +7,19 @@ import ( "context" "errors" "fmt" + "sync" "time" "go.uber.org/zap" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/consensus/snowman" - "github.com/ava-labs/avalanchego/utils/timer" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/state" + "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" @@ -33,24 +35,38 @@ const targetBlockSize = 128 * units.KiB var ( _ Builder = (*builder)(nil) - ErrEndOfTime = errors.New("program time is suspiciously far in the future") - ErrNoPendingBlocks = errors.New("no pending blocks") + ErrEndOfTime = errors.New("program time is suspiciously far in the future") + ErrNoPendingBlocks = errors.New("no pending blocks") + errMissingPreferredState = errors.New("missing preferred block state") + errCalculatingNextStakerTime = errors.New("failed calculating next staker time") ) type Builder interface { mempool.Mempool - // ResetBlockTimer schedules a timer to notify the consensus engine once - // there is a block ready to be built. If a block is ready to be built when - // this function is called, the engine will be notified directly. + // StartBlockTimer starts to issue block creation requests to advance the + // chain timestamp. + StartBlockTimer() + + // ResetBlockTimer forces the block timer to recalculate when it should + // advance the chain timestamp. ResetBlockTimer() - // BuildBlock is called on timer clock to attempt to create - // next block + // ShutdownBlockTimer stops block creation requests to advance the chain + // timestamp. + // + // Invariant: Assumes the context lock is held when calling. + ShutdownBlockTimer() + + // BuildBlock can be called to attempt to create a new block BuildBlock(context.Context) (snowman.Block, error) - // Shutdown cleanly shuts Builder down - Shutdown() + // PackBlockTxs returns an array of txs that can fit into a valid block of + // size [targetBlockSize]. The returned txs are all verified against the + // preferred state. + // + // Note: This function does not call the consensus engine. + PackBlockTxs(targetBlockSize int) ([]*txs.Tx, error) } // builder implements a simple builder to convert txs into valid blocks @@ -61,10 +77,11 @@ type builder struct { txExecutorBackend *txexecutor.Backend blkManager blockexecutor.Manager - // This timer goes off when it is time for the next validator to add/leave - // the validator set. When it goes off ResetTimer() is called, potentially - // triggering creation of a new block. - timer *timer.Timer + // resetTimer is used to signal that the block builder timer should update + // when it will trigger building of a block. + resetTimer chan struct{} + closed chan struct{} + closeOnce sync.Once } func New( @@ -73,47 +90,123 @@ func New( txExecutorBackend *txexecutor.Backend, blkManager blockexecutor.Manager, ) Builder { - builder := &builder{ + return &builder{ Mempool: mempool, txBuilder: txBuilder, txExecutorBackend: txExecutorBackend, blkManager: blkManager, + resetTimer: make(chan struct{}, 1), + closed: make(chan struct{}), } - - builder.timer = timer.NewTimer(builder.setNextBuildBlockTime) - - go txExecutorBackend.Ctx.Log.RecoverAndPanic(builder.timer.Dispatch) - return builder } -// BuildBlock builds a block to be added to consensus. -// This method removes the transactions from the returned -// blocks from the mempool. -func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { - b.Mempool.DisableAdding() - defer func() { - b.Mempool.EnableAdding() - b.ResetBlockTimer() +func (b *builder) StartBlockTimer() { + go func() { + timer := time.NewTimer(0) + defer timer.Stop() + + for { + // Invariant: The [timer] is not stopped. + select { + case <-timer.C: + case <-b.resetTimer: + if !timer.Stop() { + <-timer.C + } + case <-b.closed: + return + } + + // Note: Because the context lock is not held here, it is possible + // that [ShutdownBlockTimer] is called concurrently with this + // execution. + for { + duration, err := b.durationToSleep() + if err != nil { + b.txExecutorBackend.Ctx.Log.Error("block builder encountered a fatal error", + zap.Error(err), + ) + return + } + + if duration > 0 { + timer.Reset(duration) + break + } + + // Block needs to be issued to advance time. + b.Mempool.RequestBuildBlock(true /*=emptyBlockPermitted*/) + + // Invariant: ResetBlockTimer is guaranteed to be called after + // [durationToSleep] returns a value <= 0. This is because we + // are guaranteed to attempt to build block. After building a + // valid block, the chain will have its preference updated which + // may change the duration to sleep and trigger a timer reset. + select { + case <-b.resetTimer: + case <-b.closed: + return + } + } + } }() +} + +func (b *builder) durationToSleep() (time.Duration, error) { + // Grabbing the lock here enforces that this function is not called mid-way + // through modifying of the state. + b.txExecutorBackend.Ctx.Lock.Lock() + defer b.txExecutorBackend.Ctx.Lock.Unlock() + + // If [ShutdownBlockTimer] was called, we want to exit the block timer + // goroutine. We check this with the context lock held because + // [ShutdownBlockTimer] is expected to only be called with the context lock + // held. + select { + case <-b.closed: + return 0, nil + default: + } - ctx := b.txExecutorBackend.Ctx - ctx.Log.Debug("starting to attempt to build a block") + preferredID := b.blkManager.Preferred() + preferredState, ok := b.blkManager.GetState(preferredID) + if !ok { + return 0, fmt.Errorf("%w: %s", errMissingPreferredState, preferredID) + } - statelessBlk, err := b.buildBlock() + nextStakerChangeTime, err := txexecutor.GetNextStakerChangeTime(preferredState) if err != nil { - return nil, err + return 0, fmt.Errorf("%w of %s: %w", errCalculatingNextStakerTime, preferredID, err) } - // Remove selected txs from mempool now that we are returning the block to - // the consensus engine. - txs := statelessBlk.Txs() - b.Mempool.Remove(txs) - return b.blkManager.NewBlock(statelessBlk), nil + now := b.txExecutorBackend.Clk.Time() + return nextStakerChangeTime.Sub(now), nil +} + +func (b *builder) ResetBlockTimer() { + // Ensure that the timer will be reset at least once. + select { + case b.resetTimer <- struct{}{}: + default: + } +} + +func (b *builder) ShutdownBlockTimer() { + b.closeOnce.Do(func() { + close(b.closed) + }) } -// Returns the block we want to build and issue. -// Only modifies state to remove expired proposal txs. -func (b *builder) buildBlock() (block.Block, error) { +// BuildBlock builds a block to be added to consensus. +// This method removes the transactions from the returned +// blocks from the mempool. +func (b *builder) BuildBlock(context.Context) (snowman.Block, error) { + // If there are still transactions in the mempool, then we need to + // re-trigger block building. + defer b.Mempool.RequestBuildBlock(false /*=emptyBlockPermitted*/) + + b.txExecutorBackend.Ctx.Log.Debug("starting to attempt to build a block") + // Get the block to build on top of and retrieve the new block's context. preferredID := b.blkManager.Preferred() preferred, err := b.blkManager.GetBlock(preferredID) @@ -126,29 +219,12 @@ func (b *builder) buildBlock() (block.Block, error) { return nil, fmt.Errorf("%w: %s", state.ErrMissingParentState, preferredID) } - timestamp := b.txExecutorBackend.Clk.Time() - if parentTime := preferred.Timestamp(); parentTime.After(timestamp) { - timestamp = parentTime - } - // [timestamp] = max(now, parentTime) - - nextStakerChangeTime, err := txexecutor.GetNextStakerChangeTime(preferredState) + timestamp, timeWasCapped, err := txexecutor.NextBlockTime(preferredState, b.txExecutorBackend.Clk) if err != nil { return nil, fmt.Errorf("could not calculate next staker change time: %w", err) } - // timeWasCapped means that [timestamp] was reduced to - // [nextStakerChangeTime]. It is used as a flag for [buildApricotBlock] to - // be willing to issue an advanceTimeTx. It is also used as a flag for - // [buildBanffBlock] to force the issuance of an empty block to advance - // the time forward; if there are no available transactions. - timeWasCapped := !timestamp.Before(nextStakerChangeTime) - if timeWasCapped { - timestamp = nextStakerChangeTime - } - // [timestamp] = min(max(now, parentTime), nextStakerChangeTime) - - return buildBlock( + statelessBlk, err := buildBlock( b, preferredID, nextHeight, @@ -156,75 +232,29 @@ func (b *builder) buildBlock() (block.Block, error) { timeWasCapped, preferredState, ) -} - -func (b *builder) Shutdown() { - // There is a potential deadlock if the timer is about to execute a timeout. - // So, the lock must be released before stopping the timer. - ctx := b.txExecutorBackend.Ctx - ctx.Lock.Unlock() - b.timer.Stop() - ctx.Lock.Lock() -} - -func (b *builder) ResetBlockTimer() { - // Next time the context lock is released, we can attempt to reset the block - // timer. - b.timer.SetTimeoutIn(0) -} - -func (b *builder) setNextBuildBlockTime() { - ctx := b.txExecutorBackend.Ctx - - // Grabbing the lock here enforces that this function is not called mid-way - // through modifying of the state. - ctx.Lock.Lock() - defer ctx.Lock.Unlock() - - if !b.txExecutorBackend.Bootstrapped.Get() { - ctx.Log.Verbo("skipping block timer reset", - zap.String("reason", "not bootstrapped"), - ) - return + if err != nil { + return nil, err } - if _, err := b.buildBlock(); err == nil { - // We can build a block now - b.Mempool.RequestBuildBlock(true /*=emptyBlockPermitted*/) - return - } + return b.blkManager.NewBlock(statelessBlk), nil +} - // Wake up when it's time to add/remove the next validator/delegator +func (b *builder) PackBlockTxs(targetBlockSize int) ([]*txs.Tx, error) { preferredID := b.blkManager.Preferred() preferredState, ok := b.blkManager.GetState(preferredID) if !ok { - // The preferred block should always be a decision block - ctx.Log.Error("couldn't get preferred block state", - zap.Stringer("preferredID", preferredID), - zap.Stringer("lastAcceptedID", b.blkManager.LastAccepted()), - ) - return - } - - nextStakerChangeTime, err := txexecutor.GetNextStakerChangeTime(preferredState) - if err != nil { - ctx.Log.Error("couldn't get next staker change time", - zap.Stringer("preferredID", preferredID), - zap.Stringer("lastAcceptedID", b.blkManager.LastAccepted()), - zap.Error(err), - ) - return + return nil, fmt.Errorf("%w: %s", errMissingPreferredState, preferredID) } - now := b.txExecutorBackend.Clk.Time() - waitTime := nextStakerChangeTime.Sub(now) - ctx.Log.Debug("setting next scheduled event", - zap.Time("nextEventTime", nextStakerChangeTime), - zap.Duration("timeUntil", waitTime), + return packBlockTxs( + preferredID, + preferredState, + b.Mempool, + b.txExecutorBackend, + b.blkManager, + b.txExecutorBackend.Clk.Time(), + targetBlockSize, ) - - // Wake up when it's time to add/remove the next validator - b.timer.SetTimeoutIn(waitTime) } // [timestamp] is min(max(now, parent timestamp), next staker change time) @@ -249,25 +279,47 @@ func buildBlock( return nil, fmt.Errorf("could not build tx to reward staker: %w", err) } + var blockTxs []*txs.Tx + // TODO: Cleanup post-Durango + if builder.txExecutorBackend.Config.IsDurangoActivated(timestamp) { + blockTxs, err = packBlockTxs( + parentID, + parentState, + builder.Mempool, + builder.txExecutorBackend, + builder.blkManager, + timestamp, + targetBlockSize, + ) + if err != nil { + return nil, fmt.Errorf("failed to pack block txs: %w", err) + } + } + return block.NewBanffProposalBlock( timestamp, parentID, height, rewardValidatorTx, + blockTxs, ) } - // Clean out the mempool's transactions with invalid timestamps. - droppedStakerTxIDs := builder.Mempool.DropExpiredStakerTxs(timestamp.Add(txexecutor.SyncBound)) - for _, txID := range droppedStakerTxIDs { - builder.txExecutorBackend.Ctx.Log.Debug("dropping tx", - zap.Stringer("txID", txID), - zap.Error(err), - ) + blockTxs, err := packBlockTxs( + parentID, + parentState, + builder.Mempool, + builder.txExecutorBackend, + builder.blkManager, + timestamp, + targetBlockSize, + ) + if err != nil { + return nil, fmt.Errorf("failed to pack block txs: %w", err) } // If there is no reason to build a block, don't. - if !builder.Mempool.HasTxs() && !forceAdvanceTime { + if len(blockTxs) == 0 && !forceAdvanceTime { builder.txExecutorBackend.Ctx.Log.Debug("no pending txs to issue into a block") return nil, ErrNoPendingBlocks } @@ -277,10 +329,93 @@ func buildBlock( timestamp, parentID, height, - builder.Mempool.PeekTxs(targetBlockSize), + blockTxs, ) } +func packBlockTxs( + parentID ids.ID, + parentState state.Chain, + mempool mempool.Mempool, + backend *txexecutor.Backend, + manager blockexecutor.Manager, + timestamp time.Time, + remainingSize int, +) ([]*txs.Tx, error) { + stateDiff, err := state.NewDiffOn(parentState) + if err != nil { + return nil, err + } + + changes, err := txexecutor.AdvanceTimeTo(backend, stateDiff, timestamp) + if err != nil { + return nil, err + } + changes.Apply(stateDiff) + stateDiff.SetTimestamp(timestamp) + + var ( + blockTxs []*txs.Tx + inputs set.Set[ids.ID] + ) + + for { + tx, exists := mempool.Peek() + if !exists { + break + } + txSize := len(tx.Bytes()) + if txSize > remainingSize { + break + } + mempool.Remove(tx) + + // Invariant: [tx] has already been syntactically verified. + + txDiff, err := state.NewDiffOn(stateDiff) + if err != nil { + return nil, err + } + + executor := &txexecutor.StandardTxExecutor{ + Backend: backend, + State: txDiff, + Tx: tx, + } + + err = tx.Unsigned.Visit(executor) + if err != nil { + txID := tx.ID() + mempool.MarkDropped(txID, err) + continue + } + + if inputs.Overlaps(executor.Inputs) { + txID := tx.ID() + mempool.MarkDropped(txID, blockexecutor.ErrConflictingBlockTxs) + continue + } + err = manager.VerifyUniqueInputs(parentID, executor.Inputs) + if err != nil { + txID := tx.ID() + mempool.MarkDropped(txID, err) + continue + } + inputs.Union(executor.Inputs) + + txDiff.AddTx(tx, status.Committed) + err = txDiff.Apply(stateDiff) + if err != nil { + return nil, err + } + + remainingSize -= txSize + blockTxs = append(blockTxs, tx) + } + + return blockTxs, nil +} + // getNextStakerToReward returns the next staker txID to remove from the staking // set with a RewardValidatorTx rather than an AdvanceTimeTx. [chainTimestamp] // is the timestamp of the chain at the time this validator would be getting diff --git a/vms/platformvm/block/builder/builder_test.go b/vms/platformvm/block/builder/builder_test.go index 04abf5d65b71..0dc804ff810d 100644 --- a/vms/platformvm/block/builder/builder_test.go +++ b/vms/platformvm/block/builder/builder_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package builder @@ -14,106 +14,529 @@ import ( "go.uber.org/mock/gomock" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/consensus/snowman" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/avalanchego/vms/components/avax" - "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/block" + "github.com/ava-labs/avalanchego/vms/platformvm/reward" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" - "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" - "github.com/ava-labs/avalanchego/vms/secp256k1fx" blockexecutor "github.com/ava-labs/avalanchego/vms/platformvm/block/executor" - txbuilder "github.com/ava-labs/avalanchego/vms/platformvm/txs/builder" txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" ) -var errTestingDropped = errors.New("testing dropped") +func TestBuildBlockBasic(t *testing.T) { + require := require.New(t) + + env := newEnvironment(t) + env.ctx.Lock.Lock() + defer func() { + require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() + }() + + // Create a valid transaction + tx, err := env.txBuilder.NewCreateChainTx( + testSubnet1.ID(), + nil, + constants.AVMID, + nil, + "chain name", + []*secp256k1.PrivateKey{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + ids.ShortEmpty, + ) + require.NoError(err) + txID := tx.ID() + + // Issue the transaction + env.ctx.Lock.Unlock() + require.NoError(env.network.IssueTx(context.Background(), tx)) + env.ctx.Lock.Lock() + _, ok := env.mempool.Get(txID) + require.True(ok) + + // [BuildBlock] should build a block with the transaction + blkIntf, err := env.Builder.BuildBlock(context.Background()) + require.NoError(err) + + require.IsType(&blockexecutor.Block{}, blkIntf) + blk := blkIntf.(*blockexecutor.Block) + require.Len(blk.Txs(), 1) + require.Equal(txID, blk.Txs()[0].ID()) + + // Mempool should not contain the transaction or have marked it as dropped + _, ok = env.mempool.Get(txID) + require.False(ok) + require.NoError(env.mempool.GetDropReason(txID)) +} -// shows that a locally generated CreateChainTx can be added to mempool and then -// removed by inclusion in a block -func TestBlockBuilderAddLocalTx(t *testing.T) { +func TestBuildBlockDoesNotBuildWithEmptyMempool(t *testing.T) { require := require.New(t) env := newEnvironment(t) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() }() - // add a tx to it - tx := getValidTx(env.txBuilder, t) + tx, exists := env.mempool.Peek() + require.False(exists) + require.Nil(tx) + + // [BuildBlock] should not build an empty block + blk, err := env.Builder.BuildBlock(context.Background()) + require.ErrorIs(err, ErrNoPendingBlocks) + require.Nil(blk) +} + +func TestBuildBlockShouldReward(t *testing.T) { + require := require.New(t) + + env := newEnvironment(t) + env.ctx.Lock.Lock() + defer func() { + require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() + }() + + var ( + now = env.backend.Clk.Time() + nodeID = ids.GenerateTestNodeID() + + defaultValidatorStake = 100 * units.MilliAvax + validatorStartTime = now.Add(2 * txexecutor.SyncBound) + validatorEndTime = validatorStartTime.Add(360 * 24 * time.Hour) + ) + + // Create a valid [AddValidatorTx] + tx, err := env.txBuilder.NewAddValidatorTx( + defaultValidatorStake, + uint64(validatorStartTime.Unix()), + uint64(validatorEndTime.Unix()), + nodeID, + preFundedKeys[0].PublicKey().Address(), + reward.PercentDenominator, + []*secp256k1.PrivateKey{preFundedKeys[0]}, + preFundedKeys[0].PublicKey().Address(), + ) + require.NoError(err) txID := tx.ID() - env.sender.SendAppGossipF = func(context.Context, []byte) error { - return nil + // Issue the transaction + env.ctx.Lock.Unlock() + require.NoError(env.network.IssueTx(context.Background(), tx)) + env.ctx.Lock.Lock() + _, ok := env.mempool.Get(txID) + require.True(ok) + + // Build and accept a block with the tx + blk, err := env.Builder.BuildBlock(context.Background()) + require.NoError(err) + require.IsType(&block.BanffStandardBlock{}, blk.(*blockexecutor.Block).Block) + require.Equal([]*txs.Tx{tx}, blk.(*blockexecutor.Block).Block.Txs()) + require.NoError(blk.Verify(context.Background())) + require.NoError(blk.Accept(context.Background())) + require.True(env.blkManager.SetPreference(blk.ID())) + + // Validator should now be current + staker, err := env.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) + require.NoError(err) + require.Equal(txID, staker.TxID) + + // Should be rewarded at the end of staking period + env.backend.Clk.Set(validatorEndTime) + + for { + iter, err := env.state.GetCurrentStakerIterator() + require.NoError(err) + require.True(iter.Next()) + staker := iter.Value() + iter.Release() + + // Check that the right block was built + blk, err := env.Builder.BuildBlock(context.Background()) + require.NoError(err) + require.NoError(blk.Verify(context.Background())) + require.IsType(&block.BanffProposalBlock{}, blk.(*blockexecutor.Block).Block) + + expectedTx, err := env.txBuilder.NewRewardValidatorTx(staker.TxID) + require.NoError(err) + require.Equal([]*txs.Tx{expectedTx}, blk.(*blockexecutor.Block).Block.Txs()) + + // Commit the [ProposalBlock] with a [CommitBlock] + proposalBlk, ok := blk.(snowman.OracleBlock) + require.True(ok) + options, err := proposalBlk.Options(context.Background()) + require.NoError(err) + + commit := options[0].(*blockexecutor.Block) + require.IsType(&block.BanffCommitBlock{}, commit.Block) + + require.NoError(blk.Accept(context.Background())) + require.NoError(commit.Verify(context.Background())) + require.NoError(commit.Accept(context.Background())) + require.True(env.blkManager.SetPreference(commit.ID())) + + // Stop rewarding once our staker is rewarded + if staker.TxID == txID { + break + } } + + // Staking rewards should have been issued + rewardUTXOs, err := env.state.GetRewardUTXOs(txID) + require.NoError(err) + require.NotEmpty(rewardUTXOs) +} + +func TestBuildBlockAdvanceTime(t *testing.T) { + require := require.New(t) + + env := newEnvironment(t) + env.ctx.Lock.Lock() + defer func() { + require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() + }() + + var ( + now = env.backend.Clk.Time() + nextTime = now.Add(2 * txexecutor.SyncBound) + ) + + // Add a staker to [env.state] + env.state.PutCurrentValidator(&state.Staker{ + NextTime: nextTime, + Priority: txs.PrimaryNetworkValidatorCurrentPriority, + }) + + // Advance wall clock to [nextTime] + env.backend.Clk.Set(nextTime) + + // [BuildBlock] should build a block advancing the time to [NextTime] + blkIntf, err := env.Builder.BuildBlock(context.Background()) + require.NoError(err) + + require.IsType(&blockexecutor.Block{}, blkIntf) + blk := blkIntf.(*blockexecutor.Block) + require.Empty(blk.Txs()) + require.IsType(&block.BanffStandardBlock{}, blk.Block) + standardBlk := blk.Block.(*block.BanffStandardBlock) + require.Equal(nextTime.Unix(), standardBlk.Timestamp().Unix()) +} + +func TestBuildBlockForceAdvanceTime(t *testing.T) { + require := require.New(t) + + env := newEnvironment(t) + env.ctx.Lock.Lock() + defer func() { + require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() + }() + + // Create a valid transaction + tx, err := env.txBuilder.NewCreateChainTx( + testSubnet1.ID(), + nil, + constants.AVMID, + nil, + "chain name", + []*secp256k1.PrivateKey{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + ids.ShortEmpty, + ) + require.NoError(err) + txID := tx.ID() + + // Issue the transaction + env.ctx.Lock.Unlock() require.NoError(env.network.IssueTx(context.Background(), tx)) - require.True(env.mempool.Has(txID)) + env.ctx.Lock.Lock() + _, ok := env.mempool.Get(txID) + require.True(ok) + + var ( + now = env.backend.Clk.Time() + nextTime = now.Add(2 * txexecutor.SyncBound) + ) + + // Add a staker to [env.state] + env.state.PutCurrentValidator(&state.Staker{ + NextTime: nextTime, + Priority: txs.PrimaryNetworkValidatorCurrentPriority, + }) + + // Advance wall clock to [nextTime] + [txexecutor.SyncBound] + env.backend.Clk.Set(nextTime.Add(txexecutor.SyncBound)) + + // [BuildBlock] should build a block advancing the time to [nextTime], + // not the current wall clock. + blkIntf, err := env.Builder.BuildBlock(context.Background()) + require.NoError(err) + + require.IsType(&blockexecutor.Block{}, blkIntf) + blk := blkIntf.(*blockexecutor.Block) + require.Equal([]*txs.Tx{tx}, blk.Txs()) + require.IsType(&block.BanffStandardBlock{}, blk.Block) + standardBlk := blk.Block.(*block.BanffStandardBlock) + require.Equal(nextTime.Unix(), standardBlk.Timestamp().Unix()) +} + +func TestBuildBlockDropExpiredStakerTxs(t *testing.T) { + require := require.New(t) + + env := newEnvironment(t) + env.ctx.Lock.Lock() + defer func() { + require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() + }() + + // The [StartTime] in a staker tx is only validated pre-Durango. + // TODO: Delete this test post-Durango activation. + env.config.DurangoTime = mockable.MaxTime + + var ( + now = env.backend.Clk.Time() + defaultValidatorStake = 100 * units.MilliAvax + + // Add a validator with StartTime in the future within [MaxFutureStartTime] + validatorStartTime = now.Add(txexecutor.MaxFutureStartTime - 1*time.Second) + validatorEndTime = validatorStartTime.Add(360 * 24 * time.Hour) + ) - // show that build block include that tx and removes it from mempool + tx1, err := env.txBuilder.NewAddValidatorTx( + defaultValidatorStake, + uint64(validatorStartTime.Unix()), + uint64(validatorEndTime.Unix()), + ids.GenerateTestNodeID(), + preFundedKeys[0].PublicKey().Address(), + reward.PercentDenominator, + []*secp256k1.PrivateKey{preFundedKeys[0]}, + preFundedKeys[0].PublicKey().Address(), + ) + require.NoError(err) + require.NoError(env.mempool.Add(tx1)) + tx1ID := tx1.ID() + _, ok := env.mempool.Get(tx1ID) + require.True(ok) + + // Add a validator with StartTime before current chain time + validator2StartTime := now.Add(-5 * time.Second) + validator2EndTime := validator2StartTime.Add(360 * 24 * time.Hour) + + tx2, err := env.txBuilder.NewAddValidatorTx( + defaultValidatorStake, + uint64(validator2StartTime.Unix()), + uint64(validator2EndTime.Unix()), + ids.GenerateTestNodeID(), + preFundedKeys[1].PublicKey().Address(), + reward.PercentDenominator, + []*secp256k1.PrivateKey{preFundedKeys[1]}, + preFundedKeys[1].PublicKey().Address(), + ) + require.NoError(err) + require.NoError(env.mempool.Add(tx2)) + tx2ID := tx2.ID() + _, ok = env.mempool.Get(tx2ID) + require.True(ok) + + // Add a validator with StartTime in the future past [MaxFutureStartTime] + validator3StartTime := now.Add(txexecutor.MaxFutureStartTime + 5*time.Second) + validator3EndTime := validator2StartTime.Add(360 * 24 * time.Hour) + + tx3, err := env.txBuilder.NewAddValidatorTx( + defaultValidatorStake, + uint64(validator3StartTime.Unix()), + uint64(validator3EndTime.Unix()), + ids.GenerateTestNodeID(), + preFundedKeys[2].PublicKey().Address(), + reward.PercentDenominator, + []*secp256k1.PrivateKey{preFundedKeys[2]}, + preFundedKeys[2].PublicKey().Address(), + ) + require.NoError(err) + require.NoError(env.mempool.Add(tx3)) + tx3ID := tx3.ID() + _, ok = env.mempool.Get(tx3ID) + require.True(ok) + + // Only tx1 should be in a built block blkIntf, err := env.Builder.BuildBlock(context.Background()) require.NoError(err) require.IsType(&blockexecutor.Block{}, blkIntf) blk := blkIntf.(*blockexecutor.Block) require.Len(blk.Txs(), 1) - require.Equal(txID, blk.Txs()[0].ID()) + require.Equal(tx1ID, blk.Txs()[0].ID()) + + // Mempool should have none of the txs + _, ok = env.mempool.Get(tx1ID) + require.False(ok) + _, ok = env.mempool.Get(tx2ID) + require.False(ok) + _, ok = env.mempool.Get(tx3ID) + require.False(ok) - require.False(env.mempool.Has(txID)) + // Only tx2 and tx3 should be dropped + require.NoError(env.mempool.GetDropReason(tx1ID)) + + tx2DropReason := env.mempool.GetDropReason(tx2ID) + require.ErrorIs(tx2DropReason, txexecutor.ErrTimestampNotBeforeStartTime) + + tx3DropReason := env.mempool.GetDropReason(tx3ID) + require.ErrorIs(tx3DropReason, txexecutor.ErrFutureStakeTime) } -func TestPreviouslyDroppedTxsCanBeReAddedToMempool(t *testing.T) { +func TestBuildBlockInvalidStakingDurations(t *testing.T) { require := require.New(t) env := newEnvironment(t) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() }() - // create candidate tx - tx := getValidTx(env.txBuilder, t) + // Post-Durango, [StartTime] is no longer validated. Staking durations are + // based on the current chain timestamp and must be validated. + env.config.DurangoTime = time.Time{} + + var ( + now = env.backend.Clk.Time() + defaultValidatorStake = 100 * units.MilliAvax + + // Add a validator ending in [MaxStakeDuration] + validatorEndTime = now.Add(env.config.MaxStakeDuration) + ) + + tx1, err := env.txBuilder.NewAddValidatorTx( + defaultValidatorStake, + uint64(now.Unix()), + uint64(validatorEndTime.Unix()), + ids.GenerateTestNodeID(), + preFundedKeys[0].PublicKey().Address(), + reward.PercentDenominator, + []*secp256k1.PrivateKey{preFundedKeys[0]}, + preFundedKeys[0].PublicKey().Address(), + ) + require.NoError(err) + require.NoError(env.mempool.Add(tx1)) + tx1ID := tx1.ID() + _, ok := env.mempool.Get(tx1ID) + require.True(ok) + + // Add a validator ending past [MaxStakeDuration] + validator2EndTime := now.Add(env.config.MaxStakeDuration + time.Second) + + tx2, err := env.txBuilder.NewAddValidatorTx( + defaultValidatorStake, + uint64(now.Unix()), + uint64(validator2EndTime.Unix()), + ids.GenerateTestNodeID(), + preFundedKeys[2].PublicKey().Address(), + reward.PercentDenominator, + []*secp256k1.PrivateKey{preFundedKeys[2]}, + preFundedKeys[2].PublicKey().Address(), + ) + require.NoError(err) + require.NoError(env.mempool.Add(tx2)) + tx2ID := tx2.ID() + _, ok = env.mempool.Get(tx2ID) + require.True(ok) + + // Only tx1 should be in a built block since [MaxStakeDuration] is satisfied. + blkIntf, err := env.Builder.BuildBlock(context.Background()) + require.NoError(err) + + require.IsType(&blockexecutor.Block{}, blkIntf) + blk := blkIntf.(*blockexecutor.Block) + require.Len(blk.Txs(), 1) + require.Equal(tx1ID, blk.Txs()[0].ID()) + + // Mempool should have none of the txs + _, ok = env.mempool.Get(tx1ID) + require.False(ok) + _, ok = env.mempool.Get(tx2ID) + require.False(ok) + + // Only tx2 should be dropped + require.NoError(env.mempool.GetDropReason(tx1ID)) + + tx2DropReason := env.mempool.GetDropReason(tx2ID) + require.ErrorIs(tx2DropReason, txexecutor.ErrStakeTooLong) +} + +func TestPreviouslyDroppedTxsCannotBeReAddedToMempool(t *testing.T) { + require := require.New(t) + + env := newEnvironment(t) + env.ctx.Lock.Lock() + defer func() { + env.ctx.Lock.Lock() + require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() + }() + + // Create a valid transaction + tx, err := env.txBuilder.NewCreateChainTx( + testSubnet1.ID(), + nil, + constants.AVMID, + nil, + "chain name", + []*secp256k1.PrivateKey{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, + ids.ShortEmpty, + ) + require.NoError(err) txID := tx.ID() - // A tx simply added to mempool is obviously not marked as dropped - require.NoError(env.mempool.Add(tx)) - require.True(env.mempool.Has(txID)) - reason := env.mempool.GetDropReason(txID) - require.NoError(reason) + // Transaction should not be marked as dropped before being added to the + // mempool + require.NoError(env.mempool.GetDropReason(txID)) - // When a tx is marked as dropped, it is still available to allow re-issuance + // Mark the transaction as dropped + errTestingDropped := errors.New("testing dropped") env.mempool.MarkDropped(txID, errTestingDropped) - require.True(env.mempool.Has(txID)) // still available - reason = env.mempool.GetDropReason(txID) - require.ErrorIs(reason, errTestingDropped) - - // A previously dropped tx, popped then re-added to mempool, - // is not dropped anymore - env.mempool.Remove([]*txs.Tx{tx}) - require.NoError(env.mempool.Add(tx)) - - require.True(env.mempool.Has(txID)) - reason = env.mempool.GetDropReason(txID) - require.NoError(reason) + err = env.mempool.GetDropReason(txID) + require.ErrorIs(err, errTestingDropped) + + // Issue the transaction + env.ctx.Lock.Unlock() + err = env.network.IssueTx(context.Background(), tx) + require.ErrorIs(err, errTestingDropped) + _, ok := env.mempool.Get(txID) + require.False(ok) + + // When issued again, the mempool should still be marked as dropped + err = env.mempool.GetDropReason(txID) + require.ErrorIs(err, errTestingDropped) } func TestNoErrorOnUnexpectedSetPreferenceDuringBootstrapping(t *testing.T) { + require := require.New(t) + env := newEnvironment(t) env.ctx.Lock.Lock() env.isBootstrapped.Set(false) - env.ctx.Log = logging.NoWarn{} defer func() { - require.NoError(t, shutdownEnvironment(env)) + require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() }() - require.False(t, env.blkManager.SetPreference(ids.GenerateTestID())) // should not panic + require.True(env.blkManager.SetPreference(ids.GenerateTestID())) // should not panic } func TestGetNextStakerToReward(t *testing.T) { + var ( + now = time.Now() + txID = ids.GenerateTestID() + ) + type test struct { name string timestamp time.Time @@ -123,10 +546,6 @@ func TestGetNextStakerToReward(t *testing.T) { expectedErr error } - var ( - now = time.Now() - txID = ids.GenerateTestID() - ) tests := []test{ { name: "end of time", @@ -293,386 +712,3 @@ func TestGetNextStakerToReward(t *testing.T) { }) } } - -func TestBuildBlock(t *testing.T) { - var ( - parentID = ids.GenerateTestID() - height = uint64(1337) - output = &avax.TransferableOutput{ - Asset: avax.Asset{ID: ids.GenerateTestID()}, - Out: &secp256k1fx.TransferOutput{ - OutputOwners: secp256k1fx.OutputOwners{ - Addrs: []ids.ShortID{ids.GenerateTestShortID()}, - }, - }, - } - now = time.Now() - parentTimestamp = now.Add(-2 * time.Second) - transactions = []*txs.Tx{{ - Unsigned: &txs.AddValidatorTx{ - BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ - Ins: []*avax.TransferableInput{{ - Asset: avax.Asset{ID: ids.GenerateTestID()}, - In: &secp256k1fx.TransferInput{ - Input: secp256k1fx.Input{ - SigIndices: []uint32{0}, - }, - }, - }}, - Outs: []*avax.TransferableOutput{output}, - }}, - Validator: txs.Validator{ - // Shouldn't be dropped - Start: uint64(now.Add(2 * txexecutor.SyncBound).Unix()), - }, - StakeOuts: []*avax.TransferableOutput{output}, - RewardsOwner: &secp256k1fx.OutputOwners{ - Addrs: []ids.ShortID{ids.GenerateTestShortID()}, - }, - }, - Creds: []verify.Verifiable{ - &secp256k1fx.Credential{ - Sigs: [][secp256k1.SignatureLen]byte{{1, 3, 3, 7}}, - }, - }, - }} - stakerTxID = ids.GenerateTestID() - ) - - type test struct { - name string - builderF func(*gomock.Controller) *builder - timestamp time.Time - forceAdvanceTime bool - parentStateF func(*gomock.Controller) state.Chain - expectedBlkF func(*require.Assertions) block.Block - expectedErr error - } - - tests := []test{ - { - name: "should reward", - builderF: func(ctrl *gomock.Controller) *builder { - mempool := mempool.NewMockMempool(ctrl) - - // The tx builder should be asked to build a reward tx - txBuilder := txbuilder.NewMockBuilder(ctrl) - txBuilder.EXPECT().NewRewardValidatorTx(stakerTxID).Return(transactions[0], nil) - - return &builder{ - Mempool: mempool, - txBuilder: txBuilder, - } - }, - timestamp: parentTimestamp, - forceAdvanceTime: false, - parentStateF: func(ctrl *gomock.Controller) state.Chain { - s := state.NewMockChain(ctrl) - - // add current validator that ends at [parentTimestamp] - // i.e. it should be rewarded - currentStakerIter := state.NewMockStakerIterator(ctrl) - currentStakerIter.EXPECT().Next().Return(true) - currentStakerIter.EXPECT().Value().Return(&state.Staker{ - TxID: stakerTxID, - Priority: txs.PrimaryNetworkDelegatorCurrentPriority, - EndTime: parentTimestamp, - }) - currentStakerIter.EXPECT().Release() - - s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil) - return s - }, - expectedBlkF: func(require *require.Assertions) block.Block { - expectedBlk, err := block.NewBanffProposalBlock( - parentTimestamp, - parentID, - height, - transactions[0], - ) - require.NoError(err) - return expectedBlk - }, - expectedErr: nil, - }, - { - name: "has decision txs", - builderF: func(ctrl *gomock.Controller) *builder { - mempool := mempool.NewMockMempool(ctrl) - - // There are txs. - mempool.EXPECT().DropExpiredStakerTxs(gomock.Any()).Return([]ids.ID{}) - mempool.EXPECT().HasTxs().Return(true) - mempool.EXPECT().PeekTxs(targetBlockSize).Return(transactions) - return &builder{ - Mempool: mempool, - } - }, - timestamp: parentTimestamp, - forceAdvanceTime: false, - parentStateF: func(ctrl *gomock.Controller) state.Chain { - s := state.NewMockChain(ctrl) - - // Handle calls in [getNextStakerToReward] - // and [GetNextStakerChangeTime]. - // Next validator change time is in the future. - currentStakerIter := state.NewMockStakerIterator(ctrl) - gomock.InOrder( - // expect calls from [getNextStakerToReward] - currentStakerIter.EXPECT().Next().Return(true), - currentStakerIter.EXPECT().Value().Return(&state.Staker{ - NextTime: now.Add(time.Second), - Priority: txs.PrimaryNetworkDelegatorCurrentPriority, - }), - currentStakerIter.EXPECT().Release(), - ) - - s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil).Times(1) - return s - }, - expectedBlkF: func(require *require.Assertions) block.Block { - expectedBlk, err := block.NewBanffStandardBlock( - parentTimestamp, - parentID, - height, - transactions, - ) - require.NoError(err) - return expectedBlk - }, - expectedErr: nil, - }, - { - name: "no stakers tx", - builderF: func(ctrl *gomock.Controller) *builder { - mempool := mempool.NewMockMempool(ctrl) - - // There are no txs. - mempool.EXPECT().DropExpiredStakerTxs(gomock.Any()).Return([]ids.ID{}) - mempool.EXPECT().HasTxs().Return(false) - - clk := &mockable.Clock{} - clk.Set(now) - return &builder{ - Mempool: mempool, - txExecutorBackend: &txexecutor.Backend{ - Ctx: &snow.Context{ - Log: logging.NoLog{}, - }, - Clk: clk, - }, - } - }, - timestamp: parentTimestamp, - forceAdvanceTime: false, - parentStateF: func(ctrl *gomock.Controller) state.Chain { - s := state.NewMockChain(ctrl) - - // Handle calls in [getNextStakerToReward] - // and [GetNextStakerChangeTime]. - // Next validator change time is in the future. - currentStakerIter := state.NewMockStakerIterator(ctrl) - gomock.InOrder( - // expect calls from [getNextStakerToReward] - currentStakerIter.EXPECT().Next().Return(true), - currentStakerIter.EXPECT().Value().Return(&state.Staker{ - NextTime: now.Add(time.Second), - Priority: txs.PrimaryNetworkDelegatorCurrentPriority, - }), - currentStakerIter.EXPECT().Release(), - ) - - s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil).Times(1) - return s - }, - expectedBlkF: func(*require.Assertions) block.Block { - return nil - }, - expectedErr: ErrNoPendingBlocks, - }, - { - name: "should advance time", - builderF: func(ctrl *gomock.Controller) *builder { - mempool := mempool.NewMockMempool(ctrl) - - // There are no txs. - mempool.EXPECT().DropExpiredStakerTxs(gomock.Any()).Return([]ids.ID{}) - mempool.EXPECT().HasTxs().Return(false) - mempool.EXPECT().PeekTxs(targetBlockSize).Return(nil) - - clk := &mockable.Clock{} - clk.Set(now) - return &builder{ - Mempool: mempool, - txExecutorBackend: &txexecutor.Backend{ - Clk: clk, - }, - } - }, - timestamp: now.Add(-1 * time.Second), - forceAdvanceTime: true, - parentStateF: func(ctrl *gomock.Controller) state.Chain { - s := state.NewMockChain(ctrl) - - // add current validator that ends at [now] - 1 second. - // That is, it ends in the past but after the current chain time. - // Handle calls in [getNextStakerToReward] - // and [GetNextStakerChangeTime] - // when determining whether to issue a reward tx. - currentStakerIter := state.NewMockStakerIterator(ctrl) - gomock.InOrder( - // expect calls from [getNextStakerToReward] - currentStakerIter.EXPECT().Next().Return(true), - currentStakerIter.EXPECT().Value().Return(&state.Staker{ - NextTime: now.Add(-1 * time.Second), - Priority: txs.PrimaryNetworkDelegatorCurrentPriority, - }), - currentStakerIter.EXPECT().Release(), - ) - - s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil).Times(1) - return s - }, - expectedBlkF: func(require *require.Assertions) block.Block { - expectedBlk, err := block.NewBanffStandardBlock( - now.Add(-1*time.Second), // note the advanced time - parentID, - height, - nil, // empty block to advance time - ) - require.NoError(err) - return expectedBlk - }, - expectedErr: nil, - }, - { - name: "has a staker tx no force", - builderF: func(ctrl *gomock.Controller) *builder { - mempool := mempool.NewMockMempool(ctrl) - - // There is a tx. - mempool.EXPECT().DropExpiredStakerTxs(gomock.Any()).Return([]ids.ID{}) - mempool.EXPECT().HasTxs().Return(true) - mempool.EXPECT().PeekTxs(targetBlockSize).Return([]*txs.Tx{transactions[0]}) - - clk := &mockable.Clock{} - clk.Set(now) - return &builder{ - Mempool: mempool, - txExecutorBackend: &txexecutor.Backend{ - Clk: clk, - }, - } - }, - timestamp: parentTimestamp, - forceAdvanceTime: false, - parentStateF: func(ctrl *gomock.Controller) state.Chain { - s := state.NewMockChain(ctrl) - - // Handle calls in [getNextStakerToReward] - // and [GetNextStakerChangeTime]. - // Next validator change time is in the future. - currentStakerIter := state.NewMockStakerIterator(ctrl) - gomock.InOrder( - // expect calls from [getNextStakerToReward] - currentStakerIter.EXPECT().Next().Return(true), - currentStakerIter.EXPECT().Value().Return(&state.Staker{ - NextTime: now.Add(time.Second), - Priority: txs.PrimaryNetworkDelegatorCurrentPriority, - }), - currentStakerIter.EXPECT().Release(), - ) - - s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil).Times(1) - return s - }, - expectedBlkF: func(require *require.Assertions) block.Block { - expectedBlk, err := block.NewBanffStandardBlock( - parentTimestamp, - parentID, - height, - []*txs.Tx{transactions[0]}, - ) - require.NoError(err) - return expectedBlk - }, - expectedErr: nil, - }, - { - name: "has a staker tx with force", - builderF: func(ctrl *gomock.Controller) *builder { - mempool := mempool.NewMockMempool(ctrl) - - // There are no decision txs - // There is a staker tx. - mempool.EXPECT().DropExpiredStakerTxs(gomock.Any()).Return([]ids.ID{}) - mempool.EXPECT().HasTxs().Return(true) - mempool.EXPECT().PeekTxs(targetBlockSize).Return([]*txs.Tx{transactions[0]}) - - clk := &mockable.Clock{} - clk.Set(now) - return &builder{ - Mempool: mempool, - txExecutorBackend: &txexecutor.Backend{ - Clk: clk, - }, - } - }, - timestamp: parentTimestamp, - forceAdvanceTime: true, - parentStateF: func(ctrl *gomock.Controller) state.Chain { - s := state.NewMockChain(ctrl) - - // Handle calls in [getNextStakerToReward] - // and [GetNextStakerChangeTime]. - // Next validator change time is in the future. - currentStakerIter := state.NewMockStakerIterator(ctrl) - gomock.InOrder( - // expect calls from [getNextStakerToReward] - currentStakerIter.EXPECT().Next().Return(true), - currentStakerIter.EXPECT().Value().Return(&state.Staker{ - NextTime: now.Add(time.Second), - Priority: txs.PrimaryNetworkDelegatorCurrentPriority, - }), - currentStakerIter.EXPECT().Release(), - ) - - s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil).Times(1) - return s - }, - expectedBlkF: func(require *require.Assertions) block.Block { - expectedBlk, err := block.NewBanffStandardBlock( - parentTimestamp, - parentID, - height, - []*txs.Tx{transactions[0]}, - ) - require.NoError(err) - return expectedBlk - }, - expectedErr: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - - gotBlk, err := buildBlock( - tt.builderF(ctrl), - parentID, - height, - tt.timestamp, - tt.forceAdvanceTime, - tt.parentStateF(ctrl), - ) - if tt.expectedErr != nil { - require.ErrorIs(err, tt.expectedErr) - return - } - require.NoError(err) - require.Equal(tt.expectedBlkF(require), gotBlk) - }) - } -} diff --git a/vms/platformvm/block/builder/helpers_test.go b/vms/platformvm/block/builder/helpers_test.go index de37d08ff0dd..fa7339be6fdb 100644 --- a/vms/platformvm/block/builder/helpers_test.go +++ b/vms/platformvm/block/builder/helpers_test.go @@ -1,11 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package builder import ( "context" - "errors" "testing" "time" @@ -24,6 +23,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils" @@ -69,18 +69,13 @@ var ( defaultMinValidatorStake = 5 * units.MilliAvax defaultBalance = 100 * defaultMinValidatorStake preFundedKeys = secp256k1.TestKeys() - avaxAssetID = ids.ID{'y', 'e', 'e', 't'} defaultTxFee = uint64(100) - xChainID = ids.Empty.Prefix(0) - cChainID = ids.Empty.Prefix(1) testSubnet1 *txs.Tx testSubnet1ControlKeys = preFundedKeys[0:3] // Node IDs of genesis validators. Initialized in init function genesisNodeIDs []ids.NodeID - - errMissing = errors.New("missing") ) func init() { @@ -127,7 +122,14 @@ func newEnvironment(t *testing.T) *environment { res.isBootstrapped.Set(true) res.baseDB = versiondb.New(memdb.New()) - res.ctx, res.msm = defaultCtx(res.baseDB) + atomicDB := prefixdb.New([]byte{1}, res.baseDB) + m := atomic.NewMemory(atomicDB) + + res.ctx = snowtest.Context(t, snowtest.PChainID) + res.msm = &mutableSharedMemory{ + SharedMemory: m.NewSharedMemory(res.ctx.ChainID), + } + res.ctx.SharedMemory = res.msm res.ctx.Lock.Lock() defer res.ctx.Lock.Unlock() @@ -135,7 +137,7 @@ func newEnvironment(t *testing.T) *environment { res.fx = defaultFx(t, res.clk, res.ctx.Log, res.isBootstrapped.Get()) rewardsCalc := reward.NewCalculator(res.config.RewardConfig) - res.state = defaultState(t, res.config.Validators, res.ctx, res.baseDB, rewardsCalc) + res.state = defaultState(t, res.config, res.ctx, res.baseDB, rewardsCalc) res.atomicUTXOs = avax.NewAtomicUTXOManager(res.ctx.SharedMemory, txs.Codec) res.uptimes = uptime.NewManager(res.state, res.clk) @@ -165,6 +167,9 @@ func newEnvironment(t *testing.T) *environment { registerer := prometheus.NewRegistry() res.sender = &common.SenderTest{T: t} + res.sender.SendAppGossipF = func(context.Context, []byte) error { + return nil + } metrics, err := metrics.New("", registerer) require.NoError(err) @@ -180,13 +185,20 @@ func newEnvironment(t *testing.T) *environment { pvalidators.TestManager, ) - res.network = network.New( - res.backend.Ctx, - res.blkManager, + txVerifier := network.NewLockedTxVerifier(&res.ctx.Lock, res.blkManager) + res.network, err = network.New( + res.backend.Ctx.Log, + res.backend.Ctx.NodeID, + res.backend.Ctx.SubnetID, + res.backend.Ctx.ValidatorState, + txVerifier, res.mempool, res.backend.Config.PartialSyncPrimaryNetwork, res.sender, + registerer, + network.DefaultConfig, ) + require.NoError(err) res.Builder = New( res.mempool, @@ -194,6 +206,7 @@ func newEnvironment(t *testing.T) *environment { &res.backend, res.blkManager, ) + res.Builder.StartBlockTimer() res.blkManager.SetPreference(genesisID) addSubnet(t, res) @@ -236,7 +249,7 @@ func addSubnet(t *testing.T, env *environment) { func defaultState( t *testing.T, - validators validators.Manager, + cfg *config.Config, ctx *snow.Context, db database.Database, rewards reward.Calculator, @@ -249,7 +262,7 @@ func defaultState( db, genesisBytes, prometheus.NewRegistry(), - validators, + cfg, execCfg, ctx, metrics.Noop, @@ -263,38 +276,6 @@ func defaultState( return state } -func defaultCtx(db database.Database) (*snow.Context, *mutableSharedMemory) { - ctx := snow.DefaultContextTest() - ctx.NetworkID = 10 - ctx.XChainID = xChainID - ctx.CChainID = cChainID - ctx.AVAXAssetID = avaxAssetID - - atomicDB := prefixdb.New([]byte{1}, db) - m := atomic.NewMemory(atomicDB) - - msm := &mutableSharedMemory{ - SharedMemory: m.NewSharedMemory(ctx.ChainID), - } - ctx.SharedMemory = msm - - ctx.ValidatorState = &validators.TestState{ - GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { - subnetID, ok := map[ids.ID]ids.ID{ - constants.PlatformChainID: constants.PrimaryNetworkID, - xChainID: constants.PrimaryNetworkID, - cChainID: constants.PrimaryNetworkID, - }[chainID] - if !ok { - return ids.Empty, errMissing - } - return subnetID, nil - }, - } - - return ctx, msm -} - func defaultConfig() *config.Config { return &config.Config{ Chains: chains.TestManager, @@ -349,7 +330,7 @@ func defaultFx(t *testing.T, clk *mockable.Clock, log logging.Logger, isBootstra require := require.New(t) fxVMInt := &fxVMInt{ - registry: linearcodec.NewDefault(), + registry: linearcodec.NewDefault(time.Time{}), clk: clk, log: log, } @@ -419,7 +400,7 @@ func buildGenesisTest(t *testing.T, ctx *snow.Context) []byte { } func shutdownEnvironment(env *environment) error { - env.Builder.Shutdown() + env.Builder.ShutdownBlockTimer() if env.isBootstrapped.Get() { validatorIDs := env.config.Validators.GetValidatorIDs(constants.PrimaryNetworkID) @@ -437,17 +418,3 @@ func shutdownEnvironment(env *environment) error { env.baseDB.Close(), ) } - -func getValidTx(txBuilder txbuilder.Builder, t *testing.T) *txs.Tx { - tx, err := txBuilder.NewCreateChainTx( - testSubnet1.ID(), - nil, - constants.AVMID, - nil, - "chain name", - []*secp256k1.PrivateKey{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, - ids.ShortEmpty, - ) - require.NoError(t, err) - return tx -} diff --git a/vms/platformvm/block/builder/main_test.go b/vms/platformvm/block/builder/main_test.go index 01135c523738..31149bfbcca8 100644 --- a/vms/platformvm/block/builder/main_test.go +++ b/vms/platformvm/block/builder/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package builder diff --git a/vms/platformvm/block/builder/standard_block_test.go b/vms/platformvm/block/builder/standard_block_test.go index 827d7357728b..61dd0d5e2ca1 100644 --- a/vms/platformvm/block/builder/standard_block_test.go +++ b/vms/platformvm/block/builder/standard_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package builder @@ -26,6 +26,7 @@ func TestAtomicTxImports(t *testing.T) { env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() }() utxoID := avax.UTXOID{ @@ -41,7 +42,7 @@ func TestAtomicTxImports(t *testing.T) { peerSharedMemory := m.NewSharedMemory(env.ctx.XChainID) utxo := &avax.UTXO{ UTXOID: utxoID, - Asset: avax.Asset{ID: avaxAssetID}, + Asset: avax.Asset{ID: env.ctx.AVAXAssetID}, Out: &secp256k1fx.TransferOutput{ Amt: amount, OutputOwners: secp256k1fx.OutputOwners{ @@ -50,7 +51,7 @@ func TestAtomicTxImports(t *testing.T) { }, }, } - utxoBytes, err := txs.Codec.Marshal(txs.Version, utxo) + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) require.NoError(err) inputID := utxo.InputID() diff --git a/vms/platformvm/block/codec.go b/vms/platformvm/block/codec.go index 1034ee9f759a..33babbaf3a79 100644 --- a/vms/platformvm/block/codec.go +++ b/vms/platformvm/block/codec.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block import ( "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" @@ -13,23 +14,25 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/txs" ) -// Version is the current default codec version -const Version = txs.Version +const CodecVersion = txs.CodecVersion -// GenesisCode allows blocks of larger than usual size to be parsed. -// While this gives flexibility in accommodating large genesis blocks -// it must not be used to parse new, unverified blocks which instead -// must be processed by Codec var ( - Codec codec.Manager + // GenesisCodec allows blocks of larger than usual size to be parsed. + // While this gives flexibility in accommodating large genesis blocks + // it must not be used to parse new, unverified blocks which instead + // must be processed by Codec GenesisCodec codec.Manager + + Codec codec.Manager ) -func init() { - c := linearcodec.NewDefault() - Codec = codec.NewDefaultManager() - gc := linearcodec.NewCustomMaxLength(math.MaxInt32) - GenesisCodec = codec.NewManager(math.MaxInt32) +// TODO: Remove after v1.11.x has activated +// +// Invariant: InitCodec, Codec, and GenesisCodec must not be accessed +// concurrently +func InitCodec(durangoTime time.Time) error { + c := linearcodec.NewDefault(durangoTime) + gc := linearcodec.NewDefault(time.Time{}) errs := wrappers.Errs{} for _, c := range []linearcodec.Codec{c, gc} { @@ -40,12 +43,25 @@ func init() { txs.RegisterDUnsignedTxsTypes(c), ) } + + newCodec := codec.NewDefaultManager() + newGenesisCodec := codec.NewManager(math.MaxInt32) errs.Add( - Codec.RegisterCodec(Version, c), - GenesisCodec.RegisterCodec(Version, gc), + newCodec.RegisterCodec(CodecVersion, c), + newGenesisCodec.RegisterCodec(CodecVersion, gc), ) if errs.Errored() { - panic(errs.Err) + return errs.Err + } + + Codec = newCodec + GenesisCodec = newGenesisCodec + return nil +} + +func init() { + if err := InitCodec(time.Time{}); err != nil { + panic(err) } } diff --git a/vms/platformvm/block/commit_block.go b/vms/platformvm/block/commit_block.go index 02cbadb4e1cd..ac6dbb1ed88f 100644 --- a/vms/platformvm/block/commit_block.go +++ b/vms/platformvm/block/commit_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -43,7 +43,7 @@ func NewBanffCommitBlock( }, }, } - return blk, initialize(blk) + return blk, initialize(blk, &blk.CommonBlock) } type ApricotCommitBlock struct { @@ -75,5 +75,5 @@ func NewApricotCommitBlock( Hght: height, }, } - return blk, initialize(blk) + return blk, initialize(blk, &blk.CommonBlock) } diff --git a/vms/platformvm/block/commit_block_test.go b/vms/platformvm/block/commit_block_test.go index 256af6539c96..f89489d521a1 100644 --- a/vms/platformvm/block/commit_block_test.go +++ b/vms/platformvm/block/commit_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/platformvm/block/common_block.go b/vms/platformvm/block/common_block.go index 899fd57b7fcb..f4b46b816b87 100644 --- a/vms/platformvm/block/common_block.go +++ b/vms/platformvm/block/common_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/platformvm/block/executor/acceptor.go b/vms/platformvm/block/executor/acceptor.go index c66440aa83fe..8ee4f6e4a04f 100644 --- a/vms/platformvm/block/executor/acceptor.go +++ b/vms/platformvm/block/executor/acceptor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -172,6 +172,16 @@ func (a *acceptor) optionBlock(b, parent block.Block, blockType string) error { return err } + parentState, ok := a.blkIDToState[parentID] + if !ok { + return fmt.Errorf("%w %s", errMissingBlockState, parentID) + } + if parentState.onDecisionState != nil { + if err := parentState.onDecisionState.Apply(a.state); err != nil { + return err + } + } + blkState, ok := a.blkIDToState[blkID] if !ok { return fmt.Errorf("%w %s", errMissingBlockState, blkID) @@ -180,8 +190,23 @@ func (a *acceptor) optionBlock(b, parent block.Block, blockType string) error { return err } - if err := a.state.Commit(); err != nil { - return err + defer a.state.Abort() + batch, err := a.state.CommitBatch() + if err != nil { + return fmt.Errorf( + "failed to commit VM's database for block %s: %w", + blkID, + err, + ) + } + + // Note that this method writes [batch] to the database. + if err := a.ctx.SharedMemory.Apply(parentState.atomicRequests, batch); err != nil { + return fmt.Errorf("failed to apply vm's state to shared memory: %w", err) + } + + if onAcceptFunc := parentState.onAcceptFunc; onAcceptFunc != nil { + onAcceptFunc() } a.ctx.Log.Trace( diff --git a/vms/platformvm/block/executor/acceptor_test.go b/vms/platformvm/block/executor/acceptor_test.go index c9fafb4445d0..fb5fcd5f88af 100644 --- a/vms/platformvm/block/executor/acceptor_test.go +++ b/vms/platformvm/block/executor/acceptor_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -124,7 +125,7 @@ func TestAcceptorVisitAtomicBlock(t *testing.T) { // Set [blk]'s state in the map as though it had been verified. onAcceptState := state.NewMockDiff(ctrl) childID := ids.GenerateTestID() - atomicRequests := map[ids.ID]*atomic.Requests{ids.GenerateTestID(): nil} + atomicRequests := make(map[ids.ID]*atomic.Requests) acceptor.backend.blkIDToState[blk.ID()] = &blockState{ onAcceptState: onAcceptState, atomicRequests: atomicRequests, @@ -207,16 +208,15 @@ func TestAcceptorVisitStandardBlock(t *testing.T) { // Set [blk]'s state in the map as though it had been verified. onAcceptState := state.NewMockDiff(ctrl) childID := ids.GenerateTestID() - atomicRequests := map[ids.ID]*atomic.Requests{ids.GenerateTestID(): nil} + atomicRequests := make(map[ids.ID]*atomic.Requests) calledOnAcceptFunc := false acceptor.backend.blkIDToState[blk.ID()] = &blockState{ - onAcceptState: onAcceptState, - atomicRequests: atomicRequests, - standardBlockState: standardBlockState{ - onAcceptFunc: func() { - calledOnAcceptFunc = true - }, + onAcceptState: onAcceptState, + onAcceptFunc: func() { + calledOnAcceptFunc = true }, + + atomicRequests: atomicRequests, } // Give [blk] a child. childOnAcceptState := state.NewMockDiff(ctrl) @@ -281,13 +281,21 @@ func TestAcceptorVisitCommitBlock(t *testing.T) { parentOnAbortState := state.NewMockDiff(ctrl) parentOnCommitState := state.NewMockDiff(ctrl) parentStatelessBlk := block.NewMockBlock(ctrl) + calledOnAcceptFunc := false + atomicRequests := make(map[ids.ID]*atomic.Requests) parentState := &blockState{ - statelessBlock: parentStatelessBlk, - onAcceptState: parentOnAcceptState, proposalBlockState: proposalBlockState{ onAbortState: parentOnAbortState, onCommitState: parentOnCommitState, }, + statelessBlock: parentStatelessBlk, + + onAcceptState: parentOnAcceptState, + onAcceptFunc: func() { + calledOnAcceptFunc = true + }, + + atomicRequests: atomicRequests, } acceptor.backend.blkIDToState[parentID] = parentState @@ -309,13 +317,21 @@ func TestAcceptorVisitCommitBlock(t *testing.T) { err = acceptor.ApricotCommitBlock(blk) require.ErrorIs(err, errMissingBlockState) + parentOnCommitState.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + // Set [blk]'s state in the map as though it had been verified. acceptor.backend.blkIDToState[parentID] = parentState - onAcceptState := state.NewMockDiff(ctrl) acceptor.backend.blkIDToState[blkID] = &blockState{ - onAcceptState: onAcceptState, + onAcceptState: parentState.onCommitState, + onAcceptFunc: parentState.onAcceptFunc, + + inputs: parentState.inputs, + timestamp: parentOnCommitState.GetTimestamp(), + atomicRequests: parentState.atomicRequests, } + batch := database.NewMockBatch(ctrl) + // Set expected calls on dependencies. // Make sure the parent is accepted first. gomock.InOrder( @@ -329,12 +345,15 @@ func TestAcceptorVisitCommitBlock(t *testing.T) { s.EXPECT().SetHeight(blk.Height()).Times(1), s.EXPECT().AddStatelessBlock(blk).Times(1), - onAcceptState.EXPECT().Apply(s).Times(1), - s.EXPECT().Commit().Return(nil).Times(1), + parentOnCommitState.EXPECT().Apply(s).Times(1), + s.EXPECT().CommitBatch().Return(batch, nil).Times(1), + sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1), s.EXPECT().Checksum().Return(ids.Empty).Times(1), + s.EXPECT().Abort().Times(1), ) require.NoError(acceptor.ApricotCommitBlock(blk)) + require.True(calledOnAcceptFunc) require.Equal(blk.ID(), acceptor.backend.lastAccepted) } @@ -372,13 +391,21 @@ func TestAcceptorVisitAbortBlock(t *testing.T) { parentOnAbortState := state.NewMockDiff(ctrl) parentOnCommitState := state.NewMockDiff(ctrl) parentStatelessBlk := block.NewMockBlock(ctrl) + calledOnAcceptFunc := false + atomicRequests := make(map[ids.ID]*atomic.Requests) parentState := &blockState{ - statelessBlock: parentStatelessBlk, - onAcceptState: parentOnAcceptState, proposalBlockState: proposalBlockState{ onAbortState: parentOnAbortState, onCommitState: parentOnCommitState, }, + statelessBlock: parentStatelessBlk, + + onAcceptState: parentOnAcceptState, + onAcceptFunc: func() { + calledOnAcceptFunc = true + }, + + atomicRequests: atomicRequests, } acceptor.backend.blkIDToState[parentID] = parentState @@ -400,14 +427,21 @@ func TestAcceptorVisitAbortBlock(t *testing.T) { err = acceptor.ApricotAbortBlock(blk) require.ErrorIs(err, errMissingBlockState) + parentOnAbortState.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + // Set [blk]'s state in the map as though it had been verified. acceptor.backend.blkIDToState[parentID] = parentState - - onAcceptState := state.NewMockDiff(ctrl) acceptor.backend.blkIDToState[blkID] = &blockState{ - onAcceptState: onAcceptState, + onAcceptState: parentState.onAbortState, + onAcceptFunc: parentState.onAcceptFunc, + + inputs: parentState.inputs, + timestamp: parentOnAbortState.GetTimestamp(), + atomicRequests: parentState.atomicRequests, } + batch := database.NewMockBatch(ctrl) + // Set expected calls on dependencies. // Make sure the parent is accepted first. gomock.InOrder( @@ -421,11 +455,14 @@ func TestAcceptorVisitAbortBlock(t *testing.T) { s.EXPECT().SetHeight(blk.Height()).Times(1), s.EXPECT().AddStatelessBlock(blk).Times(1), - onAcceptState.EXPECT().Apply(s).Times(1), - s.EXPECT().Commit().Return(nil).Times(1), + parentOnAbortState.EXPECT().Apply(s).Times(1), + s.EXPECT().CommitBatch().Return(batch, nil).Times(1), + sharedMemory.EXPECT().Apply(atomicRequests, batch).Return(nil).Times(1), s.EXPECT().Checksum().Return(ids.Empty).Times(1), + s.EXPECT().Abort().Times(1), ) require.NoError(acceptor.ApricotAbortBlock(blk)) + require.True(calledOnAcceptFunc) require.Equal(blk.ID(), acceptor.backend.lastAccepted) } diff --git a/vms/platformvm/block/executor/backend.go b/vms/platformvm/block/executor/backend.go index fd0e75d0f664..c4e56545634d 100644 --- a/vms/platformvm/block/executor/backend.go +++ b/vms/platformvm/block/executor/backend.go @@ -1,18 +1,22 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor import ( + "errors" "time" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" ) +var errConflictingParentTxs = errors.New("block contains a transaction that conflicts with a transaction in a parent block") + // Shared fields used by visitors. type backend struct { mempool.Mempool @@ -95,3 +99,28 @@ func (b *backend) getTimestamp(blkID ids.ID) time.Time { // so we just return the chain time. return b.state.GetTimestamp() } + +// verifyUniqueInputs returns nil iff no blocks in the inclusive +// ancestry of [blkID] consume an input in [inputs]. +func (b *backend) verifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error { + if inputs.Len() == 0 { + return nil + } + + // Check for conflicts in ancestors. + for { + state, ok := b.blkIDToState[blkID] + if !ok { + // The parent state isn't pinned in memory. + // This means the parent must be accepted already. + return nil + } + + if state.inputs.Overlaps(inputs) { + return errConflictingParentTxs + } + + blk := state.statelessBlock + blkID = blk.Parent() + } +} diff --git a/vms/platformvm/block/executor/backend_test.go b/vms/platformvm/block/executor/backend_test.go index ce6744369593..19ad55c36d81 100644 --- a/vms/platformvm/block/executor/backend_test.go +++ b/vms/platformvm/block/executor/backend_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/block/executor/block.go b/vms/platformvm/block/executor/block.go index 733825412008..ef05f38442c5 100644 --- a/vms/platformvm/block/executor/block.go +++ b/vms/platformvm/block/executor/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/block/executor/block_state.go b/vms/platformvm/block/executor/block_state.go index 9f0cd7e0860d..ffc6f679b8f6 100644 --- a/vms/platformvm/block/executor/block_state.go +++ b/vms/platformvm/block/executor/block_state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -13,13 +13,9 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/state" ) -type standardBlockState struct { - onAcceptFunc func() - inputs set.Set[ids.ID] -} - type proposalBlockState struct { initiallyPreferCommit bool + onDecisionState state.Diff onCommitState state.Diff onAbortState state.Diff } @@ -27,11 +23,13 @@ type proposalBlockState struct { // The state of a block. // Note that not all fields will be set for a given block. type blockState struct { - standardBlockState proposalBlockState statelessBlock block.Block - onAcceptState state.Diff + onAcceptState state.Diff + onAcceptFunc func() + + inputs set.Set[ids.ID] timestamp time.Time atomicRequests map[ids.ID]*atomic.Requests } diff --git a/vms/platformvm/block/executor/block_test.go b/vms/platformvm/block/executor/block_test.go index 7153ff52d3cf..b1d73cefb7f1 100644 --- a/vms/platformvm/block/executor/block_test.go +++ b/vms/platformvm/block/executor/block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/block/executor/helpers_test.go b/vms/platformvm/block/executor/helpers_test.go index 778d9b203181..5e7b5a3fce1e 100644 --- a/vms/platformvm/block/executor/helpers_test.go +++ b/vms/platformvm/block/executor/helpers_test.go @@ -1,11 +1,9 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor import ( - "context" - "errors" "fmt" "testing" "time" @@ -25,6 +23,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils" @@ -73,16 +72,12 @@ var ( preFundedKeys = secp256k1.TestKeys() avaxAssetID = ids.ID{'y', 'e', 'e', 't'} defaultTxFee = uint64(100) - xChainID = ids.Empty.Prefix(0) - cChainID = ids.Empty.Prefix(1) genesisBlkID ids.ID testSubnet1 *txs.Tx // Node IDs of genesis validators. Initialized in init function genesisNodeIDs []ids.NodeID - - errMissing = errors.New("missing") ) func init() { @@ -138,14 +133,20 @@ func newEnvironment(t *testing.T, ctrl *gomock.Controller) *environment { res.isBootstrapped.Set(true) res.baseDB = versiondb.New(memdb.New()) - res.ctx = defaultCtx(res.baseDB) + atomicDB := prefixdb.New([]byte{1}, res.baseDB) + m := atomic.NewMemory(atomicDB) + + res.ctx = snowtest.Context(t, snowtest.PChainID) + res.ctx.AVAXAssetID = avaxAssetID + res.ctx.SharedMemory = m.NewSharedMemory(res.ctx.ChainID) + res.fx = defaultFx(res.clk, res.ctx.Log, res.isBootstrapped.Get()) rewardsCalc := reward.NewCalculator(res.config.RewardConfig) res.atomicUTXOs = avax.NewAtomicUTXOManager(res.ctx.SharedMemory, txs.Codec) if ctrl == nil { - res.state = defaultState(res.config.Validators, res.ctx, res.baseDB, rewardsCalc) + res.state = defaultState(res.config, res.ctx, res.baseDB, rewardsCalc) res.uptimes = uptime.NewManager(res.state, res.clk) res.utxosHandler = utxo.NewHandler(res.ctx, res.clk, res.fx) res.txBuilder = p_tx_builder.New( @@ -263,7 +264,7 @@ func addSubnet(env *environment) { } func defaultState( - validators validators.Manager, + cfg *config.Config, ctx *snow.Context, db database.Database, rewards reward.Calculator, @@ -274,7 +275,7 @@ func defaultState( db, genesisBytes, prometheus.NewRegistry(), - validators, + cfg, execCfg, ctx, metrics.Noop, @@ -293,35 +294,6 @@ func defaultState( return state } -func defaultCtx(db database.Database) *snow.Context { - ctx := snow.DefaultContextTest() - ctx.NetworkID = 10 - ctx.XChainID = xChainID - ctx.CChainID = cChainID - ctx.AVAXAssetID = avaxAssetID - - atomicDB := prefixdb.New([]byte{1}, db) - m := atomic.NewMemory(atomicDB) - - ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) - - ctx.ValidatorState = &validators.TestState{ - GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { - subnetID, ok := map[ids.ID]ids.ID{ - constants.PlatformChainID: constants.PrimaryNetworkID, - xChainID: constants.PrimaryNetworkID, - cChainID: constants.PrimaryNetworkID, - }[chainID] - if !ok { - return ids.Empty, errMissing - } - return subnetID, nil - }, - } - - return ctx -} - func defaultConfig() *config.Config { return &config.Config{ Chains: chains.TestManager, @@ -373,7 +345,7 @@ func (fvi *fxVMInt) Logger() logging.Logger { func defaultFx(clk *mockable.Clock, log logging.Logger, isBootstrapped bool) fx.Fx { fxVMInt := &fxVMInt{ - registry: linearcodec.NewDefault(), + registry: linearcodec.NewDefault(time.Time{}), clk: clk, log: log, } diff --git a/vms/platformvm/block/executor/manager.go b/vms/platformvm/block/executor/manager.go index 9af9cbce2c4a..ed82197a3568 100644 --- a/vms/platformvm/block/executor/manager.go +++ b/vms/platformvm/block/executor/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -8,6 +8,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/consensus/snowman" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/metrics" "github.com/ava-labs/avalanchego/vms/platformvm/state" @@ -39,6 +40,10 @@ type Manager interface { // VerifyTx verifies that the transaction can be issued based on the currently // preferred state. This should *not* be used to verify transactions in a block. VerifyTx(tx *txs.Tx) error + + // VerifyUniqueInputs verifies that the inputs are not duplicated in the + // provided blk or any of its ancestors pinned in memory. + VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error } func NewManager( @@ -107,9 +112,9 @@ func (m *manager) NewBlock(blk block.Block) snowman.Block { } } -func (m *manager) SetPreference(blockID ids.ID) (updated bool) { - updated = m.preferred == blockID - m.preferred = blockID +func (m *manager) SetPreference(blkID ids.ID) bool { + updated := m.preferred != blkID + m.preferred = blkID return updated } @@ -129,3 +134,7 @@ func (m *manager) VerifyTx(tx *txs.Tx) error { Tx: tx, }) } + +func (m *manager) VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error { + return m.backend.verifyUniqueInputs(blkID, inputs) +} diff --git a/vms/platformvm/block/executor/manager_test.go b/vms/platformvm/block/executor/manager_test.go index 8ee784c4f9f1..55cf01d7c8ab 100644 --- a/vms/platformvm/block/executor/manager_test.go +++ b/vms/platformvm/block/executor/manager_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -72,3 +72,18 @@ func TestManagerLastAccepted(t *testing.T) { require.Equal(t, lastAcceptedID, manager.LastAccepted()) } + +func TestManagerSetPreference(t *testing.T) { + require := require.New(t) + + initialPreference := ids.GenerateTestID() + manager := &manager{ + preferred: initialPreference, + } + require.False(manager.SetPreference(initialPreference)) + + newPreference := ids.GenerateTestID() + require.True(manager.SetPreference(newPreference)) + require.False(manager.SetPreference(newPreference)) + require.True(manager.SetPreference(initialPreference)) +} diff --git a/vms/platformvm/block/executor/mock_manager.go b/vms/platformvm/block/executor/mock_manager.go index f5b2bcb3608c..5e8222383071 100644 --- a/vms/platformvm/block/executor/mock_manager.go +++ b/vms/platformvm/block/executor/mock_manager.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/vms/platformvm/block/executor (interfaces: Manager) +// Source: vms/platformvm/block/executor/manager.go +// +// Generated by this command: +// +// mockgen -source=vms/platformvm/block/executor/manager.go -destination=vms/platformvm/block/executor/mock_manager.go -package=executor -exclude_interfaces= +// // Package executor is a generated GoMock package. package executor @@ -12,6 +14,7 @@ import ( ids "github.com/ava-labs/avalanchego/ids" snowman "github.com/ava-labs/avalanchego/snow/consensus/snowman" + set "github.com/ava-labs/avalanchego/utils/set" block "github.com/ava-labs/avalanchego/vms/platformvm/block" state "github.com/ava-labs/avalanchego/vms/platformvm/state" txs "github.com/ava-labs/avalanchego/vms/platformvm/txs" @@ -42,48 +45,48 @@ func (m *MockManager) EXPECT() *MockManagerMockRecorder { } // GetBlock mocks base method. -func (m *MockManager) GetBlock(arg0 ids.ID) (snowman.Block, error) { +func (m *MockManager) GetBlock(blkID ids.ID) (snowman.Block, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBlock", arg0) + ret := m.ctrl.Call(m, "GetBlock", blkID) ret0, _ := ret[0].(snowman.Block) ret1, _ := ret[1].(error) return ret0, ret1 } // GetBlock indicates an expected call of GetBlock. -func (mr *MockManagerMockRecorder) GetBlock(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetBlock(blkID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockManager)(nil).GetBlock), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockManager)(nil).GetBlock), blkID) } // GetState mocks base method. -func (m *MockManager) GetState(arg0 ids.ID) (state.Chain, bool) { +func (m *MockManager) GetState(blkID ids.ID) (state.Chain, bool) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetState", arg0) + ret := m.ctrl.Call(m, "GetState", blkID) ret0, _ := ret[0].(state.Chain) ret1, _ := ret[1].(bool) return ret0, ret1 } // GetState indicates an expected call of GetState. -func (mr *MockManagerMockRecorder) GetState(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetState(blkID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockManager)(nil).GetState), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockManager)(nil).GetState), blkID) } // GetStatelessBlock mocks base method. -func (m *MockManager) GetStatelessBlock(arg0 ids.ID) (block.Block, error) { +func (m *MockManager) GetStatelessBlock(blkID ids.ID) (block.Block, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetStatelessBlock", arg0) + ret := m.ctrl.Call(m, "GetStatelessBlock", blkID) ret0, _ := ret[0].(block.Block) ret1, _ := ret[1].(error) return ret0, ret1 } // GetStatelessBlock indicates an expected call of GetStatelessBlock. -func (mr *MockManagerMockRecorder) GetStatelessBlock(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) GetStatelessBlock(blkID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatelessBlock", reflect.TypeOf((*MockManager)(nil).GetStatelessBlock), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatelessBlock", reflect.TypeOf((*MockManager)(nil).GetStatelessBlock), blkID) } // LastAccepted mocks base method. @@ -109,7 +112,7 @@ func (m *MockManager) NewBlock(arg0 block.Block) snowman.Block { } // NewBlock indicates an expected call of NewBlock. -func (mr *MockManagerMockRecorder) NewBlock(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) NewBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBlock", reflect.TypeOf((*MockManager)(nil).NewBlock), arg0) } @@ -129,29 +132,43 @@ func (mr *MockManagerMockRecorder) Preferred() *gomock.Call { } // SetPreference mocks base method. -func (m *MockManager) SetPreference(arg0 ids.ID) bool { +func (m *MockManager) SetPreference(blkID ids.ID) bool { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetPreference", arg0) + ret := m.ctrl.Call(m, "SetPreference", blkID) ret0, _ := ret[0].(bool) return ret0 } // SetPreference indicates an expected call of SetPreference. -func (mr *MockManagerMockRecorder) SetPreference(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) SetPreference(blkID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPreference", reflect.TypeOf((*MockManager)(nil).SetPreference), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPreference", reflect.TypeOf((*MockManager)(nil).SetPreference), blkID) } // VerifyTx mocks base method. -func (m *MockManager) VerifyTx(arg0 *txs.Tx) error { +func (m *MockManager) VerifyTx(tx *txs.Tx) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VerifyTx", arg0) + ret := m.ctrl.Call(m, "VerifyTx", tx) ret0, _ := ret[0].(error) return ret0 } // VerifyTx indicates an expected call of VerifyTx. -func (mr *MockManagerMockRecorder) VerifyTx(arg0 interface{}) *gomock.Call { +func (mr *MockManagerMockRecorder) VerifyTx(tx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyTx", reflect.TypeOf((*MockManager)(nil).VerifyTx), tx) +} + +// VerifyUniqueInputs mocks base method. +func (m *MockManager) VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyUniqueInputs", blkID, inputs) + ret0, _ := ret[0].(error) + return ret0 +} + +// VerifyUniqueInputs indicates an expected call of VerifyUniqueInputs. +func (mr *MockManagerMockRecorder) VerifyUniqueInputs(blkID, inputs any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyTx", reflect.TypeOf((*MockManager)(nil).VerifyTx), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyUniqueInputs", reflect.TypeOf((*MockManager)(nil).VerifyUniqueInputs), blkID, inputs) } diff --git a/vms/platformvm/block/executor/options.go b/vms/platformvm/block/executor/options.go index 29a1d02922bd..a8ccea315b0f 100644 --- a/vms/platformvm/block/executor/options.go +++ b/vms/platformvm/block/executor/options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/block/executor/options_test.go b/vms/platformvm/block/executor/options_test.go index bf8e6e3e67b6..54bef77919b7 100644 --- a/vms/platformvm/block/executor/options_test.go +++ b/vms/platformvm/block/executor/options_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/block/executor/proposal_block_test.go b/vms/platformvm/block/executor/proposal_block_test.go index b69708f8ca9f..549a3fbe25df 100644 --- a/vms/platformvm/block/executor/proposal_block_test.go +++ b/vms/platformvm/block/executor/proposal_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -18,6 +18,7 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/reward" @@ -151,7 +152,8 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { require.NoError(shutdownEnvironment(env)) }() env.clk.Set(defaultGenesisTime) - env.config.BanffTime = time.Time{} // activate Banff + env.config.BanffTime = time.Time{} // activate Banff + env.config.DurangoTime = mockable.MaxTime // deactivate Durango // create parentBlock. It's a standard one for simplicity parentTime := defaultGenesisTime @@ -258,6 +260,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { parentID, banffParentBlk.Height(), blkTx, + []*txs.Tx{}, ) require.NoError(err) @@ -287,6 +290,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { parentID, banffParentBlk.Height()+1, blkTx, + []*txs.Tx{}, ) require.NoError(err) @@ -304,6 +308,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { parentID, banffParentBlk.Height()+1, blkTx, + []*txs.Tx{}, ) require.NoError(err) @@ -321,6 +326,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { parentID, banffParentBlk.Height()+1, blkTx, + []*txs.Tx{}, ) require.NoError(err) @@ -342,6 +348,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { parentID, banffParentBlk.Height()+1, invalidTx, + []*txs.Tx{}, ) require.NoError(err) @@ -357,6 +364,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { parentID, banffParentBlk.Height()+1, blkTx, + []*txs.Tx{}, ) require.NoError(err) @@ -373,6 +381,7 @@ func TestBanffProposalBlockTimeVerification(t *testing.T) { parentID, banffParentBlk.Height()+1, blkTx, + []*txs.Tx{}, ) require.NoError(err) @@ -634,9 +643,11 @@ func TestBanffProposalBlockUpdateStakers(t *testing.T) { require.NoError(err) // store Staker0 to state + addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx) staker0, err := state.NewCurrentStaker( addStaker0.ID(), - addStaker0.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) @@ -662,6 +673,7 @@ func TestBanffProposalBlockUpdateStakers(t *testing.T) { parentBlk.ID(), parentBlk.Height()+1, s0RewardTx, + []*txs.Tx{}, ) require.NoError(err) @@ -733,9 +745,11 @@ func TestBanffProposalBlockRemoveSubnetValidator(t *testing.T) { ) require.NoError(err) + addSubnetValTx := tx.Unsigned.(*txs.AddSubnetValidatorTx) staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddSubnetValidatorTx), + addSubnetValTx, + addSubnetValTx.StartTime(), 0, ) require.NoError(err) @@ -791,9 +805,11 @@ func TestBanffProposalBlockRemoveSubnetValidator(t *testing.T) { require.NoError(err) // store Staker0 to state + addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx) staker, err = state.NewCurrentStaker( addStaker0.ID(), - addStaker0.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) @@ -819,6 +835,7 @@ func TestBanffProposalBlockRemoveSubnetValidator(t *testing.T) { parentBlk.ID(), parentBlk.Height()+1, s0RewardTx, + []*txs.Tx{}, ) require.NoError(err) propBlk := env.blkManager.NewBlock(statelessProposalBlock) @@ -903,9 +920,11 @@ func TestBanffProposalBlockTrackedSubnet(t *testing.T) { require.NoError(err) // store Staker0 to state + addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx) staker, err = state.NewCurrentStaker( addStaker0.ID(), - addStaker0.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) @@ -931,6 +950,7 @@ func TestBanffProposalBlockTrackedSubnet(t *testing.T) { parentBlk.ID(), parentBlk.Height()+1, s0RewardTx, + []*txs.Tx{}, ) require.NoError(err) propBlk := env.blkManager.NewBlock(statelessProposalBlock) @@ -989,9 +1009,11 @@ func TestBanffProposalBlockDelegatorStakerWeight(t *testing.T) { require.NoError(err) // store Staker0 to state + addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx) staker, err := state.NewCurrentStaker( addStaker0.ID(), - addStaker0.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) @@ -1017,6 +1039,7 @@ func TestBanffProposalBlockDelegatorStakerWeight(t *testing.T) { parentBlk.ID(), parentBlk.Height()+1, s0RewardTx, + []*txs.Tx{}, ) require.NoError(err) propBlk := env.blkManager.NewBlock(statelessProposalBlock) @@ -1080,9 +1103,11 @@ func TestBanffProposalBlockDelegatorStakerWeight(t *testing.T) { require.NoError(err) // store Staker0 to state + addValTx = addStaker0.Unsigned.(*txs.AddValidatorTx) staker, err = state.NewCurrentStaker( addStaker0.ID(), - addStaker0.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) @@ -1108,6 +1133,7 @@ func TestBanffProposalBlockDelegatorStakerWeight(t *testing.T) { parentBlk.ID(), parentBlk.Height()+1, s0RewardTx, + []*txs.Tx{}, ) require.NoError(err) @@ -1170,9 +1196,11 @@ func TestBanffProposalBlockDelegatorStakers(t *testing.T) { require.NoError(err) // store Staker0 to state + addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx) staker, err := state.NewCurrentStaker( addStaker0.ID(), - addStaker0.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) @@ -1198,6 +1226,7 @@ func TestBanffProposalBlockDelegatorStakers(t *testing.T) { parentBlk.ID(), parentBlk.Height()+1, s0RewardTx, + []*txs.Tx{}, ) require.NoError(err) propBlk := env.blkManager.NewBlock(statelessProposalBlock) @@ -1260,9 +1289,11 @@ func TestBanffProposalBlockDelegatorStakers(t *testing.T) { require.NoError(err) // store Staker0 to state + addValTx = addStaker0.Unsigned.(*txs.AddValidatorTx) staker, err = state.NewCurrentStaker( addStaker0.ID(), - addStaker0.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) @@ -1288,6 +1319,7 @@ func TestBanffProposalBlockDelegatorStakers(t *testing.T) { parentBlk.ID(), parentBlk.Height()+1, s0RewardTx, + []*txs.Tx{}, ) require.NoError(err) propBlk = env.blkManager.NewBlock(statelessProposalBlock) @@ -1305,3 +1337,145 @@ func TestBanffProposalBlockDelegatorStakers(t *testing.T) { vdrWeight = env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID) require.Equal(env.config.MinDelegatorStake+env.config.MinValidatorStake, vdrWeight) } + +func TestAddValidatorProposalBlock(t *testing.T) { + require := require.New(t) + env := newEnvironment(t, nil) + defer func() { + require.NoError(shutdownEnvironment(env)) + }() + env.config.BanffTime = time.Time{} // activate Banff + env.config.DurangoTime = time.Time{} // activate Durango + + now := env.clk.Time() + + // Create validator tx + var ( + validatorStartTime = now.Add(2 * executor.SyncBound) + validatorEndTime = validatorStartTime.Add(env.config.MinStakeDuration) + nodeID = ids.GenerateTestNodeID() + ) + + addValidatorTx, err := env.txBuilder.NewAddValidatorTx( + env.config.MinValidatorStake, + uint64(validatorStartTime.Unix()), + uint64(validatorEndTime.Unix()), + nodeID, + preFundedKeys[0].PublicKey().Address(), + 10000, + []*secp256k1.PrivateKey{ + preFundedKeys[0], + preFundedKeys[1], + preFundedKeys[4], + }, + ids.ShortEmpty, + ) + require.NoError(err) + + // Add validator through a [StandardBlock] + preferredID := env.blkManager.Preferred() + preferred, err := env.blkManager.GetStatelessBlock(preferredID) + require.NoError(err) + + statelessBlk, err := block.NewBanffStandardBlock( + now.Add(executor.SyncBound), + preferredID, + preferred.Height()+1, + []*txs.Tx{addValidatorTx}, + ) + require.NoError(err) + blk := env.blkManager.NewBlock(statelessBlk) + require.NoError(blk.Verify(context.Background())) + require.NoError(blk.Accept(context.Background())) + require.True(env.blkManager.SetPreference(statelessBlk.ID())) + + // Should be current + staker, err := env.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) + require.NoError(err) + require.NotNil(staker) + + // Advance time until next staker change time is [validatorEndTime] + for { + nextStakerChangeTime, err := executor.GetNextStakerChangeTime(env.state) + require.NoError(err) + if nextStakerChangeTime.Equal(validatorEndTime) { + break + } + + preferredID = env.blkManager.Preferred() + preferred, err = env.blkManager.GetStatelessBlock(preferredID) + require.NoError(err) + + statelessBlk, err = block.NewBanffStandardBlock( + nextStakerChangeTime, + preferredID, + preferred.Height()+1, + nil, + ) + require.NoError(err) + blk = env.blkManager.NewBlock(statelessBlk) + require.NoError(blk.Verify(context.Background())) + require.NoError(blk.Accept(context.Background())) + require.True(env.blkManager.SetPreference(statelessBlk.ID())) + } + + env.clk.Set(validatorEndTime) + now = env.clk.Time() + + // Create another validator tx + validatorStartTime = now.Add(2 * executor.SyncBound) + validatorEndTime = validatorStartTime.Add(env.config.MinStakeDuration) + nodeID = ids.GenerateTestNodeID() + + addValidatorTx2, err := env.txBuilder.NewAddValidatorTx( + env.config.MinValidatorStake, + uint64(validatorStartTime.Unix()), + uint64(validatorEndTime.Unix()), + nodeID, + preFundedKeys[0].PublicKey().Address(), + 10000, + []*secp256k1.PrivateKey{ + preFundedKeys[0], + preFundedKeys[1], + preFundedKeys[4], + }, + ids.ShortEmpty, + ) + require.NoError(err) + + // Add validator through a [ProposalBlock] and reward the last one + preferredID = env.blkManager.Preferred() + preferred, err = env.blkManager.GetStatelessBlock(preferredID) + require.NoError(err) + + rewardValidatorTx, err := env.txBuilder.NewRewardValidatorTx(addValidatorTx.ID()) + require.NoError(err) + + statelessProposalBlk, err := block.NewBanffProposalBlock( + now, + preferredID, + preferred.Height()+1, + rewardValidatorTx, + []*txs.Tx{addValidatorTx2}, + ) + require.NoError(err) + blk = env.blkManager.NewBlock(statelessProposalBlk) + require.NoError(blk.Verify(context.Background())) + + options, err := blk.(snowman.OracleBlock).Options(context.Background()) + require.NoError(err) + commitBlk := options[0] + require.NoError(commitBlk.Verify(context.Background())) + + require.NoError(blk.Accept(context.Background())) + require.NoError(commitBlk.Accept(context.Background())) + + // Should be current + staker, err = env.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) + require.NoError(err) + require.NotNil(staker) + + rewardUTXOs, err := env.state.GetRewardUTXOs(addValidatorTx.ID()) + require.NoError(err) + require.NotEmpty(rewardUTXOs) +} diff --git a/vms/platformvm/block/executor/rejector.go b/vms/platformvm/block/executor/rejector.go index cfc64b050be4..b5dde1f6e84c 100644 --- a/vms/platformvm/block/executor/rejector.go +++ b/vms/platformvm/block/executor/rejector.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -82,7 +82,7 @@ func (r *rejector) rejectBlock(b block.Block, blockType string) error { } } - r.Mempool.RequestBuildBlock(false) + r.Mempool.RequestBuildBlock(false /*=emptyBlockPermitted*/) return nil } diff --git a/vms/platformvm/block/executor/rejector_test.go b/vms/platformvm/block/executor/rejector_test.go index 3ccd9c0d66b1..4391ed3d494c 100644 --- a/vms/platformvm/block/executor/rejector_test.go +++ b/vms/platformvm/block/executor/rejector_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -44,6 +44,7 @@ func TestRejectBlock(t *testing.T) { }, Creds: []verify.Verifiable{}, }, + []*txs.Tx{}, ) }, rejectFunc: func(r *rejector, b block.Block) error { diff --git a/vms/platformvm/block/executor/standard_block_test.go b/vms/platformvm/block/executor/standard_block_test.go index af1a7562cdd0..435ee6618af0 100644 --- a/vms/platformvm/block/executor/standard_block_test.go +++ b/vms/platformvm/block/executor/standard_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -628,9 +628,11 @@ func TestBanffStandardBlockRemoveSubnetValidator(t *testing.T) { ) require.NoError(err) + addSubnetValTx := tx.Unsigned.(*txs.AddSubnetValidatorTx) staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddSubnetValidatorTx), + addSubnetValTx, + addSubnetValTx.StartTime(), 0, ) require.NoError(err) diff --git a/vms/platformvm/block/executor/verifier.go b/vms/platformvm/block/executor/verifier.go index c4f25f992da8..1f46ce132c47 100644 --- a/vms/platformvm/block/executor/verifier.go +++ b/vms/platformvm/block/executor/verifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -20,13 +20,13 @@ import ( var ( _ block.Visitor = (*verifier)(nil) + ErrConflictingBlockTxs = errors.New("block contains conflicting transactions") + errApricotBlockIssuedAfterFork = errors.New("apricot block issued after fork") errBanffProposalBlockWithMultipleTransactions = errors.New("BanffProposalBlock contains multiple transactions") errBanffStandardBlockWithoutChanges = errors.New("BanffStandardBlock performs no state changes") errIncorrectBlockHeight = errors.New("incorrect block height") errChildBlockEarlierThanParent = errors.New("proposed timestamp before current chain time") - errConflictingBatchTxs = errors.New("block contains conflicting transactions") - errConflictingParentTxs = errors.New("block contains a transaction that conflicts with a transaction in a parent block") errOptionBlockTimestampNotMatchingParent = errors.New("option block proposed timestamp not matching parent block one") ) @@ -51,7 +51,8 @@ func (v *verifier) BanffCommitBlock(b *block.BanffCommitBlock) error { } func (v *verifier) BanffProposalBlock(b *block.BanffProposalBlock) error { - if len(b.Transactions) != 0 { + nextChainTime := b.Timestamp() + if !v.txExecutorBackend.Config.IsDurangoActivated(nextChainTime) && len(b.Transactions) != 0 { return errBanffProposalBlockWithMultipleTransactions } @@ -60,33 +61,48 @@ func (v *verifier) BanffProposalBlock(b *block.BanffProposalBlock) error { } parentID := b.Parent() - onCommitState, err := state.NewDiff(parentID, v.backend) - if err != nil { - return err - } - onAbortState, err := state.NewDiff(parentID, v.backend) + onDecisionState, err := state.NewDiff(parentID, v.backend) if err != nil { return err } // Apply the changes, if any, from advancing the chain time. - nextChainTime := b.Timestamp() changes, err := executor.AdvanceTimeTo( v.txExecutorBackend, - onCommitState, + onDecisionState, nextChainTime, ) if err != nil { return err } - onCommitState.SetTimestamp(nextChainTime) - changes.Apply(onCommitState) + onDecisionState.SetTimestamp(nextChainTime) + changes.Apply(onDecisionState) - onAbortState.SetTimestamp(nextChainTime) - changes.Apply(onAbortState) + inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs(b.Transactions, onDecisionState, b.Parent()) + if err != nil { + return err + } - return v.proposalBlock(&b.ApricotProposalBlock, onCommitState, onAbortState) + onCommitState, err := state.NewDiffOn(onDecisionState) + if err != nil { + return err + } + + onAbortState, err := state.NewDiffOn(onDecisionState) + if err != nil { + return err + } + + return v.proposalBlock( + &b.ApricotProposalBlock, + onDecisionState, + onCommitState, + onAbortState, + inputs, + atomicRequests, + onAcceptFunc, + ) } func (v *verifier) BanffStandardBlock(b *block.BanffStandardBlock) error { @@ -152,7 +168,7 @@ func (v *verifier) ApricotProposalBlock(b *block.ApricotProposalBlock) error { return err } - return v.proposalBlock(b, onCommitState, onAbortState) + return v.proposalBlock(b, nil, onCommitState, onAbortState, nil, nil, nil) } func (v *verifier) ApricotStandardBlock(b *block.ApricotStandardBlock) error { @@ -203,22 +219,22 @@ func (v *verifier) ApricotAtomicBlock(b *block.ApricotAtomicBlock) error { atomicExecutor.OnAccept.AddTx(b.Tx, status.Committed) - if err := v.verifyUniqueInputs(b, atomicExecutor.Inputs); err != nil { + if err := v.verifyUniqueInputs(parentID, atomicExecutor.Inputs); err != nil { return err } + v.Mempool.Remove(b.Tx) + blkID := b.ID() v.blkIDToState[blkID] = &blockState{ - standardBlockState: standardBlockState{ - inputs: atomicExecutor.Inputs, - }, statelessBlock: b, - onAcceptState: atomicExecutor.OnAccept, + + onAcceptState: atomicExecutor.OnAccept, + + inputs: atomicExecutor.Inputs, timestamp: atomicExecutor.OnAccept.GetTimestamp(), atomicRequests: atomicExecutor.AtomicRequests, } - - v.Mempool.Remove([]*txs.Tx{b.Tx}) return nil } @@ -320,7 +336,7 @@ func (v *verifier) commonBlock(b block.Block) error { // abortBlock populates the state of this block if [nil] is returned func (v *verifier) abortBlock(b block.Block) error { parentID := b.Parent() - onAcceptState, ok := v.getOnAbortState(parentID) + onAbortState, ok := v.getOnAbortState(parentID) if !ok { return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID) } @@ -328,8 +344,8 @@ func (v *verifier) abortBlock(b block.Block) error { blkID := b.ID() v.blkIDToState[blkID] = &blockState{ statelessBlock: b, - onAcceptState: onAcceptState, - timestamp: onAcceptState.GetTimestamp(), + onAcceptState: onAbortState, + timestamp: onAbortState.GetTimestamp(), } return nil } @@ -337,7 +353,7 @@ func (v *verifier) abortBlock(b block.Block) error { // commitBlock populates the state of this block if [nil] is returned func (v *verifier) commitBlock(b block.Block) error { parentID := b.Parent() - onAcceptState, ok := v.getOnCommitState(parentID) + onCommitState, ok := v.getOnCommitState(parentID) if !ok { return fmt.Errorf("%w: %s", state.ErrMissingParentState, parentID) } @@ -345,8 +361,8 @@ func (v *verifier) commitBlock(b block.Block) error { blkID := b.ID() v.blkIDToState[blkID] = &blockState{ statelessBlock: b, - onAcceptState: onAcceptState, - timestamp: onAcceptState.GetTimestamp(), + onAcceptState: onCommitState, + timestamp: onCommitState.GetTimestamp(), } return nil } @@ -354,8 +370,12 @@ func (v *verifier) commitBlock(b block.Block) error { // proposalBlock populates the state of this block if [nil] is returned func (v *verifier) proposalBlock( b *block.ApricotProposalBlock, + onDecisionState state.Diff, onCommitState state.Diff, onAbortState state.Diff, + inputs set.Set[ids.ID], + atomicRequests map[ids.ID]*atomic.Requests, + onAcceptFunc func(), ) error { txExecutor := executor.ProposalTxExecutor{ OnCommitState: onCommitState, @@ -373,21 +393,28 @@ func (v *verifier) proposalBlock( onCommitState.AddTx(b.Tx, status.Committed) onAbortState.AddTx(b.Tx, status.Aborted) + v.Mempool.Remove(b.Tx) + blkID := b.ID() v.blkIDToState[blkID] = &blockState{ proposalBlockState: proposalBlockState{ + onDecisionState: onDecisionState, onCommitState: onCommitState, onAbortState: onAbortState, initiallyPreferCommit: txExecutor.PrefersCommit, }, + statelessBlock: b, + + onAcceptFunc: onAcceptFunc, + + inputs: inputs, // It is safe to use [b.onAbortState] here because the timestamp will // never be modified by an Apricot Abort block and the timestamp will // always be the same as the Banff Proposal Block. - timestamp: onAbortState.GetTimestamp(), + timestamp: onAbortState.GetTimestamp(), + atomicRequests: atomicRequests, } - - v.Mempool.Remove([]*txs.Tx{b.Tx}) return nil } @@ -396,43 +423,67 @@ func (v *verifier) standardBlock( b *block.ApricotStandardBlock, onAcceptState state.Diff, ) error { - blkState := &blockState{ + inputs, atomicRequests, onAcceptFunc, err := v.processStandardTxs(b.Transactions, onAcceptState, b.Parent()) + if err != nil { + return err + } + + v.Mempool.Remove(b.Transactions...) + + blkID := b.ID() + v.blkIDToState[blkID] = &blockState{ statelessBlock: b, - onAcceptState: onAcceptState, + + onAcceptState: onAcceptState, + onAcceptFunc: onAcceptFunc, + timestamp: onAcceptState.GetTimestamp(), - atomicRequests: make(map[ids.ID]*atomic.Requests), + inputs: inputs, + atomicRequests: atomicRequests, } + return nil +} - // Finally we process the transactions - funcs := make([]func(), 0, len(b.Transactions)) - for _, tx := range b.Transactions { +func (v *verifier) processStandardTxs(txs []*txs.Tx, state state.Diff, parentID ids.ID) ( + set.Set[ids.ID], + map[ids.ID]*atomic.Requests, + func(), + error, +) { + var ( + onAcceptFunc func() + inputs set.Set[ids.ID] + funcs = make([]func(), 0, len(txs)) + atomicRequests = make(map[ids.ID]*atomic.Requests) + ) + for _, tx := range txs { txExecutor := executor.StandardTxExecutor{ Backend: v.txExecutorBackend, - State: onAcceptState, + State: state, Tx: tx, } if err := tx.Unsigned.Visit(&txExecutor); err != nil { txID := tx.ID() v.MarkDropped(txID, err) // cache tx as dropped - return err + return nil, nil, nil, err } // ensure it doesn't overlap with current input batch - if blkState.inputs.Overlaps(txExecutor.Inputs) { - return errConflictingBatchTxs + if inputs.Overlaps(txExecutor.Inputs) { + return nil, nil, nil, ErrConflictingBlockTxs } // Add UTXOs to batch - blkState.inputs.Union(txExecutor.Inputs) + inputs.Union(txExecutor.Inputs) - onAcceptState.AddTx(tx, status.Committed) + state.AddTx(tx, status.Committed) if txExecutor.OnAccept != nil { funcs = append(funcs, txExecutor.OnAccept) } for chainID, txRequests := range txExecutor.AtomicRequests { // Add/merge in the atomic requests represented by [tx] - chainRequests, exists := blkState.atomicRequests[chainID] + chainRequests, exists := atomicRequests[chainID] if !exists { - blkState.atomicRequests[chainID] = txRequests + atomicRequests[chainID] = txRequests continue } @@ -441,48 +492,19 @@ func (v *verifier) standardBlock( } } - if err := v.verifyUniqueInputs(b, blkState.inputs); err != nil { - return err + if err := v.verifyUniqueInputs(parentID, inputs); err != nil { + return nil, nil, nil, err } if numFuncs := len(funcs); numFuncs == 1 { - blkState.onAcceptFunc = funcs[0] + onAcceptFunc = funcs[0] } else if numFuncs > 1 { - blkState.onAcceptFunc = func() { + onAcceptFunc = func() { for _, f := range funcs { f() } } } - blkID := b.ID() - v.blkIDToState[blkID] = blkState - - v.Mempool.Remove(b.Transactions) - return nil -} - -// verifyUniqueInputs verifies that the inputs of the given block are not -// duplicated in any of the parent blocks pinned in memory. -func (v *verifier) verifyUniqueInputs(block block.Block, inputs set.Set[ids.ID]) error { - if inputs.Len() == 0 { - return nil - } - - // Check for conflicts in ancestors. - for { - parentID := block.Parent() - parentState, ok := v.blkIDToState[parentID] - if !ok { - // The parent state isn't pinned in memory. - // This means the parent must be accepted already. - return nil - } - - if parentState.inputs.Overlaps(inputs) { - return errConflictingParentTxs - } - - block = parentState.statelessBlock - } + return inputs, atomicRequests, onAcceptFunc, nil } diff --git a/vms/platformvm/block/executor/verifier_test.go b/vms/platformvm/block/executor/verifier_test.go index b84b8697b6c0..ccac3da3b7e3 100644 --- a/vms/platformvm/block/executor/verifier_test.go +++ b/vms/platformvm/block/executor/verifier_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -311,6 +311,7 @@ func TestVerifierVisitCommitBlock(t *testing.T) { mempool := mempool.NewMockMempool(ctrl) parentID := ids.GenerateTestID() parentStatelessBlk := block.NewMockBlock(ctrl) + parentOnDecisionState := state.NewMockDiff(ctrl) parentOnCommitState := state.NewMockDiff(ctrl) parentOnAbortState := state.NewMockDiff(ctrl) @@ -319,10 +320,10 @@ func TestVerifierVisitCommitBlock(t *testing.T) { parentID: { statelessBlock: parentStatelessBlk, proposalBlockState: proposalBlockState{ - onCommitState: parentOnCommitState, - onAbortState: parentOnAbortState, + onDecisionState: parentOnDecisionState, + onCommitState: parentOnCommitState, + onAbortState: parentOnAbortState, }, - standardBlockState: standardBlockState{}, }, }, Mempool: mempool, @@ -381,6 +382,7 @@ func TestVerifierVisitAbortBlock(t *testing.T) { mempool := mempool.NewMockMempool(ctrl) parentID := ids.GenerateTestID() parentStatelessBlk := block.NewMockBlock(ctrl) + parentOnDecisionState := state.NewMockDiff(ctrl) parentOnCommitState := state.NewMockDiff(ctrl) parentOnAbortState := state.NewMockDiff(ctrl) @@ -389,10 +391,10 @@ func TestVerifierVisitAbortBlock(t *testing.T) { parentID: { statelessBlock: parentStatelessBlk, proposalBlockState: proposalBlockState{ - onCommitState: parentOnCommitState, - onAbortState: parentOnAbortState, + onDecisionState: parentOnDecisionState, + onCommitState: parentOnCommitState, + onAbortState: parentOnAbortState, }, - standardBlockState: standardBlockState{}, }, }, Mempool: mempool, @@ -549,9 +551,11 @@ func TestBanffAbortBlockTimestampChecks(t *testing.T) { // setup parent state parentTime := defaultGenesisTime - s.EXPECT().GetLastAccepted().Return(parentID).Times(2) - s.EXPECT().GetTimestamp().Return(parentTime).Times(2) + s.EXPECT().GetLastAccepted().Return(parentID).Times(3) + s.EXPECT().GetTimestamp().Return(parentTime).Times(3) + onDecisionState, err := state.NewDiff(parentID, backend) + require.NoError(err) onCommitState, err := state.NewDiff(parentID, backend) require.NoError(err) onAbortState, err := state.NewDiff(parentID, backend) @@ -560,8 +564,9 @@ func TestBanffAbortBlockTimestampChecks(t *testing.T) { timestamp: test.parentTime, statelessBlock: parentStatelessBlk, proposalBlockState: proposalBlockState{ - onCommitState: onCommitState, - onAbortState: onAbortState, + onDecisionState: onDecisionState, + onCommitState: onCommitState, + onAbortState: onAbortState, }, } @@ -642,9 +647,11 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { // setup parent state parentTime := defaultGenesisTime - s.EXPECT().GetLastAccepted().Return(parentID).Times(2) - s.EXPECT().GetTimestamp().Return(parentTime).Times(2) + s.EXPECT().GetLastAccepted().Return(parentID).Times(3) + s.EXPECT().GetTimestamp().Return(parentTime).Times(3) + onDecisionState, err := state.NewDiff(parentID, backend) + require.NoError(err) onCommitState, err := state.NewDiff(parentID, backend) require.NoError(err) onAbortState, err := state.NewDiff(parentID, backend) @@ -653,8 +660,9 @@ func TestBanffCommitBlockTimestampChecks(t *testing.T) { timestamp: test.parentTime, statelessBlock: parentStatelessBlk, proposalBlockState: proposalBlockState{ - onCommitState: onCommitState, - onAbortState: onAbortState, + onDecisionState: onDecisionState, + onCommitState: onCommitState, + onAbortState: onAbortState, }, } @@ -686,11 +694,9 @@ func TestVerifierVisitStandardBlockWithDuplicateInputs(t *testing.T) { backend := &backend{ blkIDToState: map[ids.ID]*blockState{ grandParentID: { - standardBlockState: standardBlockState{ - inputs: atomicInputs, - }, statelessBlock: grandParentStatelessBlk, onAcceptState: grandParentState, + inputs: atomicInputs, }, parentID: { statelessBlock: parentStatelessBlk, @@ -784,7 +790,6 @@ func TestVerifierVisitApricotStandardBlockWithProposalBlockParent(t *testing.T) onCommitState: parentOnCommitState, onAbortState: parentOnAbortState, }, - standardBlockState: standardBlockState{}, }, }, Mempool: mempool, @@ -842,7 +847,6 @@ func TestVerifierVisitBanffStandardBlockWithProposalBlockParent(t *testing.T) { onCommitState: parentOnCommitState, onAbortState: parentOnAbortState, }, - standardBlockState: standardBlockState{}, }, }, Mempool: mempool, diff --git a/vms/platformvm/block/mock_block.go b/vms/platformvm/block/mock_block.go index 9cc2541de0d2..7bd281192253 100644 --- a/vms/platformvm/block/mock_block.go +++ b/vms/platformvm/block/mock_block.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/platformvm/block (interfaces: Block) +// +// Generated by this command: +// +// mockgen -package=block -destination=vms/platformvm/block/mock_block.go github.com/ava-labs/avalanchego/vms/platformvm/block Block +// // Package block is a generated GoMock package. package block @@ -88,7 +90,7 @@ func (m *MockBlock) InitCtx(arg0 *snow.Context) { } // InitCtx indicates an expected call of InitCtx. -func (mr *MockBlockMockRecorder) InitCtx(arg0 interface{}) *gomock.Call { +func (mr *MockBlockMockRecorder) InitCtx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*MockBlock)(nil).InitCtx), arg0) } @@ -130,7 +132,7 @@ func (m *MockBlock) Visit(arg0 Visitor) error { } // Visit indicates an expected call of Visit. -func (mr *MockBlockMockRecorder) Visit(arg0 interface{}) *gomock.Call { +func (mr *MockBlockMockRecorder) Visit(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Visit", reflect.TypeOf((*MockBlock)(nil).Visit), arg0) } @@ -144,7 +146,7 @@ func (m *MockBlock) initialize(arg0 []byte) error { } // initialize indicates an expected call of initialize. -func (mr *MockBlockMockRecorder) initialize(arg0 interface{}) *gomock.Call { +func (mr *MockBlockMockRecorder) initialize(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "initialize", reflect.TypeOf((*MockBlock)(nil).initialize), arg0) } diff --git a/vms/platformvm/block/parse.go b/vms/platformvm/block/parse.go index 1a97dca2e4fd..e667907947e4 100644 --- a/vms/platformvm/block/parse.go +++ b/vms/platformvm/block/parse.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/platformvm/block/parse_test.go b/vms/platformvm/block/parse_test.go index 906824effd96..d21ae9da9409 100644 --- a/vms/platformvm/block/parse_test.go +++ b/vms/platformvm/block/parse_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -25,12 +25,12 @@ func TestStandardBlocks(t *testing.T) { blkTimestamp := time.Now() parentID := ids.ID{'p', 'a', 'r', 'e', 'n', 't', 'I', 'D'} height := uint64(2022) - txs, err := testDecisionTxs() + decisionTxs, err := testDecisionTxs() require.NoError(err) for _, cdc := range []codec.Manager{Codec, GenesisCodec} { // build block - apricotStandardBlk, err := NewApricotStandardBlock(parentID, height, txs) + apricotStandardBlk, err := NewApricotStandardBlock(parentID, height, decisionTxs) require.NoError(err) // parse block @@ -44,10 +44,10 @@ func TestStandardBlocks(t *testing.T) { require.Equal(apricotStandardBlk.Height(), parsed.Height()) require.IsType(&ApricotStandardBlock{}, parsed) - require.Equal(txs, parsed.Txs()) + require.Equal(decisionTxs, parsed.Txs()) // check that banff standard block can be built and parsed - banffStandardBlk, err := NewBanffStandardBlock(blkTimestamp, parentID, height, txs) + banffStandardBlk, err := NewBanffStandardBlock(blkTimestamp, parentID, height, decisionTxs) require.NoError(err) // parse block @@ -61,7 +61,7 @@ func TestStandardBlocks(t *testing.T) { require.Equal(banffStandardBlk.Height(), parsed.Height()) require.IsType(&BanffStandardBlock{}, parsed) parsedBanffStandardBlk := parsed.(*BanffStandardBlock) - require.Equal(txs, parsedBanffStandardBlk.Txs()) + require.Equal(decisionTxs, parsedBanffStandardBlk.Txs()) // timestamp check for banff blocks only require.Equal(banffStandardBlk.Timestamp(), parsedBanffStandardBlk.Timestamp()) @@ -77,7 +77,9 @@ func TestProposalBlocks(t *testing.T) { blkTimestamp := time.Now() parentID := ids.ID{'p', 'a', 'r', 'e', 'n', 't', 'I', 'D'} height := uint64(2022) - tx, err := testProposalTx() + proposalTx, err := testProposalTx() + require.NoError(err) + decisionTxs, err := testDecisionTxs() require.NoError(err) for _, cdc := range []codec.Manager{Codec, GenesisCodec} { @@ -85,7 +87,7 @@ func TestProposalBlocks(t *testing.T) { apricotProposalBlk, err := NewApricotProposalBlock( parentID, height, - tx, + proposalTx, ) require.NoError(err) @@ -101,14 +103,15 @@ func TestProposalBlocks(t *testing.T) { require.IsType(&ApricotProposalBlock{}, parsed) parsedApricotProposalBlk := parsed.(*ApricotProposalBlock) - require.Equal([]*txs.Tx{tx}, parsedApricotProposalBlk.Txs()) + require.Equal([]*txs.Tx{proposalTx}, parsedApricotProposalBlk.Txs()) // check that banff proposal block can be built and parsed banffProposalBlk, err := NewBanffProposalBlock( blkTimestamp, parentID, height, - tx, + proposalTx, + []*txs.Tx{}, ) require.NoError(err) @@ -119,17 +122,47 @@ func TestProposalBlocks(t *testing.T) { // compare content require.Equal(banffProposalBlk.ID(), parsed.ID()) require.Equal(banffProposalBlk.Bytes(), parsed.Bytes()) - require.Equal(banffProposalBlk.Parent(), banffProposalBlk.Parent()) + require.Equal(banffProposalBlk.Parent(), parsed.Parent()) require.Equal(banffProposalBlk.Height(), parsed.Height()) require.IsType(&BanffProposalBlock{}, parsed) parsedBanffProposalBlk := parsed.(*BanffProposalBlock) - require.Equal([]*txs.Tx{tx}, parsedBanffProposalBlk.Txs()) + require.Equal([]*txs.Tx{proposalTx}, parsedBanffProposalBlk.Txs()) // timestamp check for banff blocks only require.Equal(banffProposalBlk.Timestamp(), parsedBanffProposalBlk.Timestamp()) // backward compatibility check require.Equal(parsedApricotProposalBlk.Txs(), parsedBanffProposalBlk.Txs()) + + // check that banff proposal block with decisionTxs can be built and parsed + banffProposalBlkWithDecisionTxs, err := NewBanffProposalBlock( + blkTimestamp, + parentID, + height, + proposalTx, + decisionTxs, + ) + require.NoError(err) + + // parse block + parsed, err = Parse(cdc, banffProposalBlkWithDecisionTxs.Bytes()) + require.NoError(err) + + // compare content + require.Equal(banffProposalBlkWithDecisionTxs.ID(), parsed.ID()) + require.Equal(banffProposalBlkWithDecisionTxs.Bytes(), parsed.Bytes()) + require.Equal(banffProposalBlkWithDecisionTxs.Parent(), parsed.Parent()) + require.Equal(banffProposalBlkWithDecisionTxs.Height(), parsed.Height()) + require.IsType(&BanffProposalBlock{}, parsed) + parsedBanffProposalBlkWithDecisionTxs := parsed.(*BanffProposalBlock) + + l := len(decisionTxs) + expectedTxs := make([]*txs.Tx, l+1) + copy(expectedTxs, decisionTxs) + expectedTxs[l] = proposalTx + require.Equal(expectedTxs, parsedBanffProposalBlkWithDecisionTxs.Txs()) + + require.Equal(banffProposalBlkWithDecisionTxs.Timestamp(), parsedBanffProposalBlkWithDecisionTxs.Timestamp()) } } @@ -224,7 +257,7 @@ func TestAtomicBlock(t *testing.T) { require := require.New(t) parentID := ids.ID{'p', 'a', 'r', 'e', 'n', 't', 'I', 'D'} height := uint64(2022) - tx, err := testAtomicTx() + atomicTx, err := testAtomicTx() require.NoError(err) for _, cdc := range []codec.Manager{Codec, GenesisCodec} { @@ -232,7 +265,7 @@ func TestAtomicBlock(t *testing.T) { atomicBlk, err := NewApricotAtomicBlock( parentID, height, - tx, + atomicTx, ) require.NoError(err) @@ -248,7 +281,7 @@ func TestAtomicBlock(t *testing.T) { require.IsType(&ApricotAtomicBlock{}, parsed) parsedAtomicBlk := parsed.(*ApricotAtomicBlock) - require.Equal([]*txs.Tx{tx}, parsedAtomicBlk.Txs()) + require.Equal([]*txs.Tx{atomicTx}, parsedAtomicBlk.Txs()) } } diff --git a/vms/platformvm/block/proposal_block.go b/vms/platformvm/block/proposal_block.go index 05e23b649949..4160db57c4a9 100644 --- a/vms/platformvm/block/proposal_block.go +++ b/vms/platformvm/block/proposal_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -18,16 +18,23 @@ var ( ) type BanffProposalBlock struct { - Time uint64 `serialize:"true" json:"time"` - // Transactions is currently unused. This is populated so that introducing - // them in the future will not require a codec change. - // - // TODO: when Transactions is used, we must correctly verify and apply their - // changes. - Transactions []*txs.Tx `serialize:"true" json:"-"` + Time uint64 `serialize:"true" json:"time"` + Transactions []*txs.Tx `serialize:"true" json:"txs"` ApricotProposalBlock `serialize:"true"` } +func (b *BanffProposalBlock) initialize(bytes []byte) error { + if err := b.ApricotProposalBlock.initialize(bytes); err != nil { + return err + } + for _, tx := range b.Transactions { + if err := tx.Initialize(txs.Codec); err != nil { + return fmt.Errorf("failed to initialize tx: %w", err) + } + } + return nil +} + func (b *BanffProposalBlock) InitCtx(ctx *snow.Context) { for _, tx := range b.Transactions { tx.Unsigned.InitCtx(ctx) @@ -39,6 +46,14 @@ func (b *BanffProposalBlock) Timestamp() time.Time { return time.Unix(int64(b.Time), 0) } +func (b *BanffProposalBlock) Txs() []*txs.Tx { + l := len(b.Transactions) + txs := make([]*txs.Tx, l+1) + copy(txs, b.Transactions) + txs[l] = b.Tx + return txs +} + func (b *BanffProposalBlock) Visit(v Visitor) error { return v.BanffProposalBlock(b) } @@ -47,19 +62,21 @@ func NewBanffProposalBlock( timestamp time.Time, parentID ids.ID, height uint64, - tx *txs.Tx, + proposalTx *txs.Tx, + decisionTxs []*txs.Tx, ) (*BanffProposalBlock, error) { blk := &BanffProposalBlock{ - Time: uint64(timestamp.Unix()), + Transactions: decisionTxs, + Time: uint64(timestamp.Unix()), ApricotProposalBlock: ApricotProposalBlock{ CommonBlock: CommonBlock{ PrntID: parentID, Hght: height, }, - Tx: tx, + Tx: proposalTx, }, } - return blk, initialize(blk) + return blk, initialize(blk, &blk.CommonBlock) } type ApricotProposalBlock struct { @@ -102,5 +119,5 @@ func NewApricotProposalBlock( }, Tx: tx, } - return blk, initialize(blk) + return blk, initialize(blk, &blk.CommonBlock) } diff --git a/vms/platformvm/block/proposal_block_test.go b/vms/platformvm/block/proposal_block_test.go index 9c1038c51c98..7fbc44191f41 100644 --- a/vms/platformvm/block/proposal_block_test.go +++ b/vms/platformvm/block/proposal_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -10,53 +10,70 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/vms/components/avax" - "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/platformvm/txs" - "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) func TestNewBanffProposalBlock(t *testing.T) { - require := require.New(t) - timestamp := time.Now().Truncate(time.Second) parentID := ids.GenerateTestID() height := uint64(1337) + proposalTx, err := testProposalTx() + require.NoError(t, err) + decisionTxs, err := testDecisionTxs() + require.NoError(t, err) + + type test struct { + name string + proposalTx *txs.Tx + decisionTxs []*txs.Tx + } - tx := &txs.Tx{ - Unsigned: &txs.AddValidatorTx{ - BaseTx: txs.BaseTx{ - BaseTx: avax.BaseTx{ - Ins: []*avax.TransferableInput{}, - Outs: []*avax.TransferableOutput{}, - }, - }, - StakeOuts: []*avax.TransferableOutput{}, - Validator: txs.Validator{}, - RewardsOwner: &secp256k1fx.OutputOwners{ - Addrs: []ids.ShortID{}, - }, + tests := []test{ + { + name: "no decision txs", + proposalTx: proposalTx, + decisionTxs: []*txs.Tx{}, + }, + { + name: "decision txs", + proposalTx: proposalTx, + decisionTxs: decisionTxs, }, - Creds: []verify.Verifiable{}, } - require.NoError(tx.Initialize(txs.Codec)) - blk, err := NewBanffProposalBlock( - timestamp, - parentID, - height, - tx, - ) - require.NoError(err) + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) - // Make sure the block and tx are initialized - require.NotEmpty(blk.Bytes()) - require.NotEmpty(blk.Tx.Bytes()) - require.NotEqual(ids.Empty, blk.Tx.ID()) - require.Equal(tx.Bytes(), blk.Tx.Bytes()) - require.Equal(timestamp, blk.Timestamp()) - require.Equal(parentID, blk.Parent()) - require.Equal(height, blk.Height()) + blk, err := NewBanffProposalBlock( + timestamp, + parentID, + height, + test.proposalTx, + test.decisionTxs, + ) + require.NoError(err) + + require.NotEmpty(blk.Bytes()) + require.Equal(parentID, blk.Parent()) + require.Equal(height, blk.Height()) + require.Equal(timestamp, blk.Timestamp()) + + l := len(test.decisionTxs) + expectedTxs := make([]*txs.Tx, l+1) + copy(expectedTxs, test.decisionTxs) + expectedTxs[l] = test.proposalTx + + blkTxs := blk.Txs() + require.Equal(expectedTxs, blkTxs) + for i, blkTx := range blkTxs { + expectedTx := expectedTxs[i] + require.NotEmpty(blkTx.Bytes()) + require.NotEqual(ids.Empty, blkTx.ID()) + require.Equal(expectedTx.Bytes(), blkTx.Bytes()) + } + }) + } } func TestNewApricotProposalBlock(t *testing.T) { @@ -64,37 +81,28 @@ func TestNewApricotProposalBlock(t *testing.T) { parentID := ids.GenerateTestID() height := uint64(1337) - - tx := &txs.Tx{ - Unsigned: &txs.AddValidatorTx{ - BaseTx: txs.BaseTx{ - BaseTx: avax.BaseTx{ - Ins: []*avax.TransferableInput{}, - Outs: []*avax.TransferableOutput{}, - }, - }, - StakeOuts: []*avax.TransferableOutput{}, - Validator: txs.Validator{}, - RewardsOwner: &secp256k1fx.OutputOwners{ - Addrs: []ids.ShortID{}, - }, - }, - Creds: []verify.Verifiable{}, - } - require.NoError(tx.Initialize(txs.Codec)) + proposalTx, err := testProposalTx() + require.NoError(err) blk, err := NewApricotProposalBlock( parentID, height, - tx, + proposalTx, ) require.NoError(err) - // Make sure the block and tx are initialized require.NotEmpty(blk.Bytes()) - require.NotEmpty(blk.Tx.Bytes()) - require.NotEqual(ids.Empty, blk.Tx.ID()) - require.Equal(tx.Bytes(), blk.Tx.Bytes()) require.Equal(parentID, blk.Parent()) require.Equal(height, blk.Height()) + + expectedTxs := []*txs.Tx{proposalTx} + + blkTxs := blk.Txs() + require.Equal(blkTxs, expectedTxs) + for i, blkTx := range blkTxs { + expectedTx := expectedTxs[i] + require.NotEmpty(blkTx.Bytes()) + require.NotEqual(ids.Empty, blkTx.ID()) + require.Equal(expectedTx.Bytes(), blkTx.Bytes()) + } } diff --git a/vms/platformvm/block/serialization_test.go b/vms/platformvm/block/serialization_test.go index 031e527be25f..8e2002c3636c 100644 --- a/vms/platformvm/block/serialization_test.go +++ b/vms/platformvm/block/serialization_test.go @@ -1,14 +1,18 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block import ( + "encoding/json" "fmt" "testing" "github.com/stretchr/testify/require" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/txs" ) @@ -105,11 +109,118 @@ func TestBanffBlockSerialization(t *testing.T) { for _, test := range tests { testName := fmt.Sprintf("%T", test.block) + block := test.block t.Run(testName, func(t *testing.T) { require := require.New(t) - require.NoError(initialize(test.block)) - require.Equal(test.bytes, test.block.Bytes()) + got, err := Codec.Marshal(CodecVersion, &block) + require.NoError(err) + require.Equal(test.bytes, got) }) } } + +func TestBanffProposalBlockJSON(t *testing.T) { + require := require.New(t) + + simpleBanffProposalBlock := &BanffProposalBlock{ + Time: 123456, + ApricotProposalBlock: ApricotProposalBlock{ + CommonBlock: CommonBlock{ + PrntID: ids.ID{'p', 'a', 'r', 'e', 'n', 't', 'I', 'D'}, + Hght: 1337, + BlockID: ids.ID{'b', 'l', 'o', 'c', 'k', 'I', 'D'}, + }, + Tx: &txs.Tx{ + Unsigned: &txs.AdvanceTimeTx{ + Time: 123457, + }, + }, + }, + } + + simpleBanffProposalBlockBytes, err := json.MarshalIndent(simpleBanffProposalBlock, "", "\t") + require.NoError(err) + + require.Equal(`{ + "time": 123456, + "txs": null, + "parentID": "rVcYrvnGXdoJBeYQRm5ZNaCGHeVyqcHHJu8Yd89kJcef6V5Eg", + "height": 1337, + "id": "kM6h4d2UKYEDzQXm7KNqyeBJLjhb42J24m4L4WACB5didf3pk", + "tx": { + "unsignedTx": { + "time": 123457 + }, + "credentials": null, + "id": "11111111111111111111111111111111LpoYY" + } +}`, string(simpleBanffProposalBlockBytes)) + + complexBanffProposalBlock := simpleBanffProposalBlock + complexBanffProposalBlock.Transactions = []*txs.Tx{ + { + Unsigned: &txs.BaseTx{ + BaseTx: avax.BaseTx{ + NetworkID: constants.MainnetID, + BlockchainID: constants.PlatformChainID, + Outs: []*avax.TransferableOutput{}, + Ins: []*avax.TransferableInput{}, + Memo: []byte("KilroyWasHere"), + }, + }, + }, + { + Unsigned: &txs.BaseTx{ + BaseTx: avax.BaseTx{ + NetworkID: constants.MainnetID, + BlockchainID: constants.PlatformChainID, + Outs: []*avax.TransferableOutput{}, + Ins: []*avax.TransferableInput{}, + Memo: []byte("KilroyWasHere2"), + }, + }, + }, + } + + complexBanffProposalBlockBytes, err := json.MarshalIndent(complexBanffProposalBlock, "", "\t") + require.NoError(err) + + require.Equal(`{ + "time": 123456, + "txs": [ + { + "unsignedTx": { + "networkID": 1, + "blockchainID": "11111111111111111111111111111111LpoYY", + "outputs": [], + "inputs": [], + "memo": "0x4b696c726f7957617348657265" + }, + "credentials": null, + "id": "11111111111111111111111111111111LpoYY" + }, + { + "unsignedTx": { + "networkID": 1, + "blockchainID": "11111111111111111111111111111111LpoYY", + "outputs": [], + "inputs": [], + "memo": "0x4b696c726f795761734865726532" + }, + "credentials": null, + "id": "11111111111111111111111111111111LpoYY" + } + ], + "parentID": "rVcYrvnGXdoJBeYQRm5ZNaCGHeVyqcHHJu8Yd89kJcef6V5Eg", + "height": 1337, + "id": "kM6h4d2UKYEDzQXm7KNqyeBJLjhb42J24m4L4WACB5didf3pk", + "tx": { + "unsignedTx": { + "time": 123457 + }, + "credentials": null, + "id": "11111111111111111111111111111111LpoYY" + } +}`, string(complexBanffProposalBlockBytes)) +} diff --git a/vms/platformvm/block/standard_block.go b/vms/platformvm/block/standard_block.go index a088a9eab696..c7d35b12f70d 100644 --- a/vms/platformvm/block/standard_block.go +++ b/vms/platformvm/block/standard_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -46,7 +46,7 @@ func NewBanffStandardBlock( Transactions: txs, }, } - return blk, initialize(blk) + return blk, initialize(blk, &blk.CommonBlock) } type ApricotStandardBlock struct { @@ -58,7 +58,7 @@ func (b *ApricotStandardBlock) initialize(bytes []byte) error { b.CommonBlock.initialize(bytes) for _, tx := range b.Transactions { if err := tx.Initialize(txs.Codec); err != nil { - return fmt.Errorf("failed to sign block: %w", err) + return fmt.Errorf("failed to initialize tx: %w", err) } } return nil @@ -93,5 +93,5 @@ func NewApricotStandardBlock( }, Transactions: txs, } - return blk, initialize(blk) + return blk, initialize(blk, &blk.CommonBlock) } diff --git a/vms/platformvm/block/standard_block_test.go b/vms/platformvm/block/standard_block_test.go index d417a37fb96a..4162aadb05e1 100644 --- a/vms/platformvm/block/standard_block_test.go +++ b/vms/platformvm/block/standard_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/platformvm/block/visitor.go b/vms/platformvm/block/visitor.go index f05dc5d05129..6c27b5386c1a 100644 --- a/vms/platformvm/block/visitor.go +++ b/vms/platformvm/block/visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/platformvm/client.go b/vms/platformvm/client.go index 3796ad7ff86a..9311fd290a7e 100644 --- a/vms/platformvm/client.go +++ b/vms/platformvm/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm diff --git a/vms/platformvm/client_permissionless_validator.go b/vms/platformvm/client_permissionless_validator.go index c9baac856073..3974f770658d 100644 --- a/vms/platformvm/client_permissionless_validator.go +++ b/vms/platformvm/client_permissionless_validator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm diff --git a/vms/platformvm/config/config.go b/vms/platformvm/config/config.go index f9504708f78a..50628c422afd 100644 --- a/vms/platformvm/config/config.go +++ b/vms/platformvm/config/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package config diff --git a/vms/platformvm/config/execution_config.go b/vms/platformvm/config/execution_config.go index bfdb191f1281..e182758e0c50 100644 --- a/vms/platformvm/config/execution_config.go +++ b/vms/platformvm/config/execution_config.go @@ -1,15 +1,18 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package config import ( "encoding/json" + "time" "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/platformvm/network" ) var DefaultExecutionConfig = ExecutionConfig{ + Network: network.DefaultConfig, BlockCacheSize: 64 * units.MiB, TxCacheSize: 128 * units.MiB, TransformedSubnetTxCacheSize: 4 * units.MiB, @@ -19,19 +22,22 @@ var DefaultExecutionConfig = ExecutionConfig{ BlockIDCacheSize: 8192, FxOwnerCacheSize: 4 * units.MiB, ChecksumsEnabled: false, + MempoolPruneFrequency: 30 * time.Minute, } // ExecutionConfig provides execution parameters of PlatformVM type ExecutionConfig struct { - BlockCacheSize int `json:"block-cache-size"` - TxCacheSize int `json:"tx-cache-size"` - TransformedSubnetTxCacheSize int `json:"transformed-subnet-tx-cache-size"` - RewardUTXOsCacheSize int `json:"reward-utxos-cache-size"` - ChainCacheSize int `json:"chain-cache-size"` - ChainDBCacheSize int `json:"chain-db-cache-size"` - BlockIDCacheSize int `json:"block-id-cache-size"` - FxOwnerCacheSize int `json:"fx-owner-cache-size"` - ChecksumsEnabled bool `json:"checksums-enabled"` + Network network.Config `json:"network"` + BlockCacheSize int `json:"block-cache-size"` + TxCacheSize int `json:"tx-cache-size"` + TransformedSubnetTxCacheSize int `json:"transformed-subnet-tx-cache-size"` + RewardUTXOsCacheSize int `json:"reward-utxos-cache-size"` + ChainCacheSize int `json:"chain-cache-size"` + ChainDBCacheSize int `json:"chain-db-cache-size"` + BlockIDCacheSize int `json:"block-id-cache-size"` + FxOwnerCacheSize int `json:"fx-owner-cache-size"` + ChecksumsEnabled bool `json:"checksums-enabled"` + MempoolPruneFrequency time.Duration `json:"mempool-prune-frequency"` } // GetExecutionConfig returns an ExecutionConfig diff --git a/vms/platformvm/config/execution_config_test.go b/vms/platformvm/config/execution_config_test.go index 0adbd862bd2d..89fd5cd55b05 100644 --- a/vms/platformvm/config/execution_config_test.go +++ b/vms/platformvm/config/execution_config_test.go @@ -1,12 +1,15 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package config import ( "testing" + "time" "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/vms/platformvm/network" ) func TestExecutionConfigUnmarshal(t *testing.T) { @@ -39,6 +42,68 @@ func TestExecutionConfigUnmarshal(t *testing.T) { t.Run("all values extracted from json", func(t *testing.T) { require := require.New(t) b := []byte(`{ + "network": { + "max-validator-set-staleness": 1, + "target-gossip-size": 2, + "pull-gossip-poll-size": 3, + "pull-gossip-frequency": 4, + "pull-gossip-throttling-period": 5, + "pull-gossip-throttling-limit": 6, + "expected-bloom-filter-elements":7, + "expected-bloom-filter-false-positive-probability": 8, + "max-bloom-filter-false-positive-probability": 9, + "legacy-push-gossip-cache-size": 10 + }, + "block-cache-size": 1, + "tx-cache-size": 2, + "transformed-subnet-tx-cache-size": 3, + "reward-utxos-cache-size": 5, + "chain-cache-size": 6, + "chain-db-cache-size": 7, + "block-id-cache-size": 8, + "fx-owner-cache-size": 9, + "checksums-enabled": true, + "mempool-prune-frequency": 60000000000 + }`) + ec, err := GetExecutionConfig(b) + require.NoError(err) + expected := &ExecutionConfig{ + Network: network.Config{ + MaxValidatorSetStaleness: 1, + TargetGossipSize: 2, + PullGossipPollSize: 3, + PullGossipFrequency: 4, + PullGossipThrottlingPeriod: 5, + PullGossipThrottlingLimit: 6, + ExpectedBloomFilterElements: 7, + ExpectedBloomFilterFalsePositiveProbability: 8, + MaxBloomFilterFalsePositiveProbability: 9, + LegacyPushGossipCacheSize: 10, + }, + BlockCacheSize: 1, + TxCacheSize: 2, + TransformedSubnetTxCacheSize: 3, + RewardUTXOsCacheSize: 5, + ChainCacheSize: 6, + ChainDBCacheSize: 7, + BlockIDCacheSize: 8, + FxOwnerCacheSize: 9, + ChecksumsEnabled: true, + MempoolPruneFrequency: time.Minute, + } + require.Equal(expected, ec) + }) + + t.Run("default values applied correctly", func(t *testing.T) { + require := require.New(t) + b := []byte(`{ + "network": { + "max-validator-set-staleness": 1, + "target-gossip-size": 2, + "pull-gossip-poll-size": 3, + "pull-gossip-frequency": 4, + "pull-gossip-throttling-period": 5 + }, "block-cache-size": 1, "tx-cache-size": 2, "transformed-subnet-tx-cache-size": 3, @@ -52,6 +117,18 @@ func TestExecutionConfigUnmarshal(t *testing.T) { ec, err := GetExecutionConfig(b) require.NoError(err) expected := &ExecutionConfig{ + Network: network.Config{ + MaxValidatorSetStaleness: 1, + TargetGossipSize: 2, + PullGossipPollSize: 3, + PullGossipFrequency: 4, + PullGossipThrottlingPeriod: 5, + PullGossipThrottlingLimit: DefaultExecutionConfig.Network.PullGossipThrottlingLimit, + ExpectedBloomFilterElements: DefaultExecutionConfig.Network.ExpectedBloomFilterElements, + ExpectedBloomFilterFalsePositiveProbability: DefaultExecutionConfig.Network.ExpectedBloomFilterFalsePositiveProbability, + MaxBloomFilterFalsePositiveProbability: DefaultExecutionConfig.Network.MaxBloomFilterFalsePositiveProbability, + LegacyPushGossipCacheSize: DefaultExecutionConfig.Network.LegacyPushGossipCacheSize, + }, BlockCacheSize: 1, TxCacheSize: 2, TransformedSubnetTxCacheSize: 3, @@ -61,6 +138,7 @@ func TestExecutionConfigUnmarshal(t *testing.T) { BlockIDCacheSize: 8, FxOwnerCacheSize: 9, ChecksumsEnabled: true, + MempoolPruneFrequency: 30 * time.Minute, } require.Equal(expected, ec) }) diff --git a/vms/platformvm/docs/validators_versioning.md b/vms/platformvm/docs/validators_versioning.md index c4fce00399c5..7db716d12677 100644 --- a/vms/platformvm/docs/validators_versioning.md +++ b/vms/platformvm/docs/validators_versioning.md @@ -18,7 +18,7 @@ GetValidatorSet(ctx context.Context, height uint64, subnetID ids.ID) (map[ids.No Validator data are collected in a struct named `validators.GetValidatorOutput` which holds for each active validator, its `NodeID`, its `Weight` and its `BLS Public Key` if it was registered. -Note that a validator `Weight` is not just its stake; its the aggregate value of the validator's own stake and all of its delegators' stake. A validator's `Weight` gauges how relevant its preference should be in consensus or Warp operations. +Note that a validator `Weight` is not just its stake; it's the aggregate value of the validator's own stake and all of its delegators' stake. A validator's `Weight` gauges how relevant its preference should be in consensus or Warp operations. We will see in the next section how the P-chain keeps track of this information over time as the validator set changes. @@ -35,7 +35,7 @@ These diffs are key to rebuilding the validator set at a given past height. In t The validators diffs track changes in a validator's `Weight` and `BLS Public key`. Along with the `NodeID` this is the data exposed by the `GetValidatorSet` method. -Note that `Weight` and `BLS Public key` behave differently throughout the validator lifetime: +Note that `Weight` and `BLS Public key` behave differently throughout the validator's lifetime: 1. `BLS Public key` cannot change through a validator's lifetime. It can only change when a validator is added/re-added and removed. 2. `Weight` can change throughout a validator's lifetime by the creation and removal of its delegators as well as by validator's own creation and removal. diff --git a/vms/platformvm/factory.go b/vms/platformvm/factory.go index 5673bebefd97..834c9c8f2450 100644 --- a/vms/platformvm/factory.go +++ b/vms/platformvm/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm diff --git a/vms/platformvm/fx/fx.go b/vms/platformvm/fx/fx.go index 706d86debfcd..4f6eceea5f7a 100644 --- a/vms/platformvm/fx/fx.go +++ b/vms/platformvm/fx/fx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package fx diff --git a/vms/platformvm/fx/mock_fx.go b/vms/platformvm/fx/mock_fx.go index b0a2d761c7b2..6878c124b822 100644 --- a/vms/platformvm/fx/mock_fx.go +++ b/vms/platformvm/fx/mock_fx.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/platformvm/fx (interfaces: Fx,Owner) +// +// Generated by this command: +// +// mockgen -package=fx -destination=vms/platformvm/fx/mock_fx.go github.com/ava-labs/avalanchego/vms/platformvm/fx Fx,Owner +// // Package fx is a generated GoMock package. package fx @@ -67,22 +69,22 @@ func (mr *MockFxMockRecorder) Bootstrapping() *gomock.Call { } // CreateOutput mocks base method. -func (m *MockFx) CreateOutput(arg0 uint64, arg1 interface{}) (interface{}, error) { +func (m *MockFx) CreateOutput(arg0 uint64, arg1 any) (any, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateOutput", arg0, arg1) - ret0, _ := ret[0].(interface{}) + ret0, _ := ret[0].(any) ret1, _ := ret[1].(error) return ret0, ret1 } // CreateOutput indicates an expected call of CreateOutput. -func (mr *MockFxMockRecorder) CreateOutput(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockFxMockRecorder) CreateOutput(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOutput", reflect.TypeOf((*MockFx)(nil).CreateOutput), arg0, arg1) } // Initialize mocks base method. -func (m *MockFx) Initialize(arg0 interface{}) error { +func (m *MockFx) Initialize(arg0 any) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Initialize", arg0) ret0, _ := ret[0].(error) @@ -90,13 +92,13 @@ func (m *MockFx) Initialize(arg0 interface{}) error { } // Initialize indicates an expected call of Initialize. -func (mr *MockFxMockRecorder) Initialize(arg0 interface{}) *gomock.Call { +func (mr *MockFxMockRecorder) Initialize(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockFx)(nil).Initialize), arg0) } // VerifyPermission mocks base method. -func (m *MockFx) VerifyPermission(arg0, arg1, arg2, arg3 interface{}) error { +func (m *MockFx) VerifyPermission(arg0, arg1, arg2, arg3 any) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "VerifyPermission", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) @@ -104,13 +106,13 @@ func (m *MockFx) VerifyPermission(arg0, arg1, arg2, arg3 interface{}) error { } // VerifyPermission indicates an expected call of VerifyPermission. -func (mr *MockFxMockRecorder) VerifyPermission(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockFxMockRecorder) VerifyPermission(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyPermission", reflect.TypeOf((*MockFx)(nil).VerifyPermission), arg0, arg1, arg2, arg3) } // VerifyTransfer mocks base method. -func (m *MockFx) VerifyTransfer(arg0, arg1, arg2, arg3 interface{}) error { +func (m *MockFx) VerifyTransfer(arg0, arg1, arg2, arg3 any) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "VerifyTransfer", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) @@ -118,7 +120,7 @@ func (m *MockFx) VerifyTransfer(arg0, arg1, arg2, arg3 interface{}) error { } // VerifyTransfer indicates an expected call of VerifyTransfer. -func (mr *MockFxMockRecorder) VerifyTransfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockFxMockRecorder) VerifyTransfer(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyTransfer", reflect.TypeOf((*MockFx)(nil).VerifyTransfer), arg0, arg1, arg2, arg3) } @@ -155,7 +157,7 @@ func (m *MockOwner) InitCtx(arg0 *snow.Context) { } // InitCtx indicates an expected call of InitCtx. -func (mr *MockOwnerMockRecorder) InitCtx(arg0 interface{}) *gomock.Call { +func (mr *MockOwnerMockRecorder) InitCtx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*MockOwner)(nil).InitCtx), arg0) } @@ -173,3 +175,17 @@ func (mr *MockOwnerMockRecorder) Verify() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockOwner)(nil).Verify)) } + +// isState mocks base method. +func (m *MockOwner) isState() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "isState") + ret0, _ := ret[0].(error) + return ret0 +} + +// isState indicates an expected call of isState. +func (mr *MockOwnerMockRecorder) isState() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "isState", reflect.TypeOf((*MockOwner)(nil).isState)) +} diff --git a/vms/platformvm/genesis/codec.go b/vms/platformvm/genesis/codec.go index 7b68ac58d634..b18c40d60cca 100644 --- a/vms/platformvm/genesis/codec.go +++ b/vms/platformvm/genesis/codec.go @@ -1,10 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis import "github.com/ava-labs/avalanchego/vms/platformvm/block" -const Version = block.Version +const CodecVersion = block.CodecVersion var Codec = block.GenesisCodec diff --git a/vms/platformvm/genesis/genesis.go b/vms/platformvm/genesis/genesis.go index 4b573b723e3f..795e73cbefa6 100644 --- a/vms/platformvm/genesis/genesis.go +++ b/vms/platformvm/genesis/genesis.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package genesis diff --git a/vms/platformvm/health.go b/vms/platformvm/health.go index 4ceed8f84adc..86c80b807b70 100644 --- a/vms/platformvm/health.go +++ b/vms/platformvm/health.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm diff --git a/vms/platformvm/main_test.go b/vms/platformvm/main_test.go index 88a571cfa5cb..d353d31664fe 100644 --- a/vms/platformvm/main_test.go +++ b/vms/platformvm/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm diff --git a/vms/platformvm/metrics/block_metrics.go b/vms/platformvm/metrics/block_metrics.go index 7b3a9b2abf6c..193369cbf901 100644 --- a/vms/platformvm/metrics/block_metrics.go +++ b/vms/platformvm/metrics/block_metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/vms/platformvm/metrics/metrics.go b/vms/platformvm/metrics/metrics.go index 7c0e616dd9b2..5357721d9bd6 100644 --- a/vms/platformvm/metrics/metrics.go +++ b/vms/platformvm/metrics/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/vms/platformvm/metrics/no_op.go b/vms/platformvm/metrics/no_op.go index 45100b49bbf5..770e30c961a1 100644 --- a/vms/platformvm/metrics/no_op.go +++ b/vms/platformvm/metrics/no_op.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/vms/platformvm/metrics/tx_metrics.go b/vms/platformvm/metrics/tx_metrics.go index 9ed07bce7ec9..f56c84aac176 100644 --- a/vms/platformvm/metrics/tx_metrics.go +++ b/vms/platformvm/metrics/tx_metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package metrics diff --git a/vms/platformvm/network/config.go b/vms/platformvm/network/config.go new file mode 100644 index 000000000000..8536504d8383 --- /dev/null +++ b/vms/platformvm/network/config.go @@ -0,0 +1,66 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "time" + + "github.com/ava-labs/avalanchego/utils/units" +) + +var DefaultConfig = Config{ + MaxValidatorSetStaleness: time.Minute, + TargetGossipSize: 20 * units.KiB, + PullGossipPollSize: 1, + PullGossipFrequency: 1500 * time.Millisecond, + PullGossipThrottlingPeriod: 10 * time.Second, + PullGossipThrottlingLimit: 2, + ExpectedBloomFilterElements: 8 * 1024, + ExpectedBloomFilterFalsePositiveProbability: .01, + MaxBloomFilterFalsePositiveProbability: .05, + LegacyPushGossipCacheSize: 512, +} + +type Config struct { + // MaxValidatorSetStaleness limits how old of a validator set the network + // will use for peer sampling and rate limiting. + MaxValidatorSetStaleness time.Duration `json:"max-validator-set-staleness"` + // TargetGossipSize is the number of bytes that will be attempted to be + // sent when pushing transactions and when responded to transaction pull + // requests. + TargetGossipSize int `json:"target-gossip-size"` + // PullGossipPollSize is the number of validators to sample when performing + // a round of pull gossip. + PullGossipPollSize int `json:"pull-gossip-poll-size"` + // PullGossipFrequency is how frequently rounds of pull gossip are + // performed. + PullGossipFrequency time.Duration `json:"pull-gossip-frequency"` + // PullGossipThrottlingPeriod is how large of a window the throttler should + // use. + PullGossipThrottlingPeriod time.Duration `json:"pull-gossip-throttling-period"` + // PullGossipThrottlingLimit is the number of pull querys that are allowed + // by a validator in every throttling window. + PullGossipThrottlingLimit int `json:"pull-gossip-throttling-limit"` + // ExpectedBloomFilterElements is the number of elements to expect when + // creating a new bloom filter. The larger this number is, the larger the + // bloom filter will be. + ExpectedBloomFilterElements int `json:"expected-bloom-filter-elements"` + // ExpectedBloomFilterFalsePositiveProbability is the expected probability + // of a false positive after having inserted ExpectedBloomFilterElements + // into a bloom filter. The smaller this number is, the larger the bloom + // filter will be. + ExpectedBloomFilterFalsePositiveProbability float64 `json:"expected-bloom-filter-false-positive-probability"` + // MaxBloomFilterFalsePositiveProbability is used to determine when the + // bloom filter should be refreshed. Once the expected probability of a + // false positive exceeds this value, the bloom filter will be regenerated. + // The smaller this number is, the more frequently that the bloom filter + // will be regenerated. + MaxBloomFilterFalsePositiveProbability float64 `json:"max-bloom-filter-false-positive-probability"` + // LegacyPushGossipCacheSize tracks the most recently received transactions + // and ensures to only gossip them once. + // + // Deprecated: The legacy push gossip mechanism is deprecated in favor of + // the p2p SDK's push gossip mechanism. + LegacyPushGossipCacheSize int `json:"legacy-push-gossip-cache-size"` +} diff --git a/vms/platformvm/network/gossip.go b/vms/platformvm/network/gossip.go new file mode 100644 index 000000000000..fa40567430f7 --- /dev/null +++ b/vms/platformvm/network/gossip.go @@ -0,0 +1,140 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/gossip" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" +) + +var ( + _ p2p.Handler = (*txGossipHandler)(nil) + _ gossip.Marshaller[*txs.Tx] = (*txMarshaller)(nil) + _ gossip.Gossipable = (*txs.Tx)(nil) +) + +// bloomChurnMultiplier is the number used to multiply the size of the mempool +// to determine how large of a bloom filter to create. +const bloomChurnMultiplier = 3 + +// txGossipHandler is the handler called when serving gossip messages +type txGossipHandler struct { + p2p.NoOpHandler + appGossipHandler p2p.Handler + appRequestHandler p2p.Handler +} + +func (t txGossipHandler) AppGossip( + ctx context.Context, + nodeID ids.NodeID, + gossipBytes []byte, +) { + t.appGossipHandler.AppGossip(ctx, nodeID, gossipBytes) +} + +func (t txGossipHandler) AppRequest( + ctx context.Context, + nodeID ids.NodeID, + deadline time.Time, + requestBytes []byte, +) ([]byte, error) { + return t.appRequestHandler.AppRequest(ctx, nodeID, deadline, requestBytes) +} + +type txMarshaller struct{} + +func (txMarshaller) MarshalGossip(tx *txs.Tx) ([]byte, error) { + return tx.Bytes(), nil +} + +func (txMarshaller) UnmarshalGossip(bytes []byte) (*txs.Tx, error) { + return txs.Parse(txs.Codec, bytes) +} + +func newGossipMempool( + mempool mempool.Mempool, + log logging.Logger, + txVerifier TxVerifier, + minTargetElements int, + targetFalsePositiveProbability, + resetFalsePositiveProbability float64, +) (*gossipMempool, error) { + bloom, err := gossip.NewBloomFilter(minTargetElements, targetFalsePositiveProbability, resetFalsePositiveProbability) + return &gossipMempool{ + Mempool: mempool, + log: log, + txVerifier: txVerifier, + bloom: bloom, + }, err +} + +type gossipMempool struct { + mempool.Mempool + log logging.Logger + txVerifier TxVerifier + + lock sync.RWMutex + bloom *gossip.BloomFilter +} + +func (g *gossipMempool) Add(tx *txs.Tx) error { + txID := tx.ID() + if _, ok := g.Mempool.Get(txID); ok { + return fmt.Errorf("tx %s dropped: %w", txID, mempool.ErrDuplicateTx) + } + + if reason := g.Mempool.GetDropReason(txID); reason != nil { + // If the tx is being dropped - just ignore it + // + // TODO: Should we allow re-verification of the transaction even if it + // failed previously? + return reason + } + + if err := g.txVerifier.VerifyTx(tx); err != nil { + g.Mempool.MarkDropped(txID, err) + return err + } + + if err := g.Mempool.Add(tx); err != nil { + g.Mempool.MarkDropped(txID, err) + return err + } + + g.lock.Lock() + defer g.lock.Unlock() + + g.bloom.Add(tx) + reset, err := gossip.ResetBloomFilterIfNeeded(g.bloom, g.Mempool.Len()*bloomChurnMultiplier) + if err != nil { + return err + } + + if reset { + g.log.Debug("resetting bloom filter") + g.Mempool.Iterate(func(tx *txs.Tx) bool { + g.bloom.Add(tx) + return true + }) + } + + g.Mempool.RequestBuildBlock(false) + return nil +} + +func (g *gossipMempool) GetFilter() (bloom []byte, salt []byte) { + g.lock.RLock() + defer g.lock.RUnlock() + + return g.bloom.Marshal() +} diff --git a/vms/platformvm/network/gossip_test.go b/vms/platformvm/network/gossip_test.go new file mode 100644 index 000000000000..dcfca4ed2eed --- /dev/null +++ b/vms/platformvm/network/gossip_test.go @@ -0,0 +1,148 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "go.uber.org/mock/gomock" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" +) + +var errFoo = errors.New("foo") + +// Add should error if verification errors +func TestGossipMempoolAddVerificationError(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + txID := ids.GenerateTestID() + tx := &txs.Tx{ + TxID: txID, + } + + mempool := mempool.NewMockMempool(ctrl) + txVerifier := testTxVerifier{err: errFoo} + + mempool.EXPECT().Get(txID).Return(nil, false) + mempool.EXPECT().GetDropReason(txID).Return(nil) + mempool.EXPECT().MarkDropped(txID, errFoo) + + gossipMempool, err := newGossipMempool( + mempool, + logging.NoLog{}, + txVerifier, + testConfig.ExpectedBloomFilterElements, + testConfig.ExpectedBloomFilterFalsePositiveProbability, + testConfig.MaxBloomFilterFalsePositiveProbability, + ) + require.NoError(err) + + err = gossipMempool.Add(tx) + require.ErrorIs(err, errFoo) + require.False(gossipMempool.bloom.Has(tx)) +} + +// Add should error if adding to the mempool errors +func TestGossipMempoolAddError(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + txID := ids.GenerateTestID() + tx := &txs.Tx{ + TxID: txID, + } + + txVerifier := testTxVerifier{} + mempool := mempool.NewMockMempool(ctrl) + + mempool.EXPECT().Get(txID).Return(nil, false) + mempool.EXPECT().GetDropReason(txID).Return(nil) + mempool.EXPECT().Add(tx).Return(errFoo) + mempool.EXPECT().MarkDropped(txID, errFoo).AnyTimes() + + gossipMempool, err := newGossipMempool( + mempool, + logging.NoLog{}, + txVerifier, + testConfig.ExpectedBloomFilterElements, + testConfig.ExpectedBloomFilterFalsePositiveProbability, + testConfig.MaxBloomFilterFalsePositiveProbability, + ) + require.NoError(err) + + err = gossipMempool.Add(tx) + require.ErrorIs(err, errFoo) + require.False(gossipMempool.bloom.Has(tx)) +} + +// Adding a duplicate to the mempool should return an error +func TestMempoolDuplicate(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + testMempool := mempool.NewMockMempool(ctrl) + txVerifier := testTxVerifier{} + + txID := ids.GenerateTestID() + tx := &txs.Tx{ + TxID: txID, + } + + testMempool.EXPECT().Get(txID).Return(tx, true) + + gossipMempool, err := newGossipMempool( + testMempool, + logging.NoLog{}, + txVerifier, + testConfig.ExpectedBloomFilterElements, + testConfig.ExpectedBloomFilterFalsePositiveProbability, + testConfig.MaxBloomFilterFalsePositiveProbability, + ) + require.NoError(err) + + err = gossipMempool.Add(tx) + require.ErrorIs(err, mempool.ErrDuplicateTx) + require.False(gossipMempool.bloom.Has(tx)) +} + +// Adding a tx to the mempool should add it to the bloom filter +func TestGossipAddBloomFilter(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + txID := ids.GenerateTestID() + tx := &txs.Tx{ + TxID: txID, + } + + txVerifier := testTxVerifier{} + mempool := mempool.NewMockMempool(ctrl) + + mempool.EXPECT().Get(txID).Return(nil, false) + mempool.EXPECT().GetDropReason(txID).Return(nil) + mempool.EXPECT().Add(tx).Return(nil) + mempool.EXPECT().Len().Return(0) + mempool.EXPECT().RequestBuildBlock(false) + + gossipMempool, err := newGossipMempool( + mempool, + logging.NoLog{}, + txVerifier, + testConfig.ExpectedBloomFilterElements, + testConfig.ExpectedBloomFilterFalsePositiveProbability, + testConfig.MaxBloomFilterFalsePositiveProbability, + ) + require.NoError(err) + + require.NoError(gossipMempool.Add(tx)) + require.True(gossipMempool.bloom.Has(tx)) +} diff --git a/vms/platformvm/network/main_test.go b/vms/platformvm/network/main_test.go index be0fab18f587..ed2cfd9ecee7 100644 --- a/vms/platformvm/network/main_test.go +++ b/vms/platformvm/network/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network diff --git a/vms/platformvm/network/network.go b/vms/platformvm/network/network.go index 5f4945093d60..6844654a41c1 100644 --- a/vms/platformvm/network/network.go +++ b/vms/platformvm/network/network.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -6,77 +6,189 @@ package network import ( "context" "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/network/p2p" + "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/components/message" - "github.com/ava-labs/avalanchego/vms/platformvm/block/executor" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" ) -// We allow [recentCacheSize] to be fairly large because we only store hashes -// in the cache, not entire transactions. -const recentCacheSize = 512 - -var _ Network = (*network)(nil) +const txGossipHandlerID = 0 type Network interface { common.AppHandler + // Gossip starts gossiping transactions and blocks until it completes. + Gossip(ctx context.Context) // IssueTx verifies the transaction at the currently preferred state, adds // it to the mempool, and gossips it to the network. - // - // Invariant: Assumes the context lock is held. IssueTx(context.Context, *txs.Tx) error } type network struct { - // We embed a noop handler for all unhandled messages - common.AppHandler + *p2p.Network - ctx *snow.Context - manager executor.Manager - mempool mempool.Mempool + log logging.Logger + txVerifier TxVerifier + mempool *gossipMempool partialSyncPrimaryNetwork bool appSender common.AppSender + txPushGossiper gossip.Accumulator[*txs.Tx] + txPullGossiper gossip.Gossiper + txGossipFrequency time.Duration + // gossip related attributes recentTxsLock sync.Mutex recentTxs *cache.LRU[ids.ID, struct{}] } func New( - ctx *snow.Context, - manager executor.Manager, + log logging.Logger, + nodeID ids.NodeID, + subnetID ids.ID, + vdrs validators.State, + txVerifier TxVerifier, mempool mempool.Mempool, partialSyncPrimaryNetwork bool, appSender common.AppSender, -) Network { - return &network{ - AppHandler: common.NewNoOpAppHandler(ctx.Log), + registerer prometheus.Registerer, + config Config, +) (Network, error) { + p2pNetwork, err := p2p.NewNetwork(log, appSender, registerer, "p2p") + if err != nil { + return nil, err + } + + marshaller := txMarshaller{} + validators := p2p.NewValidators( + p2pNetwork.Peers, + log, + subnetID, + vdrs, + config.MaxValidatorSetStaleness, + ) + txGossipClient := p2pNetwork.NewClient( + txGossipHandlerID, + p2p.WithValidatorSampling(validators), + ) + txGossipMetrics, err := gossip.NewMetrics(registerer, "tx") + if err != nil { + return nil, err + } + + txPushGossiper := gossip.NewPushGossiper[*txs.Tx]( + marshaller, + txGossipClient, + txGossipMetrics, + config.TargetGossipSize, + ) + + gossipMempool, err := newGossipMempool( + mempool, + log, + txVerifier, + config.ExpectedBloomFilterElements, + config.ExpectedBloomFilterFalsePositiveProbability, + config.MaxBloomFilterFalsePositiveProbability, + ) + if err != nil { + return nil, err + } + + var txPullGossiper gossip.Gossiper + txPullGossiper = gossip.NewPullGossiper[*txs.Tx]( + log, + marshaller, + gossipMempool, + txGossipClient, + txGossipMetrics, + config.PullGossipPollSize, + ) + + // Gossip requests are only served if a node is a validator + txPullGossiper = gossip.ValidatorGossiper{ + Gossiper: txPullGossiper, + NodeID: nodeID, + Validators: validators, + } + + handler := gossip.NewHandler[*txs.Tx]( + log, + marshaller, + txPushGossiper, + gossipMempool, + txGossipMetrics, + config.TargetGossipSize, + ) + + validatorHandler := p2p.NewValidatorHandler( + p2p.NewThrottlerHandler( + handler, + p2p.NewSlidingWindowThrottler( + config.PullGossipThrottlingPeriod, + config.PullGossipThrottlingLimit, + ), + log, + ), + validators, + log, + ) + + // We allow pushing txs between all peers, but only serve gossip requests + // from validators + txGossipHandler := txGossipHandler{ + appGossipHandler: handler, + appRequestHandler: validatorHandler, + } - ctx: ctx, - manager: manager, - mempool: mempool, + if err := p2pNetwork.AddHandler(txGossipHandlerID, txGossipHandler); err != nil { + return nil, err + } + + return &network{ + Network: p2pNetwork, + log: log, + txVerifier: txVerifier, + mempool: gossipMempool, partialSyncPrimaryNetwork: partialSyncPrimaryNetwork, appSender: appSender, - recentTxs: &cache.LRU[ids.ID, struct{}]{Size: recentCacheSize}, + txPushGossiper: txPushGossiper, + txPullGossiper: txPullGossiper, + txGossipFrequency: config.PullGossipFrequency, + recentTxs: &cache.LRU[ids.ID, struct{}]{Size: config.LegacyPushGossipCacheSize}, + }, nil +} + +func (n *network) Gossip(ctx context.Context) { + // If the node is running partial sync, we should not perform any pull + // gossip. + if n.partialSyncPrimaryNetwork { + return } + + gossip.Every(ctx, n.log, n.txPullGossiper, n.txGossipFrequency) } func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, msgBytes []byte) error { - n.ctx.Log.Debug("called AppGossip message handler", + n.log.Debug("called AppGossip message handler", zap.Stringer("nodeID", nodeID), zap.Int("messageLen", len(msgBytes)), ) if n.partialSyncPrimaryNetwork { - n.ctx.Log.Debug("dropping AppGossip message", + n.log.Debug("dropping AppGossip message", zap.String("reason", "primary network is not being fully synced"), ) return nil @@ -84,15 +196,16 @@ func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, msgBytes []b msgIntf, err := message.Parse(msgBytes) if err != nil { - n.ctx.Log.Debug("dropping AppGossip message", + n.log.Debug("forwarding AppGossip to p2p network", zap.String("reason", "failed to parse message"), ) - return nil + + return n.Network.AppGossip(ctx, nodeID, msgBytes) } msg, ok := msgIntf.(*message.Tx) if !ok { - n.ctx.Log.Debug("dropping unexpected message", + n.log.Debug("dropping unexpected message", zap.Stringer("nodeID", nodeID), ) return nil @@ -100,7 +213,7 @@ func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, msgBytes []b tx, err := txs.Parse(txs.Codec, msg.Tx) if err != nil { - n.ctx.Log.Verbo("received invalid tx", + n.log.Verbo("received invalid tx", zap.Stringer("nodeID", nodeID), zap.Binary("tx", msg.Tx), zap.Error(err), @@ -109,20 +222,11 @@ func (n *network) AppGossip(ctx context.Context, nodeID ids.NodeID, msgBytes []b } txID := tx.ID() - // We need to grab the context lock here to avoid racy behavior with - // transaction verification + mempool modifications. - // - // Invariant: tx should not be referenced again without the context lock - // held to avoid any data races. - n.ctx.Lock.Lock() - defer n.ctx.Lock.Unlock() - - if reason := n.mempool.GetDropReason(txID); reason != nil { - // If the tx is being dropped - just ignore it - return nil - } if err := n.issueTx(tx); err == nil { - n.gossipTx(ctx, txID, msgBytes) + n.legacyGossipTx(ctx, txID, msgBytes) + + n.txPushGossiper.Add(tx) + return n.txPushGossiper.Gossip(ctx) } return nil } @@ -142,29 +246,13 @@ func (n *network) IssueTx(ctx context.Context, tx *txs.Tx) error { } txID := tx.ID() - n.gossipTx(ctx, txID, msgBytes) - return nil + n.legacyGossipTx(ctx, txID, msgBytes) + n.txPushGossiper.Add(tx) + return n.txPushGossiper.Gossip(ctx) } // returns nil if the tx is in the mempool func (n *network) issueTx(tx *txs.Tx) error { - txID := tx.ID() - if n.mempool.Has(txID) { - // The tx is already in the mempool - return nil - } - - // Verify the tx at the currently preferred state - if err := n.manager.VerifyTx(tx); err != nil { - n.ctx.Log.Debug("tx failed verification", - zap.Stringer("txID", txID), - zap.Error(err), - ) - - n.mempool.MarkDropped(txID, err) - return err - } - // If we are partially syncing the Primary Network, we should not be // maintaining the transaction mempool locally. if n.partialSyncPrimaryNetwork { @@ -172,21 +260,18 @@ func (n *network) issueTx(tx *txs.Tx) error { } if err := n.mempool.Add(tx); err != nil { - n.ctx.Log.Debug("tx failed to be added to the mempool", - zap.Stringer("txID", txID), + n.log.Debug("tx failed to be added to the mempool", + zap.Stringer("txID", tx.ID()), zap.Error(err), ) - n.mempool.MarkDropped(txID, err) return err } - n.mempool.RequestBuildBlock(false) - return nil } -func (n *network) gossipTx(ctx context.Context, txID ids.ID, msgBytes []byte) { +func (n *network) legacyGossipTx(ctx context.Context, txID ids.ID, msgBytes []byte) { n.recentTxsLock.Lock() _, has := n.recentTxs.Get(txID) n.recentTxs.Put(txID, struct{}{}) @@ -197,12 +282,12 @@ func (n *network) gossipTx(ctx context.Context, txID ids.ID, msgBytes []byte) { return } - n.ctx.Log.Debug("gossiping tx", + n.log.Debug("gossiping tx", zap.Stringer("txID", txID), ) if err := n.appSender.SendAppGossip(ctx, msgBytes); err != nil { - n.ctx.Log.Error("failed to gossip tx", + n.log.Error("failed to gossip tx", zap.Stringer("txID", txID), zap.Error(err), ) diff --git a/vms/platformvm/network/network_test.go b/vms/platformvm/network/network_test.go index 000cbda7e195..181fbc163cea 100644 --- a/vms/platformvm/network/network_test.go +++ b/vms/platformvm/network/network_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package network @@ -7,23 +7,50 @@ import ( "context" "errors" "testing" + "time" + + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/message" - "github.com/ava-labs/avalanchego/vms/platformvm/block/executor" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" ) -var errTest = errors.New("test error") +var ( + errTest = errors.New("test error") + + testConfig = Config{ + MaxValidatorSetStaleness: time.Second, + TargetGossipSize: 1, + PullGossipPollSize: 1, + PullGossipFrequency: time.Second, + PullGossipThrottlingPeriod: time.Second, + PullGossipThrottlingLimit: 1, + ExpectedBloomFilterElements: 10, + ExpectedBloomFilterFalsePositiveProbability: .1, + MaxBloomFilterFalsePositiveProbability: .5, + LegacyPushGossipCacheSize: 512, + } +) + +var _ TxVerifier = (*testTxVerifier)(nil) + +type testTxVerifier struct { + err error +} + +func (t testTxVerifier) VerifyTx(*txs.Tx) error { + return t.err +} func TestNetworkAppGossip(t *testing.T) { testTx := &txs.Tx{ @@ -83,7 +110,6 @@ func TestNetworkAppGossip(t *testing.T) { }, }, { - // Issue returns nil because mempool has tx. We should gossip the tx. name: "issuance succeeds", msgBytesFunc: func() []byte { msg := message.Tx{ @@ -95,13 +121,18 @@ func TestNetworkAppGossip(t *testing.T) { }, mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(true) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) mempool.EXPECT().GetDropReason(gomock.Any()).Return(nil) + mempool.EXPECT().Add(gomock.Any()).Return(nil) + mempool.EXPECT().Len().Return(0) + mempool.EXPECT().RequestBuildBlock(false) return mempool }, appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { + // we should gossip the tx twice because sdk and legacy gossip + // currently runs together appSender := common.NewMockSender(ctrl) - appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()) + appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Times(2) return appSender }, }, @@ -118,6 +149,7 @@ func TestNetworkAppGossip(t *testing.T) { }, mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) mempool.EXPECT().GetDropReason(gomock.Any()).Return(errTest) return mempool }, @@ -153,27 +185,36 @@ func TestNetworkAppGossip(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require := require.New(t) + ctx := context.Background() ctrl := gomock.NewController(t) - n := New( - &snow.Context{ - Log: logging.NoLog{}, - }, - executor.NewMockManager(ctrl), // Manager is unused in this test + snowCtx := snowtest.Context(t, ids.Empty) + n, err := New( + logging.NoLog{}, + ids.EmptyNodeID, + ids.Empty, + snowCtx.ValidatorState, + testTxVerifier{}, tt.mempoolFunc(ctrl), tt.partialSyncPrimaryNetwork, tt.appSenderFunc(ctrl), + prometheus.NewRegistry(), + DefaultConfig, ) - require.NoError(n.AppGossip(context.Background(), ids.GenerateTestNodeID(), tt.msgBytesFunc())) + require.NoError(err) + + require.NoError(n.AppGossip(ctx, ids.GenerateTestNodeID(), tt.msgBytesFunc())) }) } } func TestNetworkIssueTx(t *testing.T) { + tx := &txs.Tx{} + type test struct { name string mempoolFunc func(*gomock.Controller) mempool.Mempool - managerFunc func(*gomock.Controller) executor.Manager + txVerifier testTxVerifier partialSyncPrimaryNetwork bool appSenderFunc func(*gomock.Controller) common.AppSender expectedErr error @@ -184,34 +225,22 @@ func TestNetworkIssueTx(t *testing.T) { name: "mempool has transaction", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(true) + mempool.EXPECT().Get(gomock.Any()).Return(tx, true) return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - // Unused in this test - return executor.NewMockManager(ctrl) - }, appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Should gossip the tx - appSender := common.NewMockSender(ctrl) - appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil) - return appSender + return common.NewMockSender(ctrl) }, - expectedErr: nil, + expectedErr: mempool.ErrDuplicateTx, }, { name: "transaction marked as dropped in mempool", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) - mempool.EXPECT().MarkDropped(gomock.Any(), gomock.Any()) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) + mempool.EXPECT().GetDropReason(gomock.Any()).Return(errTest) return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - manager := executor.NewMockManager(ctrl) - manager.EXPECT().VerifyTx(gomock.Any()).Return(errTest) - return manager - }, appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { // Shouldn't gossip the tx return common.NewMockSender(ctrl) @@ -222,15 +251,12 @@ func TestNetworkIssueTx(t *testing.T) { name: "transaction invalid", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) + mempool.EXPECT().GetDropReason(gomock.Any()).Return(nil) mempool.EXPECT().MarkDropped(gomock.Any(), gomock.Any()) return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - manager := executor.NewMockManager(ctrl) - manager.EXPECT().VerifyTx(gomock.Any()).Return(errTest) - return manager - }, + txVerifier: testTxVerifier{err: errTest}, appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { // Shouldn't gossip the tx return common.NewMockSender(ctrl) @@ -241,16 +267,12 @@ func TestNetworkIssueTx(t *testing.T) { name: "can't add transaction to mempool", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) + mempool.EXPECT().GetDropReason(gomock.Any()).Return(nil) mempool.EXPECT().Add(gomock.Any()).Return(errTest) - mempool.EXPECT().MarkDropped(gomock.Any(), errTest) + mempool.EXPECT().MarkDropped(gomock.Any(), gomock.Any()) return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - manager := executor.NewMockManager(ctrl) - manager.EXPECT().VerifyTx(gomock.Any()).Return(nil) - return manager - }, appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { // Shouldn't gossip the tx return common.NewMockSender(ctrl) @@ -260,20 +282,14 @@ func TestNetworkIssueTx(t *testing.T) { { name: "AppGossip tx but do not add to mempool if primary network is not being fully synced", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { - mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) - return mempool - }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - manager := executor.NewMockManager(ctrl) - manager.EXPECT().VerifyTx(gomock.Any()).Return(nil) - return manager + return mempool.NewMockMempool(ctrl) }, partialSyncPrimaryNetwork: true, appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Should gossip the tx + // we should gossip the tx twice because sdk and legacy gossip + // currently runs together appSender := common.NewMockSender(ctrl) - appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil) + appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil).Times(2) return appSender }, expectedErr: nil, @@ -282,20 +298,18 @@ func TestNetworkIssueTx(t *testing.T) { name: "happy path", mempoolFunc: func(ctrl *gomock.Controller) mempool.Mempool { mempool := mempool.NewMockMempool(ctrl) - mempool.EXPECT().Has(gomock.Any()).Return(false) + mempool.EXPECT().Get(gomock.Any()).Return(nil, false) + mempool.EXPECT().GetDropReason(gomock.Any()).Return(nil) mempool.EXPECT().Add(gomock.Any()).Return(nil) + mempool.EXPECT().Len().Return(0) mempool.EXPECT().RequestBuildBlock(false) return mempool }, - managerFunc: func(ctrl *gomock.Controller) executor.Manager { - manager := executor.NewMockManager(ctrl) - manager.EXPECT().VerifyTx(gomock.Any()).Return(nil) - return manager - }, appSenderFunc: func(ctrl *gomock.Controller) common.AppSender { - // Should gossip the tx + // we should gossip the tx twice because sdk and legacy gossip + // currently runs together appSender := common.NewMockSender(ctrl) - appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil) + appSender.EXPECT().SendAppGossip(gomock.Any(), gomock.Any()).Return(nil).Times(2) return appSender }, expectedErr: nil, @@ -307,16 +321,22 @@ func TestNetworkIssueTx(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - n := New( - &snow.Context{ - Log: logging.NoLog{}, - }, - tt.managerFunc(ctrl), + snowCtx := snowtest.Context(t, ids.Empty) + n, err := New( + snowCtx.Log, + snowCtx.NodeID, + snowCtx.SubnetID, + snowCtx.ValidatorState, + tt.txVerifier, tt.mempoolFunc(ctrl), tt.partialSyncPrimaryNetwork, tt.appSenderFunc(ctrl), + prometheus.NewRegistry(), + testConfig, ) - err := n.IssueTx(context.Background(), &txs.Tx{}) + require.NoError(err) + + err = n.IssueTx(context.Background(), tx) require.ErrorIs(err, tt.expectedErr) }) } @@ -328,27 +348,32 @@ func TestNetworkGossipTx(t *testing.T) { appSender := common.NewMockSender(ctrl) - nIntf := New( - &snow.Context{ - Log: logging.NoLog{}, - }, - executor.NewMockManager(ctrl), + snowCtx := snowtest.Context(t, ids.Empty) + nIntf, err := New( + snowCtx.Log, + snowCtx.NodeID, + snowCtx.SubnetID, + snowCtx.ValidatorState, + testTxVerifier{}, mempool.NewMockMempool(ctrl), false, appSender, + prometheus.NewRegistry(), + testConfig, ) + require.NoError(err) require.IsType(&network{}, nIntf) n := nIntf.(*network) // Case: Tx was recently gossiped txID := ids.GenerateTestID() n.recentTxs.Put(txID, struct{}{}) - n.gossipTx(context.Background(), txID, []byte{}) + n.legacyGossipTx(context.Background(), txID, []byte{}) // Didn't make a call to SendAppGossip // Case: Tx was not recently gossiped msgBytes := []byte{1, 2, 3} appSender.EXPECT().SendAppGossip(gomock.Any(), msgBytes).Return(nil) - n.gossipTx(context.Background(), ids.GenerateTestID(), msgBytes) + n.legacyGossipTx(context.Background(), ids.GenerateTestID(), msgBytes) // Did make a call to SendAppGossip } diff --git a/vms/platformvm/network/tx_verifier.go b/vms/platformvm/network/tx_verifier.go new file mode 100644 index 000000000000..ee76c8b0056b --- /dev/null +++ b/vms/platformvm/network/tx_verifier.go @@ -0,0 +1,36 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "sync" + + "github.com/ava-labs/avalanchego/vms/platformvm/txs" +) + +var _ TxVerifier = (*LockedTxVerifier)(nil) + +type TxVerifier interface { + // VerifyTx verifies that the transaction should be issued into the mempool. + VerifyTx(tx *txs.Tx) error +} + +type LockedTxVerifier struct { + lock sync.Locker + txVerifier TxVerifier +} + +func (l *LockedTxVerifier) VerifyTx(tx *txs.Tx) error { + l.lock.Lock() + defer l.lock.Unlock() + + return l.txVerifier.VerifyTx(tx) +} + +func NewLockedTxVerifier(lock sync.Locker, txVerifier TxVerifier) *LockedTxVerifier { + return &LockedTxVerifier{ + lock: lock, + txVerifier: txVerifier, + } +} diff --git a/vms/platformvm/reward/calculator.go b/vms/platformvm/reward/calculator.go index 30ba7c3270d7..79a845ef8980 100644 --- a/vms/platformvm/reward/calculator.go +++ b/vms/platformvm/reward/calculator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package reward diff --git a/vms/platformvm/reward/calculator_test.go b/vms/platformvm/reward/calculator_test.go index 1462bd2e3664..d2fd17ff9e2b 100644 --- a/vms/platformvm/reward/calculator_test.go +++ b/vms/platformvm/reward/calculator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package reward diff --git a/vms/platformvm/reward/config.go b/vms/platformvm/reward/config.go index 17a0a0d0e83b..ccabc398f83a 100644 --- a/vms/platformvm/reward/config.go +++ b/vms/platformvm/reward/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package reward diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index fe9fa66f7b4c..16e5b16844c6 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm @@ -489,7 +489,7 @@ func (s *Service) GetUTXOs(_ *http.Request, args *api.GetUTXOsArgs, response *ap response.UTXOs = make([]string, len(utxos)) for i, utxo := range utxos { - bytes, err := txs.Codec.Marshal(txs.Version, utxo) + bytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) if err != nil { return fmt.Errorf("couldn't serialize UTXO %q: %w", utxo.InputID(), err) } @@ -1191,6 +1191,21 @@ func (s *Service) AddValidator(req *http.Request, args *AddValidatorArgs, reply zap.String("method", "addValidator"), ) + tx, changeAddr, err := s.buildAddValidatorTx(args) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + reply.TxID = tx.ID() + reply.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) + if err != nil { + return fmt.Errorf("couldn't format address: %w", err) + } + + return s.vm.issueTx(req.Context(), tx) +} + +func (s *Service) buildAddValidatorTx(args *AddValidatorArgs) (*txs.Tx, ids.ShortID, error) { now := s.vm.clock.Time() minAddStakerTime := now.Add(minAddStakerDelay) minAddStakerUnix := json.Uint64(minAddStakerTime.Unix()) @@ -1203,13 +1218,13 @@ func (s *Service) AddValidator(req *http.Request, args *AddValidatorArgs, reply switch { case args.RewardAddress == "": - return errNoRewardAddress + return nil, ids.ShortEmpty, errNoRewardAddress case args.StartTime < minAddStakerUnix: - return errStartTimeTooSoon + return nil, ids.ShortEmpty, errStartTimeTooSoon case args.StartTime > maxAddStakerUnix: - return errStartTimeTooLate + return nil, ids.ShortEmpty, errStartTimeTooLate case args.DelegationFeeRate < 0 || args.DelegationFeeRate > 100: - return errInvalidDelegationRate + return nil, ids.ShortEmpty, errInvalidDelegationRate } // Parse the node ID @@ -1223,13 +1238,13 @@ func (s *Service) AddValidator(req *http.Request, args *AddValidatorArgs, reply // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.addrManager, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the reward address rewardAddress, err := avax.ParseServiceAddress(s.addrManager, args.RewardAddress) if err != nil { - return fmt.Errorf("problem while parsing reward address: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("problem while parsing reward address: %w", err) } s.vm.ctx.Lock.Lock() @@ -1237,25 +1252,25 @@ func (s *Service) AddValidator(req *http.Request, args *AddValidatorArgs, reply user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password) if err != nil { - return err + return nil, ids.ShortEmpty, err } defer user.Close() // Get the user's keys privKeys, err := keystore.GetKeychain(user, fromAddrs) if err != nil { - return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't get addresses controlled by the user: %w", err) } // Parse the change address. if len(privKeys.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr := privKeys.Keys[0].PublicKey().Address() // By default, use a key controlled by the user if args.ChangeAddr != "" { changeAddr, err = avax.ParseServiceAddress(s.addrManager, args.ChangeAddr) if err != nil { - return fmt.Errorf("couldn't parse changeAddr: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't parse changeAddr: %w", err) } } @@ -1276,17 +1291,10 @@ func (s *Service) AddValidator(req *http.Request, args *AddValidatorArgs, reply changeAddr, ) if err != nil { - return fmt.Errorf("couldn't create tx: %w", err) + return nil, ids.ShortEmpty, err } - reply.TxID = tx.ID() - reply.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) - - return utils.Err( - err, - s.vm.Network.IssueTx(req.Context(), tx), - user.Close(), - ) + return tx, changeAddr, user.Close() } // AddDelegatorArgs are the arguments to AddDelegator @@ -1305,6 +1313,21 @@ func (s *Service) AddDelegator(req *http.Request, args *AddDelegatorArgs, reply zap.String("method", "addDelegator"), ) + tx, changeAddr, err := s.buildAddDelegatorTx(args) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + reply.TxID = tx.ID() + reply.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) + if err != nil { + return fmt.Errorf("couldn't format address: %w", err) + } + + return s.vm.issueTx(req.Context(), tx) +} + +func (s *Service) buildAddDelegatorTx(args *AddDelegatorArgs) (*txs.Tx, ids.ShortID, error) { now := s.vm.clock.Time() minAddStakerTime := now.Add(minAddStakerDelay) minAddStakerUnix := json.Uint64(minAddStakerTime.Unix()) @@ -1317,11 +1340,11 @@ func (s *Service) AddDelegator(req *http.Request, args *AddDelegatorArgs, reply switch { case args.RewardAddress == "": - return errNoRewardAddress + return nil, ids.ShortEmpty, errNoRewardAddress case args.StartTime < minAddStakerUnix: - return errStartTimeTooSoon + return nil, ids.ShortEmpty, errStartTimeTooSoon case args.StartTime > maxAddStakerUnix: - return errStartTimeTooLate + return nil, ids.ShortEmpty, errStartTimeTooLate } var nodeID ids.NodeID @@ -1334,13 +1357,13 @@ func (s *Service) AddDelegator(req *http.Request, args *AddDelegatorArgs, reply // Parse the reward address rewardAddress, err := avax.ParseServiceAddress(s.addrManager, args.RewardAddress) if err != nil { - return fmt.Errorf("problem parsing 'rewardAddress': %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("problem parsing 'rewardAddress': %w", err) } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.addrManager, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1348,25 +1371,25 @@ func (s *Service) AddDelegator(req *http.Request, args *AddDelegatorArgs, reply user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password) if err != nil { - return err + return nil, ids.ShortEmpty, err } defer user.Close() privKeys, err := keystore.GetKeychain(user, fromAddrs) if err != nil { - return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't get addresses controlled by the user: %w", err) } // Parse the change address. Assumes that if the user has no keys, // this operation will fail so the change address can be anything. if len(privKeys.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr := privKeys.Keys[0].PublicKey().Address() // By default, use a key controlled by the user if args.ChangeAddr != "" { changeAddr, err = avax.ParseServiceAddress(s.addrManager, args.ChangeAddr) if err != nil { - return fmt.Errorf("couldn't parse changeAddr: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't parse changeAddr: %w", err) } } @@ -1386,17 +1409,10 @@ func (s *Service) AddDelegator(req *http.Request, args *AddDelegatorArgs, reply changeAddr, // Change address ) if err != nil { - return fmt.Errorf("couldn't create tx: %w", err) + return nil, ids.ShortEmpty, err } - reply.TxID = tx.ID() - reply.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) - - return utils.Err( - err, - s.vm.Network.IssueTx(req.Context(), tx), - user.Close(), - ) + return tx, changeAddr, user.Close() } // AddSubnetValidatorArgs are the arguments to AddSubnetValidator @@ -1416,6 +1432,21 @@ func (s *Service) AddSubnetValidator(req *http.Request, args *AddSubnetValidator zap.String("method", "addSubnetValidator"), ) + tx, changeAddr, err := s.buildAddSubnetValidatorTx(args) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + response.TxID = tx.ID() + response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) + if err != nil { + return fmt.Errorf("couldn't format address: %w", err) + } + + return s.vm.issueTx(req.Context(), tx) +} + +func (s *Service) buildAddSubnetValidatorTx(args *AddSubnetValidatorArgs) (*txs.Tx, ids.ShortID, error) { now := s.vm.clock.Time() minAddStakerTime := now.Add(minAddStakerDelay) minAddStakerUnix := json.Uint64(minAddStakerTime.Unix()) @@ -1428,26 +1459,26 @@ func (s *Service) AddSubnetValidator(req *http.Request, args *AddSubnetValidator switch { case args.SubnetID == "": - return errNoSubnetID + return nil, ids.ShortEmpty, errNoSubnetID case args.StartTime < minAddStakerUnix: - return errStartTimeTooSoon + return nil, ids.ShortEmpty, errStartTimeTooSoon case args.StartTime > maxAddStakerUnix: - return errStartTimeTooLate + return nil, ids.ShortEmpty, errStartTimeTooLate } // Parse the subnet ID subnetID, err := ids.FromString(args.SubnetID) if err != nil { - return fmt.Errorf("problem parsing subnetID %q: %w", args.SubnetID, err) + return nil, ids.ShortEmpty, fmt.Errorf("problem parsing subnetID %q: %w", args.SubnetID, err) } if subnetID == constants.PrimaryNetworkID { - return errNamedSubnetCantBePrimary + return nil, ids.ShortEmpty, errNamedSubnetCantBePrimary } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.addrManager, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1455,24 +1486,24 @@ func (s *Service) AddSubnetValidator(req *http.Request, args *AddSubnetValidator user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password) if err != nil { - return err + return nil, ids.ShortEmpty, err } defer user.Close() keys, err := keystore.GetKeychain(user, fromAddrs) if err != nil { - return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't get addresses controlled by the user: %w", err) } // Parse the change address. if len(keys.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr := keys.Keys[0].PublicKey().Address() // By default, use a key controlled by the user if args.ChangeAddr != "" { changeAddr, err = avax.ParseServiceAddress(s.addrManager, args.ChangeAddr) if err != nil { - return fmt.Errorf("couldn't parse changeAddr: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't parse changeAddr: %w", err) } } @@ -1492,17 +1523,10 @@ func (s *Service) AddSubnetValidator(req *http.Request, args *AddSubnetValidator changeAddr, ) if err != nil { - return fmt.Errorf("couldn't create tx: %w", err) + return nil, ids.ShortEmpty, err } - response.TxID = tx.ID() - response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) - - return utils.Err( - err, - s.vm.Network.IssueTx(req.Context(), tx), - user.Close(), - ) + return tx, changeAddr, user.Close() } // CreateSubnetArgs are the arguments to CreateSubnet @@ -1521,16 +1545,31 @@ func (s *Service) CreateSubnet(req *http.Request, args *CreateSubnetArgs, respon zap.String("method", "createSubnet"), ) + tx, changeAddr, err := s.buildCreateSubnetTx(args) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + response.TxID = tx.ID() + response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) + if err != nil { + return fmt.Errorf("couldn't format address: %w", err) + } + + return s.vm.issueTx(req.Context(), tx) +} + +func (s *Service) buildCreateSubnetTx(args *CreateSubnetArgs) (*txs.Tx, ids.ShortID, error) { // Parse the control keys controlKeys, err := avax.ParseServiceAddresses(s.addrManager, args.ControlKeys) if err != nil { - return err + return nil, ids.ShortEmpty, err } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.addrManager, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1538,25 +1577,25 @@ func (s *Service) CreateSubnet(req *http.Request, args *CreateSubnetArgs, respon user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password) if err != nil { - return err + return nil, ids.ShortEmpty, err } defer user.Close() privKeys, err := keystore.GetKeychain(user, fromAddrs) if err != nil { - return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't get addresses controlled by the user: %w", err) } // Parse the change address. Assumes that if the user has no keys, // this operation will fail so the change address can be anything. if len(privKeys.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr := privKeys.Keys[0].PublicKey().Address() // By default, use a key controlled by the user if args.ChangeAddr != "" { changeAddr, err = avax.ParseServiceAddress(s.addrManager, args.ChangeAddr) if err != nil { - return fmt.Errorf("couldn't parse changeAddr: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't parse changeAddr: %w", err) } } @@ -1568,17 +1607,10 @@ func (s *Service) CreateSubnet(req *http.Request, args *CreateSubnetArgs, respon changeAddr, ) if err != nil { - return fmt.Errorf("couldn't create tx: %w", err) + return nil, ids.ShortEmpty, err } - response.TxID = tx.ID() - response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) - - return utils.Err( - err, - s.vm.Network.IssueTx(req.Context(), tx), - user.Close(), - ) + return tx, changeAddr, user.Close() } // ExportAVAXArgs are the arguments to ExportAVAX @@ -1605,8 +1637,23 @@ func (s *Service) ExportAVAX(req *http.Request, args *ExportAVAXArgs, response * zap.String("method", "exportAVAX"), ) + tx, changeAddr, err := s.buildExportAVAX(args) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + response.TxID = tx.ID() + response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) + if err != nil { + return fmt.Errorf("couldn't format address: %w", err) + } + + return s.vm.issueTx(req.Context(), tx) +} + +func (s *Service) buildExportAVAX(args *ExportAVAXArgs) (*txs.Tx, ids.ShortID, error) { if args.Amount == 0 { - return errNoAmount + return nil, ids.ShortEmpty, errNoAmount } // Get the chainID and parse the to address @@ -1614,18 +1661,18 @@ func (s *Service) ExportAVAX(req *http.Request, args *ExportAVAXArgs, response * if err != nil { chainID, err = s.vm.ctx.BCLookup.Lookup(args.TargetChain) if err != nil { - return err + return nil, ids.ShortEmpty, err } to, err = ids.ShortFromString(args.To) if err != nil { - return err + return nil, ids.ShortEmpty, err } } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.addrManager, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1633,25 +1680,25 @@ func (s *Service) ExportAVAX(req *http.Request, args *ExportAVAXArgs, response * user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password) if err != nil { - return err + return nil, ids.ShortEmpty, err } defer user.Close() privKeys, err := keystore.GetKeychain(user, fromAddrs) if err != nil { - return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't get addresses controlled by the user: %w", err) } // Parse the change address. Assumes that if the user has no keys, // this operation will fail so the change address can be anything. if len(privKeys.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr := privKeys.Keys[0].PublicKey().Address() // By default, use a key controlled by the user if args.ChangeAddr != "" { changeAddr, err = avax.ParseServiceAddress(s.addrManager, args.ChangeAddr) if err != nil { - return fmt.Errorf("couldn't parse changeAddr: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't parse changeAddr: %w", err) } } @@ -1664,17 +1711,10 @@ func (s *Service) ExportAVAX(req *http.Request, args *ExportAVAXArgs, response * changeAddr, // Change address ) if err != nil { - return fmt.Errorf("couldn't create tx: %w", err) + return nil, ids.ShortEmpty, err } - response.TxID = tx.ID() - response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) - - return utils.Err( - err, - s.vm.Network.IssueTx(req.Context(), tx), - user.Close(), - ) + return tx, changeAddr, user.Close() } // ImportAVAXArgs are the arguments to ImportAVAX @@ -1697,22 +1737,37 @@ func (s *Service) ImportAVAX(req *http.Request, args *ImportAVAXArgs, response * zap.String("method", "importAVAX"), ) + tx, changeAddr, err := s.buildImportAVAXTx(args) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + response.TxID = tx.ID() + response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) + if err != nil { + return fmt.Errorf("problem formatting address: %w", err) + } + + return s.vm.issueTx(req.Context(), tx) +} + +func (s *Service) buildImportAVAXTx(args *ImportAVAXArgs) (*txs.Tx, ids.ShortID, error) { // Parse the sourceCHain chainID, err := s.vm.ctx.BCLookup.Lookup(args.SourceChain) if err != nil { - return fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err) + return nil, ids.ShortEmpty, fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err) } // Parse the to address to, err := avax.ParseServiceAddress(s.addrManager, args.To) if err != nil { // Parse address - return fmt.Errorf("couldn't parse argument 'to' to an address: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't parse argument 'to' to an address: %w", err) } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.addrManager, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1720,25 +1775,25 @@ func (s *Service) ImportAVAX(req *http.Request, args *ImportAVAXArgs, response * user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password) if err != nil { - return err + return nil, ids.ShortEmpty, err } defer user.Close() privKeys, err := keystore.GetKeychain(user, fromAddrs) if err != nil { // Get keys - return fmt.Errorf("couldn't get keys controlled by the user: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't get keys controlled by the user: %w", err) } // Parse the change address. Assumes that if the user has no keys, // this operation will fail so the change address can be anything. if len(privKeys.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr := privKeys.Keys[0].PublicKey().Address() // By default, use a key controlled by the user if args.ChangeAddr != "" { changeAddr, err = avax.ParseServiceAddress(s.addrManager, args.ChangeAddr) if err != nil { - return fmt.Errorf("couldn't parse changeAddr: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't parse changeAddr: %w", err) } } @@ -1749,17 +1804,10 @@ func (s *Service) ImportAVAX(req *http.Request, args *ImportAVAXArgs, response * changeAddr, ) if err != nil { - return err + return nil, ids.ShortEmpty, err } - response.TxID = tx.ID() - response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) - - return utils.Err( - err, - s.vm.Network.IssueTx(req.Context(), tx), - user.Close(), - ) + return tx, changeAddr, user.Close() } /* @@ -1793,28 +1841,43 @@ func (s *Service) CreateBlockchain(req *http.Request, args *CreateBlockchainArgs zap.String("method", "createBlockchain"), ) + tx, changeAddr, err := s.buildCreateBlockchainTx(args) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + response.TxID = tx.ID() + response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) + if err != nil { + return fmt.Errorf("problem formatting address: %w", err) + } + + return s.vm.issueTx(req.Context(), tx) +} + +func (s *Service) buildCreateBlockchainTx(args *CreateBlockchainArgs) (*txs.Tx, ids.ShortID, error) { switch { case args.Name == "": - return errMissingName + return nil, ids.ShortEmpty, errMissingName case args.VMID == "": - return errMissingVMID + return nil, ids.ShortEmpty, errMissingVMID } genesisBytes, err := formatting.Decode(args.Encoding, args.GenesisData) if err != nil { - return fmt.Errorf("problem parsing genesis data: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("problem parsing genesis data: %w", err) } vmID, err := s.vm.Chains.LookupVM(args.VMID) if err != nil { - return fmt.Errorf("no VM with ID '%s' found", args.VMID) + return nil, ids.ShortEmpty, fmt.Errorf("no VM with ID '%s' found", args.VMID) } fxIDs := []ids.ID(nil) for _, fxIDStr := range args.FxIDs { fxID, err := s.vm.Chains.LookupVM(fxIDStr) if err != nil { - return fmt.Errorf("no FX with ID '%s' found", fxIDStr) + return nil, ids.ShortEmpty, fmt.Errorf("no FX with ID '%s' found", fxIDStr) } fxIDs = append(fxIDs, fxID) } @@ -1826,13 +1889,13 @@ func (s *Service) CreateBlockchain(req *http.Request, args *CreateBlockchainArgs } if args.SubnetID == constants.PrimaryNetworkID { - return txs.ErrCantValidatePrimaryNetwork + return nil, ids.ShortEmpty, txs.ErrCantValidatePrimaryNetwork } // Parse the from addresses fromAddrs, err := avax.ParseServiceAddresses(s.addrManager, args.From) if err != nil { - return err + return nil, ids.ShortEmpty, err } s.vm.ctx.Lock.Lock() @@ -1840,25 +1903,25 @@ func (s *Service) CreateBlockchain(req *http.Request, args *CreateBlockchainArgs user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password) if err != nil { - return err + return nil, ids.ShortEmpty, err } defer user.Close() keys, err := keystore.GetKeychain(user, fromAddrs) if err != nil { - return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't get addresses controlled by the user: %w", err) } // Parse the change address. Assumes that if the user has no keys, // this operation will fail so the change address can be anything. if len(keys.Keys) == 0 { - return errNoKeys + return nil, ids.ShortEmpty, errNoKeys } changeAddr := keys.Keys[0].PublicKey().Address() // By default, use a key controlled by the user if args.ChangeAddr != "" { changeAddr, err = avax.ParseServiceAddress(s.addrManager, args.ChangeAddr) if err != nil { - return fmt.Errorf("couldn't parse changeAddr: %w", err) + return nil, ids.ShortEmpty, fmt.Errorf("couldn't parse changeAddr: %w", err) } } @@ -1873,17 +1936,10 @@ func (s *Service) CreateBlockchain(req *http.Request, args *CreateBlockchainArgs changeAddr, // Change address ) if err != nil { - return fmt.Errorf("couldn't create tx: %w", err) + return nil, ids.ShortEmpty, err } - response.TxID = tx.ID() - response.ChangeAddr, err = s.addrManager.FormatLocalAddress(changeAddr) - - return utils.Err( - err, - s.vm.Network.IssueTx(req.Context(), tx), - user.Close(), - ) + return tx, changeAddr, user.Close() } // GetBlockchainStatusArgs is the arguments for calling GetBlockchainStatus @@ -2170,10 +2226,7 @@ func (s *Service) IssueTx(req *http.Request, args *api.FormattedTx, response *ap return fmt.Errorf("couldn't parse tx: %w", err) } - s.vm.ctx.Lock.Lock() - defer s.vm.ctx.Lock.Unlock() - - if err := s.vm.Network.IssueTx(req.Context(), tx); err != nil { + if err := s.vm.issueTx(req.Context(), tx); err != nil { return fmt.Errorf("couldn't issue tx: %w", err) } @@ -2259,7 +2312,7 @@ func (s *Service) GetTxStatus(_ *http.Request, args *GetTxStatusArgs, response * return err } - if s.vm.Builder.Has(args.TxID) { + if _, ok := s.vm.Builder.Get(args.TxID); ok { // Found the tx in the mempool. Report tx is processing. response.Status = status.Processing return nil @@ -2373,7 +2426,7 @@ func (s *Service) GetStake(_ *http.Request, args *GetStakeArgs, response *GetSta response.Staked = response.Stakeds[s.vm.ctx.AVAXAssetID] response.Outputs = make([]string, len(stakedOuts)) for i, output := range stakedOuts { - bytes, err := txs.Codec.Marshal(txs.Version, output) + bytes, err := txs.Codec.Marshal(txs.CodecVersion, output) if err != nil { return fmt.Errorf("couldn't serialize output %s: %w", output.ID, err) } @@ -2555,9 +2608,9 @@ func (s *Service) GetRewardUTXOs(_ *http.Request, args *api.GetTxArgs, reply *Ge reply.NumFetched = json.Uint64(len(utxos)) reply.UTXOs = make([]string, len(utxos)) for i, utxo := range utxos { - utxoBytes, err := txs.GenesisCodec.Marshal(txs.Version, utxo) + utxoBytes, err := txs.GenesisCodec.Marshal(txs.CodecVersion, utxo) if err != nil { - return fmt.Errorf("failed to encode UTXO to bytes: %w", err) + return fmt.Errorf("couldn't encode UTXO to bytes: %w", err) } utxoStr, err := formatting.Encode(args.Encoding, utxoBytes) diff --git a/vms/platformvm/service_test.go b/vms/platformvm/service_test.go index 8e2cc3790fc3..2c6b72255c3f 100644 --- a/vms/platformvm/service_test.go +++ b/vms/platformvm/service_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm @@ -73,7 +73,7 @@ var ( ) func defaultService(t *testing.T) (*Service, *mutableSharedMemory) { - vm, _, mutableSharedMemory := defaultVM(t) + vm, _, mutableSharedMemory := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer vm.ctx.Lock.Unlock() ks := keystore.New(logging.NoLog{}, memdb.New()) @@ -181,7 +181,7 @@ func TestGetTxStatus(t *testing.T) { m := atomic.NewMemory(prefixdb.New([]byte{}, service.vm.db)) sm := m.NewSharedMemory(service.vm.ctx.ChainID) - peerSharedMemory := m.NewSharedMemory(xChainID) + peerSharedMemory := m.NewSharedMemory(service.vm.ctx.XChainID) // #nosec G404 utxo := &avax.UTXO{ @@ -189,7 +189,7 @@ func TestGetTxStatus(t *testing.T) { TxID: ids.GenerateTestID(), OutputIndex: rand.Uint32(), }, - Asset: avax.Asset{ID: avaxAssetID}, + Asset: avax.Asset{ID: service.vm.ctx.AVAXAssetID}, Out: &secp256k1fx.TransferOutput{ Amt: 1234567, OutputOwners: secp256k1fx.OutputOwners{ @@ -199,7 +199,7 @@ func TestGetTxStatus(t *testing.T) { }, }, } - utxoBytes, err := txs.Codec.Marshal(txs.Version, utxo) + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) require.NoError(err) inputID := utxo.InputID() @@ -217,14 +217,16 @@ func TestGetTxStatus(t *testing.T) { }, })) - oldSharedMemory := mutableSharedMemory.SharedMemory mutableSharedMemory.SharedMemory = sm - tx, err := service.vm.txBuilder.NewImportTx(xChainID, ids.ShortEmpty, []*secp256k1.PrivateKey{recipientKey}, ids.ShortEmpty) + tx, err := service.vm.txBuilder.NewImportTx( + service.vm.ctx.XChainID, + ids.ShortEmpty, + []*secp256k1.PrivateKey{recipientKey}, + ids.ShortEmpty, + ) require.NoError(err) - mutableSharedMemory.SharedMemory = oldSharedMemory - service.vm.ctx.Lock.Unlock() var ( @@ -235,15 +237,9 @@ func TestGetTxStatus(t *testing.T) { require.Equal(status.Unknown, resp.Status) require.Zero(resp.Reason) - service.vm.ctx.Lock.Lock() - // put the chain in existing chain list - err = service.vm.Network.IssueTx(context.Background(), tx) - require.ErrorIs(err, database.ErrNotFound) // Missing shared memory UTXO - - mutableSharedMemory.SharedMemory = sm - require.NoError(service.vm.Network.IssueTx(context.Background(), tx)) + service.vm.ctx.Lock.Lock() block, err := service.vm.BuildBlock(context.Background()) require.NoError(err) @@ -337,9 +333,8 @@ func TestGetTx(t *testing.T) { err = service.GetTx(nil, arg, &response) require.ErrorIs(err, database.ErrNotFound) // We haven't issued the tx yet - service.vm.ctx.Lock.Lock() - require.NoError(service.vm.Network.IssueTx(context.Background(), tx)) + service.vm.ctx.Lock.Lock() blk, err := service.vm.BuildBlock(context.Background()) require.NoError(err) @@ -399,8 +394,8 @@ func TestGetBalance(t *testing.T) { }() // Ensure GetStake is correct for each of the genesis validators - genesis, _ := defaultGenesis(t) - for _, utxo := range genesis.UTXOs { + genesis, _ := defaultGenesis(t, service.vm.ctx.AVAXAssetID) + for idx, utxo := range genesis.UTXOs { request := GetBalanceRequest{ Addresses: []string{ fmt.Sprintf("P-%s", utxo.Address), @@ -409,9 +404,14 @@ func TestGetBalance(t *testing.T) { reply := GetBalanceResponse{} require.NoError(service.GetBalance(nil, &request, &reply)) - - require.Equal(json.Uint64(defaultBalance), reply.Balance) - require.Equal(json.Uint64(defaultBalance), reply.Unlocked) + balance := defaultBalance + if idx == 0 { + // we use the first key to fund a subnet creation in [defaultGenesis]. + // As such we need to account for the subnet creation fee + balance = defaultBalance - service.vm.Config.GetCreateSubnetTxFee(service.vm.clock.Time()) + } + require.Equal(json.Uint64(balance), reply.Balance) + require.Equal(json.Uint64(balance), reply.Unlocked) require.Equal(json.Uint64(0), reply.LockedStakeable) require.Equal(json.Uint64(0), reply.LockedNotStakeable) } @@ -428,7 +428,7 @@ func TestGetStake(t *testing.T) { }() // Ensure GetStake is correct for each of the genesis validators - genesis, _ := defaultGenesis(t) + genesis, _ := defaultGenesis(t, service.vm.ctx.AVAXAssetID) addrsStrs := []string{} for i, validator := range genesis.Validators { addr := fmt.Sprintf("P-%s", validator.RewardOwner.Addresses[0]) @@ -495,11 +495,12 @@ func TestGetStake(t *testing.T) { // Add a delegator stakeAmount := service.vm.MinDelegatorStake + 12345 delegatorNodeID := genesisNodeIDs[0] - delegatorEndTime := uint64(defaultGenesisTime.Add(defaultMinStakingDuration).Unix()) + delegatorStartTime := defaultValidateStartTime + delegatorEndTime := defaultGenesisTime.Add(defaultMinStakingDuration) tx, err := service.vm.txBuilder.NewAddDelegatorTx( stakeAmount, - uint64(defaultGenesisTime.Unix()), - delegatorEndTime, + uint64(delegatorStartTime.Unix()), + uint64(delegatorEndTime.Unix()), delegatorNodeID, ids.GenerateTestShortID(), []*secp256k1.PrivateKey{keys[0]}, @@ -507,9 +508,11 @@ func TestGetStake(t *testing.T) { ) require.NoError(err) + addDelTx := tx.Unsigned.(*txs.AddDelegatorTx) staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddDelegatorTx), + addDelTx, + delegatorStartTime, 0, ) require.NoError(err) @@ -600,7 +603,7 @@ func TestGetCurrentValidators(t *testing.T) { service.vm.ctx.Lock.Unlock() }() - genesis, _ := defaultGenesis(t) + genesis, _ := defaultGenesis(t, service.vm.ctx.AVAXAssetID) // Call getValidators args := GetCurrentValidatorsArgs{SubnetID: constants.PrimaryNetworkID} @@ -627,15 +630,15 @@ func TestGetCurrentValidators(t *testing.T) { // Add a delegator stakeAmount := service.vm.MinDelegatorStake + 12345 validatorNodeID := genesisNodeIDs[1] - delegatorStartTime := uint64(defaultValidateStartTime.Unix()) - delegatorEndTime := uint64(defaultValidateStartTime.Add(defaultMinStakingDuration).Unix()) + delegatorStartTime := defaultValidateStartTime + delegatorEndTime := delegatorStartTime.Add(defaultMinStakingDuration) service.vm.ctx.Lock.Lock() delTx, err := service.vm.txBuilder.NewAddDelegatorTx( stakeAmount, - delegatorStartTime, - delegatorEndTime, + uint64(delegatorStartTime.Unix()), + uint64(delegatorEndTime.Unix()), validatorNodeID, ids.GenerateTestShortID(), []*secp256k1.PrivateKey{keys[0]}, @@ -643,9 +646,11 @@ func TestGetCurrentValidators(t *testing.T) { ) require.NoError(err) + addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) staker, err := state.NewCurrentStaker( delTx.ID(), - delTx.Unsigned.(*txs.AddDelegatorTx), + addDelTx, + delegatorStartTime, 0, ) require.NoError(err) @@ -687,8 +692,8 @@ func TestGetCurrentValidators(t *testing.T) { require.Len(*innerVdr.Delegators, 1) delegator := (*innerVdr.Delegators)[0] require.Equal(delegator.NodeID, innerVdr.NodeID) - require.Equal(uint64(delegator.StartTime), delegatorStartTime) - require.Equal(uint64(delegator.EndTime), delegatorEndTime) + require.Equal(int64(delegator.StartTime), delegatorStartTime.Unix()) + require.Equal(int64(delegator.EndTime), delegatorEndTime.Unix()) require.Equal(uint64(delegator.Weight), stakeAmount) } require.True(found) diff --git a/vms/platformvm/signer/empty.go b/vms/platformvm/signer/empty.go index 7b5dec06cbcf..21412ae6d0b1 100644 --- a/vms/platformvm/signer/empty.go +++ b/vms/platformvm/signer/empty.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/vms/platformvm/signer/empty_test.go b/vms/platformvm/signer/empty_test.go index e6a6307b9842..9fe949f4677d 100644 --- a/vms/platformvm/signer/empty_test.go +++ b/vms/platformvm/signer/empty_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/vms/platformvm/signer/proof_of_possession.go b/vms/platformvm/signer/proof_of_possession.go index 35ddcb320745..8b32975b4969 100644 --- a/vms/platformvm/signer/proof_of_possession.go +++ b/vms/platformvm/signer/proof_of_possession.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/vms/platformvm/signer/proof_of_possession_test.go b/vms/platformvm/signer/proof_of_possession_test.go index 8214554cebfe..9f4f3feefa3c 100644 --- a/vms/platformvm/signer/proof_of_possession_test.go +++ b/vms/platformvm/signer/proof_of_possession_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/vms/platformvm/signer/signer.go b/vms/platformvm/signer/signer.go index 7269ad199534..31bf212ddca6 100644 --- a/vms/platformvm/signer/signer.go +++ b/vms/platformvm/signer/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package signer diff --git a/vms/platformvm/stakeable/stakeable_lock.go b/vms/platformvm/stakeable/stakeable_lock.go index 5c09cbfdda8a..58149266175e 100644 --- a/vms/platformvm/stakeable/stakeable_lock.go +++ b/vms/platformvm/stakeable/stakeable_lock.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package stakeable diff --git a/vms/platformvm/stakeable/stakeable_lock_test.go b/vms/platformvm/stakeable/stakeable_lock_test.go index b733aa0244c5..0ea53e9cc426 100644 --- a/vms/platformvm/stakeable/stakeable_lock_test.go +++ b/vms/platformvm/stakeable/stakeable_lock_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package stakeable diff --git a/vms/platformvm/state/diff.go b/vms/platformvm/state/diff.go index d509fa69e0dd..907c3c56ef7d 100644 --- a/vms/platformvm/state/diff.go +++ b/vms/platformvm/state/diff.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -17,7 +17,8 @@ import ( ) var ( - _ Diff = (*diff)(nil) + _ Diff = (*diff)(nil) + _ Versions = stateGetter{} ErrMissingParentState = errors.New("missing parent state") ) @@ -74,6 +75,20 @@ func NewDiff( }, nil } +type stateGetter struct { + state Chain +} + +func (s stateGetter) GetState(ids.ID) (Chain, bool) { + return s.state, true +} + +func NewDiffOn(parentState Chain) (Diff, error) { + return NewDiff(ids.Empty, stateGetter{ + state: parentState, + }) +} + func (d *diff) GetTimestamp() time.Time { return d.timestamp } diff --git a/vms/platformvm/state/diff_test.go b/vms/platformvm/state/diff_test.go index 50c87b2d3a53..e8d51039bdba 100644 --- a/vms/platformvm/state/diff_test.go +++ b/vms/platformvm/state/diff_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -38,7 +38,7 @@ func TestDiffCreation(t *testing.T) { ctrl := gomock.NewController(t) lastAcceptedID := ids.GenerateTestID() - state, _ := newInitializedState(require) + state := newInitializedState(require) versions := NewMockVersions(ctrl) versions.EXPECT().GetState(lastAcceptedID).AnyTimes().Return(state, true) @@ -52,7 +52,7 @@ func TestDiffCurrentSupply(t *testing.T) { ctrl := gomock.NewController(t) lastAcceptedID := ids.GenerateTestID() - state, _ := newInitializedState(require) + state := newInitializedState(require) versions := NewMockVersions(ctrl) versions.EXPECT().GetState(lastAcceptedID).AnyTimes().Return(state, true) @@ -250,7 +250,7 @@ func TestDiffSubnet(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - state, _ := newInitializedState(require) + state := newInitializedState(require) // Initialize parent with one subnet parentStateCreateSubnetTx := &txs.Tx{ @@ -298,7 +298,7 @@ func TestDiffChain(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - state, _ := newInitializedState(require) + state := newInitializedState(require) subnetID := ids.GenerateTestID() // Initialize parent with one chain @@ -397,7 +397,7 @@ func TestDiffRewardUTXO(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - state, _ := newInitializedState(require) + state := newInitializedState(require) txID := ids.GenerateTestID() @@ -523,7 +523,7 @@ func TestDiffSubnetOwner(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - state, _ := newInitializedState(require) + state := newInitializedState(require) states := NewMockVersions(ctrl) lastAcceptedID := ids.GenerateTestID() @@ -585,7 +585,7 @@ func TestDiffStacking(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - state, _ := newInitializedState(require) + state := newInitializedState(require) states := NewMockVersions(ctrl) lastAcceptedID := ids.GenerateTestID() @@ -637,7 +637,7 @@ func TestDiffStacking(t *testing.T) { require.Equal(owner1, owner) // Create a second diff on first diff and verify that subnet owner returns correctly - stackedDiff, err := wrapState(statesDiff) + stackedDiff, err := NewDiffOn(statesDiff) require.NoError(err) owner, err = stackedDiff.GetSubnetOwner(subnetID) require.NoError(err) @@ -674,17 +674,3 @@ func TestDiffStacking(t *testing.T) { require.NoError(err) require.Equal(owner3, owner) } - -type stateGetter struct { - state Chain -} - -func (s stateGetter) GetState(ids.ID) (Chain, bool) { - return s.state, true -} - -func wrapState(parentState Chain) (Diff, error) { - return NewDiff(ids.Empty, stateGetter{ - state: parentState, - }) -} diff --git a/vms/platformvm/state/disk_staker_diff_iterator.go b/vms/platformvm/state/disk_staker_diff_iterator.go index efac5ec7b6d7..1c6e88338724 100644 --- a/vms/platformvm/state/disk_staker_diff_iterator.go +++ b/vms/platformvm/state/disk_staker_diff_iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/disk_staker_diff_iterator_test.go b/vms/platformvm/state/disk_staker_diff_iterator_test.go index 543f42a4b9c3..af719e7a0beb 100644 --- a/vms/platformvm/state/disk_staker_diff_iterator_test.go +++ b/vms/platformvm/state/disk_staker_diff_iterator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/empty_iterator.go b/vms/platformvm/state/empty_iterator.go index 69766c194fce..3ec5f04f82a9 100644 --- a/vms/platformvm/state/empty_iterator.go +++ b/vms/platformvm/state/empty_iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/empty_iterator_test.go b/vms/platformvm/state/empty_iterator_test.go index b5bb43d1f640..19cd4f06dc06 100644 --- a/vms/platformvm/state/empty_iterator_test.go +++ b/vms/platformvm/state/empty_iterator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/masked_iterator.go b/vms/platformvm/state/masked_iterator.go index 8551a05889f6..9ceee9712b40 100644 --- a/vms/platformvm/state/masked_iterator.go +++ b/vms/platformvm/state/masked_iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/masked_iterator_test.go b/vms/platformvm/state/masked_iterator_test.go index 8ba719d3e732..ccc37d6ffb3d 100644 --- a/vms/platformvm/state/masked_iterator_test.go +++ b/vms/platformvm/state/masked_iterator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/merged_iterator.go b/vms/platformvm/state/merged_iterator.go index 6c5bdafe801e..059001b3144f 100644 --- a/vms/platformvm/state/merged_iterator.go +++ b/vms/platformvm/state/merged_iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/merged_iterator_test.go b/vms/platformvm/state/merged_iterator_test.go index c85b35941b0f..e6cd52451f73 100644 --- a/vms/platformvm/state/merged_iterator_test.go +++ b/vms/platformvm/state/merged_iterator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/metadata_codec.go b/vms/platformvm/state/metadata_codec.go index 6240bbd879ca..65832ed77460 100644 --- a/vms/platformvm/state/metadata_codec.go +++ b/vms/platformvm/state/metadata_codec.go @@ -1,27 +1,36 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state import ( "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" + "github.com/ava-labs/avalanchego/utils" ) const ( - v0tag = "v0" - v0 = uint16(0) + CodecVersion0Tag = "v0" + CodecVersion0 uint16 = 0 + + CodecVersion1Tag = "v1" + CodecVersion1 uint16 = 1 ) -var metadataCodec codec.Manager +var MetadataCodec codec.Manager func init() { - c := linearcodec.New([]string{v0tag}, math.MaxInt32) - metadataCodec = codec.NewManager(math.MaxInt32) + c0 := linearcodec.New(time.Time{}, []string{CodecVersion0Tag}, math.MaxInt32) + c1 := linearcodec.New(time.Time{}, []string{CodecVersion0Tag, CodecVersion1Tag}, math.MaxInt32) + MetadataCodec = codec.NewManager(math.MaxInt32) - err := metadataCodec.RegisterCodec(v0, c) + err := utils.Err( + MetadataCodec.RegisterCodec(CodecVersion0, c0), + MetadataCodec.RegisterCodec(CodecVersion1, c1), + ) if err != nil { panic(err) } diff --git a/vms/platformvm/state/metadata_delegator.go b/vms/platformvm/state/metadata_delegator.go index 04e7ef6a8795..06099d813cde 100644 --- a/vms/platformvm/state/metadata_delegator.go +++ b/vms/platformvm/state/metadata_delegator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -9,17 +9,36 @@ import ( ) type delegatorMetadata struct { - PotentialReward uint64 + PotentialReward uint64 `v1:"true"` + StakerStartTime uint64 `v1:"true"` txID ids.ID } func parseDelegatorMetadata(bytes []byte, metadata *delegatorMetadata) error { var err error - metadata.PotentialReward, err = database.ParseUInt64(bytes) + switch len(bytes) { + case database.Uint64Size: + // only potential reward was stored + metadata.PotentialReward, err = database.ParseUInt64(bytes) + default: + _, err = MetadataCodec.Unmarshal(bytes, metadata) + } return err } -func writeDelegatorMetadata(db database.KeyValueWriter, metadata *delegatorMetadata) error { - return database.PutUInt64(db, metadata.txID[:], metadata.PotentialReward) +func writeDelegatorMetadata(db database.KeyValueWriter, metadata *delegatorMetadata, codecVersion uint16) error { + // The "0" codec is skipped for [delegatorMetadata]. This is to ensure the + // [validatorMetadata] codec version is the same as the [delegatorMetadata] + // codec version. + // + // TODO: Cleanup post-Durango activation. + if codecVersion == 0 { + return database.PutUInt64(db, metadata.txID[:], metadata.PotentialReward) + } + metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata) + if err != nil { + return err + } + return db.Put(metadata.txID[:], metadataBytes) } diff --git a/vms/platformvm/state/metadata_delegator_test.go b/vms/platformvm/state/metadata_delegator_test.go new file mode 100644 index 000000000000..9c9d6c1c0044 --- /dev/null +++ b/vms/platformvm/state/metadata_delegator_test.go @@ -0,0 +1,141 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/wrappers" +) + +func TestParseDelegatorMetadata(t *testing.T) { + type test struct { + name string + bytes []byte + expected *delegatorMetadata + expectedErr error + } + tests := []test{ + { + name: "potential reward only no codec", + bytes: []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, + }, + expected: &delegatorMetadata{ + PotentialReward: 123, + StakerStartTime: 0, + }, + expectedErr: nil, + }, + { + name: "potential reward + staker start time with codec v1", + bytes: []byte{ + // codec version + 0x00, 0x01, + // potential reward + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, + // staker start time + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + }, + expected: &delegatorMetadata{ + PotentialReward: 123, + StakerStartTime: 456, + }, + expectedErr: nil, + }, + { + name: "invalid codec version", + bytes: []byte{ + // codec version + 0x00, 0x02, + // potential reward + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, + // staker start time + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + }, + expected: nil, + expectedErr: codec.ErrUnknownVersion, + }, + { + name: "short byte len", + bytes: []byte{ + // codec version + 0x00, 0x01, + // potential reward + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, + // staker start time + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + expected: nil, + expectedErr: wrappers.ErrInsufficientLength, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + var metadata delegatorMetadata + err := parseDelegatorMetadata(tt.bytes, &metadata) + require.ErrorIs(err, tt.expectedErr) + if tt.expectedErr != nil { + return + } + require.Equal(tt.expected, &metadata) + }) + } +} + +func TestWriteDelegatorMetadata(t *testing.T) { + type test struct { + name string + version uint16 + metadata *delegatorMetadata + expected []byte + } + tests := []test{ + { + name: CodecVersion0Tag, + version: CodecVersion0, + metadata: &delegatorMetadata{ + PotentialReward: 123, + StakerStartTime: 456, + }, + expected: []byte{ + // potential reward + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, + }, + }, + { + name: CodecVersion1Tag, + version: CodecVersion1, + metadata: &delegatorMetadata{ + PotentialReward: 123, + StakerStartTime: 456, + }, + expected: []byte{ + // codec version + 0x00, 0x01, + // potential reward + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, + // staker start time + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc8, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + db := memdb.New() + tt.metadata.txID = ids.GenerateTestID() + require.NoError(writeDelegatorMetadata(db, tt.metadata, tt.version)) + bytes, err := db.Get(tt.metadata.txID[:]) + require.NoError(err) + require.Equal(tt.expected, bytes) + }) + } +} diff --git a/vms/platformvm/state/metadata_validator.go b/vms/platformvm/state/metadata_validator.go index 6b839ccad801..0c725368505b 100644 --- a/vms/platformvm/state/metadata_validator.go +++ b/vms/platformvm/state/metadata_validator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -32,6 +32,7 @@ type validatorMetadata struct { LastUpdated uint64 `v0:"true"` // Unix time in seconds PotentialReward uint64 `v0:"true"` PotentialDelegateeReward uint64 `v0:"true"` + StakerStartTime uint64 ` v1:"true"` txID ids.ID lastUpdated time.Time @@ -58,7 +59,7 @@ func parseValidatorMetadata(bytes []byte, metadata *validatorMetadata) error { // potential reward and uptime was stored but potential delegatee reward // was not tmp := preDelegateeRewardMetadata{} - if _, err := metadataCodec.Unmarshal(bytes, &tmp); err != nil { + if _, err := MetadataCodec.Unmarshal(bytes, &tmp); err != nil { return err } @@ -67,7 +68,7 @@ func parseValidatorMetadata(bytes []byte, metadata *validatorMetadata) error { metadata.PotentialReward = tmp.PotentialReward default: // everything was stored - if _, err := metadataCodec.Unmarshal(bytes, metadata); err != nil { + if _, err := MetadataCodec.Unmarshal(bytes, metadata); err != nil { return err } } @@ -130,6 +131,7 @@ type validatorState interface { WriteValidatorMetadata( dbPrimary database.KeyValueWriter, dbSubnet database.KeyValueWriter, + codecVersion uint16, ) error } @@ -230,13 +232,14 @@ func (m *metadata) DeleteValidatorMetadata(vdrID ids.NodeID, subnetID ids.ID) { func (m *metadata) WriteValidatorMetadata( dbPrimary database.KeyValueWriter, dbSubnet database.KeyValueWriter, + codecVersion uint16, ) error { for vdrID, updatedSubnets := range m.updatedMetadata { for subnetID := range updatedSubnets { metadata := m.metadata[vdrID][subnetID] metadata.LastUpdated = uint64(metadata.lastUpdated.Unix()) - metadataBytes, err := metadataCodec.Marshal(v0, metadata) + metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata) if err != nil { return err } diff --git a/vms/platformvm/state/metadata_validator_test.go b/vms/platformvm/state/metadata_validator_test.go index 68f18e62bd72..3a041a26b2eb 100644 --- a/vms/platformvm/state/metadata_validator_test.go +++ b/vms/platformvm/state/metadata_validator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -81,8 +81,9 @@ func TestWriteValidatorMetadata(t *testing.T) { primaryDB := memdb.New() subnetDB := memdb.New() + // write empty uptimes - require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB)) + require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB, CodecVersion1)) // load uptime nodeID := ids.GenerateTestNodeID() @@ -96,7 +97,7 @@ func TestWriteValidatorMetadata(t *testing.T) { state.LoadValidatorMetadata(nodeID, subnetID, testUptimeReward) // write state, should not reflect to DB yet - require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB)) + require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB, CodecVersion1)) require.False(primaryDB.Has(testUptimeReward.txID[:])) require.False(subnetDB.Has(testUptimeReward.txID[:])) @@ -112,7 +113,7 @@ func TestWriteValidatorMetadata(t *testing.T) { require.NoError(state.SetUptime(nodeID, subnetID, newUpDuration, newLastUpdated)) // write uptimes, should reflect to subnet DB - require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB)) + require.NoError(state.WriteValidatorMetadata(primaryDB, subnetDB, CodecVersion1)) require.False(primaryDB.Has(testUptimeReward.txID[:])) require.True(subnetDB.Has(testUptimeReward.txID[:])) } @@ -252,7 +253,7 @@ func TestParseValidatorMetadata(t *testing.T) { name: "invalid codec version", bytes: []byte{ // codec version - 0x00, 0x01, + 0x00, 0x02, // up duration 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0x8D, 0x80, // last updated diff --git a/vms/platformvm/state/mock_staker_iterator.go b/vms/platformvm/state/mock_staker_iterator.go index 6ef7e9fb2d51..62ba31d8b1c6 100644 --- a/vms/platformvm/state/mock_staker_iterator.go +++ b/vms/platformvm/state/mock_staker_iterator.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/platformvm/state (interfaces: StakerIterator) +// +// Generated by this command: +// +// mockgen -package=state -destination=vms/platformvm/state/mock_staker_iterator.go github.com/ava-labs/avalanchego/vms/platformvm/state StakerIterator +// // Package state is a generated GoMock package. package state diff --git a/vms/platformvm/state/mock_state.go b/vms/platformvm/state/mock_state.go index 8a3aac7d81f1..cfb9dd16944a 100644 --- a/vms/platformvm/state/mock_state.go +++ b/vms/platformvm/state/mock_state.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/platformvm/state (interfaces: Chain,Diff,State,Versions) +// +// Generated by this command: +// +// mockgen -package=state -destination=vms/platformvm/state/mock_state.go github.com/ava-labs/avalanchego/vms/platformvm/state Chain,Diff,State,Versions +// // Package state is a generated GoMock package. package state @@ -55,7 +57,7 @@ func (m *MockChain) AddChain(arg0 *txs.Tx) { } // AddChain indicates an expected call of AddChain. -func (mr *MockChainMockRecorder) AddChain(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) AddChain(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddChain", reflect.TypeOf((*MockChain)(nil).AddChain), arg0) } @@ -67,7 +69,7 @@ func (m *MockChain) AddRewardUTXO(arg0 ids.ID, arg1 *avax.UTXO) { } // AddRewardUTXO indicates an expected call of AddRewardUTXO. -func (mr *MockChainMockRecorder) AddRewardUTXO(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) AddRewardUTXO(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewardUTXO", reflect.TypeOf((*MockChain)(nil).AddRewardUTXO), arg0, arg1) } @@ -79,7 +81,7 @@ func (m *MockChain) AddSubnet(arg0 *txs.Tx) { } // AddSubnet indicates an expected call of AddSubnet. -func (mr *MockChainMockRecorder) AddSubnet(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) AddSubnet(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSubnet", reflect.TypeOf((*MockChain)(nil).AddSubnet), arg0) } @@ -91,7 +93,7 @@ func (m *MockChain) AddSubnetTransformation(arg0 *txs.Tx) { } // AddSubnetTransformation indicates an expected call of AddSubnetTransformation. -func (mr *MockChainMockRecorder) AddSubnetTransformation(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) AddSubnetTransformation(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSubnetTransformation", reflect.TypeOf((*MockChain)(nil).AddSubnetTransformation), arg0) } @@ -103,7 +105,7 @@ func (m *MockChain) AddTx(arg0 *txs.Tx, arg1 status.Status) { } // AddTx indicates an expected call of AddTx. -func (mr *MockChainMockRecorder) AddTx(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) AddTx(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTx", reflect.TypeOf((*MockChain)(nil).AddTx), arg0, arg1) } @@ -115,7 +117,7 @@ func (m *MockChain) AddUTXO(arg0 *avax.UTXO) { } // AddUTXO indicates an expected call of AddUTXO. -func (mr *MockChainMockRecorder) AddUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) AddUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUTXO", reflect.TypeOf((*MockChain)(nil).AddUTXO), arg0) } @@ -127,7 +129,7 @@ func (m *MockChain) DeleteCurrentDelegator(arg0 *Staker) { } // DeleteCurrentDelegator indicates an expected call of DeleteCurrentDelegator. -func (mr *MockChainMockRecorder) DeleteCurrentDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) DeleteCurrentDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentDelegator", reflect.TypeOf((*MockChain)(nil).DeleteCurrentDelegator), arg0) } @@ -139,7 +141,7 @@ func (m *MockChain) DeleteCurrentValidator(arg0 *Staker) { } // DeleteCurrentValidator indicates an expected call of DeleteCurrentValidator. -func (mr *MockChainMockRecorder) DeleteCurrentValidator(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) DeleteCurrentValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentValidator", reflect.TypeOf((*MockChain)(nil).DeleteCurrentValidator), arg0) } @@ -151,7 +153,7 @@ func (m *MockChain) DeletePendingDelegator(arg0 *Staker) { } // DeletePendingDelegator indicates an expected call of DeletePendingDelegator. -func (mr *MockChainMockRecorder) DeletePendingDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) DeletePendingDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePendingDelegator", reflect.TypeOf((*MockChain)(nil).DeletePendingDelegator), arg0) } @@ -163,7 +165,7 @@ func (m *MockChain) DeletePendingValidator(arg0 *Staker) { } // DeletePendingValidator indicates an expected call of DeletePendingValidator. -func (mr *MockChainMockRecorder) DeletePendingValidator(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) DeletePendingValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePendingValidator", reflect.TypeOf((*MockChain)(nil).DeletePendingValidator), arg0) } @@ -175,7 +177,7 @@ func (m *MockChain) DeleteUTXO(arg0 ids.ID) { } // DeleteUTXO indicates an expected call of DeleteUTXO. -func (mr *MockChainMockRecorder) DeleteUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) DeleteUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUTXO", reflect.TypeOf((*MockChain)(nil).DeleteUTXO), arg0) } @@ -190,7 +192,7 @@ func (m *MockChain) GetCurrentDelegatorIterator(arg0 ids.ID, arg1 ids.NodeID) (S } // GetCurrentDelegatorIterator indicates an expected call of GetCurrentDelegatorIterator. -func (mr *MockChainMockRecorder) GetCurrentDelegatorIterator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetCurrentDelegatorIterator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentDelegatorIterator", reflect.TypeOf((*MockChain)(nil).GetCurrentDelegatorIterator), arg0, arg1) } @@ -220,7 +222,7 @@ func (m *MockChain) GetCurrentSupply(arg0 ids.ID) (uint64, error) { } // GetCurrentSupply indicates an expected call of GetCurrentSupply. -func (mr *MockChainMockRecorder) GetCurrentSupply(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetCurrentSupply(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentSupply", reflect.TypeOf((*MockChain)(nil).GetCurrentSupply), arg0) } @@ -235,7 +237,7 @@ func (m *MockChain) GetCurrentValidator(arg0 ids.ID, arg1 ids.NodeID) (*Staker, } // GetCurrentValidator indicates an expected call of GetCurrentValidator. -func (mr *MockChainMockRecorder) GetCurrentValidator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetCurrentValidator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidator", reflect.TypeOf((*MockChain)(nil).GetCurrentValidator), arg0, arg1) } @@ -250,7 +252,7 @@ func (m *MockChain) GetDelegateeReward(arg0 ids.ID, arg1 ids.NodeID) (uint64, er } // GetDelegateeReward indicates an expected call of GetDelegateeReward. -func (mr *MockChainMockRecorder) GetDelegateeReward(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetDelegateeReward(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDelegateeReward", reflect.TypeOf((*MockChain)(nil).GetDelegateeReward), arg0, arg1) } @@ -265,7 +267,7 @@ func (m *MockChain) GetPendingDelegatorIterator(arg0 ids.ID, arg1 ids.NodeID) (S } // GetPendingDelegatorIterator indicates an expected call of GetPendingDelegatorIterator. -func (mr *MockChainMockRecorder) GetPendingDelegatorIterator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetPendingDelegatorIterator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingDelegatorIterator", reflect.TypeOf((*MockChain)(nil).GetPendingDelegatorIterator), arg0, arg1) } @@ -295,7 +297,7 @@ func (m *MockChain) GetPendingValidator(arg0 ids.ID, arg1 ids.NodeID) (*Staker, } // GetPendingValidator indicates an expected call of GetPendingValidator. -func (mr *MockChainMockRecorder) GetPendingValidator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetPendingValidator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockChain)(nil).GetPendingValidator), arg0, arg1) } @@ -310,7 +312,7 @@ func (m *MockChain) GetSubnetOwner(arg0 ids.ID) (fx.Owner, error) { } // GetSubnetOwner indicates an expected call of GetSubnetOwner. -func (mr *MockChainMockRecorder) GetSubnetOwner(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetSubnetOwner(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetOwner", reflect.TypeOf((*MockChain)(nil).GetSubnetOwner), arg0) } @@ -325,7 +327,7 @@ func (m *MockChain) GetSubnetTransformation(arg0 ids.ID) (*txs.Tx, error) { } // GetSubnetTransformation indicates an expected call of GetSubnetTransformation. -func (mr *MockChainMockRecorder) GetSubnetTransformation(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetSubnetTransformation(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetTransformation", reflect.TypeOf((*MockChain)(nil).GetSubnetTransformation), arg0) } @@ -355,7 +357,7 @@ func (m *MockChain) GetTx(arg0 ids.ID) (*txs.Tx, status.Status, error) { } // GetTx indicates an expected call of GetTx. -func (mr *MockChainMockRecorder) GetTx(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetTx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTx", reflect.TypeOf((*MockChain)(nil).GetTx), arg0) } @@ -370,7 +372,7 @@ func (m *MockChain) GetUTXO(arg0 ids.ID) (*avax.UTXO, error) { } // GetUTXO indicates an expected call of GetUTXO. -func (mr *MockChainMockRecorder) GetUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) GetUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUTXO", reflect.TypeOf((*MockChain)(nil).GetUTXO), arg0) } @@ -382,7 +384,7 @@ func (m *MockChain) PutCurrentDelegator(arg0 *Staker) { } // PutCurrentDelegator indicates an expected call of PutCurrentDelegator. -func (mr *MockChainMockRecorder) PutCurrentDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) PutCurrentDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCurrentDelegator", reflect.TypeOf((*MockChain)(nil).PutCurrentDelegator), arg0) } @@ -394,7 +396,7 @@ func (m *MockChain) PutCurrentValidator(arg0 *Staker) { } // PutCurrentValidator indicates an expected call of PutCurrentValidator. -func (mr *MockChainMockRecorder) PutCurrentValidator(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) PutCurrentValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCurrentValidator", reflect.TypeOf((*MockChain)(nil).PutCurrentValidator), arg0) } @@ -406,7 +408,7 @@ func (m *MockChain) PutPendingDelegator(arg0 *Staker) { } // PutPendingDelegator indicates an expected call of PutPendingDelegator. -func (mr *MockChainMockRecorder) PutPendingDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) PutPendingDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutPendingDelegator", reflect.TypeOf((*MockChain)(nil).PutPendingDelegator), arg0) } @@ -418,7 +420,7 @@ func (m *MockChain) PutPendingValidator(arg0 *Staker) { } // PutPendingValidator indicates an expected call of PutPendingValidator. -func (mr *MockChainMockRecorder) PutPendingValidator(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) PutPendingValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutPendingValidator", reflect.TypeOf((*MockChain)(nil).PutPendingValidator), arg0) } @@ -430,7 +432,7 @@ func (m *MockChain) SetCurrentSupply(arg0 ids.ID, arg1 uint64) { } // SetCurrentSupply indicates an expected call of SetCurrentSupply. -func (mr *MockChainMockRecorder) SetCurrentSupply(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) SetCurrentSupply(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCurrentSupply", reflect.TypeOf((*MockChain)(nil).SetCurrentSupply), arg0, arg1) } @@ -444,7 +446,7 @@ func (m *MockChain) SetDelegateeReward(arg0 ids.ID, arg1 ids.NodeID, arg2 uint64 } // SetDelegateeReward indicates an expected call of SetDelegateeReward. -func (mr *MockChainMockRecorder) SetDelegateeReward(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) SetDelegateeReward(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDelegateeReward", reflect.TypeOf((*MockChain)(nil).SetDelegateeReward), arg0, arg1, arg2) } @@ -456,7 +458,7 @@ func (m *MockChain) SetSubnetOwner(arg0 ids.ID, arg1 fx.Owner) { } // SetSubnetOwner indicates an expected call of SetSubnetOwner. -func (mr *MockChainMockRecorder) SetSubnetOwner(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) SetSubnetOwner(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetOwner", reflect.TypeOf((*MockChain)(nil).SetSubnetOwner), arg0, arg1) } @@ -468,7 +470,7 @@ func (m *MockChain) SetTimestamp(arg0 time.Time) { } // SetTimestamp indicates an expected call of SetTimestamp. -func (mr *MockChainMockRecorder) SetTimestamp(arg0 interface{}) *gomock.Call { +func (mr *MockChainMockRecorder) SetTimestamp(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimestamp", reflect.TypeOf((*MockChain)(nil).SetTimestamp), arg0) } @@ -503,7 +505,7 @@ func (m *MockDiff) AddChain(arg0 *txs.Tx) { } // AddChain indicates an expected call of AddChain. -func (mr *MockDiffMockRecorder) AddChain(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) AddChain(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddChain", reflect.TypeOf((*MockDiff)(nil).AddChain), arg0) } @@ -515,7 +517,7 @@ func (m *MockDiff) AddRewardUTXO(arg0 ids.ID, arg1 *avax.UTXO) { } // AddRewardUTXO indicates an expected call of AddRewardUTXO. -func (mr *MockDiffMockRecorder) AddRewardUTXO(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) AddRewardUTXO(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewardUTXO", reflect.TypeOf((*MockDiff)(nil).AddRewardUTXO), arg0, arg1) } @@ -527,7 +529,7 @@ func (m *MockDiff) AddSubnet(arg0 *txs.Tx) { } // AddSubnet indicates an expected call of AddSubnet. -func (mr *MockDiffMockRecorder) AddSubnet(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) AddSubnet(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSubnet", reflect.TypeOf((*MockDiff)(nil).AddSubnet), arg0) } @@ -539,7 +541,7 @@ func (m *MockDiff) AddSubnetTransformation(arg0 *txs.Tx) { } // AddSubnetTransformation indicates an expected call of AddSubnetTransformation. -func (mr *MockDiffMockRecorder) AddSubnetTransformation(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) AddSubnetTransformation(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSubnetTransformation", reflect.TypeOf((*MockDiff)(nil).AddSubnetTransformation), arg0) } @@ -551,7 +553,7 @@ func (m *MockDiff) AddTx(arg0 *txs.Tx, arg1 status.Status) { } // AddTx indicates an expected call of AddTx. -func (mr *MockDiffMockRecorder) AddTx(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) AddTx(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTx", reflect.TypeOf((*MockDiff)(nil).AddTx), arg0, arg1) } @@ -563,7 +565,7 @@ func (m *MockDiff) AddUTXO(arg0 *avax.UTXO) { } // AddUTXO indicates an expected call of AddUTXO. -func (mr *MockDiffMockRecorder) AddUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) AddUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUTXO", reflect.TypeOf((*MockDiff)(nil).AddUTXO), arg0) } @@ -577,7 +579,7 @@ func (m *MockDiff) Apply(arg0 Chain) error { } // Apply indicates an expected call of Apply. -func (mr *MockDiffMockRecorder) Apply(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) Apply(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Apply", reflect.TypeOf((*MockDiff)(nil).Apply), arg0) } @@ -589,7 +591,7 @@ func (m *MockDiff) DeleteCurrentDelegator(arg0 *Staker) { } // DeleteCurrentDelegator indicates an expected call of DeleteCurrentDelegator. -func (mr *MockDiffMockRecorder) DeleteCurrentDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) DeleteCurrentDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentDelegator", reflect.TypeOf((*MockDiff)(nil).DeleteCurrentDelegator), arg0) } @@ -601,7 +603,7 @@ func (m *MockDiff) DeleteCurrentValidator(arg0 *Staker) { } // DeleteCurrentValidator indicates an expected call of DeleteCurrentValidator. -func (mr *MockDiffMockRecorder) DeleteCurrentValidator(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) DeleteCurrentValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentValidator", reflect.TypeOf((*MockDiff)(nil).DeleteCurrentValidator), arg0) } @@ -613,7 +615,7 @@ func (m *MockDiff) DeletePendingDelegator(arg0 *Staker) { } // DeletePendingDelegator indicates an expected call of DeletePendingDelegator. -func (mr *MockDiffMockRecorder) DeletePendingDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) DeletePendingDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePendingDelegator", reflect.TypeOf((*MockDiff)(nil).DeletePendingDelegator), arg0) } @@ -625,7 +627,7 @@ func (m *MockDiff) DeletePendingValidator(arg0 *Staker) { } // DeletePendingValidator indicates an expected call of DeletePendingValidator. -func (mr *MockDiffMockRecorder) DeletePendingValidator(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) DeletePendingValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePendingValidator", reflect.TypeOf((*MockDiff)(nil).DeletePendingValidator), arg0) } @@ -637,7 +639,7 @@ func (m *MockDiff) DeleteUTXO(arg0 ids.ID) { } // DeleteUTXO indicates an expected call of DeleteUTXO. -func (mr *MockDiffMockRecorder) DeleteUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) DeleteUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUTXO", reflect.TypeOf((*MockDiff)(nil).DeleteUTXO), arg0) } @@ -652,7 +654,7 @@ func (m *MockDiff) GetCurrentDelegatorIterator(arg0 ids.ID, arg1 ids.NodeID) (St } // GetCurrentDelegatorIterator indicates an expected call of GetCurrentDelegatorIterator. -func (mr *MockDiffMockRecorder) GetCurrentDelegatorIterator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetCurrentDelegatorIterator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentDelegatorIterator", reflect.TypeOf((*MockDiff)(nil).GetCurrentDelegatorIterator), arg0, arg1) } @@ -682,7 +684,7 @@ func (m *MockDiff) GetCurrentSupply(arg0 ids.ID) (uint64, error) { } // GetCurrentSupply indicates an expected call of GetCurrentSupply. -func (mr *MockDiffMockRecorder) GetCurrentSupply(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetCurrentSupply(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentSupply", reflect.TypeOf((*MockDiff)(nil).GetCurrentSupply), arg0) } @@ -697,7 +699,7 @@ func (m *MockDiff) GetCurrentValidator(arg0 ids.ID, arg1 ids.NodeID) (*Staker, e } // GetCurrentValidator indicates an expected call of GetCurrentValidator. -func (mr *MockDiffMockRecorder) GetCurrentValidator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetCurrentValidator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidator", reflect.TypeOf((*MockDiff)(nil).GetCurrentValidator), arg0, arg1) } @@ -712,7 +714,7 @@ func (m *MockDiff) GetDelegateeReward(arg0 ids.ID, arg1 ids.NodeID) (uint64, err } // GetDelegateeReward indicates an expected call of GetDelegateeReward. -func (mr *MockDiffMockRecorder) GetDelegateeReward(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetDelegateeReward(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDelegateeReward", reflect.TypeOf((*MockDiff)(nil).GetDelegateeReward), arg0, arg1) } @@ -727,7 +729,7 @@ func (m *MockDiff) GetPendingDelegatorIterator(arg0 ids.ID, arg1 ids.NodeID) (St } // GetPendingDelegatorIterator indicates an expected call of GetPendingDelegatorIterator. -func (mr *MockDiffMockRecorder) GetPendingDelegatorIterator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetPendingDelegatorIterator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingDelegatorIterator", reflect.TypeOf((*MockDiff)(nil).GetPendingDelegatorIterator), arg0, arg1) } @@ -757,7 +759,7 @@ func (m *MockDiff) GetPendingValidator(arg0 ids.ID, arg1 ids.NodeID) (*Staker, e } // GetPendingValidator indicates an expected call of GetPendingValidator. -func (mr *MockDiffMockRecorder) GetPendingValidator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetPendingValidator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockDiff)(nil).GetPendingValidator), arg0, arg1) } @@ -772,7 +774,7 @@ func (m *MockDiff) GetSubnetOwner(arg0 ids.ID) (fx.Owner, error) { } // GetSubnetOwner indicates an expected call of GetSubnetOwner. -func (mr *MockDiffMockRecorder) GetSubnetOwner(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetSubnetOwner(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetOwner", reflect.TypeOf((*MockDiff)(nil).GetSubnetOwner), arg0) } @@ -787,7 +789,7 @@ func (m *MockDiff) GetSubnetTransformation(arg0 ids.ID) (*txs.Tx, error) { } // GetSubnetTransformation indicates an expected call of GetSubnetTransformation. -func (mr *MockDiffMockRecorder) GetSubnetTransformation(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetSubnetTransformation(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetTransformation", reflect.TypeOf((*MockDiff)(nil).GetSubnetTransformation), arg0) } @@ -817,7 +819,7 @@ func (m *MockDiff) GetTx(arg0 ids.ID) (*txs.Tx, status.Status, error) { } // GetTx indicates an expected call of GetTx. -func (mr *MockDiffMockRecorder) GetTx(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetTx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTx", reflect.TypeOf((*MockDiff)(nil).GetTx), arg0) } @@ -832,7 +834,7 @@ func (m *MockDiff) GetUTXO(arg0 ids.ID) (*avax.UTXO, error) { } // GetUTXO indicates an expected call of GetUTXO. -func (mr *MockDiffMockRecorder) GetUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) GetUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUTXO", reflect.TypeOf((*MockDiff)(nil).GetUTXO), arg0) } @@ -844,7 +846,7 @@ func (m *MockDiff) PutCurrentDelegator(arg0 *Staker) { } // PutCurrentDelegator indicates an expected call of PutCurrentDelegator. -func (mr *MockDiffMockRecorder) PutCurrentDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) PutCurrentDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCurrentDelegator", reflect.TypeOf((*MockDiff)(nil).PutCurrentDelegator), arg0) } @@ -856,7 +858,7 @@ func (m *MockDiff) PutCurrentValidator(arg0 *Staker) { } // PutCurrentValidator indicates an expected call of PutCurrentValidator. -func (mr *MockDiffMockRecorder) PutCurrentValidator(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) PutCurrentValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCurrentValidator", reflect.TypeOf((*MockDiff)(nil).PutCurrentValidator), arg0) } @@ -868,7 +870,7 @@ func (m *MockDiff) PutPendingDelegator(arg0 *Staker) { } // PutPendingDelegator indicates an expected call of PutPendingDelegator. -func (mr *MockDiffMockRecorder) PutPendingDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) PutPendingDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutPendingDelegator", reflect.TypeOf((*MockDiff)(nil).PutPendingDelegator), arg0) } @@ -880,7 +882,7 @@ func (m *MockDiff) PutPendingValidator(arg0 *Staker) { } // PutPendingValidator indicates an expected call of PutPendingValidator. -func (mr *MockDiffMockRecorder) PutPendingValidator(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) PutPendingValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutPendingValidator", reflect.TypeOf((*MockDiff)(nil).PutPendingValidator), arg0) } @@ -892,7 +894,7 @@ func (m *MockDiff) SetCurrentSupply(arg0 ids.ID, arg1 uint64) { } // SetCurrentSupply indicates an expected call of SetCurrentSupply. -func (mr *MockDiffMockRecorder) SetCurrentSupply(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) SetCurrentSupply(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCurrentSupply", reflect.TypeOf((*MockDiff)(nil).SetCurrentSupply), arg0, arg1) } @@ -906,7 +908,7 @@ func (m *MockDiff) SetDelegateeReward(arg0 ids.ID, arg1 ids.NodeID, arg2 uint64) } // SetDelegateeReward indicates an expected call of SetDelegateeReward. -func (mr *MockDiffMockRecorder) SetDelegateeReward(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) SetDelegateeReward(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDelegateeReward", reflect.TypeOf((*MockDiff)(nil).SetDelegateeReward), arg0, arg1, arg2) } @@ -918,7 +920,7 @@ func (m *MockDiff) SetSubnetOwner(arg0 ids.ID, arg1 fx.Owner) { } // SetSubnetOwner indicates an expected call of SetSubnetOwner. -func (mr *MockDiffMockRecorder) SetSubnetOwner(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) SetSubnetOwner(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetOwner", reflect.TypeOf((*MockDiff)(nil).SetSubnetOwner), arg0, arg1) } @@ -930,7 +932,7 @@ func (m *MockDiff) SetTimestamp(arg0 time.Time) { } // SetTimestamp indicates an expected call of SetTimestamp. -func (mr *MockDiffMockRecorder) SetTimestamp(arg0 interface{}) *gomock.Call { +func (mr *MockDiffMockRecorder) SetTimestamp(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimestamp", reflect.TypeOf((*MockDiff)(nil).SetTimestamp), arg0) } @@ -977,7 +979,7 @@ func (m *MockState) AddChain(arg0 *txs.Tx) { } // AddChain indicates an expected call of AddChain. -func (mr *MockStateMockRecorder) AddChain(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddChain(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddChain", reflect.TypeOf((*MockState)(nil).AddChain), arg0) } @@ -989,7 +991,7 @@ func (m *MockState) AddRewardUTXO(arg0 ids.ID, arg1 *avax.UTXO) { } // AddRewardUTXO indicates an expected call of AddRewardUTXO. -func (mr *MockStateMockRecorder) AddRewardUTXO(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddRewardUTXO(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewardUTXO", reflect.TypeOf((*MockState)(nil).AddRewardUTXO), arg0, arg1) } @@ -1001,7 +1003,7 @@ func (m *MockState) AddStatelessBlock(arg0 block.Block) { } // AddStatelessBlock indicates an expected call of AddStatelessBlock. -func (mr *MockStateMockRecorder) AddStatelessBlock(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddStatelessBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddStatelessBlock", reflect.TypeOf((*MockState)(nil).AddStatelessBlock), arg0) } @@ -1013,7 +1015,7 @@ func (m *MockState) AddSubnet(arg0 *txs.Tx) { } // AddSubnet indicates an expected call of AddSubnet. -func (mr *MockStateMockRecorder) AddSubnet(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddSubnet(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSubnet", reflect.TypeOf((*MockState)(nil).AddSubnet), arg0) } @@ -1025,7 +1027,7 @@ func (m *MockState) AddSubnetTransformation(arg0 *txs.Tx) { } // AddSubnetTransformation indicates an expected call of AddSubnetTransformation. -func (mr *MockStateMockRecorder) AddSubnetTransformation(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddSubnetTransformation(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSubnetTransformation", reflect.TypeOf((*MockState)(nil).AddSubnetTransformation), arg0) } @@ -1037,7 +1039,7 @@ func (m *MockState) AddTx(arg0 *txs.Tx, arg1 status.Status) { } // AddTx indicates an expected call of AddTx. -func (mr *MockStateMockRecorder) AddTx(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddTx(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddTx", reflect.TypeOf((*MockState)(nil).AddTx), arg0, arg1) } @@ -1049,7 +1051,7 @@ func (m *MockState) AddUTXO(arg0 *avax.UTXO) { } // AddUTXO indicates an expected call of AddUTXO. -func (mr *MockStateMockRecorder) AddUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) AddUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUTXO", reflect.TypeOf((*MockState)(nil).AddUTXO), arg0) } @@ -1063,7 +1065,7 @@ func (m *MockState) ApplyValidatorPublicKeyDiffs(arg0 context.Context, arg1 map[ } // ApplyValidatorPublicKeyDiffs indicates an expected call of ApplyValidatorPublicKeyDiffs. -func (mr *MockStateMockRecorder) ApplyValidatorPublicKeyDiffs(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) ApplyValidatorPublicKeyDiffs(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyValidatorPublicKeyDiffs", reflect.TypeOf((*MockState)(nil).ApplyValidatorPublicKeyDiffs), arg0, arg1, arg2, arg3) } @@ -1077,7 +1079,7 @@ func (m *MockState) ApplyValidatorWeightDiffs(arg0 context.Context, arg1 map[ids } // ApplyValidatorWeightDiffs indicates an expected call of ApplyValidatorWeightDiffs. -func (mr *MockStateMockRecorder) ApplyValidatorWeightDiffs(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) ApplyValidatorWeightDiffs(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyValidatorWeightDiffs", reflect.TypeOf((*MockState)(nil).ApplyValidatorWeightDiffs), arg0, arg1, arg2, arg3, arg4) } @@ -1146,7 +1148,7 @@ func (m *MockState) DeleteCurrentDelegator(arg0 *Staker) { } // DeleteCurrentDelegator indicates an expected call of DeleteCurrentDelegator. -func (mr *MockStateMockRecorder) DeleteCurrentDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) DeleteCurrentDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentDelegator", reflect.TypeOf((*MockState)(nil).DeleteCurrentDelegator), arg0) } @@ -1158,7 +1160,7 @@ func (m *MockState) DeleteCurrentValidator(arg0 *Staker) { } // DeleteCurrentValidator indicates an expected call of DeleteCurrentValidator. -func (mr *MockStateMockRecorder) DeleteCurrentValidator(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) DeleteCurrentValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCurrentValidator", reflect.TypeOf((*MockState)(nil).DeleteCurrentValidator), arg0) } @@ -1170,7 +1172,7 @@ func (m *MockState) DeletePendingDelegator(arg0 *Staker) { } // DeletePendingDelegator indicates an expected call of DeletePendingDelegator. -func (mr *MockStateMockRecorder) DeletePendingDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) DeletePendingDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePendingDelegator", reflect.TypeOf((*MockState)(nil).DeletePendingDelegator), arg0) } @@ -1182,7 +1184,7 @@ func (m *MockState) DeletePendingValidator(arg0 *Staker) { } // DeletePendingValidator indicates an expected call of DeletePendingValidator. -func (mr *MockStateMockRecorder) DeletePendingValidator(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) DeletePendingValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePendingValidator", reflect.TypeOf((*MockState)(nil).DeletePendingValidator), arg0) } @@ -1194,7 +1196,7 @@ func (m *MockState) DeleteUTXO(arg0 ids.ID) { } // DeleteUTXO indicates an expected call of DeleteUTXO. -func (mr *MockStateMockRecorder) DeleteUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) DeleteUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUTXO", reflect.TypeOf((*MockState)(nil).DeleteUTXO), arg0) } @@ -1209,7 +1211,7 @@ func (m *MockState) GetBlockIDAtHeight(arg0 uint64) (ids.ID, error) { } // GetBlockIDAtHeight indicates an expected call of GetBlockIDAtHeight. -func (mr *MockStateMockRecorder) GetBlockIDAtHeight(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetBlockIDAtHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockIDAtHeight", reflect.TypeOf((*MockState)(nil).GetBlockIDAtHeight), arg0) } @@ -1224,7 +1226,7 @@ func (m *MockState) GetChains(arg0 ids.ID) ([]*txs.Tx, error) { } // GetChains indicates an expected call of GetChains. -func (mr *MockStateMockRecorder) GetChains(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetChains(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChains", reflect.TypeOf((*MockState)(nil).GetChains), arg0) } @@ -1239,7 +1241,7 @@ func (m *MockState) GetCurrentDelegatorIterator(arg0 ids.ID, arg1 ids.NodeID) (S } // GetCurrentDelegatorIterator indicates an expected call of GetCurrentDelegatorIterator. -func (mr *MockStateMockRecorder) GetCurrentDelegatorIterator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetCurrentDelegatorIterator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentDelegatorIterator", reflect.TypeOf((*MockState)(nil).GetCurrentDelegatorIterator), arg0, arg1) } @@ -1269,7 +1271,7 @@ func (m *MockState) GetCurrentSupply(arg0 ids.ID) (uint64, error) { } // GetCurrentSupply indicates an expected call of GetCurrentSupply. -func (mr *MockStateMockRecorder) GetCurrentSupply(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetCurrentSupply(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentSupply", reflect.TypeOf((*MockState)(nil).GetCurrentSupply), arg0) } @@ -1284,7 +1286,7 @@ func (m *MockState) GetCurrentValidator(arg0 ids.ID, arg1 ids.NodeID) (*Staker, } // GetCurrentValidator indicates an expected call of GetCurrentValidator. -func (mr *MockStateMockRecorder) GetCurrentValidator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetCurrentValidator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentValidator", reflect.TypeOf((*MockState)(nil).GetCurrentValidator), arg0, arg1) } @@ -1299,7 +1301,7 @@ func (m *MockState) GetDelegateeReward(arg0 ids.ID, arg1 ids.NodeID) (uint64, er } // GetDelegateeReward indicates an expected call of GetDelegateeReward. -func (mr *MockStateMockRecorder) GetDelegateeReward(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetDelegateeReward(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDelegateeReward", reflect.TypeOf((*MockState)(nil).GetDelegateeReward), arg0, arg1) } @@ -1328,7 +1330,7 @@ func (m *MockState) GetPendingDelegatorIterator(arg0 ids.ID, arg1 ids.NodeID) (S } // GetPendingDelegatorIterator indicates an expected call of GetPendingDelegatorIterator. -func (mr *MockStateMockRecorder) GetPendingDelegatorIterator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetPendingDelegatorIterator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingDelegatorIterator", reflect.TypeOf((*MockState)(nil).GetPendingDelegatorIterator), arg0, arg1) } @@ -1358,7 +1360,7 @@ func (m *MockState) GetPendingValidator(arg0 ids.ID, arg1 ids.NodeID) (*Staker, } // GetPendingValidator indicates an expected call of GetPendingValidator. -func (mr *MockStateMockRecorder) GetPendingValidator(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetPendingValidator(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPendingValidator", reflect.TypeOf((*MockState)(nil).GetPendingValidator), arg0, arg1) } @@ -1373,7 +1375,7 @@ func (m *MockState) GetRewardUTXOs(arg0 ids.ID) ([]*avax.UTXO, error) { } // GetRewardUTXOs indicates an expected call of GetRewardUTXOs. -func (mr *MockStateMockRecorder) GetRewardUTXOs(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetRewardUTXOs(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRewardUTXOs", reflect.TypeOf((*MockState)(nil).GetRewardUTXOs), arg0) } @@ -1388,7 +1390,7 @@ func (m *MockState) GetStartTime(arg0 ids.NodeID, arg1 ids.ID) (time.Time, error } // GetStartTime indicates an expected call of GetStartTime. -func (mr *MockStateMockRecorder) GetStartTime(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetStartTime(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStartTime", reflect.TypeOf((*MockState)(nil).GetStartTime), arg0, arg1) } @@ -1403,7 +1405,7 @@ func (m *MockState) GetStatelessBlock(arg0 ids.ID) (block.Block, error) { } // GetStatelessBlock indicates an expected call of GetStatelessBlock. -func (mr *MockStateMockRecorder) GetStatelessBlock(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetStatelessBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatelessBlock", reflect.TypeOf((*MockState)(nil).GetStatelessBlock), arg0) } @@ -1418,7 +1420,7 @@ func (m *MockState) GetSubnetOwner(arg0 ids.ID) (fx.Owner, error) { } // GetSubnetOwner indicates an expected call of GetSubnetOwner. -func (mr *MockStateMockRecorder) GetSubnetOwner(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetSubnetOwner(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetOwner", reflect.TypeOf((*MockState)(nil).GetSubnetOwner), arg0) } @@ -1433,7 +1435,7 @@ func (m *MockState) GetSubnetTransformation(arg0 ids.ID) (*txs.Tx, error) { } // GetSubnetTransformation indicates an expected call of GetSubnetTransformation. -func (mr *MockStateMockRecorder) GetSubnetTransformation(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetSubnetTransformation(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubnetTransformation", reflect.TypeOf((*MockState)(nil).GetSubnetTransformation), arg0) } @@ -1478,7 +1480,7 @@ func (m *MockState) GetTx(arg0 ids.ID) (*txs.Tx, status.Status, error) { } // GetTx indicates an expected call of GetTx. -func (mr *MockStateMockRecorder) GetTx(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetTx(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTx", reflect.TypeOf((*MockState)(nil).GetTx), arg0) } @@ -1493,7 +1495,7 @@ func (m *MockState) GetUTXO(arg0 ids.ID) (*avax.UTXO, error) { } // GetUTXO indicates an expected call of GetUTXO. -func (mr *MockStateMockRecorder) GetUTXO(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetUTXO(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUTXO", reflect.TypeOf((*MockState)(nil).GetUTXO), arg0) } @@ -1509,7 +1511,7 @@ func (m *MockState) GetUptime(arg0 ids.NodeID, arg1 ids.ID) (time.Duration, time } // GetUptime indicates an expected call of GetUptime. -func (mr *MockStateMockRecorder) GetUptime(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetUptime(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUptime", reflect.TypeOf((*MockState)(nil).GetUptime), arg0, arg1) } @@ -1523,7 +1525,7 @@ func (m *MockState) PruneAndIndex(arg0 sync.Locker, arg1 logging.Logger) error { } // PruneAndIndex indicates an expected call of PruneAndIndex. -func (mr *MockStateMockRecorder) PruneAndIndex(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) PruneAndIndex(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PruneAndIndex", reflect.TypeOf((*MockState)(nil).PruneAndIndex), arg0, arg1) } @@ -1535,7 +1537,7 @@ func (m *MockState) PutCurrentDelegator(arg0 *Staker) { } // PutCurrentDelegator indicates an expected call of PutCurrentDelegator. -func (mr *MockStateMockRecorder) PutCurrentDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) PutCurrentDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCurrentDelegator", reflect.TypeOf((*MockState)(nil).PutCurrentDelegator), arg0) } @@ -1547,7 +1549,7 @@ func (m *MockState) PutCurrentValidator(arg0 *Staker) { } // PutCurrentValidator indicates an expected call of PutCurrentValidator. -func (mr *MockStateMockRecorder) PutCurrentValidator(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) PutCurrentValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutCurrentValidator", reflect.TypeOf((*MockState)(nil).PutCurrentValidator), arg0) } @@ -1559,7 +1561,7 @@ func (m *MockState) PutPendingDelegator(arg0 *Staker) { } // PutPendingDelegator indicates an expected call of PutPendingDelegator. -func (mr *MockStateMockRecorder) PutPendingDelegator(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) PutPendingDelegator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutPendingDelegator", reflect.TypeOf((*MockState)(nil).PutPendingDelegator), arg0) } @@ -1571,7 +1573,7 @@ func (m *MockState) PutPendingValidator(arg0 *Staker) { } // PutPendingValidator indicates an expected call of PutPendingValidator. -func (mr *MockStateMockRecorder) PutPendingValidator(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) PutPendingValidator(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutPendingValidator", reflect.TypeOf((*MockState)(nil).PutPendingValidator), arg0) } @@ -1583,7 +1585,7 @@ func (m *MockState) SetCurrentSupply(arg0 ids.ID, arg1 uint64) { } // SetCurrentSupply indicates an expected call of SetCurrentSupply. -func (mr *MockStateMockRecorder) SetCurrentSupply(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetCurrentSupply(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCurrentSupply", reflect.TypeOf((*MockState)(nil).SetCurrentSupply), arg0, arg1) } @@ -1597,7 +1599,7 @@ func (m *MockState) SetDelegateeReward(arg0 ids.ID, arg1 ids.NodeID, arg2 uint64 } // SetDelegateeReward indicates an expected call of SetDelegateeReward. -func (mr *MockStateMockRecorder) SetDelegateeReward(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetDelegateeReward(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDelegateeReward", reflect.TypeOf((*MockState)(nil).SetDelegateeReward), arg0, arg1, arg2) } @@ -1609,7 +1611,7 @@ func (m *MockState) SetHeight(arg0 uint64) { } // SetHeight indicates an expected call of SetHeight. -func (mr *MockStateMockRecorder) SetHeight(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHeight", reflect.TypeOf((*MockState)(nil).SetHeight), arg0) } @@ -1621,7 +1623,7 @@ func (m *MockState) SetLastAccepted(arg0 ids.ID) { } // SetLastAccepted indicates an expected call of SetLastAccepted. -func (mr *MockStateMockRecorder) SetLastAccepted(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetLastAccepted(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastAccepted", reflect.TypeOf((*MockState)(nil).SetLastAccepted), arg0) } @@ -1633,7 +1635,7 @@ func (m *MockState) SetSubnetOwner(arg0 ids.ID, arg1 fx.Owner) { } // SetSubnetOwner indicates an expected call of SetSubnetOwner. -func (mr *MockStateMockRecorder) SetSubnetOwner(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetSubnetOwner(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSubnetOwner", reflect.TypeOf((*MockState)(nil).SetSubnetOwner), arg0, arg1) } @@ -1645,7 +1647,7 @@ func (m *MockState) SetTimestamp(arg0 time.Time) { } // SetTimestamp indicates an expected call of SetTimestamp. -func (mr *MockStateMockRecorder) SetTimestamp(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetTimestamp(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimestamp", reflect.TypeOf((*MockState)(nil).SetTimestamp), arg0) } @@ -1659,7 +1661,7 @@ func (m *MockState) SetUptime(arg0 ids.NodeID, arg1 ids.ID, arg2 time.Duration, } // SetUptime indicates an expected call of SetUptime. -func (mr *MockStateMockRecorder) SetUptime(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetUptime(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUptime", reflect.TypeOf((*MockState)(nil).SetUptime), arg0, arg1, arg2, arg3) } @@ -1689,7 +1691,7 @@ func (m *MockState) UTXOIDs(arg0 []byte, arg1 ids.ID, arg2 int) ([]ids.ID, error } // UTXOIDs indicates an expected call of UTXOIDs. -func (mr *MockStateMockRecorder) UTXOIDs(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) UTXOIDs(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UTXOIDs", reflect.TypeOf((*MockState)(nil).UTXOIDs), arg0, arg1, arg2) } @@ -1727,7 +1729,7 @@ func (m *MockVersions) GetState(arg0 ids.ID) (Chain, bool) { } // GetState indicates an expected call of GetState. -func (mr *MockVersionsMockRecorder) GetState(arg0 interface{}) *gomock.Call { +func (mr *MockVersionsMockRecorder) GetState(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockVersions)(nil).GetState), arg0) } diff --git a/vms/platformvm/state/slice_iterator_test.go b/vms/platformvm/state/slice_iterator_test.go index 96a686cddf92..408ffe837a27 100644 --- a/vms/platformvm/state/slice_iterator_test.go +++ b/vms/platformvm/state/slice_iterator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/staker.go b/vms/platformvm/state/staker.go index 37bc512e36cb..a9ba52595062 100644 --- a/vms/platformvm/state/staker.go +++ b/vms/platformvm/state/staker.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -83,7 +83,12 @@ func (s *Staker) Less(than *Staker) bool { return bytes.Compare(s.TxID[:], than.TxID[:]) == -1 } -func NewCurrentStaker(txID ids.ID, staker txs.Staker, potentialReward uint64) (*Staker, error) { +func NewCurrentStaker( + txID ids.ID, + staker txs.Staker, + startTime time.Time, + potentialReward uint64, +) (*Staker, error) { publicKey, _, err := staker.PublicKey() if err != nil { return nil, err @@ -95,7 +100,7 @@ func NewCurrentStaker(txID ids.ID, staker txs.Staker, potentialReward uint64) (* PublicKey: publicKey, SubnetID: staker.SubnetID(), Weight: staker.Weight(), - StartTime: staker.StartTime(), + StartTime: startTime, EndTime: endTime, PotentialReward: potentialReward, NextTime: endTime, @@ -103,7 +108,7 @@ func NewCurrentStaker(txID ids.ID, staker txs.Staker, potentialReward uint64) (* }, nil } -func NewPendingStaker(txID ids.ID, staker txs.Staker) (*Staker, error) { +func NewPendingStaker(txID ids.ID, staker txs.ScheduledStaker) (*Staker, error) { publicKey, _, err := staker.PublicKey() if err != nil { return nil, err diff --git a/vms/platformvm/state/staker_diff_iterator.go b/vms/platformvm/state/staker_diff_iterator.go index d9c194cd2702..d47ab49ac572 100644 --- a/vms/platformvm/state/staker_diff_iterator.go +++ b/vms/platformvm/state/staker_diff_iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/staker_diff_iterator_test.go b/vms/platformvm/state/staker_diff_iterator_test.go index c008b06fb716..468b8800a23d 100644 --- a/vms/platformvm/state/staker_diff_iterator_test.go +++ b/vms/platformvm/state/staker_diff_iterator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/staker_status.go b/vms/platformvm/state/staker_status.go index b74064c4dc0b..0adc46244f92 100644 --- a/vms/platformvm/state/staker_status.go +++ b/vms/platformvm/state/staker_status.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/staker_test.go b/vms/platformvm/state/staker_test.go index 747f442e5eda..1b72385dbd13 100644 --- a/vms/platformvm/state/staker_test.go +++ b/vms/platformvm/state/staker_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -144,20 +144,19 @@ func TestNewCurrentStaker(t *testing.T) { subnetID := ids.GenerateTestID() weight := uint64(12345) startTime := time.Now() - endTime := time.Now() + endTime := startTime.Add(time.Hour) potentialReward := uint64(54321) currentPriority := txs.SubnetPermissionedValidatorCurrentPriority stakerTx := txs.NewMockStaker(ctrl) + stakerTx.EXPECT().EndTime().Return(endTime) stakerTx.EXPECT().NodeID().Return(nodeID) stakerTx.EXPECT().PublicKey().Return(publicKey, true, nil) stakerTx.EXPECT().SubnetID().Return(subnetID) stakerTx.EXPECT().Weight().Return(weight) - stakerTx.EXPECT().StartTime().Return(startTime) - stakerTx.EXPECT().EndTime().Return(endTime) stakerTx.EXPECT().CurrentPriority().Return(currentPriority) - staker, err := NewCurrentStaker(txID, stakerTx, potentialReward) + staker, err := NewCurrentStaker(txID, stakerTx, startTime, potentialReward) require.NotNil(staker) require.NoError(err) require.Equal(txID, staker.TxID) @@ -173,7 +172,7 @@ func TestNewCurrentStaker(t *testing.T) { stakerTx.EXPECT().PublicKey().Return(nil, false, errCustom) - _, err = NewCurrentStaker(txID, stakerTx, potentialReward) + _, err = NewCurrentStaker(txID, stakerTx, startTime, potentialReward) require.ErrorIs(err, errCustom) } @@ -192,7 +191,7 @@ func TestNewPendingStaker(t *testing.T) { endTime := time.Now() pendingPriority := txs.SubnetPermissionedValidatorPendingPriority - stakerTx := txs.NewMockStaker(ctrl) + stakerTx := txs.NewMockScheduledStaker(ctrl) stakerTx.EXPECT().NodeID().Return(nodeID) stakerTx.EXPECT().PublicKey().Return(publicKey, true, nil) stakerTx.EXPECT().SubnetID().Return(subnetID) diff --git a/vms/platformvm/state/stakers.go b/vms/platformvm/state/stakers.go index 5276ff4f8204..f787749f72df 100644 --- a/vms/platformvm/state/stakers.go +++ b/vms/platformvm/state/stakers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/stakers_test.go b/vms/platformvm/state/stakers_test.go index 9894d9479653..5c6d9a8b28f8 100644 --- a/vms/platformvm/state/stakers_test.go +++ b/vms/platformvm/state/stakers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/state.go b/vms/platformvm/state/state.go index 4627fc706626..92e646ed6975 100644 --- a/vms/platformvm/state/state.go +++ b/vms/platformvm/state/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -289,6 +289,7 @@ type state struct { validators validators.Manager ctx *snow.Context + cfg *config.Config metrics metrics.Metrics rewards reward.Calculator @@ -452,7 +453,7 @@ func New( db database.Database, genesisBytes []byte, metricsReg prometheus.Registerer, - validators validators.Manager, + cfg *config.Config, execCfg *config.ExecutionConfig, ctx *snow.Context, metrics metrics.Metrics, @@ -461,7 +462,7 @@ func New( s, err := newState( db, metrics, - validators, + cfg, execCfg, ctx, metricsReg, @@ -505,7 +506,7 @@ func New( func newState( db database.Database, metrics metrics.Metrics, - validators validators.Manager, + cfg *config.Config, execCfg *config.ExecutionConfig, ctx *snow.Context, metricsReg prometheus.Registerer, @@ -628,8 +629,9 @@ func newState( return &state{ validatorState: newValidatorState(), - validators: validators, + validators: cfg.Validators, ctx: ctx, + cfg: cfg, metrics: metrics, rewards: rewards, baseDB: baseDB, @@ -1188,7 +1190,7 @@ func (s *state) ApplyValidatorWeightDiffs( Height: height, SubnetID: subnetID, } - prefixBytes, err := block.GenesisCodec.Marshal(block.Version, prefixStruct) + prefixBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, prefixStruct) if err != nil { return err } @@ -1320,13 +1322,20 @@ func (s *state) syncGenesis(genesisBlk block.Block, genesis *genesis.Genesis) er // Persist primary network validator set at genesis for _, vdrTx := range genesis.Validators { - validatorTx, ok := vdrTx.Unsigned.(txs.ValidatorTx) + // We expect genesis validator txs to be either AddValidatorTx or + // AddPermissionlessValidatorTx. + // + // TODO: Enforce stricter type check + validatorTx, ok := vdrTx.Unsigned.(txs.ScheduledStaker) if !ok { - return fmt.Errorf("expected tx type txs.ValidatorTx but got %T", vdrTx.Unsigned) + return fmt.Errorf("expected a scheduled staker but got %T", vdrTx.Unsigned) } stakeAmount := validatorTx.Weight() - stakeDuration := validatorTx.EndTime().Sub(validatorTx.StartTime()) + // Note: We use [StartTime()] here because genesis transactions are + // guaranteed to be pre-Durango activation. + startTime := validatorTx.StartTime() + stakeDuration := validatorTx.EndTime().Sub(startTime) currentSupply, err := s.GetCurrentSupply(constants.PrimaryNetworkID) if err != nil { return err @@ -1342,7 +1351,7 @@ func (s *state) syncGenesis(genesisBlk block.Block, genesis *genesis.Genesis) er return err } - staker, err := NewCurrentStaker(vdrTx.ID(), validatorTx, potentialReward) + staker, err := NewCurrentStaker(vdrTx.ID(), validatorTx, startTime, potentialReward) if err != nil { return err } @@ -1448,25 +1457,35 @@ func (s *state) loadCurrentValidators() error { } tx, _, err := s.GetTx(txID) if err != nil { - return err + return fmt.Errorf("failed loading validator transaction txID %s, %w", txID, err) + } + + stakerTx, ok := tx.Unsigned.(txs.Staker) + if !ok { + return fmt.Errorf("expected tx type txs.Staker but got %T", tx.Unsigned) } metadataBytes := validatorIt.Value() metadata := &validatorMetadata{ txID: txID, - // Note: we don't provide [LastUpdated] here because we expect it to + } + if scheduledStakerTx, ok := tx.Unsigned.(txs.ScheduledStaker); ok { + // Populate [StakerStartTime] using the tx as a default in the event + // it was added pre-durango and is not stored in the database. + // + // Note: We do not populate [LastUpdated] since it is expected to // always be present on disk. + metadata.StakerStartTime = uint64(scheduledStakerTx.StartTime().Unix()) } if err := parseValidatorMetadata(metadataBytes, metadata); err != nil { return err } - stakerTx, ok := tx.Unsigned.(txs.Staker) - if !ok { - return fmt.Errorf("expected tx type txs.Staker but got %T", tx.Unsigned) - } - - staker, err := NewCurrentStaker(txID, stakerTx, metadata.PotentialReward) + staker, err := NewCurrentStaker( + txID, + stakerTx, + time.Unix(int64(metadata.StakerStartTime), 0), + metadata.PotentialReward) if err != nil { return err } @@ -1500,15 +1519,24 @@ func (s *state) loadCurrentValidators() error { metadataBytes := subnetValidatorIt.Value() metadata := &validatorMetadata{ txID: txID, - // use the start time as the fallback value - // in case it's not stored in the database - LastUpdated: uint64(stakerTx.StartTime().Unix()), + } + if scheduledStakerTx, ok := tx.Unsigned.(txs.ScheduledStaker); ok { + // Populate [StakerStartTime] and [LastUpdated] using the tx as a + // default in the event they are not stored in the database. + startTime := uint64(scheduledStakerTx.StartTime().Unix()) + metadata.StakerStartTime = startTime + metadata.LastUpdated = startTime } if err := parseValidatorMetadata(metadataBytes, metadata); err != nil { return err } - staker, err := NewCurrentStaker(txID, stakerTx, metadata.PotentialReward) + staker, err := NewCurrentStaker( + txID, + stakerTx, + time.Unix(int64(metadata.StakerStartTime), 0), + metadata.PotentialReward, + ) if err != nil { return err } @@ -1538,20 +1566,32 @@ func (s *state) loadCurrentValidators() error { return err } + stakerTx, ok := tx.Unsigned.(txs.Staker) + if !ok { + return fmt.Errorf("expected tx type txs.Staker but got %T", tx.Unsigned) + } + + metadataBytes := delegatorIt.Value() metadata := &delegatorMetadata{ txID: txID, } - err = parseDelegatorMetadata(delegatorIt.Value(), metadata) + if scheduledStakerTx, ok := tx.Unsigned.(txs.ScheduledStaker); ok { + // Populate [StakerStartTime] using the tx as a default in the + // event it was added pre-durango and is not stored in the + // database. + metadata.StakerStartTime = uint64(scheduledStakerTx.StartTime().Unix()) + } + err = parseDelegatorMetadata(metadataBytes, metadata) if err != nil { return err } - stakerTx, ok := tx.Unsigned.(txs.Staker) - if !ok { - return fmt.Errorf("expected tx type txs.Staker but got %T", tx.Unsigned) - } - - staker, err := NewCurrentStaker(txID, stakerTx, metadata.PotentialReward) + staker, err := NewCurrentStaker( + txID, + stakerTx, + time.Unix(int64(metadata.StakerStartTime), 0), + metadata.PotentialReward, + ) if err != nil { return err } @@ -1595,7 +1635,7 @@ func (s *state) loadPendingValidators() error { return err } - stakerTx, ok := tx.Unsigned.(txs.Staker) + stakerTx, ok := tx.Unsigned.(txs.ScheduledStaker) if !ok { return fmt.Errorf("expected tx type txs.Staker but got %T", tx.Unsigned) } @@ -1630,7 +1670,7 @@ func (s *state) loadPendingValidators() error { return err } - stakerTx, ok := tx.Unsigned.(txs.Staker) + stakerTx, ok := tx.Unsigned.(txs.ScheduledStaker) if !ok { return fmt.Errorf("expected tx type txs.Staker but got %T", tx.Unsigned) } @@ -1695,11 +1735,16 @@ func (s *state) initValidatorSets() error { } func (s *state) write(updateValidators bool, height uint64) error { + codecVersion := CodecVersion1 + if !s.cfg.IsDurangoActivated(s.GetTimestamp()) { + codecVersion = CodecVersion0 + } + return utils.Err( s.writeBlocks(), - s.writeCurrentStakers(updateValidators, height), + s.writeCurrentStakers(updateValidators, height, codecVersion), s.writePendingStakers(), - s.WriteValidatorMetadata(s.currentValidatorList, s.currentSubnetValidatorList), // Must be called after writeCurrentStakers + s.WriteValidatorMetadata(s.currentValidatorList, s.currentSubnetValidatorList, codecVersion), // Must be called after writeCurrentStakers s.writeTXs(), s.writeRewardUTXOs(), s.writeUTXOs(), @@ -1925,7 +1970,7 @@ func (s *state) GetBlockIDAtHeight(height uint64) (ids.ID, error) { return blkID, nil } -func (s *state) writeCurrentStakers(updateValidators bool, height uint64) error { +func (s *state) writeCurrentStakers(updateValidators bool, height uint64, codecVersion uint16) error { heightBytes := database.PackUInt64(height) rawNestedPublicKeyDiffDB := prefixdb.New(heightBytes, s.nestedValidatorPublicKeyDiffsDB) nestedPKDiffDB := linkeddb.NewDefault(rawNestedPublicKeyDiffDB) @@ -1945,7 +1990,7 @@ func (s *state) writeCurrentStakers(updateValidators bool, height uint64) error Height: height, SubnetID: subnetID, } - prefixBytes, err := block.GenesisCodec.Marshal(block.Version, prefixStruct) + prefixBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, prefixStruct) if err != nil { return fmt.Errorf("failed to create prefix bytes: %w", err) } @@ -1984,17 +2029,19 @@ func (s *state) writeCurrentStakers(updateValidators bool, height uint64) error // // Invariant: It's impossible for a delegator to have been // rewarded in the same block that the validator was added. + startTime := uint64(staker.StartTime.Unix()) metadata := &validatorMetadata{ txID: staker.TxID, lastUpdated: staker.StartTime, UpDuration: 0, - LastUpdated: uint64(staker.StartTime.Unix()), + LastUpdated: startTime, + StakerStartTime: startTime, PotentialReward: staker.PotentialReward, PotentialDelegateeReward: 0, } - metadataBytes, err := metadataCodec.Marshal(v0, metadata) + metadataBytes, err := MetadataCodec.Marshal(codecVersion, metadata) if err != nil { return fmt.Errorf("failed to serialize current validator: %w", err) } @@ -2047,6 +2094,7 @@ func (s *state) writeCurrentStakers(updateValidators bool, height uint64) error delegatorDB, weightDiff, validatorDiff, + codecVersion, ) if err != nil { return err @@ -2066,7 +2114,7 @@ func (s *state) writeCurrentStakers(updateValidators bool, height uint64) error } // TODO: Remove this once we no longer support version rollbacks. - weightDiffBytes, err := block.GenesisCodec.Marshal(block.Version, weightDiff) + weightDiffBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, weightDiff) if err != nil { return fmt.Errorf("failed to serialize validator weight diff: %w", err) } @@ -2122,6 +2170,7 @@ func writeCurrentDelegatorDiff( currentDelegatorList linkeddb.LinkedDB, weightDiff *ValidatorWeightDiff, validatorDiff *diffValidator, + codecVersion uint16, ) error { addedDelegatorIterator := NewTreeIterator(validatorDiff.addedDelegators) defer addedDelegatorIterator.Release() @@ -2135,8 +2184,9 @@ func writeCurrentDelegatorDiff( metadata := &delegatorMetadata{ txID: staker.TxID, PotentialReward: staker.PotentialReward, + StakerStartTime: uint64(staker.StartTime.Unix()), } - if err := writeDelegatorMetadata(currentDelegatorList, metadata); err != nil { + if err := writeDelegatorMetadata(currentDelegatorList, metadata, codecVersion); err != nil { return fmt.Errorf("failed to write current delegator to list: %w", err) } } @@ -2225,7 +2275,7 @@ func (s *state) writeTXs() error { // Note that we're serializing a [txBytesAndStatus] here, not a // *txs.Tx, so we don't use [txs.Codec]. - txBytes, err := txs.GenesisCodec.Marshal(txs.Version, &stx) + txBytes, err := txs.GenesisCodec.Marshal(txs.CodecVersion, &stx) if err != nil { return fmt.Errorf("failed to serialize tx: %w", err) } @@ -2250,7 +2300,7 @@ func (s *state) writeRewardUTXOs() error { txDB := linkeddb.NewDefault(rawTxDB) for _, utxo := range utxos { - utxoBytes, err := txs.GenesisCodec.Marshal(txs.Version, utxo) + utxoBytes, err := txs.GenesisCodec.Marshal(txs.CodecVersion, utxo) if err != nil { return fmt.Errorf("failed to serialize reward UTXO: %w", err) } @@ -2298,7 +2348,7 @@ func (s *state) writeSubnetOwners() error { owner := owner delete(s.subnetOwners, subnetID) - ownerBytes, err := block.GenesisCodec.Marshal(block.Version, &owner) + ownerBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &owner) if err != nil { return fmt.Errorf("failed to marshal subnet owner: %w", err) } @@ -2379,7 +2429,7 @@ func (s *state) writeMetadata() error { } if s.indexedHeights != nil { - indexedHeightsBytes, err := block.GenesisCodec.Marshal(block.Version, s.indexedHeights) + indexedHeightsBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, s.indexedHeights) if err != nil { return err } diff --git a/vms/platformvm/state/state_test.go b/vms/platformvm/state/state_test.go index 3c36310c0576..c0fdb1e60269 100644 --- a/vms/platformvm/state/state_test.go +++ b/vms/platformvm/state/state_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state import ( "context" + "fmt" "math" "testing" "time" @@ -32,8 +33,11 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/genesis" "github.com/ava-labs/avalanchego/vms/platformvm/metrics" "github.com/ava-labs/avalanchego/vms/platformvm/reward" + "github.com/ava-labs/avalanchego/vms/platformvm/signer" + "github.com/ava-labs/avalanchego/vms/platformvm/status" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/vms/types" safemath "github.com/ava-labs/avalanchego/utils/math" ) @@ -49,23 +53,23 @@ func TestStateInitialization(t *testing.T) { require := require.New(t) s, db := newUninitializedState(require) - shouldInit, err := s.(*state).shouldInit() + shouldInit, err := s.shouldInit() require.NoError(err) require.True(shouldInit) - require.NoError(s.(*state).doneInit()) + require.NoError(s.doneInit()) require.NoError(s.Commit()) s = newStateFromDB(require, db) - shouldInit, err = s.(*state).shouldInit() + shouldInit, err = s.shouldInit() require.NoError(err) require.False(shouldInit) } func TestStateSyncGenesis(t *testing.T) { require := require.New(t) - state, _ := newInitializedState(require) + state := newInitializedState(require) staker, err := state.GetCurrentValidator(constants.PrimaryNetworkID, initialNodeID) require.NoError(err) @@ -88,8 +92,626 @@ func TestStateSyncGenesis(t *testing.T) { assertIteratorsEqual(t, EmptyIterator, delegatorIterator) } -func newInitializedState(require *require.Assertions) (State, database.Database) { - s, db := newUninitializedState(require) +// Whenever we store a staker, a whole bunch a data structures are updated +// This test is meant to capture which updates are carried out +func TestPersistStakers(t *testing.T) { + tests := map[string]struct { + // Insert or delete a staker to state and store it + storeStaker func(*require.Assertions, ids.ID /*=subnetID*/, *state) *Staker + + // Check that the staker is duly stored/removed in P-chain state + checkStakerInState func(*require.Assertions, *state, *Staker) + + // Check whether validators are duly reported in the validator set, + // with the right weight and showing the BLS key + checkValidatorsSet func(*require.Assertions, *state, *Staker) + + // Check that node duly track stakers uptimes + checkValidatorUptimes func(*require.Assertions, *state, *Staker) + + // Check whether weight/bls keys diffs are duly stored + checkDiffs func(*require.Assertions, *state, *Staker, uint64) + }{ + "add current validator": { + storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { + var ( + startTime = time.Now().Unix() + endTime = time.Now().Add(14 * 24 * time.Hour).Unix() + + validatorsData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: uint64(endTime), + Wght: 1234, + } + validatorReward uint64 = 5678 + ) + + utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) + addPermValTx := &txs.Tx{Unsigned: utx} + r.NoError(addPermValTx.Initialize(txs.Codec)) + + staker, err := NewCurrentStaker( + addPermValTx.ID(), + utx, + time.Unix(startTime, 0), + validatorReward, + ) + r.NoError(err) + + s.PutCurrentValidator(staker) + s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + return staker + }, + checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { + retrievedStaker, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) + r.NoError(err) + r.Equal(staker, retrievedStaker) + }, + checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { + valsMap := s.cfg.Validators.GetMap(staker.SubnetID) + r.Len(valsMap, 1) + valOut, found := valsMap[staker.NodeID] + r.True(found) + r.Equal(valOut, &validators.GetValidatorOutput{ + NodeID: staker.NodeID, + PublicKey: staker.PublicKey, + Weight: staker.Weight, + }) + }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + upDuration, lastUpdated, err := s.GetUptime(staker.NodeID, staker.SubnetID) + r.NoError(err) + r.Equal(upDuration, time.Duration(0)) + r.Equal(lastUpdated, staker.StartTime) + }, + checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { + weightDiffBytes, err := s.flatValidatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + r.NoError(err) + weightDiff, err := unmarshalWeightDiff(weightDiffBytes) + r.NoError(err) + r.Equal(weightDiff, &ValidatorWeightDiff{ + Decrease: false, + Amount: staker.Weight, + }) + + blsDiffBytes, err := s.flatValidatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + if staker.SubnetID == constants.PrimaryNetworkID { + r.NoError(err) + r.Nil(blsDiffBytes) + } else { + r.ErrorIs(err, database.ErrNotFound) + } + }, + }, + "add current delegator": { + storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { + // insert the delegator and its validator + var ( + valStartTime = time.Now().Truncate(time.Second).Unix() + delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() + delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() + valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() + + validatorsData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: uint64(valEndTime), + Wght: 1234, + } + validatorReward uint64 = 5678 + + delegatorData = txs.Validator{ + NodeID: validatorsData.NodeID, + End: uint64(delEndTime), + Wght: validatorsData.Wght / 2, + } + delegatorReward uint64 = 5432 + ) + + utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) + addPermValTx := &txs.Tx{Unsigned: utxVal} + r.NoError(addPermValTx.Initialize(txs.Codec)) + + val, err := NewCurrentStaker( + addPermValTx.ID(), + utxVal, + time.Unix(valStartTime, 0), + validatorReward, + ) + r.NoError(err) + + utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) + addPermDelTx := &txs.Tx{Unsigned: utxDel} + r.NoError(addPermDelTx.Initialize(txs.Codec)) + + del, err := NewCurrentStaker( + addPermDelTx.ID(), + utxDel, + time.Unix(delStartTime, 0), + delegatorReward, + ) + r.NoError(err) + + s.PutCurrentValidator(val) + s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + + s.PutCurrentDelegator(del) + s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + return del + }, + checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { + delIt, err := s.GetCurrentDelegatorIterator(staker.SubnetID, staker.NodeID) + r.NoError(err) + r.True(delIt.Next()) + retrievedDelegator := delIt.Value() + r.False(delIt.Next()) + delIt.Release() + r.Equal(staker, retrievedDelegator) + }, + checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { + val, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) + r.NoError(err) + + valsMap := s.cfg.Validators.GetMap(staker.SubnetID) + r.Len(valsMap, 1) + valOut, found := valsMap[staker.NodeID] + r.True(found) + r.Equal(valOut.NodeID, staker.NodeID) + r.Equal(valOut.Weight, val.Weight+staker.Weight) + }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + }, + checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { + // validator's weight must increase of delegator's weight amount + weightDiffBytes, err := s.flatValidatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + r.NoError(err) + weightDiff, err := unmarshalWeightDiff(weightDiffBytes) + r.NoError(err) + r.Equal(weightDiff, &ValidatorWeightDiff{ + Decrease: false, + Amount: staker.Weight, + }) + }, + }, + "add pending validator": { + storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { + var ( + startTime = time.Now().Unix() + endTime = time.Now().Add(14 * 24 * time.Hour).Unix() + + validatorsData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + Start: uint64(startTime), + End: uint64(endTime), + Wght: 1234, + } + ) + + utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) + addPermValTx := &txs.Tx{Unsigned: utx} + r.NoError(addPermValTx.Initialize(txs.Codec)) + + staker, err := NewPendingStaker( + addPermValTx.ID(), + utx, + ) + r.NoError(err) + + s.PutPendingValidator(staker) + s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + return staker + }, + checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { + retrievedStaker, err := s.GetPendingValidator(staker.SubnetID, staker.NodeID) + r.NoError(err) + r.Equal(staker, retrievedStaker) + }, + checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { + // pending validators are not showed in validators set + valsMap := s.cfg.Validators.GetMap(staker.SubnetID) + r.Len(valsMap, 0) + }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + // pending validators uptime is not tracked + _, _, err := s.GetUptime(staker.NodeID, staker.SubnetID) + r.ErrorIs(err, database.ErrNotFound) + }, + checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { + // pending validators weight diff and bls diffs are not stored + _, err := s.flatValidatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + r.ErrorIs(err, database.ErrNotFound) + + _, err = s.flatValidatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + r.ErrorIs(err, database.ErrNotFound) + }, + }, + "add pending delegator": { + storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { + // insert the delegator and its validator + var ( + valStartTime = time.Now().Truncate(time.Second).Unix() + delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() + delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() + valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() + + validatorsData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + Start: uint64(valStartTime), + End: uint64(valEndTime), + Wght: 1234, + } + + delegatorData = txs.Validator{ + NodeID: validatorsData.NodeID, + Start: uint64(delStartTime), + End: uint64(delEndTime), + Wght: validatorsData.Wght / 2, + } + ) + + utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) + addPermValTx := &txs.Tx{Unsigned: utxVal} + r.NoError(addPermValTx.Initialize(txs.Codec)) + + val, err := NewPendingStaker(addPermValTx.ID(), utxVal) + r.NoError(err) + + utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) + addPermDelTx := &txs.Tx{Unsigned: utxDel} + r.NoError(addPermDelTx.Initialize(txs.Codec)) + + del, err := NewPendingStaker(addPermDelTx.ID(), utxDel) + r.NoError(err) + + s.PutPendingValidator(val) + s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + + s.PutPendingDelegator(del) + s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + + return del + }, + checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { + delIt, err := s.GetPendingDelegatorIterator(staker.SubnetID, staker.NodeID) + r.NoError(err) + r.True(delIt.Next()) + retrievedDelegator := delIt.Value() + r.False(delIt.Next()) + delIt.Release() + r.Equal(staker, retrievedDelegator) + }, + checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { + valsMap := s.cfg.Validators.GetMap(staker.SubnetID) + r.Len(valsMap, 0) + }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + // nothing to do here + }, + checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { + // nothing to do here + }, + }, + "delete current validator": { + storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { + // add them remove the validator + var ( + startTime = time.Now().Unix() + endTime = time.Now().Add(14 * 24 * time.Hour).Unix() + + validatorsData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: uint64(endTime), + Wght: 1234, + } + validatorReward uint64 = 5678 + ) + + utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) + addPermValTx := &txs.Tx{Unsigned: utx} + r.NoError(addPermValTx.Initialize(txs.Codec)) + + staker, err := NewCurrentStaker( + addPermValTx.ID(), + utx, + time.Unix(startTime, 0), + validatorReward, + ) + r.NoError(err) + + s.PutCurrentValidator(staker) + s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + + s.DeleteCurrentValidator(staker) + r.NoError(s.Commit()) + return staker + }, + checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { + _, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) + r.ErrorIs(err, database.ErrNotFound) + }, + checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { + // deleted validators are not showed in the validators set anymore + valsMap := s.cfg.Validators.GetMap(staker.SubnetID) + r.Len(valsMap, 0) + }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + // uptimes of delete validators are dropped + _, _, err := s.GetUptime(staker.NodeID, staker.SubnetID) + r.ErrorIs(err, database.ErrNotFound) + }, + checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { + weightDiffBytes, err := s.flatValidatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + r.NoError(err) + weightDiff, err := unmarshalWeightDiff(weightDiffBytes) + r.NoError(err) + r.Equal(weightDiff, &ValidatorWeightDiff{ + Decrease: true, + Amount: staker.Weight, + }) + + blsDiffBytes, err := s.flatValidatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + if staker.SubnetID == constants.PrimaryNetworkID { + r.NoError(err) + r.Equal(bls.DeserializePublicKey(blsDiffBytes), staker.PublicKey) + } else { + r.ErrorIs(err, database.ErrNotFound) + } + }, + }, + "delete current delegator": { + storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { + // insert validator and delegator, then remove the delegator + var ( + valStartTime = time.Now().Truncate(time.Second).Unix() + delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() + delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() + valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() + + validatorsData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: uint64(valEndTime), + Wght: 1234, + } + validatorReward uint64 = 5678 + + delegatorData = txs.Validator{ + NodeID: validatorsData.NodeID, + End: uint64(delEndTime), + Wght: validatorsData.Wght / 2, + } + delegatorReward uint64 = 5432 + ) + + utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) + addPermValTx := &txs.Tx{Unsigned: utxVal} + r.NoError(addPermValTx.Initialize(txs.Codec)) + + val, err := NewCurrentStaker( + addPermValTx.ID(), + utxVal, + time.Unix(valStartTime, 0), + validatorReward, + ) + r.NoError(err) + + utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) + addPermDelTx := &txs.Tx{Unsigned: utxDel} + r.NoError(addPermDelTx.Initialize(txs.Codec)) + + del, err := NewCurrentStaker( + addPermDelTx.ID(), + utxDel, + time.Unix(delStartTime, 0), + delegatorReward, + ) + r.NoError(err) + + s.PutCurrentValidator(val) + s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + + s.PutCurrentDelegator(del) + s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + + s.DeleteCurrentDelegator(del) + r.NoError(s.Commit()) + + return del + }, + checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { + delIt, err := s.GetCurrentDelegatorIterator(staker.SubnetID, staker.NodeID) + r.NoError(err) + r.False(delIt.Next()) + delIt.Release() + }, + checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { + val, err := s.GetCurrentValidator(staker.SubnetID, staker.NodeID) + r.NoError(err) + + valsMap := s.cfg.Validators.GetMap(staker.SubnetID) + r.Len(valsMap, 1) + valOut, found := valsMap[staker.NodeID] + r.True(found) + r.Equal(valOut.NodeID, staker.NodeID) + r.Equal(valOut.Weight, val.Weight) + }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + // nothing to do here + }, + checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { + // validator's weight must decrease of delegator's weight amount + weightDiffBytes, err := s.flatValidatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + r.NoError(err) + weightDiff, err := unmarshalWeightDiff(weightDiffBytes) + r.NoError(err) + r.Equal(weightDiff, &ValidatorWeightDiff{ + Decrease: true, + Amount: staker.Weight, + }) + }, + }, + "delete pending validator": { + storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { + var ( + startTime = time.Now().Unix() + endTime = time.Now().Add(14 * 24 * time.Hour).Unix() + + validatorsData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + Start: uint64(startTime), + End: uint64(endTime), + Wght: 1234, + } + ) + + utx := createPermissionlessValidatorTx(r, subnetID, validatorsData) + addPermValTx := &txs.Tx{Unsigned: utx} + r.NoError(addPermValTx.Initialize(txs.Codec)) + + staker, err := NewPendingStaker( + addPermValTx.ID(), + utx, + ) + r.NoError(err) + + s.PutPendingValidator(staker) + s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + + s.DeletePendingValidator(staker) + r.NoError(s.Commit()) + + return staker + }, + checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { + _, err := s.GetPendingValidator(staker.SubnetID, staker.NodeID) + r.ErrorIs(err, database.ErrNotFound) + }, + checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { + valsMap := s.cfg.Validators.GetMap(staker.SubnetID) + r.Len(valsMap, 0) + }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + _, _, err := s.GetUptime(staker.NodeID, staker.SubnetID) + r.ErrorIs(err, database.ErrNotFound) + }, + checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { + _, err := s.flatValidatorWeightDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + r.ErrorIs(err, database.ErrNotFound) + + _, err = s.flatValidatorPublicKeyDiffsDB.Get(marshalDiffKey(staker.SubnetID, height, staker.NodeID)) + r.ErrorIs(err, database.ErrNotFound) + }, + }, + "delete pending delegator": { + storeStaker: func(r *require.Assertions, subnetID ids.ID, s *state) *Staker { + // insert validator and delegator the remove the validator + var ( + valStartTime = time.Now().Truncate(time.Second).Unix() + delStartTime = time.Unix(valStartTime, 0).Add(time.Hour).Unix() + delEndTime = time.Unix(delStartTime, 0).Add(30 * 24 * time.Hour).Unix() + valEndTime = time.Unix(valStartTime, 0).Add(365 * 24 * time.Hour).Unix() + + validatorsData = txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + Start: uint64(valStartTime), + End: uint64(valEndTime), + Wght: 1234, + } + + delegatorData = txs.Validator{ + NodeID: validatorsData.NodeID, + Start: uint64(delStartTime), + End: uint64(delEndTime), + Wght: validatorsData.Wght / 2, + } + ) + + utxVal := createPermissionlessValidatorTx(r, subnetID, validatorsData) + addPermValTx := &txs.Tx{Unsigned: utxVal} + r.NoError(addPermValTx.Initialize(txs.Codec)) + + val, err := NewPendingStaker(addPermValTx.ID(), utxVal) + r.NoError(err) + + utxDel := createPermissionlessDelegatorTx(subnetID, delegatorData) + addPermDelTx := &txs.Tx{Unsigned: utxDel} + r.NoError(addPermDelTx.Initialize(txs.Codec)) + + del, err := NewPendingStaker(addPermDelTx.ID(), utxDel) + r.NoError(err) + + s.PutPendingValidator(val) + s.AddTx(addPermValTx, status.Committed) // this is currently needed to reload the staker + + s.PutPendingDelegator(del) + s.AddTx(addPermDelTx, status.Committed) // this is currently needed to reload the staker + r.NoError(s.Commit()) + + s.DeletePendingDelegator(del) + r.NoError(s.Commit()) + return del + }, + checkStakerInState: func(r *require.Assertions, s *state, staker *Staker) { + delIt, err := s.GetPendingDelegatorIterator(staker.SubnetID, staker.NodeID) + r.NoError(err) + r.False(delIt.Next()) + delIt.Release() + }, + checkValidatorsSet: func(r *require.Assertions, s *state, staker *Staker) { + valsMap := s.cfg.Validators.GetMap(staker.SubnetID) + r.Len(valsMap, 0) + }, + checkValidatorUptimes: func(r *require.Assertions, s *state, staker *Staker) { + }, + checkDiffs: func(r *require.Assertions, s *state, staker *Staker, height uint64) { + }, + }, + } + + subnetIDs := []ids.ID{constants.PrimaryNetworkID, ids.GenerateTestID()} + for _, subnetID := range subnetIDs { + for name, test := range tests { + t.Run(fmt.Sprintf("%s - subnetID %s", name, subnetID), func(t *testing.T) { + require := require.New(t) + + state, db := newUninitializedState(require) + + // create and store the staker + staker := test.storeStaker(require, subnetID, state) + + // check all relevant data are stored + test.checkStakerInState(require, state, staker) + test.checkValidatorsSet(require, state, staker) + test.checkValidatorUptimes(require, state, staker) + test.checkDiffs(require, state, staker, 0 /*height*/) + + // rebuild the state + rebuiltState := newStateFromDB(require, db) + + // load relevant quantities + require.NoError(rebuiltState.loadCurrentValidators()) + require.NoError(rebuiltState.loadPendingValidators()) + require.NoError(rebuiltState.initValidatorSets()) + + // check again that all relevant data are still available in rebuilt state + test.checkStakerInState(require, state, staker) + test.checkValidatorsSet(require, state, staker) + test.checkValidatorUptimes(require, state, staker) + test.checkDiffs(require, state, staker, 0 /*height*/) + }) + } + } +} + +func newInitializedState(require *require.Assertions) State { + s, _ := newUninitializedState(require) initialValidator := &txs.AddValidatorTx{ Validator: txs.Validator{ @@ -150,22 +772,24 @@ func newInitializedState(require *require.Assertions) (State, database.Database) genesisBlk, err := block.NewApricotCommitBlock(genesisBlkID, 0) require.NoError(err) - require.NoError(s.(*state).syncGenesis(genesisBlk, genesisState)) + require.NoError(s.syncGenesis(genesisBlk, genesisState)) - return s, db + return s } -func newUninitializedState(require *require.Assertions) (State, database.Database) { +func newUninitializedState(require *require.Assertions) (*state, database.Database) { db := memdb.New() return newStateFromDB(require, db), db } -func newStateFromDB(require *require.Assertions, db database.Database) State { +func newStateFromDB(require *require.Assertions, db database.Database) *state { execCfg, _ := config.GetExecutionConfig(nil) state, err := newState( db, metrics.Noop, - validators.NewManager(), + &config.Config{ + Validators: validators.NewManager(), + }, execCfg, &snow.Context{}, prometheus.NewRegistry(), @@ -181,6 +805,136 @@ func newStateFromDB(require *require.Assertions, db database.Database) State { return state } +func createPermissionlessValidatorTx(r *require.Assertions, subnetID ids.ID, validatorsData txs.Validator) *txs.AddPermissionlessValidatorTx { + var sig signer.Signer = &signer.Empty{} + if subnetID == constants.PrimaryNetworkID { + sk, err := bls.NewSecretKey() + r.NoError(err) + sig = signer.NewProofOfPossession(sk) + } + + return &txs.AddPermissionlessValidatorTx{ + BaseTx: txs.BaseTx{ + BaseTx: avax.BaseTx{ + NetworkID: constants.MainnetID, + BlockchainID: constants.PlatformChainID, + Outs: []*avax.TransferableOutput{}, + Ins: []*avax.TransferableInput{ + { + UTXOID: avax.UTXOID{ + TxID: ids.GenerateTestID(), + OutputIndex: 1, + }, + Asset: avax.Asset{ + ID: ids.GenerateTestID(), + }, + In: &secp256k1fx.TransferInput{ + Amt: 2 * units.KiloAvax, + Input: secp256k1fx.Input{ + SigIndices: []uint32{1}, + }, + }, + }, + }, + Memo: types.JSONByteSlice{}, + }, + }, + Validator: validatorsData, + Subnet: subnetID, + Signer: sig, + + StakeOuts: []*avax.TransferableOutput{ + { + Asset: avax.Asset{ + ID: ids.GenerateTestID(), + }, + Out: &secp256k1fx.TransferOutput{ + Amt: 2 * units.KiloAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + }, + }, + }, + ValidatorRewardsOwner: &secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + DelegatorRewardsOwner: &secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + DelegationShares: reward.PercentDenominator, + } +} + +func createPermissionlessDelegatorTx(subnetID ids.ID, delegatorData txs.Validator) *txs.AddPermissionlessDelegatorTx { + return &txs.AddPermissionlessDelegatorTx{ + BaseTx: txs.BaseTx{ + BaseTx: avax.BaseTx{ + NetworkID: constants.MainnetID, + BlockchainID: constants.PlatformChainID, + Outs: []*avax.TransferableOutput{}, + Ins: []*avax.TransferableInput{ + { + UTXOID: avax.UTXOID{ + TxID: ids.GenerateTestID(), + OutputIndex: 1, + }, + Asset: avax.Asset{ + ID: ids.GenerateTestID(), + }, + In: &secp256k1fx.TransferInput{ + Amt: 2 * units.KiloAvax, + Input: secp256k1fx.Input{ + SigIndices: []uint32{1}, + }, + }, + }, + }, + Memo: types.JSONByteSlice{}, + }, + }, + Validator: delegatorData, + Subnet: subnetID, + + StakeOuts: []*avax.TransferableOutput{ + { + Asset: avax.Asset{ + ID: ids.GenerateTestID(), + }, + Out: &secp256k1fx.TransferOutput{ + Amt: 2 * units.KiloAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + }, + }, + }, + DelegationRewardsOwner: &secp256k1fx.OutputOwners{ + Locktime: 0, + Threshold: 1, + Addrs: []ids.ShortID{ + ids.GenerateTestShortID(), + }, + }, + } +} + func TestValidatorWeightDiff(t *testing.T) { type test struct { name string @@ -326,7 +1080,7 @@ func TestValidatorWeightDiff(t *testing.T) { func TestStateAddRemoveValidator(t *testing.T) { require := require.New(t) - state, _ := newInitializedState(require) + state := newInitializedState(require) var ( numNodes = 3 @@ -579,23 +1333,25 @@ func TestParsedStateBlock(t *testing.T) { } { - blk, err := block.NewApricotProposalBlock(ids.GenerateTestID(), 1000, &txs.Tx{ + tx := &txs.Tx{ Unsigned: &txs.RewardValidatorTx{ TxID: ids.GenerateTestID(), }, - }) + } + require.NoError(tx.Initialize(txs.Codec)) + blk, err := block.NewApricotProposalBlock(ids.GenerateTestID(), 1000, tx) require.NoError(err) blks = append(blks, blk) } { - blk, err := block.NewApricotStandardBlock(ids.GenerateTestID(), 1000, []*txs.Tx{ - { - Unsigned: &txs.RewardValidatorTx{ - TxID: ids.GenerateTestID(), - }, + tx := &txs.Tx{ + Unsigned: &txs.RewardValidatorTx{ + TxID: ids.GenerateTestID(), }, - }) + } + require.NoError(tx.Initialize(txs.Codec)) + blk, err := block.NewApricotStandardBlock(ids.GenerateTestID(), 1000, []*txs.Tx{tx}) require.NoError(err) blks = append(blks, blk) } @@ -613,23 +1369,27 @@ func TestParsedStateBlock(t *testing.T) { } { - blk, err := block.NewBanffProposalBlock(time.Now(), ids.GenerateTestID(), 1000, &txs.Tx{ + tx := &txs.Tx{ Unsigned: &txs.RewardValidatorTx{ TxID: ids.GenerateTestID(), }, - }) + } + require.NoError(tx.Initialize(txs.Codec)) + + blk, err := block.NewBanffProposalBlock(time.Now(), ids.GenerateTestID(), 1000, tx, []*txs.Tx{}) require.NoError(err) blks = append(blks, blk) } { - blk, err := block.NewBanffStandardBlock(time.Now(), ids.GenerateTestID(), 1000, []*txs.Tx{ - { - Unsigned: &txs.RewardValidatorTx{ - TxID: ids.GenerateTestID(), - }, + tx := &txs.Tx{ + Unsigned: &txs.RewardValidatorTx{ + TxID: ids.GenerateTestID(), }, - }) + } + require.NoError(tx.Initialize(txs.Codec)) + + blk, err := block.NewBanffStandardBlock(time.Now(), ids.GenerateTestID(), 1000, []*txs.Tx{tx}) require.NoError(err) blks = append(blks, blk) } @@ -641,7 +1401,7 @@ func TestParsedStateBlock(t *testing.T) { Status: choices.Accepted, } - stBlkBytes, err := block.GenesisCodec.Marshal(block.Version, &stBlk) + stBlkBytes, err := block.GenesisCodec.Marshal(block.CodecVersion, &stBlk) require.NoError(err) gotBlk, _, isStateBlk, err := parseStoredBlock(stBlkBytes) @@ -659,7 +1419,7 @@ func TestParsedStateBlock(t *testing.T) { func TestStateSubnetOwner(t *testing.T) { require := require.New(t) - state, _ := newInitializedState(require) + state := newInitializedState(require) ctrl := gomock.NewController(t) var ( diff --git a/vms/platformvm/state/tree_iterator.go b/vms/platformvm/state/tree_iterator.go index a71b35e21346..920bc1377902 100644 --- a/vms/platformvm/state/tree_iterator.go +++ b/vms/platformvm/state/tree_iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/tree_iterator_test.go b/vms/platformvm/state/tree_iterator_test.go index 57fa5727a4f4..7047d350bae1 100644 --- a/vms/platformvm/state/tree_iterator_test.go +++ b/vms/platformvm/state/tree_iterator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/state/versions.go b/vms/platformvm/state/versions.go index da84182bb683..6afb0fe8e5f2 100644 --- a/vms/platformvm/state/versions.go +++ b/vms/platformvm/state/versions.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/platformvm/status/blockchain_status.go b/vms/platformvm/status/blockchain_status.go index 1ab25e588b3d..5d427e5a9383 100644 --- a/vms/platformvm/status/blockchain_status.go +++ b/vms/platformvm/status/blockchain_status.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package status diff --git a/vms/platformvm/status/blockchain_status_test.go b/vms/platformvm/status/blockchain_status_test.go index 97b96badcb93..d0710d2f2cb1 100644 --- a/vms/platformvm/status/blockchain_status_test.go +++ b/vms/platformvm/status/blockchain_status_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package status diff --git a/vms/platformvm/status/status.go b/vms/platformvm/status/status.go index a67fb6c38e81..2a674250d20e 100644 --- a/vms/platformvm/status/status.go +++ b/vms/platformvm/status/status.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package status diff --git a/vms/platformvm/status/status_test.go b/vms/platformvm/status/status_test.go index 59316f983722..cd6ed5f814f7 100644 --- a/vms/platformvm/status/status_test.go +++ b/vms/platformvm/status/status_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package status diff --git a/vms/platformvm/txs/add_delegator_test.go b/vms/platformvm/txs/add_delegator_test.go index cf053d45baba..ac3290fb2431 100644 --- a/vms/platformvm/txs/add_delegator_test.go +++ b/vms/platformvm/txs/add_delegator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -23,8 +23,7 @@ var preFundedKeys = secp256k1.TestKeys() func TestAddDelegatorTxSyntacticVerify(t *testing.T) { require := require.New(t) clk := mockable.Clock{} - ctx := snow.DefaultContextTest() - ctx.AVAXAssetID = ids.GenerateTestID() + ctx := snowtest.Context(t, snowtest.PChainID) signers := [][]*secp256k1.PrivateKey{preFundedKeys} var ( @@ -130,8 +129,7 @@ func TestAddDelegatorTxSyntacticVerify(t *testing.T) { func TestAddDelegatorTxSyntacticVerifyNotAVAX(t *testing.T) { require := require.New(t) clk := mockable.Clock{} - ctx := snow.DefaultContextTest() - ctx.AVAXAssetID = ids.GenerateTestID() + ctx := snowtest.Context(t, snowtest.PChainID) signers := [][]*secp256k1.PrivateKey{preFundedKeys} var ( diff --git a/vms/platformvm/txs/add_delegator_tx.go b/vms/platformvm/txs/add_delegator_tx.go index 4f6fbe395b02..3df97cf0af74 100644 --- a/vms/platformvm/txs/add_delegator_tx.go +++ b/vms/platformvm/txs/add_delegator_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -19,7 +19,8 @@ import ( ) var ( - _ DelegatorTx = (*AddDelegatorTx)(nil) + _ DelegatorTx = (*AddDelegatorTx)(nil) + _ ScheduledStaker = (*AddDelegatorTx)(nil) errDelegatorWeightMismatch = errors.New("delegator weight is not equal to total stake weight") errStakeMustBeAVAX = errors.New("stake must be AVAX") diff --git a/vms/platformvm/txs/add_permissionless_delegator_tx.go b/vms/platformvm/txs/add_permissionless_delegator_tx.go index 43db685d7629..9c29b97339d0 100644 --- a/vms/platformvm/txs/add_permissionless_delegator_tx.go +++ b/vms/platformvm/txs/add_permissionless_delegator_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -17,7 +17,10 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) -var _ DelegatorTx = (*AddPermissionlessDelegatorTx)(nil) +var ( + _ DelegatorTx = (*AddPermissionlessDelegatorTx)(nil) + _ ScheduledStaker = (*AddPermissionlessDelegatorTx)(nil) +) // AddPermissionlessDelegatorTx is an unsigned addPermissionlessDelegatorTx type AddPermissionlessDelegatorTx struct { diff --git a/vms/platformvm/txs/add_permissionless_delegator_tx_test.go b/vms/platformvm/txs/add_permissionless_delegator_tx_test.go index c70bf720bfe3..1099e910f09b 100644 --- a/vms/platformvm/txs/add_permissionless_delegator_tx_test.go +++ b/vms/platformvm/txs/add_permissionless_delegator_tx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -216,7 +216,7 @@ func TestAddPermissionlessPrimaryDelegatorSerialization(t *testing.T) { 0x44, 0x55, 0x66, 0x77, } var unsignedSimpleAddPrimaryTx UnsignedTx = simpleAddPrimaryTx - unsignedSimpleAddPrimaryTxBytes, err := Codec.Marshal(Version, &unsignedSimpleAddPrimaryTx) + unsignedSimpleAddPrimaryTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleAddPrimaryTx) require.NoError(err) require.Equal(expectedUnsignedSimpleAddPrimaryTxBytes, unsignedSimpleAddPrimaryTxBytes) @@ -599,7 +599,7 @@ func TestAddPermissionlessPrimaryDelegatorSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, } var unsignedComplexAddPrimaryTx UnsignedTx = complexAddPrimaryTx - unsignedComplexAddPrimaryTxBytes, err := Codec.Marshal(Version, &unsignedComplexAddPrimaryTx) + unsignedComplexAddPrimaryTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexAddPrimaryTx) require.NoError(err) require.Equal(expectedUnsignedComplexAddPrimaryTxBytes, unsignedComplexAddPrimaryTxBytes) @@ -972,7 +972,7 @@ func TestAddPermissionlessSubnetDelegatorSerialization(t *testing.T) { 0x44, 0x55, 0x66, 0x77, } var unsignedSimpleAddSubnetTx UnsignedTx = simpleAddSubnetTx - unsignedSimpleAddSubnetTxBytes, err := Codec.Marshal(Version, &unsignedSimpleAddSubnetTx) + unsignedSimpleAddSubnetTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleAddSubnetTx) require.NoError(err) require.Equal(expectedUnsignedSimpleAddSubnetTxBytes, unsignedSimpleAddSubnetTxBytes) @@ -1355,7 +1355,7 @@ func TestAddPermissionlessSubnetDelegatorSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, } var unsignedComplexAddSubnetTx UnsignedTx = complexAddSubnetTx - unsignedComplexAddSubnetTxBytes, err := Codec.Marshal(Version, &unsignedComplexAddSubnetTx) + unsignedComplexAddSubnetTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexAddSubnetTx) require.NoError(err) require.Equal(expectedUnsignedComplexAddSubnetTxBytes, unsignedComplexAddSubnetTxBytes) diff --git a/vms/platformvm/txs/add_permissionless_validator_tx.go b/vms/platformvm/txs/add_permissionless_validator_tx.go index 8f313ae000b9..0f655c8daea4 100644 --- a/vms/platformvm/txs/add_permissionless_validator_tx.go +++ b/vms/platformvm/txs/add_permissionless_validator_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -21,7 +21,8 @@ import ( ) var ( - _ ValidatorTx = (*AddPermissionlessValidatorTx)(nil) + _ ValidatorTx = (*AddPermissionlessValidatorTx)(nil) + _ ScheduledStaker = (*AddPermissionlessDelegatorTx)(nil) errEmptyNodeID = errors.New("validator nodeID cannot be empty") errNoStake = errors.New("no stake") diff --git a/vms/platformvm/txs/add_permissionless_validator_tx_test.go b/vms/platformvm/txs/add_permissionless_validator_tx_test.go index 80e4d3b6ae93..58dd373010b7 100644 --- a/vms/platformvm/txs/add_permissionless_validator_tx_test.go +++ b/vms/platformvm/txs/add_permissionless_validator_tx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -267,7 +267,7 @@ func TestAddPermissionlessPrimaryValidator(t *testing.T) { 0x00, 0x0f, 0x42, 0x40, } var unsignedSimpleAddPrimaryTx UnsignedTx = simpleAddPrimaryTx - unsignedSimpleAddPrimaryTxBytes, err := Codec.Marshal(Version, &unsignedSimpleAddPrimaryTx) + unsignedSimpleAddPrimaryTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleAddPrimaryTx) require.NoError(err) require.Equal(expectedUnsignedSimpleAddPrimaryTxBytes, unsignedSimpleAddPrimaryTxBytes) @@ -695,7 +695,7 @@ func TestAddPermissionlessPrimaryValidator(t *testing.T) { 0x00, 0x0f, 0x42, 0x40, } var unsignedComplexAddPrimaryTx UnsignedTx = complexAddPrimaryTx - unsignedComplexAddPrimaryTxBytes, err := Codec.Marshal(Version, &unsignedComplexAddPrimaryTx) + unsignedComplexAddPrimaryTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexAddPrimaryTx) require.NoError(err) require.Equal(expectedUnsignedComplexAddPrimaryTxBytes, unsignedComplexAddPrimaryTxBytes) } @@ -954,7 +954,7 @@ func TestAddPermissionlessSubnetValidator(t *testing.T) { 0x00, 0x0f, 0x42, 0x40, } var unsignedSimpleAddSubnetTx UnsignedTx = simpleAddSubnetTx - unsignedSimpleAddSubnetTxBytes, err := Codec.Marshal(Version, &unsignedSimpleAddSubnetTx) + unsignedSimpleAddSubnetTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleAddSubnetTx) require.NoError(err) require.Equal(expectedUnsignedSimpleAddSubnetTxBytes, unsignedSimpleAddSubnetTxBytes) @@ -1362,7 +1362,7 @@ func TestAddPermissionlessSubnetValidator(t *testing.T) { 0x00, 0x0f, 0x42, 0x40, } var unsignedComplexAddSubnetTx UnsignedTx = complexAddSubnetTx - unsignedComplexAddSubnetTxBytes, err := Codec.Marshal(Version, &unsignedComplexAddSubnetTx) + unsignedComplexAddSubnetTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexAddSubnetTx) require.NoError(err) require.Equal(expectedUnsignedComplexAddSubnetTxBytes, unsignedComplexAddSubnetTxBytes) } diff --git a/vms/platformvm/txs/add_subnet_validator_test.go b/vms/platformvm/txs/add_subnet_validator_test.go index 8e07469ea2a0..8dc8d76782ac 100644 --- a/vms/platformvm/txs/add_subnet_validator_test.go +++ b/vms/platformvm/txs/add_subnet_validator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/timer/mockable" @@ -22,7 +22,7 @@ import ( func TestAddSubnetValidatorTxSyntacticVerify(t *testing.T) { require := require.New(t) clk := mockable.Clock{} - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.PChainID) signers := [][]*secp256k1.PrivateKey{preFundedKeys} var ( @@ -140,7 +140,7 @@ func TestAddSubnetValidatorTxSyntacticVerify(t *testing.T) { func TestAddSubnetValidatorMarshal(t *testing.T) { require := require.New(t) clk := mockable.Clock{} - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.PChainID) signers := [][]*secp256k1.PrivateKey{preFundedKeys} var ( @@ -201,7 +201,7 @@ func TestAddSubnetValidatorMarshal(t *testing.T) { require.NoError(err) require.NoError(stx.SyntacticVerify(ctx)) - txBytes, err := Codec.Marshal(Version, stx) + txBytes, err := Codec.Marshal(CodecVersion, stx) require.NoError(err) parsedTx, err := Parse(Codec, txBytes) diff --git a/vms/platformvm/txs/add_subnet_validator_tx.go b/vms/platformvm/txs/add_subnet_validator_tx.go index 0ac3474e1bd6..b6ce0d0fe4da 100644 --- a/vms/platformvm/txs/add_subnet_validator_tx.go +++ b/vms/platformvm/txs/add_subnet_validator_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -14,7 +14,8 @@ import ( ) var ( - _ StakerTx = (*AddSubnetValidatorTx)(nil) + _ StakerTx = (*AddSubnetValidatorTx)(nil) + _ ScheduledStaker = (*AddSubnetValidatorTx)(nil) errAddPrimaryNetworkValidator = errors.New("can't add primary network validator with AddSubnetValidatorTx") ) diff --git a/vms/platformvm/txs/add_validator_test.go b/vms/platformvm/txs/add_validator_test.go index 1076b2da69b2..daf32f66746d 100644 --- a/vms/platformvm/txs/add_validator_test.go +++ b/vms/platformvm/txs/add_validator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -22,8 +22,7 @@ import ( func TestAddValidatorTxSyntacticVerify(t *testing.T) { require := require.New(t) clk := mockable.Clock{} - ctx := snow.DefaultContextTest() - ctx.AVAXAssetID = ids.GenerateTestID() + ctx := snowtest.Context(t, snowtest.PChainID) signers := [][]*secp256k1.PrivateKey{preFundedKeys} var ( @@ -146,8 +145,7 @@ func TestAddValidatorTxSyntacticVerify(t *testing.T) { func TestAddValidatorTxSyntacticVerifyNotAVAX(t *testing.T) { require := require.New(t) clk := mockable.Clock{} - ctx := snow.DefaultContextTest() - ctx.AVAXAssetID = ids.GenerateTestID() + ctx := snowtest.Context(t, snowtest.PChainID) signers := [][]*secp256k1.PrivateKey{preFundedKeys} var ( diff --git a/vms/platformvm/txs/add_validator_tx.go b/vms/platformvm/txs/add_validator_tx.go index be6a93c2e42b..b6ab65b56e8f 100644 --- a/vms/platformvm/txs/add_validator_tx.go +++ b/vms/platformvm/txs/add_validator_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -19,7 +19,8 @@ import ( ) var ( - _ ValidatorTx = (*AddValidatorTx)(nil) + _ ValidatorTx = (*AddValidatorTx)(nil) + _ ScheduledStaker = (*AddValidatorTx)(nil) errTooManyShares = fmt.Errorf("a staker can only require at most %d shares from delegators", reward.PercentDenominator) ) diff --git a/vms/platformvm/txs/advance_time_tx.go b/vms/platformvm/txs/advance_time_tx.go index fc889da9ef0b..80b277fcb7e5 100644 --- a/vms/platformvm/txs/advance_time_tx.go +++ b/vms/platformvm/txs/advance_time_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/base_tx.go b/vms/platformvm/txs/base_tx.go index 5ffb308fe425..8a0be1edd76c 100644 --- a/vms/platformvm/txs/base_tx.go +++ b/vms/platformvm/txs/base_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/base_tx_test.go b/vms/platformvm/txs/base_tx_test.go index c6cba1570312..14bfc7b2f236 100644 --- a/vms/platformvm/txs/base_tx_test.go +++ b/vms/platformvm/txs/base_tx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -118,7 +118,7 @@ func TestBaseTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, } var unsignedSimpleBaseTx UnsignedTx = simpleBaseTx - unsignedSimpleBaseTxBytes, err := Codec.Marshal(Version, &unsignedSimpleBaseTx) + unsignedSimpleBaseTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleBaseTx) require.NoError(err) require.Equal(expectedUnsignedSimpleBaseTxBytes, unsignedSimpleBaseTxBytes) @@ -358,7 +358,7 @@ func TestBaseTxSerialization(t *testing.T) { 0x01, 0x23, 0x45, 0x21, } var unsignedComplexBaseTx UnsignedTx = complexBaseTx - unsignedComplexBaseTxBytes, err := Codec.Marshal(Version, &unsignedComplexBaseTx) + unsignedComplexBaseTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexBaseTx) require.NoError(err) require.Equal(expectedUnsignedComplexBaseTxBytes, unsignedComplexBaseTxBytes) diff --git a/vms/platformvm/txs/builder/builder.go b/vms/platformvm/txs/builder/builder.go index 6c796d085abb..3427bf2b1015 100644 --- a/vms/platformvm/txs/builder/builder.go +++ b/vms/platformvm/txs/builder/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package builder diff --git a/vms/platformvm/txs/builder/mock_builder.go b/vms/platformvm/txs/builder/mock_builder.go deleted file mode 100644 index 19f74a7bed2f..000000000000 --- a/vms/platformvm/txs/builder/mock_builder.go +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/vms/platformvm/txs/builder (interfaces: Builder) - -// Package builder is a generated GoMock package. -package builder - -import ( - reflect "reflect" - time "time" - - ids "github.com/ava-labs/avalanchego/ids" - secp256k1 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" - txs "github.com/ava-labs/avalanchego/vms/platformvm/txs" - secp256k1fx "github.com/ava-labs/avalanchego/vms/secp256k1fx" - gomock "go.uber.org/mock/gomock" -) - -// MockBuilder is a mock of Builder interface. -type MockBuilder struct { - ctrl *gomock.Controller - recorder *MockBuilderMockRecorder -} - -// MockBuilderMockRecorder is the mock recorder for MockBuilder. -type MockBuilderMockRecorder struct { - mock *MockBuilder -} - -// NewMockBuilder creates a new mock instance. -func NewMockBuilder(ctrl *gomock.Controller) *MockBuilder { - mock := &MockBuilder{ctrl: ctrl} - mock.recorder = &MockBuilderMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBuilder) EXPECT() *MockBuilderMockRecorder { - return m.recorder -} - -// NewAddDelegatorTx mocks base method. -func (m *MockBuilder) NewAddDelegatorTx(arg0, arg1, arg2 uint64, arg3 ids.NodeID, arg4 ids.ShortID, arg5 []*secp256k1.PrivateKey, arg6 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewAddDelegatorTx", arg0, arg1, arg2, arg3, arg4, arg5, arg6) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewAddDelegatorTx indicates an expected call of NewAddDelegatorTx. -func (mr *MockBuilderMockRecorder) NewAddDelegatorTx(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAddDelegatorTx", reflect.TypeOf((*MockBuilder)(nil).NewAddDelegatorTx), arg0, arg1, arg2, arg3, arg4, arg5, arg6) -} - -// NewAddSubnetValidatorTx mocks base method. -func (m *MockBuilder) NewAddSubnetValidatorTx(arg0, arg1, arg2 uint64, arg3 ids.NodeID, arg4 ids.ID, arg5 []*secp256k1.PrivateKey, arg6 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewAddSubnetValidatorTx", arg0, arg1, arg2, arg3, arg4, arg5, arg6) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewAddSubnetValidatorTx indicates an expected call of NewAddSubnetValidatorTx. -func (mr *MockBuilderMockRecorder) NewAddSubnetValidatorTx(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAddSubnetValidatorTx", reflect.TypeOf((*MockBuilder)(nil).NewAddSubnetValidatorTx), arg0, arg1, arg2, arg3, arg4, arg5, arg6) -} - -// NewAddValidatorTx mocks base method. -func (m *MockBuilder) NewAddValidatorTx(arg0, arg1, arg2 uint64, arg3 ids.NodeID, arg4 ids.ShortID, arg5 uint32, arg6 []*secp256k1.PrivateKey, arg7 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewAddValidatorTx", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewAddValidatorTx indicates an expected call of NewAddValidatorTx. -func (mr *MockBuilderMockRecorder) NewAddValidatorTx(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAddValidatorTx", reflect.TypeOf((*MockBuilder)(nil).NewAddValidatorTx), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) -} - -// NewAdvanceTimeTx mocks base method. -func (m *MockBuilder) NewAdvanceTimeTx(arg0 time.Time) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewAdvanceTimeTx", arg0) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewAdvanceTimeTx indicates an expected call of NewAdvanceTimeTx. -func (mr *MockBuilderMockRecorder) NewAdvanceTimeTx(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewAdvanceTimeTx", reflect.TypeOf((*MockBuilder)(nil).NewAdvanceTimeTx), arg0) -} - -// NewBaseTx mocks base method. -func (m *MockBuilder) NewBaseTx(arg0 uint64, arg1 secp256k1fx.OutputOwners, arg2 []*secp256k1.PrivateKey, arg3 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewBaseTx", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewBaseTx indicates an expected call of NewBaseTx. -func (mr *MockBuilderMockRecorder) NewBaseTx(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewBaseTx", reflect.TypeOf((*MockBuilder)(nil).NewBaseTx), arg0, arg1, arg2, arg3) -} - -// NewCreateChainTx mocks base method. -func (m *MockBuilder) NewCreateChainTx(arg0 ids.ID, arg1 []byte, arg2 ids.ID, arg3 []ids.ID, arg4 string, arg5 []*secp256k1.PrivateKey, arg6 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewCreateChainTx", arg0, arg1, arg2, arg3, arg4, arg5, arg6) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewCreateChainTx indicates an expected call of NewCreateChainTx. -func (mr *MockBuilderMockRecorder) NewCreateChainTx(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewCreateChainTx", reflect.TypeOf((*MockBuilder)(nil).NewCreateChainTx), arg0, arg1, arg2, arg3, arg4, arg5, arg6) -} - -// NewCreateSubnetTx mocks base method. -func (m *MockBuilder) NewCreateSubnetTx(arg0 uint32, arg1 []ids.ShortID, arg2 []*secp256k1.PrivateKey, arg3 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewCreateSubnetTx", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewCreateSubnetTx indicates an expected call of NewCreateSubnetTx. -func (mr *MockBuilderMockRecorder) NewCreateSubnetTx(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewCreateSubnetTx", reflect.TypeOf((*MockBuilder)(nil).NewCreateSubnetTx), arg0, arg1, arg2, arg3) -} - -// NewExportTx mocks base method. -func (m *MockBuilder) NewExportTx(arg0 uint64, arg1 ids.ID, arg2 ids.ShortID, arg3 []*secp256k1.PrivateKey, arg4 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewExportTx", arg0, arg1, arg2, arg3, arg4) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewExportTx indicates an expected call of NewExportTx. -func (mr *MockBuilderMockRecorder) NewExportTx(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewExportTx", reflect.TypeOf((*MockBuilder)(nil).NewExportTx), arg0, arg1, arg2, arg3, arg4) -} - -// NewImportTx mocks base method. -func (m *MockBuilder) NewImportTx(arg0 ids.ID, arg1 ids.ShortID, arg2 []*secp256k1.PrivateKey, arg3 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewImportTx", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewImportTx indicates an expected call of NewImportTx. -func (mr *MockBuilderMockRecorder) NewImportTx(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewImportTx", reflect.TypeOf((*MockBuilder)(nil).NewImportTx), arg0, arg1, arg2, arg3) -} - -// NewRemoveSubnetValidatorTx mocks base method. -func (m *MockBuilder) NewRemoveSubnetValidatorTx(arg0 ids.NodeID, arg1 ids.ID, arg2 []*secp256k1.PrivateKey, arg3 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewRemoveSubnetValidatorTx", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewRemoveSubnetValidatorTx indicates an expected call of NewRemoveSubnetValidatorTx. -func (mr *MockBuilderMockRecorder) NewRemoveSubnetValidatorTx(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewRemoveSubnetValidatorTx", reflect.TypeOf((*MockBuilder)(nil).NewRemoveSubnetValidatorTx), arg0, arg1, arg2, arg3) -} - -// NewRewardValidatorTx mocks base method. -func (m *MockBuilder) NewRewardValidatorTx(arg0 ids.ID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewRewardValidatorTx", arg0) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewRewardValidatorTx indicates an expected call of NewRewardValidatorTx. -func (mr *MockBuilderMockRecorder) NewRewardValidatorTx(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewRewardValidatorTx", reflect.TypeOf((*MockBuilder)(nil).NewRewardValidatorTx), arg0) -} - -// NewTransferSubnetOwnershipTx mocks base method. -func (m *MockBuilder) NewTransferSubnetOwnershipTx(arg0 ids.ID, arg1 uint32, arg2 []ids.ShortID, arg3 []*secp256k1.PrivateKey, arg4 ids.ShortID) (*txs.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewTransferSubnetOwnershipTx", arg0, arg1, arg2, arg3, arg4) - ret0, _ := ret[0].(*txs.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// NewTransferSubnetOwnershipTx indicates an expected call of NewTransferSubnetOwnershipTx. -func (mr *MockBuilderMockRecorder) NewTransferSubnetOwnershipTx(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewTransferSubnetOwnershipTx", reflect.TypeOf((*MockBuilder)(nil).NewTransferSubnetOwnershipTx), arg0, arg1, arg2, arg3, arg4) -} diff --git a/vms/platformvm/txs/codec.go b/vms/platformvm/txs/codec.go index d743376d1acb..36fe2e5a8eb0 100644 --- a/vms/platformvm/txs/codec.go +++ b/vms/platformvm/txs/codec.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs import ( "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" @@ -15,8 +16,7 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" ) -// Version is the current default codec version -const Version = 0 +const CodecVersion = 0 var ( Codec codec.Manager @@ -28,11 +28,13 @@ var ( GenesisCodec codec.Manager ) -func init() { - c := linearcodec.NewDefault() - Codec = codec.NewDefaultManager() - gc := linearcodec.NewCustomMaxLength(math.MaxInt32) - GenesisCodec = codec.NewManager(math.MaxInt32) +// TODO: Remove after v1.11.x has activated +// +// Invariant: InitCodec, Codec, and GenesisCodec must not be accessed +// concurrently +func InitCodec(durangoTime time.Time) error { + c := linearcodec.NewDefault(durangoTime) + gc := linearcodec.NewDefault(time.Time{}) errs := wrappers.Errs{} for _, c := range []linearcodec.Codec{c, gc} { @@ -47,12 +49,25 @@ func init() { errs.Add(RegisterDUnsignedTxsTypes(c)) } + + newCodec := codec.NewDefaultManager() + newGenesisCodec := codec.NewManager(math.MaxInt32) errs.Add( - Codec.RegisterCodec(Version, c), - GenesisCodec.RegisterCodec(Version, gc), + newCodec.RegisterCodec(CodecVersion, c), + newGenesisCodec.RegisterCodec(CodecVersion, gc), ) if errs.Errored() { - panic(errs.Err) + return errs.Err + } + + Codec = newCodec + GenesisCodec = newGenesisCodec + return nil +} + +func init() { + if err := InitCodec(time.Time{}); err != nil { + panic(err) } } diff --git a/vms/platformvm/txs/create_chain_test.go b/vms/platformvm/txs/create_chain_test.go index 7154072bd8a9..787aaa2a7ccb 100644 --- a/vms/platformvm/txs/create_chain_test.go +++ b/vms/platformvm/txs/create_chain_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -17,7 +17,7 @@ import ( ) func TestUnsignedCreateChainTxVerify(t *testing.T) { - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.PChainID) testSubnet1ID := ids.GenerateTestID() testSubnet1ControlKeys := []*secp256k1.PrivateKey{ preFundedKeys[0], diff --git a/vms/platformvm/txs/create_chain_tx.go b/vms/platformvm/txs/create_chain_tx.go index 37166e91ff98..84a9c72f43b3 100644 --- a/vms/platformvm/txs/create_chain_tx.go +++ b/vms/platformvm/txs/create_chain_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/create_subnet_tx.go b/vms/platformvm/txs/create_subnet_tx.go index 02f41faefe4c..e560c9dd5f94 100644 --- a/vms/platformvm/txs/create_subnet_tx.go +++ b/vms/platformvm/txs/create_subnet_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/executor/advance_time_test.go b/vms/platformvm/txs/executor/advance_time_test.go index 694d6b7ff7fa..bb66c7ef7f82 100644 --- a/vms/platformvm/txs/executor/advance_time_test.go +++ b/vms/platformvm/txs/executor/advance_time_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -24,7 +24,7 @@ import ( // for the primary network func TestAdvanceTimeTxUpdatePrimaryNetworkStakers(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -33,10 +33,16 @@ func TestAdvanceTimeTxUpdatePrimaryNetworkStakers(t *testing.T) { // Case: Timestamp is after next validator start time // Add a pending validator - pendingValidatorStartTime := defaultGenesisTime.Add(1 * time.Second) + pendingValidatorStartTime := defaultValidateStartTime.Add(1 * time.Second) pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMinStakingDuration) nodeID := ids.GenerateTestNodeID() - addPendingValidatorTx, err := addPendingValidator(env, pendingValidatorStartTime, pendingValidatorEndTime, nodeID, []*secp256k1.PrivateKey{preFundedKeys[0]}) + addPendingValidatorTx, err := addPendingValidator( + env, + pendingValidatorStartTime, + pendingValidatorEndTime, + nodeID, + []*secp256k1.PrivateKey{preFundedKeys[0]}, + ) require.NoError(err) tx, err := env.txBuilder.NewAdvanceTimeTx(pendingValidatorStartTime) @@ -83,12 +89,12 @@ func TestAdvanceTimeTxUpdatePrimaryNetworkStakers(t *testing.T) { // Ensure semantic verification fails when proposed timestamp is at or before current timestamp func TestAdvanceTimeTxTimestampTooEarly(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) defer func() { require.NoError(shutdownEnvironment(env)) }() - tx, err := env.txBuilder.NewAdvanceTimeTx(defaultGenesisTime) + tx, err := env.txBuilder.NewAdvanceTimeTx(env.state.GetTimestamp()) require.NoError(err) onCommitState, err := state.NewDiff(lastAcceptedID, env) @@ -110,12 +116,12 @@ func TestAdvanceTimeTxTimestampTooEarly(t *testing.T) { // Ensure semantic verification fails when proposed timestamp is after next validator set change time func TestAdvanceTimeTxTimestampTooLate(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() // Case: Timestamp is after next validator start time // Add a pending validator - pendingValidatorStartTime := defaultGenesisTime.Add(1 * time.Second) + pendingValidatorStartTime := defaultValidateStartTime.Add(1 * time.Second) pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMinStakingDuration) nodeID := ids.GenerateTestNodeID() _, err := addPendingValidator(env, pendingValidatorStartTime, pendingValidatorEndTime, nodeID, []*secp256k1.PrivateKey{preFundedKeys[0]}) @@ -144,7 +150,7 @@ func TestAdvanceTimeTxTimestampTooLate(t *testing.T) { require.NoError(shutdownEnvironment(env)) // Case: Timestamp is after next validator end time - env = newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env = newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -207,8 +213,8 @@ func TestAdvanceTimeTxUpdateStakers(t *testing.T) { // Staker5: |--------------------| staker1 := staker{ nodeID: ids.GenerateTestNodeID(), - startTime: defaultGenesisTime.Add(1 * time.Minute), - endTime: defaultGenesisTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute), + startTime: defaultValidateStartTime.Add(1 * time.Minute), + endTime: defaultValidateStartTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute), } staker2 := staker{ nodeID: ids.GenerateTestNodeID(), @@ -346,7 +352,7 @@ func TestAdvanceTimeTxUpdateStakers(t *testing.T) { for _, test := range tests { t.Run(test.description, func(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -451,7 +457,7 @@ func TestAdvanceTimeTxUpdateStakers(t *testing.T) { // is after the new timestamp func TestAdvanceTimeTxRemoveSubnetValidator(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -476,9 +482,11 @@ func TestAdvanceTimeTxRemoveSubnetValidator(t *testing.T) { ) require.NoError(err) + addSubnetValTx := tx.Unsigned.(*txs.AddSubnetValidatorTx) staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddSubnetValidatorTx), + addSubnetValTx, + addSubnetValTx.StartTime(), 0, ) require.NoError(err) @@ -553,7 +561,7 @@ func TestTrackedSubnet(t *testing.T) { for _, tracked := range []bool{true, false} { t.Run(fmt.Sprintf("tracked %t", tracked), func(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -568,8 +576,8 @@ func TestTrackedSubnet(t *testing.T) { // Add a subnet validator to the staker set subnetValidatorNodeID := genesisNodeIDs[0] - subnetVdr1StartTime := defaultGenesisTime.Add(1 * time.Minute) - subnetVdr1EndTime := defaultGenesisTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute) + subnetVdr1StartTime := defaultValidateStartTime.Add(1 * time.Minute) + subnetVdr1EndTime := defaultValidateStartTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute) tx, err := env.txBuilder.NewAddSubnetValidatorTx( 1, // Weight uint64(subnetVdr1StartTime.Unix()), // Start time @@ -623,7 +631,7 @@ func TestTrackedSubnet(t *testing.T) { func TestAdvanceTimeTxDelegatorStakerWeight(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -632,7 +640,7 @@ func TestAdvanceTimeTxDelegatorStakerWeight(t *testing.T) { // Case: Timestamp is after next validator start time // Add a pending validator - pendingValidatorStartTime := defaultGenesisTime.Add(1 * time.Second) + pendingValidatorStartTime := defaultValidateStartTime.Add(1 * time.Second) pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMaxStakingDuration) nodeID := ids.GenerateTestNodeID() _, err := addPendingValidator( @@ -730,7 +738,7 @@ func TestAdvanceTimeTxDelegatorStakerWeight(t *testing.T) { func TestAdvanceTimeTxDelegatorStakers(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -739,7 +747,7 @@ func TestAdvanceTimeTxDelegatorStakers(t *testing.T) { // Case: Timestamp is after next validator start time // Add a pending validator - pendingValidatorStartTime := defaultGenesisTime.Add(1 * time.Second) + pendingValidatorStartTime := defaultValidateStartTime.Add(1 * time.Second) pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMinStakingDuration) nodeID := ids.GenerateTestNodeID() _, err := addPendingValidator(env, pendingValidatorStartTime, pendingValidatorEndTime, nodeID, []*secp256k1.PrivateKey{preFundedKeys[0]}) @@ -827,15 +835,15 @@ func TestAdvanceTimeTxDelegatorStakers(t *testing.T) { // Test method InitiallyPrefersCommit func TestAdvanceTimeTxInitiallyPrefersCommit(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) }() - env.clk.Set(defaultGenesisTime) // VM's clock reads the genesis time + now := env.clk.Time() // Proposed advancing timestamp to 1 second after sync bound - tx, err := env.txBuilder.NewAdvanceTimeTx(defaultGenesisTime.Add(SyncBound)) + tx, err := env.txBuilder.NewAdvanceTimeTx(now.Add(SyncBound)) require.NoError(err) onCommitState, err := state.NewDiff(lastAcceptedID, env) @@ -857,16 +865,19 @@ func TestAdvanceTimeTxInitiallyPrefersCommit(t *testing.T) { func TestAdvanceTimeTxAfterBanff(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) }() env.clk.Set(defaultGenesisTime) // VM's clock reads the genesis time - env.config.BanffTime = defaultGenesisTime.Add(SyncBound) + upgradeTime := env.clk.Time().Add(SyncBound) + env.config.BanffTime = upgradeTime + env.config.CortinaTime = upgradeTime + env.config.DurangoTime = upgradeTime // Proposed advancing timestamp to the banff timestamp - tx, err := env.txBuilder.NewAdvanceTimeTx(defaultGenesisTime.Add(SyncBound)) + tx, err := env.txBuilder.NewAdvanceTimeTx(upgradeTime) require.NoError(err) onCommitState, err := state.NewDiff(lastAcceptedID, env) @@ -888,16 +899,17 @@ func TestAdvanceTimeTxAfterBanff(t *testing.T) { // Ensure marshaling/unmarshaling works func TestAdvanceTimeTxUnmarshal(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) }() - tx, err := env.txBuilder.NewAdvanceTimeTx(defaultGenesisTime) + chainTime := env.state.GetTimestamp() + tx, err := env.txBuilder.NewAdvanceTimeTx(chainTime.Add(time.Second)) require.NoError(err) - bytes, err := txs.Codec.Marshal(txs.Version, tx) + bytes, err := txs.Codec.Marshal(txs.CodecVersion, tx) require.NoError(err) var unmarshaledTx txs.Tx diff --git a/vms/platformvm/txs/executor/atomic_tx_executor.go b/vms/platformvm/txs/executor/atomic_tx_executor.go index 3b7dc60ec173..2a35cb45eeab 100644 --- a/vms/platformvm/txs/executor/atomic_tx_executor.go +++ b/vms/platformvm/txs/executor/atomic_tx_executor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/txs/executor/backend.go b/vms/platformvm/txs/executor/backend.go index f043521a56bc..a5e017090701 100644 --- a/vms/platformvm/txs/executor/backend.go +++ b/vms/platformvm/txs/executor/backend.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/txs/executor/create_chain_test.go b/vms/platformvm/txs/executor/create_chain_test.go index 72315d3c4dd5..3b0502616473 100644 --- a/vms/platformvm/txs/executor/create_chain_test.go +++ b/vms/platformvm/txs/executor/create_chain_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -25,7 +25,7 @@ import ( // Ensure Execute fails when there are not enough control sigs func TestCreateChainTxInsufficientControlSigs(t *testing.T) { require := require.New(t) - env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -60,7 +60,7 @@ func TestCreateChainTxInsufficientControlSigs(t *testing.T) { // Ensure Execute fails when an incorrect control signature is given func TestCreateChainTxWrongControlSig(t *testing.T) { require := require.New(t) - env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -102,7 +102,7 @@ func TestCreateChainTxWrongControlSig(t *testing.T) { // its validator set doesn't exist func TestCreateChainTxNoSuchSubnet(t *testing.T) { require := require.New(t) - env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -136,7 +136,7 @@ func TestCreateChainTxNoSuchSubnet(t *testing.T) { // Ensure valid tx passes semanticVerify func TestCreateChainTxValid(t *testing.T) { require := require.New(t) - env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -195,7 +195,7 @@ func TestCreateChainTxAP3FeeChange(t *testing.T) { t.Run(test.name, func(t *testing.T) { require := require.New(t) - env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.config.ApricotPhase3Time = ap3Time defer func() { diff --git a/vms/platformvm/txs/executor/create_subnet_test.go b/vms/platformvm/txs/executor/create_subnet_test.go index 182e28ae83c9..158eded74867 100644 --- a/vms/platformvm/txs/executor/create_subnet_test.go +++ b/vms/platformvm/txs/executor/create_subnet_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -49,7 +49,7 @@ func TestCreateSubnetTxAP3FeeChange(t *testing.T) { t.Run(test.name, func(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.config.ApricotPhase3Time = ap3Time env.ctx.Lock.Lock() defer func() { diff --git a/vms/platformvm/txs/executor/export_test.go b/vms/platformvm/txs/executor/export_test.go index 380b3dd5a489..1272d2815142 100644 --- a/vms/platformvm/txs/executor/export_test.go +++ b/vms/platformvm/txs/executor/export_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -15,7 +15,7 @@ import ( ) func TestNewExportTx(t *testing.T) { - env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(t, shutdownEnvironment(env)) @@ -33,13 +33,13 @@ func TestNewExportTx(t *testing.T) { tests := []test{ { description: "P->X export", - destinationChainID: xChainID, + destinationChainID: env.ctx.XChainID, sourceKeys: []*secp256k1.PrivateKey{sourceKey}, timestamp: defaultValidateStartTime, }, { description: "P->C export", - destinationChainID: cChainID, + destinationChainID: env.ctx.CChainID, sourceKeys: []*secp256k1.PrivateKey{sourceKey}, timestamp: env.config.ApricotPhase5Time, }, diff --git a/vms/platformvm/txs/executor/helpers_test.go b/vms/platformvm/txs/executor/helpers_test.go index df3150e04bdd..1ea645efcfb8 100644 --- a/vms/platformvm/txs/executor/helpers_test.go +++ b/vms/platformvm/txs/executor/helpers_test.go @@ -1,11 +1,9 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor import ( - "context" - "errors" "fmt" "math" "testing" @@ -21,10 +19,10 @@ import ( "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/memdb" - "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/database/versiondb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils" @@ -64,10 +62,7 @@ var ( defaultMinValidatorStake = 5 * units.MilliAvax defaultBalance = 100 * defaultMinValidatorStake preFundedKeys = secp256k1.TestKeys() - avaxAssetID = ids.ID{'y', 'e', 'e', 't'} defaultTxFee = uint64(100) - xChainID = ids.Empty.Prefix(0) - cChainID = ids.Empty.Prefix(1) lastAcceptedID = ids.GenerateTestID() testSubnet1 *txs.Tx @@ -75,8 +70,6 @@ var ( // Node IDs of genesis validators. Initialized in init function genesisNodeIDs []ids.NodeID - - errMissing = errors.New("missing") ) func init() { @@ -119,20 +112,25 @@ func (e *environment) SetState(blkID ids.ID, chainState state.Chain) { e.states[blkID] = chainState } -func newEnvironment(t *testing.T, postBanff, postCortina bool) *environment { +func newEnvironment(t *testing.T, postBanff, postCortina, postDurango bool) *environment { var isBootstrapped utils.Atomic[bool] isBootstrapped.Set(true) - config := defaultConfig(postBanff, postCortina) - clk := defaultClock(postBanff || postCortina) + config := defaultConfig(postBanff, postCortina, postDurango) + clk := defaultClock(postBanff || postCortina || postDurango) baseDB := versiondb.New(memdb.New()) - ctx, msm := defaultCtx(baseDB) + ctx := snowtest.Context(t, snowtest.PChainID) + m := atomic.NewMemory(baseDB) + msm := &mutableSharedMemory{ + SharedMemory: m.NewSharedMemory(ctx.ChainID), + } + ctx.SharedMemory = msm fx := defaultFx(clk, ctx.Log, isBootstrapped.Get()) rewards := reward.NewCalculator(config.RewardConfig) - baseState := defaultState(config.Validators, ctx, baseDB, rewards) + baseState := defaultState(config, ctx, baseDB, rewards) atomicUTXOs := avax.NewAtomicUTXOManager(ctx.SharedMemory, txs.Codec) uptimes := uptime.NewManager(baseState, clk) @@ -140,7 +138,7 @@ func newEnvironment(t *testing.T, postBanff, postCortina bool) *environment { txBuilder := builder.New( ctx, - &config, + config, clk, fx, baseState, @@ -149,7 +147,7 @@ func newEnvironment(t *testing.T, postBanff, postCortina bool) *environment { ) backend := Backend{ - Config: &config, + Config: config, Ctx: ctx, Clk: clk, Bootstrapped: &isBootstrapped, @@ -161,7 +159,7 @@ func newEnvironment(t *testing.T, postBanff, postCortina bool) *environment { env := &environment{ isBootstrapped: &isBootstrapped, - config: &config, + config: config, clk: clk, baseDB: baseDB, ctx: ctx, @@ -215,10 +213,11 @@ func addSubnet( stateDiff.AddTx(testSubnet1, status.Committed) require.NoError(stateDiff.Apply(env.state)) + require.NoError(env.state.Commit()) } func defaultState( - validators validators.Manager, + cfg *config.Config, ctx *snow.Context, db database.Database, rewards reward.Calculator, @@ -229,7 +228,7 @@ func defaultState( db, genesisBytes, prometheus.NewRegistry(), - validators, + cfg, execCfg, ctx, metrics.Noop, @@ -248,39 +247,7 @@ func defaultState( return state } -func defaultCtx(db database.Database) (*snow.Context, *mutableSharedMemory) { - ctx := snow.DefaultContextTest() - ctx.NetworkID = 10 - ctx.XChainID = xChainID - ctx.CChainID = cChainID - ctx.AVAXAssetID = avaxAssetID - - atomicDB := prefixdb.New([]byte{1}, db) - m := atomic.NewMemory(atomicDB) - - msm := &mutableSharedMemory{ - SharedMemory: m.NewSharedMemory(ctx.ChainID), - } - ctx.SharedMemory = msm - - ctx.ValidatorState = &validators.TestState{ - GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { - subnetID, ok := map[ids.ID]ids.ID{ - constants.PlatformChainID: constants.PrimaryNetworkID, - xChainID: constants.PrimaryNetworkID, - cChainID: constants.PrimaryNetworkID, - }[chainID] - if !ok { - return ids.Empty, errMissing - } - return subnetID, nil - }, - } - - return ctx, msm -} - -func defaultConfig(postBanff, postCortina bool) config.Config { +func defaultConfig(postBanff, postCortina, postDurango bool) *config.Config { banffTime := mockable.MaxTime if postBanff { banffTime = defaultValidateEndTime.Add(-2 * time.Second) @@ -289,8 +256,12 @@ func defaultConfig(postBanff, postCortina bool) config.Config { if postCortina { cortinaTime = defaultValidateStartTime.Add(-2 * time.Second) } + durangoTime := mockable.MaxTime + if postDurango { + durangoTime = defaultValidateStartTime.Add(-2 * time.Second) + } - return config.Config{ + return &config.Config{ Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), Validators: validators.NewManager(), @@ -312,6 +283,7 @@ func defaultConfig(postBanff, postCortina bool) config.Config { ApricotPhase5Time: defaultValidateEndTime, BanffTime: banffTime, CortinaTime: cortinaTime, + DurangoTime: durangoTime, } } @@ -346,7 +318,7 @@ func (fvi *fxVMInt) Logger() logging.Logger { func defaultFx(clk *mockable.Clock, log logging.Logger, isBootstrapped bool) fx.Fx { fxVMInt := &fxVMInt{ - registry: linearcodec.NewDefault(), + registry: linearcodec.NewDefault(time.Time{}), clk: clk, log: log, } diff --git a/vms/platformvm/txs/executor/import_test.go b/vms/platformvm/txs/executor/import_test.go index 3d78429cf906..5d281da57185 100644 --- a/vms/platformvm/txs/executor/import_test.go +++ b/vms/platformvm/txs/executor/import_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -22,7 +22,7 @@ import ( ) func TestNewImportTx(t *testing.T) { - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) defer func() { require.NoError(t, shutdownEnvironment(env)) }() @@ -67,7 +67,7 @@ func TestNewImportTx(t *testing.T) { }, }, } - utxoBytes, err := txs.Codec.Marshal(txs.Version, utxo) + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) require.NoError(t, err) inputID := utxo.InputID() @@ -118,9 +118,9 @@ func TestNewImportTx(t *testing.T) { }, { description: "attempting to import from C-chain", - sourceChainID: cChainID, + sourceChainID: env.ctx.CChainID, sharedMemory: fundedSharedMemory( - cChainID, + env.ctx.CChainID, map[ids.ID]uint64{ env.ctx.AVAXAssetID: env.config.TxFee, }, diff --git a/vms/platformvm/txs/executor/proposal_tx_executor.go b/vms/platformvm/txs/executor/proposal_tx_executor.go index bd329b3f2576..c3893be429d3 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/txs/executor/proposal_tx_executor_test.go b/vms/platformvm/txs/executor/proposal_tx_executor_test.go index bc95f3ed39b2..930c0d9c253c 100644 --- a/vms/platformvm/txs/executor/proposal_tx_executor_test.go +++ b/vms/platformvm/txs/executor/proposal_tx_executor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -46,9 +46,11 @@ func TestProposalTxExecuteAddDelegator(t *testing.T) { ) require.NoError(t, err) + addValTx := tx.Unsigned.(*txs.AddValidatorTx) staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(t, err) @@ -74,9 +76,11 @@ func TestProposalTxExecuteAddDelegator(t *testing.T) { ) require.NoError(t, err) + addValTx := tx.Unsigned.(*txs.AddValidatorTx) staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(t, err) @@ -87,7 +91,7 @@ func TestProposalTxExecuteAddDelegator(t *testing.T) { require.NoError(t, target.state.Commit()) } - dummyH := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + dummyH := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) currentTimestamp := dummyH.state.GetTimestamp() type test struct { @@ -241,7 +245,7 @@ func TestProposalTxExecuteAddDelegator(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { require := require.New(t) - freshTH := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + freshTH := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) freshTH.config.ApricotPhase3Time = tt.AP3Time defer func() { require.NoError(shutdownEnvironment(freshTH)) @@ -282,7 +286,7 @@ func TestProposalTxExecuteAddDelegator(t *testing.T) { func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -354,7 +358,7 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { // Add a validator to pending validator set of primary network // Starts validating primary network 10 seconds after genesis pendingDSValidatorID := ids.GenerateTestNodeID() - dsStartTime := defaultGenesisTime.Add(10 * time.Second) + dsStartTime := defaultValidateStartTime.Add(10 * time.Second) dsEndTime := dsStartTime.Add(5 * defaultMinStakingDuration) addDSTx, err := env.txBuilder.NewAddValidatorTx( @@ -398,9 +402,11 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { require.ErrorIs(err, ErrNotValidator) } + addValTx := addDSTx.Unsigned.(*txs.AddValidatorTx) staker, err := state.NewCurrentStaker( addDSTx.ID(), - addDSTx.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) @@ -504,7 +510,7 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { // Case: Proposed validator start validating at/before current timestamp // First, advance the timestamp - newTimestamp := defaultGenesisTime.Add(2 * time.Second) + newTimestamp := defaultValidateStartTime.Add(2 * time.Second) env.state.SetTimestamp(newTimestamp) { @@ -536,7 +542,7 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { } // reset the timestamp - env.state.SetTimestamp(defaultGenesisTime) + env.state.SetTimestamp(defaultValidateStartTime) // Case: Proposed validator already validating the subnet // First, add validator as validator of subnet @@ -551,9 +557,11 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { ) require.NoError(err) + addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx) staker, err = state.NewCurrentStaker( subnetTx.ID(), - subnetTx.Unsigned.(*txs.AddSubnetValidatorTx), + addSubnetValTx, + addSubnetValTx.StartTime(), 0, ) require.NoError(err) @@ -599,9 +607,9 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { { // Case: Too few signatures tx, err := env.txBuilder.NewAddSubnetValidatorTx( - defaultWeight, // weight - uint64(defaultGenesisTime.Unix())+1, // start time - uint64(defaultGenesisTime.Add(defaultMinStakingDuration).Unix())+1, // end time + defaultWeight, // weight + uint64(defaultValidateStartTime.Unix())+1, // start time + uint64(defaultValidateStartTime.Add(defaultMinStakingDuration).Unix())+1, // end time nodeID, // node ID testSubnet1.ID(), // subnet ID []*secp256k1.PrivateKey{testSubnet1ControlKeys[0], testSubnet1ControlKeys[2]}, @@ -635,9 +643,9 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { { // Case: Control Signature from invalid key (keys[3] is not a control key) tx, err := env.txBuilder.NewAddSubnetValidatorTx( - defaultWeight, // weight - uint64(defaultGenesisTime.Unix())+1, // start time - uint64(defaultGenesisTime.Add(defaultMinStakingDuration).Unix())+1, // end time + defaultWeight, // weight + uint64(defaultValidateStartTime.Unix())+1, // start time + uint64(defaultValidateStartTime.Add(defaultMinStakingDuration).Unix())+1, // end time nodeID, // node ID testSubnet1.ID(), // subnet ID []*secp256k1.PrivateKey{testSubnet1ControlKeys[0], preFundedKeys[1]}, @@ -670,9 +678,9 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { // Case: Proposed validator in pending validator set for subnet // First, add validator to pending validator set of subnet tx, err := env.txBuilder.NewAddSubnetValidatorTx( - defaultWeight, // weight - uint64(defaultGenesisTime.Unix())+1, // start time - uint64(defaultGenesisTime.Add(defaultMinStakingDuration).Unix())+1, // end time + defaultWeight, // weight + uint64(defaultValidateStartTime.Unix())+1, // start time + uint64(defaultValidateStartTime.Add(defaultMinStakingDuration).Unix())+1, // end time nodeID, // node ID testSubnet1.ID(), // subnet ID []*secp256k1.PrivateKey{testSubnet1ControlKeys[0], testSubnet1ControlKeys[1]}, @@ -680,9 +688,11 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { ) require.NoError(err) + addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx) staker, err = state.NewCurrentStaker( subnetTx.ID(), - subnetTx.Unsigned.(*txs.AddSubnetValidatorTx), + addSubnetValTx, + addSubnetValTx.StartTime(), 0, ) require.NoError(err) @@ -711,19 +721,20 @@ func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { func TestProposalTxExecuteAddValidator(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) }() nodeID := ids.GenerateTestNodeID() + chainTime := env.state.GetTimestamp() { // Case: Validator's start time too early tx, err := env.txBuilder.NewAddValidatorTx( env.config.MinValidatorStake, - uint64(defaultValidateStartTime.Unix()), + uint64(chainTime.Unix()), uint64(defaultValidateEndTime.Unix()), nodeID, ids.ShortEmpty, @@ -813,7 +824,7 @@ func TestProposalTxExecuteAddValidator(t *testing.T) { { // Case: Validator in pending validator set of primary network - startTime := defaultGenesisTime.Add(1 * time.Second) + startTime := defaultValidateStartTime.Add(1 * time.Second) tx, err := env.txBuilder.NewAddValidatorTx( env.config.MinValidatorStake, // stake amount uint64(startTime.Unix()), // start time @@ -826,9 +837,12 @@ func TestProposalTxExecuteAddValidator(t *testing.T) { ) require.NoError(err) - staker, err := state.NewPendingStaker( + addValTx := tx.Unsigned.(*txs.AddValidatorTx) + staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), + 0, ) require.NoError(err) diff --git a/vms/platformvm/txs/executor/reward_validator_test.go b/vms/platformvm/txs/executor/reward_validator_test.go index 5871b9eef531..973dea4de9be 100644 --- a/vms/platformvm/txs/executor/reward_validator_test.go +++ b/vms/platformvm/txs/executor/reward_validator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -25,7 +25,7 @@ import ( func TestRewardValidatorTxExecuteOnCommit(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) defer func() { require.NoError(shutdownEnvironment(env)) }() @@ -128,7 +128,7 @@ func TestRewardValidatorTxExecuteOnCommit(t *testing.T) { func TestRewardValidatorTxExecuteOnAbort(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) defer func() { require.NoError(shutdownEnvironment(env)) }() @@ -225,7 +225,7 @@ func TestRewardValidatorTxExecuteOnAbort(t *testing.T) { func TestRewardDelegatorTxExecuteOnCommitPreDelegateeDeferral(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) defer func() { require.NoError(shutdownEnvironment(env)) }() @@ -264,16 +264,20 @@ func TestRewardDelegatorTxExecuteOnCommitPreDelegateeDeferral(t *testing.T) { ) require.NoError(err) + addValTx := vdrTx.Unsigned.(*txs.AddValidatorTx) vdrStaker, err := state.NewCurrentStaker( vdrTx.ID(), - vdrTx.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) + addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) delStaker, err := state.NewCurrentStaker( delTx.ID(), - delTx.Unsigned.(*txs.AddDelegatorTx), + addDelTx, + addDelTx.StartTime(), 1000000, ) require.NoError(err) @@ -345,7 +349,7 @@ func TestRewardDelegatorTxExecuteOnCommitPreDelegateeDeferral(t *testing.T) { func TestRewardDelegatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { require := require.New(t) - env := newEnvironment(t, true /*=postBanff*/, true /*=postCortina*/) + env := newEnvironment(t, true /*=postBanff*/, true /*=postCortina*/, false /*=postDurango*/) defer func() { require.NoError(shutdownEnvironment(env)) }() @@ -384,18 +388,22 @@ func TestRewardDelegatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { ) require.NoError(err) + addValTx := vdrTx.Unsigned.(*txs.AddValidatorTx) vdrRewardAmt := uint64(2000000) vdrStaker, err := state.NewCurrentStaker( vdrTx.ID(), - vdrTx.Unsigned.(*txs.AddValidatorTx), + addValTx, + time.Unix(int64(vdrStartTime), 0), vdrRewardAmt, ) require.NoError(err) + addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) delRewardAmt := uint64(1000000) delStaker, err := state.NewCurrentStaker( delTx.ID(), - delTx.Unsigned.(*txs.AddDelegatorTx), + addDelTx, + time.Unix(int64(delStartTime), 0), delRewardAmt, ) require.NoError(err) @@ -560,7 +568,7 @@ func TestRewardDelegatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { func TestRewardDelegatorTxAndValidatorTxExecuteOnCommitPostDelegateeDeferral(t *testing.T) { require := require.New(t) - env := newEnvironment(t, true /*=postBanff*/, true /*=postCortina*/) + env := newEnvironment(t, true /*=postBanff*/, true /*=postCortina*/, false /*=postDurango*/) defer func() { require.NoError(shutdownEnvironment(env)) }() @@ -599,18 +607,22 @@ func TestRewardDelegatorTxAndValidatorTxExecuteOnCommitPostDelegateeDeferral(t * ) require.NoError(err) + addValTx := vdrTx.Unsigned.(*txs.AddValidatorTx) vdrRewardAmt := uint64(2000000) vdrStaker, err := state.NewCurrentStaker( vdrTx.ID(), - vdrTx.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), vdrRewardAmt, ) require.NoError(err) + addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) delRewardAmt := uint64(1000000) delStaker, err := state.NewCurrentStaker( delTx.ID(), - delTx.Unsigned.(*txs.AddDelegatorTx), + addDelTx, + time.Unix(int64(delStartTime), 0), delRewardAmt, ) require.NoError(err) @@ -718,7 +730,7 @@ func TestRewardDelegatorTxAndValidatorTxExecuteOnCommitPostDelegateeDeferral(t * func TestRewardDelegatorTxExecuteOnAbort(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) defer func() { require.NoError(shutdownEnvironment(env)) }() @@ -759,16 +771,20 @@ func TestRewardDelegatorTxExecuteOnAbort(t *testing.T) { ) require.NoError(err) + addValTx := vdrTx.Unsigned.(*txs.AddValidatorTx) vdrStaker, err := state.NewCurrentStaker( vdrTx.ID(), - vdrTx.Unsigned.(*txs.AddValidatorTx), + addValTx, + addValTx.StartTime(), 0, ) require.NoError(err) + addDelTx := delTx.Unsigned.(*txs.AddDelegatorTx) delStaker, err := state.NewCurrentStaker( delTx.ID(), - delTx.Unsigned.(*txs.AddDelegatorTx), + addDelTx, + addDelTx.StartTime(), 1000000, ) require.NoError(err) diff --git a/vms/platformvm/txs/executor/staker_tx_verification.go b/vms/platformvm/txs/executor/staker_tx_verification.go index 7fae0e78a85b..a17bbbffc14d 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification.go +++ b/vms/platformvm/txs/executor/staker_tx_verification.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -7,6 +7,7 @@ import ( "errors" "fmt" "math" + "time" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" @@ -43,7 +44,11 @@ var ( // verifySubnetValidatorPrimaryNetworkRequirements verifies the primary // network requirements for [subnetValidator]. An error is returned if they // are not fulfilled. -func verifySubnetValidatorPrimaryNetworkRequirements(chainState state.Chain, subnetValidator txs.Validator) error { +func verifySubnetValidatorPrimaryNetworkRequirements( + isDurangoActive bool, + chainState state.Chain, + subnetValidator txs.Validator, +) error { primaryNetworkValidator, err := GetValidator(chainState, constants.PrimaryNetworkID, subnetValidator.NodeID) if err == database.ErrNotFound { return fmt.Errorf( @@ -62,8 +67,12 @@ func verifySubnetValidatorPrimaryNetworkRequirements(chainState state.Chain, sub // Ensure that the period this validator validates the specified subnet // is a subset of the time they validate the primary network. + startTime := chainState.GetTimestamp() + if !isDurangoActive { + startTime = subnetValidator.StartTime() + } if !txs.BoundedBy( - subnetValidator.StartTime(), + startTime, subnetValidator.EndTime(), primaryNetworkValidator.StartTime, primaryNetworkValidator.EndTime, @@ -91,8 +100,15 @@ func verifyAddValidatorTx( return nil, err } - duration := tx.Validator.Duration() - + var ( + currentTimestamp = chainState.GetTimestamp() + isDurangoActive = backend.Config.IsDurangoActivated(currentTimestamp) + startTime = currentTimestamp + ) + if !isDurangoActive { + startTime = tx.StartTime() + } + duration := tx.EndTime().Sub(startTime) switch { case tx.Validator.Wght < backend.Config.MinValidatorStake: // Ensure validator is staking at least the minimum amount @@ -123,16 +139,8 @@ func verifyAddValidatorTx( return outs, nil } - currentTimestamp := chainState.GetTimestamp() - // Ensure the proposed validator starts after the current time - startTime := tx.StartTime() - if !currentTimestamp.Before(startTime) { - return nil, fmt.Errorf( - "%w: %s >= %s", - ErrTimestampNotBeforeStartTime, - currentTimestamp, - startTime, - ) + if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil { + return nil, err } _, err := GetValidator(chainState, constants.PrimaryNetworkID, tx.Validator.NodeID) @@ -165,14 +173,9 @@ func verifyAddValidatorTx( return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) } - // Make sure the tx doesn't start too far in the future. This is done last - // to allow the verifier visitor to explicitly check for this error. - maxStartTime := currentTimestamp.Add(MaxFutureStartTime) - if startTime.After(maxStartTime) { - return nil, ErrFutureStakeTime - } - - return outs, nil + // verifyStakerStartsSoon is checked last to allow + // the verifier visitor to explicitly check for this error. + return outs, verifyStakerStartsSoon(isDurangoActive, currentTimestamp, startTime) } // verifyAddSubnetValidatorTx carries out the validation for an @@ -188,7 +191,16 @@ func verifyAddSubnetValidatorTx( return err } - duration := tx.Validator.Duration() + var ( + currentTimestamp = chainState.GetTimestamp() + isDurangoActive = backend.Config.IsDurangoActivated(currentTimestamp) + startTime = currentTimestamp + ) + if !isDurangoActive { + startTime = tx.StartTime() + } + duration := tx.EndTime().Sub(startTime) + switch { case duration < backend.Config.MinStakeDuration: // Ensure staking length is not too short @@ -203,16 +215,8 @@ func verifyAddSubnetValidatorTx( return nil } - currentTimestamp := chainState.GetTimestamp() - // Ensure the proposed validator starts after the current timestamp - validatorStartTime := tx.StartTime() - if !currentTimestamp.Before(validatorStartTime) { - return fmt.Errorf( - "%w: %s >= %s", - ErrTimestampNotBeforeStartTime, - currentTimestamp, - validatorStartTime, - ) + if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil { + return err } _, err := GetValidator(chainState, tx.SubnetValidator.Subnet, tx.Validator.NodeID) @@ -232,7 +236,7 @@ func verifyAddSubnetValidatorTx( ) } - if err := verifySubnetValidatorPrimaryNetworkRequirements(chainState, tx.Validator); err != nil { + if err := verifySubnetValidatorPrimaryNetworkRequirements(isDurangoActive, chainState, tx.Validator); err != nil { return err } @@ -255,14 +259,9 @@ func verifyAddSubnetValidatorTx( return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) } - // Make sure the tx doesn't start too far in the future. This is done last - // to allow the verifier visitor to explicitly check for this error. - maxStartTime := currentTimestamp.Add(MaxFutureStartTime) - if validatorStartTime.After(maxStartTime) { - return ErrFutureStakeTime - } - - return nil + // verifyStakerStartsSoon is checked last to allow + // the verifier visitor to explicitly check for this error. + return verifyStakerStartsSoon(isDurangoActive, currentTimestamp, startTime) } // Returns the representation of [tx.NodeID] validating [tx.Subnet]. @@ -349,7 +348,17 @@ func verifyAddDelegatorTx( return nil, err } - duration := tx.Validator.Duration() + var ( + currentTimestamp = chainState.GetTimestamp() + isDurangoActive = backend.Config.IsDurangoActivated(currentTimestamp) + endTime = tx.EndTime() + startTime = currentTimestamp + ) + if !isDurangoActive { + startTime = tx.StartTime() + } + duration := endTime.Sub(startTime) + switch { case duration < backend.Config.MinStakeDuration: // Ensure staking length is not too short @@ -372,16 +381,8 @@ func verifyAddDelegatorTx( return outs, nil } - currentTimestamp := chainState.GetTimestamp() - // Ensure the proposed validator starts after the current timestamp - validatorStartTime := tx.StartTime() - if !currentTimestamp.Before(validatorStartTime) { - return nil, fmt.Errorf( - "%w: %s >= %s", - ErrTimestampNotBeforeStartTime, - currentTimestamp, - validatorStartTime, - ) + if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil { + return nil, err } primaryNetworkValidator, err := GetValidator(chainState, constants.PrimaryNetworkID, tx.Validator.NodeID) @@ -402,21 +403,22 @@ func verifyAddDelegatorTx( maximumWeight = safemath.Min(maximumWeight, backend.Config.MaxValidatorStake) } - txID := sTx.ID() - newStaker, err := state.NewPendingStaker(txID, tx) - if err != nil { - return nil, err - } - if !txs.BoundedBy( - newStaker.StartTime, - newStaker.EndTime, + startTime, + endTime, primaryNetworkValidator.StartTime, primaryNetworkValidator.EndTime, ) { return nil, ErrPeriodMismatch } - overDelegated, err := overDelegated(chainState, primaryNetworkValidator, maximumWeight, newStaker) + overDelegated, err := overDelegated( + chainState, + primaryNetworkValidator, + maximumWeight, + tx.Validator.Wght, + startTime, + endTime, + ) if err != nil { return nil, err } @@ -438,14 +440,9 @@ func verifyAddDelegatorTx( return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) } - // Make sure the tx doesn't start too far in the future. This is done last - // to allow the verifier visitor to explicitly check for this error. - maxStartTime := currentTimestamp.Add(MaxFutureStartTime) - if validatorStartTime.After(maxStartTime) { - return nil, ErrFutureStakeTime - } - - return outs, nil + // verifyStakerStartsSoon is checked last to allow + // the verifier visitor to explicitly check for this error. + return outs, verifyStakerStartsSoon(isDurangoActive, currentTimestamp, startTime) } // verifyAddPermissionlessValidatorTx carries out the validation for an @@ -465,16 +462,18 @@ func verifyAddPermissionlessValidatorTx( return nil } - currentTimestamp := chainState.GetTimestamp() - // Ensure the proposed validator starts after the current time - startTime := tx.StartTime() - if !currentTimestamp.Before(startTime) { - return fmt.Errorf( - "%w: %s >= %s", - ErrTimestampNotBeforeStartTime, - currentTimestamp, - startTime, - ) + var ( + currentTimestamp = chainState.GetTimestamp() + isDurangoActive = backend.Config.IsDurangoActivated(currentTimestamp) + startTime = currentTimestamp + ) + if !isDurangoActive { + startTime = tx.StartTime() + } + duration := tx.EndTime().Sub(startTime) + + if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil { + return err } validatorRules, err := getValidatorRules(backend, chainState, tx.Subnet) @@ -482,7 +481,6 @@ func verifyAddPermissionlessValidatorTx( return err } - duration := tx.Validator.Duration() stakedAssetID := tx.StakeOuts[0].AssetID() switch { case tx.Validator.Wght < validatorRules.minValidatorStake: @@ -535,7 +533,7 @@ func verifyAddPermissionlessValidatorTx( var txFee uint64 if tx.Subnet != constants.PrimaryNetworkID { - if err := verifySubnetValidatorPrimaryNetworkRequirements(chainState, tx.Validator); err != nil { + if err := verifySubnetValidatorPrimaryNetworkRequirements(isDurangoActive, chainState, tx.Validator); err != nil { return err } @@ -562,14 +560,9 @@ func verifyAddPermissionlessValidatorTx( return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) } - // Make sure the tx doesn't start too far in the future. This is done last - // to allow the verifier visitor to explicitly check for this error. - maxStartTime := currentTimestamp.Add(MaxFutureStartTime) - if startTime.After(maxStartTime) { - return ErrFutureStakeTime - } - - return nil + // verifyStakerStartsSoon is checked last to allow + // the verifier visitor to explicitly check for this error. + return verifyStakerStartsSoon(isDurangoActive, currentTimestamp, startTime) } // verifyAddPermissionlessDelegatorTx carries out the validation for an @@ -589,15 +582,19 @@ func verifyAddPermissionlessDelegatorTx( return nil } - currentTimestamp := chainState.GetTimestamp() - // Ensure the proposed validator starts after the current timestamp - startTime := tx.StartTime() - if !currentTimestamp.Before(startTime) { - return fmt.Errorf( - "chain timestamp (%s) not before validator's start time (%s)", - currentTimestamp, - startTime, - ) + var ( + currentTimestamp = chainState.GetTimestamp() + isDurangoActive = backend.Config.IsDurangoActivated(currentTimestamp) + endTime = tx.EndTime() + startTime = currentTimestamp + ) + if !isDurangoActive { + startTime = tx.StartTime() + } + duration := endTime.Sub(startTime) + + if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil { + return err } delegatorRules, err := getDelegatorRules(backend, chainState, tx.Subnet) @@ -605,7 +602,6 @@ func verifyAddPermissionlessDelegatorTx( return err } - duration := tx.Validator.Duration() stakedAssetID := tx.StakeOuts[0].AssetID() switch { case tx.Validator.Wght < delegatorRules.minDelegatorStake: @@ -649,21 +645,22 @@ func verifyAddPermissionlessDelegatorTx( } maximumWeight = safemath.Min(maximumWeight, delegatorRules.maxValidatorStake) - txID := sTx.ID() - newStaker, err := state.NewPendingStaker(txID, tx) - if err != nil { - return err - } - if !txs.BoundedBy( - newStaker.StartTime, - newStaker.EndTime, + startTime, + endTime, validator.StartTime, validator.EndTime, ) { return ErrPeriodMismatch } - overDelegated, err := overDelegated(chainState, validator, maximumWeight, newStaker) + overDelegated, err := overDelegated( + chainState, + validator, + maximumWeight, + tx.Validator.Wght, + startTime, + endTime, + ) if err != nil { return err } @@ -706,14 +703,9 @@ func verifyAddPermissionlessDelegatorTx( return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) } - // Make sure the tx doesn't start too far in the future. This is done last - // to allow the verifier visitor to explicitly check for this error. - maxStartTime := currentTimestamp.Add(MaxFutureStartTime) - if startTime.After(maxStartTime) { - return ErrFutureStakeTime - } - - return nil + // verifyStakerStartsSoon is checked last to allow + // the verifier visitor to explicitly check for this error. + return verifyStakerStartsSoon(isDurangoActive, currentTimestamp, startTime) } // Returns an error if the given tx is invalid. @@ -762,3 +754,36 @@ func verifyTransferSubnetOwnershipTx( return nil } + +// Ensure the proposed validator starts after the current time +func verifyStakerStartTime(isDurangoActive bool, chainTime, stakerTime time.Time) error { + // Pre Durango activation, start time must be after current chain time. + // Post Durango activation, start time is not validated + if isDurangoActive { + return nil + } + + if !chainTime.Before(stakerTime) { + return fmt.Errorf( + "%w: %s >= %s", + ErrTimestampNotBeforeStartTime, + chainTime, + stakerTime, + ) + } + return nil +} + +func verifyStakerStartsSoon(isDurangoActive bool, chainTime, stakerStartTime time.Time) error { + if isDurangoActive { + return nil + } + + // Make sure the tx doesn't start too far in the future. This is done last + // to allow the verifier visitor to explicitly check for this error. + maxStartTime := chainTime.Add(MaxFutureStartTime) + if stakerStartTime.After(maxStartTime) { + return ErrFutureStakeTime + } + return nil +} diff --git a/vms/platformvm/txs/executor/staker_tx_verification_helpers.go b/vms/platformvm/txs/executor/staker_tx_verification_helpers.go index ea84cb16906f..2fb875552b86 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification_helpers.go +++ b/vms/platformvm/txs/executor/staker_tx_verification_helpers.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -151,13 +151,15 @@ func overDelegated( state state.Chain, validator *state.Staker, weightLimit uint64, - delegator *state.Staker, + delegatorWeight uint64, + delegatorStartTime time.Time, + delegatorEndTime time.Time, ) (bool, error) { - maxWeight, err := GetMaxWeight(state, validator, delegator.StartTime, delegator.EndTime) + maxWeight, err := GetMaxWeight(state, validator, delegatorStartTime, delegatorEndTime) if err != nil { return true, err } - newMaxWeight, err := math.Add64(maxWeight, delegator.Weight) + newMaxWeight, err := math.Add64(maxWeight, delegatorWeight) if err != nil { return true, err } diff --git a/vms/platformvm/txs/executor/staker_tx_verification_test.go b/vms/platformvm/txs/executor/staker_tx_verification_test.go index aa8953c8f22e..b59daf0da2b0 100644 --- a/vms/platformvm/txs/executor/staker_tx_verification_test.go +++ b/vms/platformvm/txs/executor/staker_tx_verification_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -14,6 +14,7 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/timer/mockable" @@ -27,6 +28,8 @@ import ( ) func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { + ctx := snowtest.Context(t, snowtest.PChainID) + type test struct { name string backendF func(*gomock.Controller) *Backend @@ -37,6 +40,12 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { } var ( + // in the following tests we set the fork time for forks we want active + // to activeForkTime, which is ensured to be before any other time related + // quantity (based on now) + activeForkTime = time.Unix(0, 0) + now = time.Now().Truncate(time.Second) // after activeForkTime + subnetID = ids.GenerateTestID() customAssetID = ids.GenerateTestID() unsignedTransformTx = &txs.TransformSubnetTx{ @@ -52,21 +61,24 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { Creds: []verify.Verifiable{}, } // This tx already passed syntactic verification. + startTime = now.Add(time.Second) + endTime = startTime.Add(time.Second * time.Duration(unsignedTransformTx.MinStakeDuration)) verifiedTx = txs.AddPermissionlessValidatorTx{ BaseTx: txs.BaseTx{ SyntacticallyVerified: true, BaseTx: avax.BaseTx{ - NetworkID: 1, - BlockchainID: ids.GenerateTestID(), + NetworkID: ctx.NetworkID, + BlockchainID: ctx.ChainID, Outs: []*avax.TransferableOutput{}, Ins: []*avax.TransferableInput{}, }, }, Validator: txs.Validator{ NodeID: ids.GenerateTestNodeID(), - Start: 1, - End: 1 + uint64(unsignedTransformTx.MinStakeDuration), - Wght: unsignedTransformTx.MinValidatorStake, + // Note: [Start] is not set here as it will be ignored + // Post-Durango in favor of the current chain time + End: uint64(endTime.Unix()), + Wght: unsignedTransformTx.MinValidatorStake, }, Subnet: subnetID, StakeOuts: []*avax.TransferableOutput{ @@ -98,7 +110,10 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { name: "fail syntactic verification", backendF: func(*gomock.Controller) *Backend { return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, } }, stateF: func(*gomock.Controller) state.Chain { @@ -116,7 +131,10 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { name: "not bootstrapped", backendF: func(*gomock.Controller) *Backend { return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, Bootstrapped: &utils.Atomic[bool]{}, } }, @@ -137,7 +155,11 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped := &utils.Atomic[bool]{} bootstrapped.Set(true) return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + CortinaTime: activeForkTime, + DurangoTime: mockable.MaxTime, + }, Bootstrapped: bootstrapped, } }, @@ -160,13 +182,16 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped := &utils.Atomic[bool]{} bootstrapped.Set(true) return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { state := state.NewMockChain(ctrl) - state.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + state.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) return state }, @@ -186,13 +211,16 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped := &utils.Atomic[bool]{} bootstrapped.Set(true) return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { state := state.NewMockChain(ctrl) - state.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + state.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) return state }, @@ -212,13 +240,16 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped := &utils.Atomic[bool]{} bootstrapped.Set(true) return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { state := state.NewMockChain(ctrl) - state.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + state.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) return state }, @@ -239,13 +270,16 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped := &utils.Atomic[bool]{} bootstrapped.Set(true) return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { state := state.NewMockChain(ctrl) - state.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + state.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) return state }, @@ -256,9 +290,9 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { tx := verifiedTx // Note that this copies [verifiedTx] tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake tx.DelegationShares = unsignedTransformTx.MinDelegationFee + // Note the duration is 1 less than the minimum - tx.Validator.Start = 1 - tx.Validator.End = uint64(unsignedTransformTx.MinStakeDuration) + tx.Validator.End = tx.Validator.Start + uint64(unsignedTransformTx.MinStakeDuration) - 1 return &tx }, expectedErr: ErrStakeTooShort, @@ -269,13 +303,16 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped := &utils.Atomic[bool]{} bootstrapped.Set(true) return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { state := state.NewMockChain(ctrl) - state.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + state.EXPECT().GetTimestamp().Return(time.Unix(1, 0)) // chain time is after fork activation since time.Unix(1, 0).After(activeForkTime) state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) return state }, @@ -286,9 +323,9 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { tx := verifiedTx // Note that this copies [verifiedTx] tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake tx.DelegationShares = unsignedTransformTx.MinDelegationFee + // Note the duration is more than the maximum - tx.Validator.Start = 1 - tx.Validator.End = 2 + uint64(unsignedTransformTx.MaxStakeDuration) + tx.Validator.End = uint64(unsignedTransformTx.MaxStakeDuration) + 2 return &tx }, expectedErr: ErrStakeTooLong, @@ -299,15 +336,18 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped := &utils.Atomic[bool]{} bootstrapped.Set(true) return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { - state := state.NewMockChain(ctrl) - state.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) - state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) - return state + mockState := state.NewMockChain(ctrl) + mockState.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) + mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) + return mockState }, sTxF: func() *txs.Tx { return &verifiedSignedTx @@ -331,17 +371,20 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped := &utils.Atomic[bool]{} bootstrapped.Set(true) return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { - state := state.NewMockChain(ctrl) - state.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) - state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) + mockState := state.NewMockChain(ctrl) + mockState.EXPECT().GetTimestamp().Return(now) // chain time is after latest fork activation since now.After(activeForkTime) + mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) // State says validator exists - state.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, nil) - return state + mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, nil) + return mockState }, sTxF: func() *txs.Tx { return &verifiedSignedTx @@ -357,20 +400,22 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { bootstrapped := &utils.Atomic[bool]{} bootstrapped.Set(true) return &Backend{ - Ctx: snow.DefaultContextTest(), + Ctx: ctx, + Config: &config.Config{ + DurangoTime: activeForkTime, // activate latest fork + }, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { mockState := state.NewMockChain(ctrl) - mockState.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) // Validator time isn't subset of primary network validator time primaryNetworkVdr := &state.Staker{ - StartTime: verifiedTx.StartTime().Add(time.Second), - EndTime: verifiedTx.EndTime(), + EndTime: verifiedTx.EndTime().Add(-1 * time.Second), } mockState.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, verifiedTx.NodeID()).Return(primaryNetworkVdr, nil) return mockState @@ -403,20 +448,20 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { FlowChecker: flowChecker, Config: &config.Config{ AddSubnetValidatorFee: 1, + DurangoTime: activeForkTime, // activate latest fork, }, - Ctx: snow.DefaultContextTest(), + Ctx: ctx, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { mockState := state.NewMockChain(ctrl) - mockState.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) primaryNetworkVdr := &state.Staker{ - StartTime: verifiedTx.StartTime(), - EndTime: verifiedTx.EndTime(), + EndTime: mockable.MaxTime, } mockState.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, verifiedTx.NodeID()).Return(primaryNetworkVdr, nil) return mockState @@ -448,15 +493,17 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { return &Backend{ FlowChecker: flowChecker, Config: &config.Config{ + CortinaTime: activeForkTime, + DurangoTime: mockable.MaxTime, AddSubnetValidatorFee: 1, }, - Ctx: snow.DefaultContextTest(), + Ctx: ctx, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { mockState := state.NewMockChain(ctrl) - mockState.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is Cortina fork activation since now.After(activeForkTime) mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) @@ -473,7 +520,7 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { txF: func() *txs.AddPermissionlessValidatorTx { // Note this copies [verifiedTx] tx := verifiedTx - tx.Validator.Start = uint64(MaxFutureStartTime.Seconds()) + 1 + tx.Validator.Start = uint64(now.Add(MaxFutureStartTime).Add(time.Second).Unix()) tx.Validator.End = tx.Validator.Start + uint64(unsignedTransformTx.MinStakeDuration) return &tx }, @@ -499,20 +546,20 @@ func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { FlowChecker: flowChecker, Config: &config.Config{ AddSubnetValidatorFee: 1, + DurangoTime: activeForkTime, // activate latest fork, }, - Ctx: snow.DefaultContextTest(), + Ctx: ctx, Bootstrapped: bootstrapped, } }, stateF: func(ctrl *gomock.Controller) state.Chain { mockState := state.NewMockChain(ctrl) - mockState.EXPECT().GetTimestamp().Return(time.Unix(0, 0)) + mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after Durango fork activation since now.After(activeForkTime) mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) primaryNetworkVdr := &state.Staker{ - StartTime: time.Unix(0, 0), - EndTime: mockable.MaxTime, + EndTime: mockable.MaxTime, } mockState.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, verifiedTx.NodeID()).Return(primaryNetworkVdr, nil) return mockState diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index 63069cb5d5d5..e780673e6431 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -24,8 +24,9 @@ import ( var ( _ txs.Visitor = (*StandardTxExecutor)(nil) - errEmptyNodeID = errors.New("validator nodeID cannot be empty") - errMaxStakeDurationTooLarge = errors.New("max stake duration must be less than or equal to the global max stake duration") + errEmptyNodeID = errors.New("validator nodeID cannot be empty") + errMaxStakeDurationTooLarge = errors.New("max stake duration must be less than or equal to the global max stake duration") + errMissingStartTimePreDurango = errors.New("staker transactions must have a StartTime pre-Durango") ) type StandardTxExecutor struct { @@ -253,7 +254,7 @@ func (e *StandardTxExecutor) ExportTx(tx *txs.ExportTx) error { Out: out.Out, } - utxoBytes, err := txs.Codec.Marshal(txs.Version, utxo) + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) if err != nil { return fmt.Errorf("failed to marshal UTXO: %w", err) } @@ -290,13 +291,11 @@ func (e *StandardTxExecutor) AddValidatorTx(tx *txs.AddValidatorTx) error { return err } - txID := e.Tx.ID() - newStaker, err := state.NewPendingStaker(txID, tx) - if err != nil { + if err := e.putStaker(tx); err != nil { return err } - e.State.PutPendingValidator(newStaker) + txID := e.Tx.ID() avax.Consume(e.State, tx.Ins) avax.Produce(e.State, txID, tx.Outs) @@ -321,16 +320,13 @@ func (e *StandardTxExecutor) AddSubnetValidatorTx(tx *txs.AddSubnetValidatorTx) return err } - txID := e.Tx.ID() - newStaker, err := state.NewPendingStaker(txID, tx) - if err != nil { + if err := e.putStaker(tx); err != nil { return err } - e.State.PutPendingValidator(newStaker) + txID := e.Tx.ID() avax.Consume(e.State, tx.Ins) avax.Produce(e.State, txID, tx.Outs) - return nil } @@ -344,16 +340,13 @@ func (e *StandardTxExecutor) AddDelegatorTx(tx *txs.AddDelegatorTx) error { return err } - txID := e.Tx.ID() - newStaker, err := state.NewPendingStaker(txID, tx) - if err != nil { + if err := e.putStaker(tx); err != nil { return err } - e.State.PutPendingDelegator(newStaker) + txID := e.Tx.ID() avax.Consume(e.State, tx.Ins) avax.Produce(e.State, txID, tx.Outs) - return nil } @@ -444,13 +437,11 @@ func (e *StandardTxExecutor) AddPermissionlessValidatorTx(tx *txs.AddPermissionl return err } - txID := e.Tx.ID() - newStaker, err := state.NewPendingStaker(txID, tx) - if err != nil { + if err := e.putStaker(tx); err != nil { return err } - e.State.PutPendingValidator(newStaker) + txID := e.Tx.ID() avax.Consume(e.State, tx.Ins) avax.Produce(e.State, txID, tx.Outs) @@ -478,16 +469,13 @@ func (e *StandardTxExecutor) AddPermissionlessDelegatorTx(tx *txs.AddPermissionl return err } - txID := e.Tx.ID() - newStaker, err := state.NewPendingStaker(txID, tx) - if err != nil { + if err := e.putStaker(tx); err != nil { return err } - e.State.PutPendingDelegator(newStaker) + txID := e.Tx.ID() avax.Consume(e.State, tx.Ins) avax.Produce(e.State, txID, tx.Outs) - return nil } @@ -511,7 +499,6 @@ func (e *StandardTxExecutor) TransferSubnetOwnershipTx(tx *txs.TransferSubnetOwn txID := e.Tx.ID() avax.Consume(e.State, tx.Ins) avax.Produce(e.State, txID, tx.Outs) - return nil } @@ -539,9 +526,78 @@ func (e *StandardTxExecutor) BaseTx(tx *txs.BaseTx) error { return err } + txID := e.Tx.ID() // Consume the UTXOS avax.Consume(e.State, tx.Ins) // Produce the UTXOS - avax.Produce(e.State, e.Tx.ID(), tx.Outs) + avax.Produce(e.State, txID, tx.Outs) + return nil +} + +// Creates the staker as defined in [stakerTx] and adds it to [e.State]. +func (e *StandardTxExecutor) putStaker(stakerTx txs.Staker) error { + var ( + chainTime = e.State.GetTimestamp() + txID = e.Tx.ID() + staker *state.Staker + err error + ) + + if !e.Config.IsDurangoActivated(chainTime) { + // Pre-Durango, stakers set a future [StartTime] and are added to the + // pending staker set. They are promoted to the current staker set once + // the chain time reaches [StartTime]. + scheduledStakerTx, ok := stakerTx.(txs.ScheduledStaker) + if !ok { + return fmt.Errorf("%w: %T", errMissingStartTimePreDurango, stakerTx) + } + staker, err = state.NewPendingStaker(txID, scheduledStakerTx) + } else { + // Only calculate the potentialReward for permissionless stakers. + // Recall that we only need to check if this is a permissioned + // validator as there are no permissioned delegators + var potentialReward uint64 + if !stakerTx.CurrentPriority().IsPermissionedValidator() { + subnetID := stakerTx.SubnetID() + currentSupply, err := e.State.GetCurrentSupply(subnetID) + if err != nil { + return err + } + + rewards, err := GetRewardsCalculator(e.Backend, e.State, subnetID) + if err != nil { + return err + } + + // Post-Durango, stakers are immediately added to the current staker + // set. Their [StartTime] is the current chain time. + stakeDuration := stakerTx.EndTime().Sub(chainTime) + potentialReward = rewards.Calculate( + stakeDuration, + stakerTx.Weight(), + currentSupply, + ) + + e.State.SetCurrentSupply(subnetID, currentSupply+potentialReward) + } + + staker, err = state.NewCurrentStaker(txID, stakerTx, chainTime, potentialReward) + } + if err != nil { + return err + } + + switch priority := staker.Priority; { + case priority.IsCurrentValidator(): + e.State.PutCurrentValidator(staker) + case priority.IsCurrentDelegator(): + e.State.PutCurrentDelegator(staker) + case priority.IsPendingValidator(): + e.State.PutPendingValidator(staker) + case priority.IsPendingDelegator(): + e.State.PutPendingDelegator(staker) + default: + return fmt.Errorf("staker %s, unexpected priority %d", staker.TxID, priority) + } return nil } diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 78e15078e133..a86c579fee79 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -41,14 +41,14 @@ var errTest = errors.New("non-nil error") func TestStandardTxExecutorAddValidatorTxEmptyID(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) }() chainTime := env.state.GetTimestamp() - startTime := defaultGenesisTime.Add(1 * time.Second) + startTime := defaultValidateStartTime.Add(1 * time.Second) tests := []struct { banffTime time.Time @@ -102,27 +102,29 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { nodeID := genesisNodeIDs[0] newValidatorID := ids.GenerateTestNodeID() - newValidatorStartTime := uint64(defaultValidateStartTime.Add(5 * time.Second).Unix()) - newValidatorEndTime := uint64(defaultValidateEndTime.Add(-5 * time.Second).Unix()) + newValidatorStartTime := defaultValidateStartTime.Add(5 * time.Second) + newValidatorEndTime := defaultValidateEndTime.Add(-5 * time.Second) // [addMinStakeValidator] adds a new validator to the primary network's // pending validator set with the minimum staking amount addMinStakeValidator := func(target *environment) { tx, err := target.txBuilder.NewAddValidatorTx( - target.config.MinValidatorStake, // stake amount - newValidatorStartTime, // start time - newValidatorEndTime, // end time - newValidatorID, // node ID - rewardAddress, // Reward Address - reward.PercentDenominator, // Shares + target.config.MinValidatorStake, // stake amount + uint64(newValidatorStartTime.Unix()), // start time + uint64(newValidatorEndTime.Unix()), // end time + newValidatorID, // node ID + rewardAddress, // Reward Address + reward.PercentDenominator, // Shares []*secp256k1.PrivateKey{preFundedKeys[0]}, ids.ShortEmpty, ) require.NoError(t, err) + addValTx := tx.Unsigned.(*txs.AddValidatorTx) staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddValidatorTx), + addValTx, + newValidatorStartTime, 0, ) require.NoError(t, err) @@ -137,20 +139,22 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { // pending validator set with the maximum staking amount addMaxStakeValidator := func(target *environment) { tx, err := target.txBuilder.NewAddValidatorTx( - target.config.MaxValidatorStake, // stake amount - newValidatorStartTime, // start time - newValidatorEndTime, // end time - newValidatorID, // node ID - rewardAddress, // Reward Address - reward.PercentDenominator, // Shared + target.config.MaxValidatorStake, // stake amount + uint64(newValidatorStartTime.Unix()), // start time + uint64(newValidatorEndTime.Unix()), // end time + newValidatorID, // node ID + rewardAddress, // Reward Address + reward.PercentDenominator, // Shared []*secp256k1.PrivateKey{preFundedKeys[0]}, ids.ShortEmpty, ) require.NoError(t, err) + addValTx := tx.Unsigned.(*txs.AddValidatorTx) staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddValidatorTx), + addValTx, + newValidatorStartTime, 0, ) require.NoError(t, err) @@ -161,14 +165,14 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { require.NoError(t, target.state.Commit()) } - dummyH := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + dummyH := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) currentTimestamp := dummyH.state.GetTimestamp() type test struct { description string stakeAmount uint64 - startTime uint64 - endTime uint64 + startTime time.Time + endTime time.Time nodeID ids.NodeID rewardAddress ids.ShortID feeKeys []*secp256k1.PrivateKey @@ -182,8 +186,8 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { { description: "validator stops validating earlier than delegator", stakeAmount: dummyH.config.MinDelegatorStake, - startTime: uint64(defaultValidateStartTime.Unix()) + 1, - endTime: uint64(defaultValidateEndTime.Unix()) + 1, + startTime: defaultValidateStartTime.Add(time.Second), + endTime: defaultValidateEndTime.Add(time.Second), nodeID: nodeID, rewardAddress: rewardAddress, feeKeys: []*secp256k1.PrivateKey{preFundedKeys[0]}, @@ -195,8 +199,8 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { { description: fmt.Sprintf("delegator should not be added more than (%s) in the future", MaxFutureStartTime), stakeAmount: dummyH.config.MinDelegatorStake, - startTime: uint64(currentTimestamp.Add(MaxFutureStartTime + time.Second).Unix()), - endTime: uint64(currentTimestamp.Add(MaxFutureStartTime + defaultMinStakingDuration + time.Second).Unix()), + startTime: currentTimestamp.Add(MaxFutureStartTime + time.Second), + endTime: currentTimestamp.Add(MaxFutureStartTime + defaultMinStakingDuration + time.Second), nodeID: nodeID, rewardAddress: rewardAddress, feeKeys: []*secp256k1.PrivateKey{preFundedKeys[0]}, @@ -208,8 +212,8 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { { description: "validator not in the current or pending validator sets", stakeAmount: dummyH.config.MinDelegatorStake, - startTime: uint64(defaultValidateStartTime.Add(5 * time.Second).Unix()), - endTime: uint64(defaultValidateEndTime.Add(-5 * time.Second).Unix()), + startTime: defaultValidateStartTime.Add(5 * time.Second), + endTime: defaultValidateEndTime.Add(-5 * time.Second), nodeID: newValidatorID, rewardAddress: rewardAddress, feeKeys: []*secp256k1.PrivateKey{preFundedKeys[0]}, @@ -221,7 +225,7 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { { description: "delegator starts before validator", stakeAmount: dummyH.config.MinDelegatorStake, - startTime: newValidatorStartTime - 1, // start validating subnet before primary network + startTime: newValidatorStartTime.Add(-1 * time.Second), // start validating subnet before primary network endTime: newValidatorEndTime, nodeID: newValidatorID, rewardAddress: rewardAddress, @@ -235,7 +239,7 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { description: "delegator stops before validator", stakeAmount: dummyH.config.MinDelegatorStake, startTime: newValidatorStartTime, - endTime: newValidatorEndTime + 1, // stop validating subnet after stopping validating primary network + endTime: newValidatorEndTime.Add(time.Second), // stop validating subnet after stopping validating primary network nodeID: newValidatorID, rewardAddress: rewardAddress, feeKeys: []*secp256k1.PrivateKey{preFundedKeys[0]}, @@ -260,8 +264,8 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { { description: "starts delegating at current timestamp", stakeAmount: dummyH.config.MinDelegatorStake, // weight - startTime: uint64(currentTimestamp.Unix()), // start time - endTime: uint64(defaultValidateEndTime.Unix()), // end time + startTime: currentTimestamp, // start time + endTime: defaultValidateEndTime, // end time nodeID: nodeID, // node ID rewardAddress: rewardAddress, // Reward Address feeKeys: []*secp256k1.PrivateKey{preFundedKeys[0]}, // tx fee payer @@ -272,12 +276,12 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { }, { description: "tx fee paying key has no funds", - stakeAmount: dummyH.config.MinDelegatorStake, // weight - startTime: uint64(defaultValidateStartTime.Unix()) + 1, // start time - endTime: uint64(defaultValidateEndTime.Unix()), // end time - nodeID: nodeID, // node ID - rewardAddress: rewardAddress, // Reward Address - feeKeys: []*secp256k1.PrivateKey{preFundedKeys[1]}, // tx fee payer + stakeAmount: dummyH.config.MinDelegatorStake, // weight + startTime: defaultValidateStartTime.Add(time.Second), // start time + endTime: defaultValidateEndTime, // end time + nodeID: nodeID, // node ID + rewardAddress: rewardAddress, // Reward Address + feeKeys: []*secp256k1.PrivateKey{preFundedKeys[1]}, // tx fee payer setup: func(target *environment) { // Remove all UTXOs owned by keys[1] utxoIDs, err := target.state.UTXOIDs( preFundedKeys[1].PublicKey().Address().Bytes(), @@ -326,7 +330,7 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { require := require.New(t) - freshTH := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + freshTH := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) freshTH.config.ApricotPhase3Time = tt.AP3Time defer func() { require.NoError(shutdownEnvironment(freshTH)) @@ -334,8 +338,8 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { tx, err := freshTH.txBuilder.NewAddDelegatorTx( tt.stakeAmount, - tt.startTime, - tt.endTime, + uint64(tt.startTime.Unix()), + uint64(tt.endTime.Unix()), tt.nodeID, tt.rewardAddress, tt.feeKeys, @@ -374,14 +378,13 @@ func TestStandardTxExecutorAddDelegator(t *testing.T) { func TestStandardTxExecutorAddSubnetValidator(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) }() nodeID := genesisNodeIDs[0] - env.config.BanffTime = env.state.GetTimestamp() { // Case: Proposed validator currently validating primary network @@ -481,9 +484,11 @@ func TestStandardTxExecutorAddSubnetValidator(t *testing.T) { require.ErrorIs(err, ErrNotValidator) } + addValTx := addDSTx.Unsigned.(*txs.AddValidatorTx) staker, err := state.NewCurrentStaker( addDSTx.ID(), - addDSTx.Unsigned.(*txs.AddValidatorTx), + addValTx, + dsStartTime, 0, ) require.NoError(err) @@ -617,9 +622,11 @@ func TestStandardTxExecutorAddSubnetValidator(t *testing.T) { ) require.NoError(err) + addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx) staker, err = state.NewCurrentStaker( subnetTx.ID(), - subnetTx.Unsigned.(*txs.AddSubnetValidatorTx), + addSubnetValTx, + defaultValidateStartTime, 0, ) require.NoError(err) @@ -771,9 +778,11 @@ func TestStandardTxExecutorAddSubnetValidator(t *testing.T) { ) require.NoError(err) + addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx) staker, err = state.NewCurrentStaker( subnetTx.ID(), - subnetTx.Unsigned.(*txs.AddSubnetValidatorTx), + addSubnetValTx, + defaultValidateStartTime, 0, ) require.NoError(err) @@ -796,9 +805,9 @@ func TestStandardTxExecutorAddSubnetValidator(t *testing.T) { } } -func TestStandardTxExecutorAddValidator(t *testing.T) { +func TestStandardTxExecutorBanffAddValidator(t *testing.T) { require := require.New(t) - env := newEnvironment(t, false /*=postBanff*/, false /*=postCortina*/) + env := newEnvironment(t, true /*=postBanff*/, false /*=postCortina*/, false /*=postDurango*/) env.ctx.Lock.Lock() defer func() { require.NoError(shutdownEnvironment(env)) @@ -806,8 +815,6 @@ func TestStandardTxExecutorAddValidator(t *testing.T) { nodeID := ids.GenerateTestNodeID() - env.config.BanffTime = env.state.GetTimestamp() - { // Case: Validator's start time too early tx, err := env.txBuilder.NewAddValidatorTx( @@ -862,7 +869,7 @@ func TestStandardTxExecutorAddValidator(t *testing.T) { { // Case: Validator in current validator set of primary network - startTime := defaultGenesisTime.Add(1 * time.Second) + startTime := defaultValidateStartTime.Add(1 * time.Second) tx, err := env.txBuilder.NewAddValidatorTx( env.config.MinValidatorStake, // stake amount uint64(startTime.Unix()), // start time @@ -875,9 +882,11 @@ func TestStandardTxExecutorAddValidator(t *testing.T) { ) require.NoError(err) + addValTx := tx.Unsigned.(*txs.AddValidatorTx) staker, err := state.NewCurrentStaker( tx.ID(), - tx.Unsigned.(*txs.AddValidatorTx), + addValTx, + startTime, 0, ) require.NoError(err) @@ -899,7 +908,7 @@ func TestStandardTxExecutorAddValidator(t *testing.T) { { // Case: Validator in pending validator set of primary network - startTime := defaultGenesisTime.Add(1 * time.Second) + startTime := defaultValidateStartTime.Add(1 * time.Second) tx, err := env.txBuilder.NewAddValidatorTx( env.config.MinValidatorStake, // stake amount uint64(startTime.Unix()), // start time @@ -935,7 +944,7 @@ func TestStandardTxExecutorAddValidator(t *testing.T) { { // Case: Validator doesn't have enough tokens to cover stake amount - startTime := defaultGenesisTime.Add(1 * time.Second) + startTime := defaultValidateStartTime.Add(1 * time.Second) tx, err := env.txBuilder.NewAddValidatorTx( // create the tx env.config.MinValidatorStake, uint64(startTime.Unix()), @@ -969,6 +978,51 @@ func TestStandardTxExecutorAddValidator(t *testing.T) { } } +func TestStandardTxExecutorDurangoAddValidator(t *testing.T) { + require := require.New(t) + env := newEnvironment(t, true /*=postBanff*/, true /*=postCortina*/, true /*=postDurango*/) + env.ctx.Lock.Lock() + defer func() { + require.NoError(shutdownEnvironment(env)) + env.ctx.Lock.Unlock() + }() + + var ( + nodeID = ids.GenerateTestNodeID() + chainTime = env.state.GetTimestamp() + endTime = chainTime.Add(defaultMaxStakingDuration) + ) + + addValTx, err := env.txBuilder.NewAddValidatorTx( + env.config.MinValidatorStake, + 0, + uint64(endTime.Unix()), + nodeID, + ids.ShortEmpty, + reward.PercentDenominator, + []*secp256k1.PrivateKey{preFundedKeys[0]}, + ids.ShortEmpty, // change addr + ) + require.NoError(err) + + onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) + require.NoError(err) + + require.NoError(addValTx.Unsigned.Visit(&StandardTxExecutor{ + Backend: &env.backend, + State: onAcceptState, + Tx: addValTx, + })) + + // Check that a current validator is added + val, err := onAcceptState.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) + require.NoError(err) + + require.Equal(addValTx.ID(), val.TxID) + require.Equal(chainTime, val.StartTime) + require.Equal(endTime, val.EndTime) +} + // Returns a RemoveSubnetValidatorTx that passes syntactic verification. func newRemoveSubnetValidatorTx(t *testing.T) (*txs.RemoveSubnetValidatorTx, *txs.Tx) { t.Helper() @@ -1032,13 +1086,13 @@ func newRemoveSubnetValidatorTx(t *testing.T) (*txs.RemoveSubnetValidatorTx, *tx // mock implementations that can be used in tests // for verifying RemoveSubnetValidatorTx. type removeSubnetValidatorTxVerifyEnv struct { - banffTime time.Time - fx *fx.MockFx - flowChecker *utxo.MockVerifier - unsignedTx *txs.RemoveSubnetValidatorTx - tx *txs.Tx - state *state.MockDiff - staker *state.Staker + latestForkTime time.Time + fx *fx.MockFx + flowChecker *utxo.MockVerifier + unsignedTx *txs.RemoveSubnetValidatorTx + tx *txs.Tx + state *state.MockDiff + staker *state.Staker } // Returns mock implementations that can be used in tests @@ -1052,12 +1106,12 @@ func newValidRemoveSubnetValidatorTxVerifyEnv(t *testing.T, ctrl *gomock.Control unsignedTx, tx := newRemoveSubnetValidatorTx(t) mockState := state.NewMockDiff(ctrl) return removeSubnetValidatorTxVerifyEnv{ - banffTime: now, - fx: mockFx, - flowChecker: mockFlowChecker, - unsignedTx: unsignedTx, - tx: tx, - state: mockState, + latestForkTime: now, + fx: mockFx, + flowChecker: mockFlowChecker, + unsignedTx: unsignedTx, + tx: tx, + state: mockState, staker: &state.Staker{ TxID: ids.GenerateTestID(), NodeID: ids.GenerateTestNodeID(), @@ -1093,7 +1147,9 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1118,7 +1174,9 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1143,7 +1201,9 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1171,7 +1231,9 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1197,7 +1259,9 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1222,7 +1286,9 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1249,7 +1315,9 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1279,7 +1347,9 @@ func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1383,13 +1453,13 @@ func newTransformSubnetTx(t *testing.T) (*txs.TransformSubnetTx, *txs.Tx) { // mock implementations that can be used in tests // for verifying TransformSubnetTx. type transformSubnetTxVerifyEnv struct { - banffTime time.Time - fx *fx.MockFx - flowChecker *utxo.MockVerifier - unsignedTx *txs.TransformSubnetTx - tx *txs.Tx - state *state.MockDiff - staker *state.Staker + latestForkTime time.Time + fx *fx.MockFx + flowChecker *utxo.MockVerifier + unsignedTx *txs.TransformSubnetTx + tx *txs.Tx + state *state.MockDiff + staker *state.Staker } // Returns mock implementations that can be used in tests @@ -1403,12 +1473,12 @@ func newValidTransformSubnetTxVerifyEnv(t *testing.T, ctrl *gomock.Controller) t unsignedTx, tx := newTransformSubnetTx(t) mockState := state.NewMockDiff(ctrl) return transformSubnetTxVerifyEnv{ - banffTime: now, - fx: mockFx, - flowChecker: mockFlowChecker, - unsignedTx: unsignedTx, - tx: tx, - state: mockState, + latestForkTime: now, + fx: mockFx, + flowChecker: mockFlowChecker, + unsignedTx: unsignedTx, + tx: tx, + state: mockState, staker: &state.Staker{ TxID: ids.GenerateTestID(), NodeID: ids.GenerateTestNodeID(), @@ -1434,7 +1504,9 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1458,7 +1530,9 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, }, Bootstrapped: &utils.Atomic[bool]{}, Fx: env.fx, @@ -1483,7 +1557,9 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, MaxStakeDuration: math.MaxInt64, }, Bootstrapped: &utils.Atomic[bool]{}, @@ -1514,7 +1590,9 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, MaxStakeDuration: math.MaxInt64, }, Bootstrapped: &utils.Atomic[bool]{}, @@ -1550,7 +1628,9 @@ func TestStandardExecutorTransformSubnetTx(t *testing.T) { e := &StandardTxExecutor{ Backend: &Backend{ Config: &config.Config{ - BanffTime: env.banffTime, + BanffTime: env.latestForkTime, + CortinaTime: env.latestForkTime, + DurangoTime: env.latestForkTime, MaxStakeDuration: math.MaxInt64, }, Bootstrapped: &utils.Atomic[bool]{}, diff --git a/vms/platformvm/txs/executor/state_changes.go b/vms/platformvm/txs/executor/state_changes.go index 7b0c9355e195..2b327c4f8b96 100644 --- a/vms/platformvm/txs/executor/state_changes.go +++ b/vms/platformvm/txs/executor/state_changes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/txs/executor/subnet_tx_verification.go b/vms/platformvm/txs/executor/subnet_tx_verification.go index bf384be9fa89..f1a75f6f2f3f 100644 --- a/vms/platformvm/txs/executor/subnet_tx_verification.go +++ b/vms/platformvm/txs/executor/subnet_tx_verification.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor diff --git a/vms/platformvm/txs/executor/tx_mempool_verifier.go b/vms/platformvm/txs/executor/tx_mempool_verifier.go index f6eff499c2ec..f2e7d09673e6 100644 --- a/vms/platformvm/txs/executor/tx_mempool_verifier.go +++ b/vms/platformvm/txs/executor/tx_mempool_verifier.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package executor @@ -9,6 +9,7 @@ import ( "time" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/platformvm/state" "github.com/ava-labs/avalanchego/vms/platformvm/txs" ) @@ -108,7 +109,7 @@ func (v *MempoolTxVerifier) standardBaseState() (state.Diff, error) { return nil, err } - nextBlkTime, err := v.nextBlockTime(state) + nextBlkTime, _, err := NextBlockTime(state, v.Clk) if err != nil { return nil, err } @@ -123,20 +124,26 @@ func (v *MempoolTxVerifier) standardBaseState() (state.Diff, error) { return state, nil } -func (v *MempoolTxVerifier) nextBlockTime(state state.Diff) (time.Time, error) { +func NextBlockTime(state state.Chain, clk *mockable.Clock) (time.Time, bool, error) { var ( - parentTime = state.GetTimestamp() - nextBlkTime = v.Clk.Time() + timestamp = clk.Time() + parentTime = state.GetTimestamp() ) - if parentTime.After(nextBlkTime) { - nextBlkTime = parentTime + if parentTime.After(timestamp) { + timestamp = parentTime } + // [timestamp] = max(now, parentTime) + nextStakerChangeTime, err := GetNextStakerChangeTime(state) if err != nil { - return time.Time{}, fmt.Errorf("could not calculate next staker change time: %w", err) + return time.Time{}, false, fmt.Errorf("failed getting next staker change time: %w", err) } - if !nextBlkTime.Before(nextStakerChangeTime) { - nextBlkTime = nextStakerChangeTime + + // timeWasCapped means that [timestamp] was reduced to [nextStakerChangeTime] + timeWasCapped := !timestamp.Before(nextStakerChangeTime) + if timeWasCapped { + timestamp = nextStakerChangeTime } - return nextBlkTime, nil + // [timestamp] = min(max(now, parentTime), nextStakerChangeTime) + return timestamp, timeWasCapped, nil } diff --git a/vms/platformvm/txs/export_tx.go b/vms/platformvm/txs/export_tx.go index b124263aae12..19dc3a076f7a 100644 --- a/vms/platformvm/txs/export_tx.go +++ b/vms/platformvm/txs/export_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/import_tx.go b/vms/platformvm/txs/import_tx.go index 121986991012..563242dad34a 100644 --- a/vms/platformvm/txs/import_tx.go +++ b/vms/platformvm/txs/import_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/mempool/mempool.go b/vms/platformvm/txs/mempool/mempool.go index ce0d6a96f071..34ee9c283745 100644 --- a/vms/platformvm/txs/mempool/mempool.go +++ b/vms/platformvm/txs/mempool/mempool.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package mempool @@ -6,15 +6,16 @@ package mempool import ( "errors" "fmt" - "time" + "sync" "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/linkedhashmap" - "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/setmap" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm/txs" ) @@ -27,8 +28,6 @@ const ( // droppedTxIDsCacheSize is the maximum number of dropped txIDs to cache droppedTxIDsCacheSize = 64 - initialConsumedUTXOsSize = 512 - // maxMempoolSize is the maximum number of bytes allowed in the mempool maxMempoolSize = 64 * units.MiB ) @@ -36,38 +35,25 @@ const ( var ( _ Mempool = (*mempool)(nil) - errDuplicateTx = errors.New("duplicate tx") - errTxTooLarge = errors.New("tx too large") - errMempoolFull = errors.New("mempool is full") - errConflictsWithOtherTx = errors.New("tx conflicts with other tx") - errCantIssueAdvanceTimeTx = errors.New("can not issue an advance time tx") - errCantIssueRewardValidatorTx = errors.New("can not issue a reward validator tx") + ErrDuplicateTx = errors.New("duplicate tx") + ErrTxTooLarge = errors.New("tx too large") + ErrMempoolFull = errors.New("mempool is full") + ErrConflictsWithOtherTx = errors.New("tx conflicts with other tx") + ErrCantIssueAdvanceTimeTx = errors.New("can not issue an advance time tx") + ErrCantIssueRewardValidatorTx = errors.New("can not issue a reward validator tx") ) type Mempool interface { - // we may want to be able to stop valid transactions - // from entering the mempool, e.g. during blocks creation - EnableAdding() - DisableAdding() - Add(tx *txs.Tx) error - Has(txID ids.ID) bool - Get(txID ids.ID) *txs.Tx - Remove(txs []*txs.Tx) - - // Following Banff activation, all mempool transactions, - // (both decision and staker) are included into Standard blocks. - // HasTxs allow to check for availability of any mempool transaction. - HasTxs() bool - // PeekTxs returns the next txs for Banff blocks - // up to maxTxsBytes without removing them from the mempool. - PeekTxs(maxTxsBytes int) []*txs.Tx - - // Drops all [txs.Staker] transactions whose [StartTime] is before - // [minStartTime] from [mempool]. The dropped tx ids are returned. - // - // TODO: Remove once [StartTime] field is ignored in staker txs - DropExpiredStakerTxs(minStartTime time.Time) []ids.ID + Get(txID ids.ID) (*txs.Tx, bool) + // Remove [txs] and any conflicts of [txs] from the mempool. + Remove(txs ...*txs.Tx) + + // Peek returns the oldest tx in the mempool. + Peek() (tx *txs.Tx, exists bool) + + // Iterate iterates over the txs until f returns false + Iterate(f func(tx *txs.Tx) bool) // RequestBuildBlock notifies the consensus engine that a block should be // built. If [emptyBlockPermitted] is true, the notification will be sent @@ -81,27 +67,24 @@ type Mempool interface { // possibly reissued. MarkDropped(txID ids.ID, reason error) GetDropReason(txID ids.ID) error + + // Len returns the number of txs in the mempool. + Len() int } // Transactions from clients that have not yet been put into blocks and added to // consensus type mempool struct { - // If true, drop transactions added to the mempool via Add. - dropIncoming bool - - bytesAvailableMetric prometheus.Gauge - bytesAvailable int - - unissuedTxs linkedhashmap.LinkedHashmap[ids.ID, *txs.Tx] - numTxs prometheus.Gauge - - // Key: Tx ID - // Value: Verification error - droppedTxIDs *cache.LRU[ids.ID, error] - - consumedUTXOs set.Set[ids.ID] + lock sync.RWMutex + unissuedTxs linkedhashmap.LinkedHashmap[ids.ID, *txs.Tx] + consumedUTXOs *setmap.SetMap[ids.ID, ids.ID] // TxID -> Consumed UTXOs + bytesAvailable int + droppedTxIDs *cache.LRU[ids.ID, error] // TxID -> verification error toEngine chan<- common.Message + + numTxs prometheus.Gauge + bytesAvailableMetric prometheus.Gauge } func New( @@ -109,70 +92,54 @@ func New( registerer prometheus.Registerer, toEngine chan<- common.Message, ) (Mempool, error) { - bytesAvailableMetric := prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "bytes_available", - Help: "Number of bytes of space currently available in the mempool", - }) - if err := registerer.Register(bytesAvailableMetric); err != nil { - return nil, err - } - - numTxs := prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "txs", - Help: "Number of decision/staker transactions in the mempool", - }) - if err := registerer.Register(numTxs); err != nil { - return nil, err + m := &mempool{ + unissuedTxs: linkedhashmap.New[ids.ID, *txs.Tx](), + consumedUTXOs: setmap.New[ids.ID, ids.ID](), + bytesAvailable: maxMempoolSize, + droppedTxIDs: &cache.LRU[ids.ID, error]{Size: droppedTxIDsCacheSize}, + toEngine: toEngine, + numTxs: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "txs", + Help: "Number of decision/staker transactions in the mempool", + }), + bytesAvailableMetric: prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Name: "bytes_available", + Help: "Number of bytes of space currently available in the mempool", + }), } + m.bytesAvailableMetric.Set(maxMempoolSize) - bytesAvailableMetric.Set(maxMempoolSize) - return &mempool{ - bytesAvailableMetric: bytesAvailableMetric, - bytesAvailable: maxMempoolSize, - - unissuedTxs: linkedhashmap.New[ids.ID, *txs.Tx](), - numTxs: numTxs, - - droppedTxIDs: &cache.LRU[ids.ID, error]{Size: droppedTxIDsCacheSize}, - consumedUTXOs: set.NewSet[ids.ID](initialConsumedUTXOsSize), - dropIncoming: false, // enable tx adding by default - toEngine: toEngine, - }, nil -} - -func (m *mempool) EnableAdding() { - m.dropIncoming = false -} - -func (m *mempool) DisableAdding() { - m.dropIncoming = true + err := utils.Err( + registerer.Register(m.numTxs), + registerer.Register(m.bytesAvailableMetric), + ) + return m, err } func (m *mempool) Add(tx *txs.Tx) error { - if m.dropIncoming { - return fmt.Errorf("tx %s not added because mempool is closed", tx.ID()) - } + m.lock.Lock() + defer m.lock.Unlock() switch tx.Unsigned.(type) { case *txs.AdvanceTimeTx: - return errCantIssueAdvanceTimeTx + return ErrCantIssueAdvanceTimeTx case *txs.RewardValidatorTx: - return errCantIssueRewardValidatorTx + return ErrCantIssueRewardValidatorTx default: } // Note: a previously dropped tx can be re-added txID := tx.ID() - if m.Has(txID) { - return fmt.Errorf("%w: %s", errDuplicateTx, txID) + if _, ok := m.unissuedTxs.Get(txID); ok { + return fmt.Errorf("%w: %s", ErrDuplicateTx, txID) } txSize := len(tx.Bytes()) if txSize > MaxTxSize { return fmt.Errorf("%w: %s size (%d) > max size (%d)", - errTxTooLarge, + ErrTxTooLarge, txID, txSize, MaxTxSize, @@ -180,7 +147,7 @@ func (m *mempool) Add(tx *txs.Tx) error { } if txSize > m.bytesAvailable { return fmt.Errorf("%w: %s size (%d) > available space (%d)", - errMempoolFull, + ErrMempoolFull, txID, txSize, m.bytesAvailable, @@ -188,17 +155,17 @@ func (m *mempool) Add(tx *txs.Tx) error { } inputs := tx.Unsigned.InputIDs() - if m.consumedUTXOs.Overlaps(inputs) { - return fmt.Errorf("%w: %s", errConflictsWithOtherTx, txID) + if m.consumedUTXOs.HasOverlap(inputs) { + return fmt.Errorf("%w: %s", ErrConflictsWithOtherTx, txID) } - m.unissuedTxs.Put(tx.ID(), tx) + m.unissuedTxs.Put(txID, tx) m.numTxs.Inc() m.bytesAvailable -= txSize m.bytesAvailableMetric.Set(float64(m.bytesAvailable)) // Mark these UTXOs as consumed in the mempool - m.consumedUTXOs.Union(inputs) + m.consumedUTXOs.Put(txID, inputs) // An explicitly added tx must not be marked as dropped. m.droppedTxIDs.Evict(txID) @@ -206,51 +173,64 @@ func (m *mempool) Add(tx *txs.Tx) error { return nil } -func (m *mempool) Has(txID ids.ID) bool { - return m.Get(txID) != nil +func (m *mempool) Get(txID ids.ID) (*txs.Tx, bool) { + return m.unissuedTxs.Get(txID) } -func (m *mempool) Get(txID ids.ID) *txs.Tx { - tx, _ := m.unissuedTxs.Get(txID) - return tx -} +func (m *mempool) Remove(txs ...*txs.Tx) { + m.lock.Lock() + defer m.lock.Unlock() -func (m *mempool) Remove(txsToRemove []*txs.Tx) { - for _, tx := range txsToRemove { + for _, tx := range txs { txID := tx.ID() - if !m.unissuedTxs.Delete(txID) { + // If the transaction is in the mempool, remove it. + if _, ok := m.consumedUTXOs.DeleteKey(txID); ok { + m.unissuedTxs.Delete(txID) + m.bytesAvailable += len(tx.Bytes()) continue } - m.numTxs.Dec() - - m.bytesAvailable += len(tx.Bytes()) - m.bytesAvailableMetric.Set(float64(m.bytesAvailable)) + // If the transaction isn't in the mempool, remove any conflicts it has. inputs := tx.Unsigned.InputIDs() - m.consumedUTXOs.Difference(inputs) + for _, removed := range m.consumedUTXOs.DeleteOverlapping(inputs) { + tx, _ := m.unissuedTxs.Get(removed.Key) + m.unissuedTxs.Delete(removed.Key) + m.bytesAvailable += len(tx.Bytes()) + } } + m.bytesAvailableMetric.Set(float64(m.bytesAvailable)) + m.numTxs.Set(float64(m.unissuedTxs.Len())) } -func (m *mempool) HasTxs() bool { - return m.unissuedTxs.Len() > 0 +func (m *mempool) Peek() (*txs.Tx, bool) { + _, tx, exists := m.unissuedTxs.Oldest() + return tx, exists } -func (m *mempool) PeekTxs(maxTxsBytes int) []*txs.Tx { - var txs []*txs.Tx - txIter := m.unissuedTxs.NewIterator() - size := 0 - for txIter.Next() { - tx := txIter.Value() - size += len(tx.Bytes()) - if size > maxTxsBytes { - return txs +func (m *mempool) Iterate(f func(tx *txs.Tx) bool) { + m.lock.RLock() + defer m.lock.RUnlock() + + itr := m.unissuedTxs.NewIterator() + for itr.Next() { + if !f(itr.Value()) { + return } - txs = append(txs, tx) } - return txs } func (m *mempool) MarkDropped(txID ids.ID, reason error) { + if errors.Is(reason, ErrMempoolFull) { + return + } + + m.lock.RLock() + defer m.lock.RUnlock() + + if _, ok := m.unissuedTxs.Get(txID); ok { + return + } + m.droppedTxIDs.Put(txID, reason) } @@ -260,7 +240,7 @@ func (m *mempool) GetDropReason(txID ids.ID) error { } func (m *mempool) RequestBuildBlock(emptyBlockPermitted bool) { - if !emptyBlockPermitted && !m.HasTxs() { + if !emptyBlockPermitted && m.unissuedTxs.Len() == 0 { return } @@ -270,37 +250,9 @@ func (m *mempool) RequestBuildBlock(emptyBlockPermitted bool) { } } -// Drops all [txs.Staker] transactions whose [StartTime] is before -// [minStartTime] from [mempool]. The dropped tx ids are returned. -// -// TODO: Remove once [StartTime] field is ignored in staker txs -func (m *mempool) DropExpiredStakerTxs(minStartTime time.Time) []ids.ID { - var droppedTxIDs []ids.ID - - txIter := m.unissuedTxs.NewIterator() - for txIter.Next() { - tx := txIter.Value() - stakerTx, ok := tx.Unsigned.(txs.Staker) - if !ok { - continue - } - - startTime := stakerTx.StartTime() - if !startTime.Before(minStartTime) { - continue - } - - txID := tx.ID() - err := fmt.Errorf( - "synchrony bound (%s) is later than staker start time (%s)", - minStartTime, - startTime, - ) - - m.Remove([]*txs.Tx{tx}) - m.MarkDropped(txID, err) // cache tx as dropped - droppedTxIDs = append(droppedTxIDs, txID) - } +func (m *mempool) Len() int { + m.lock.RLock() + defer m.lock.RUnlock() - return droppedTxIDs + return m.unissuedTxs.Len() } diff --git a/vms/platformvm/txs/mempool/mempool_test.go b/vms/platformvm/txs/mempool/mempool_test.go index 1d92132ebbcd..6d569b50c6b2 100644 --- a/vms/platformvm/txs/mempool/mempool_test.go +++ b/vms/platformvm/txs/mempool/mempool_test.go @@ -1,11 +1,9 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package mempool import ( - "errors" - "math" "testing" "time" @@ -14,7 +12,9 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/secp256k1fx" @@ -39,7 +39,12 @@ func TestBlockBuilderMaxMempoolSizeHandling(t *testing.T) { mpool.(*mempool).bytesAvailable = len(tx.Bytes()) - 1 err = mpool.Add(tx) - require.True(errors.Is(err, errMempoolFull), err, "max mempool size breached") + require.ErrorIs(err, ErrMempoolFull) + + // tx should not be marked as dropped if the mempool is full + txID := tx.ID() + mpool.MarkDropped(txID, err) + require.NoError(mpool.GetDropReason(txID)) // shortcut to simulated almost filled mempool mpool.(*mempool).bytesAvailable = len(tx.Bytes()) @@ -58,42 +63,24 @@ func TestDecisionTxsInMempool(t *testing.T) { decisionTxs, err := createTestDecisionTxs(2) require.NoError(err) - // txs must not already there before we start - require.False(mpool.HasTxs()) - for _, tx := range decisionTxs { // tx not already there - require.False(mpool.Has(tx.ID())) + _, ok := mpool.Get(tx.ID()) + require.False(ok) // we can insert require.NoError(mpool.Add(tx)) // we can get it - require.True(mpool.Has(tx.ID())) - - retrieved := mpool.Get(tx.ID()) - require.NotNil(retrieved) - require.Equal(tx, retrieved) - - // we can peek it - peeked := mpool.PeekTxs(math.MaxInt) - - // tx will be among those peeked, - // in NO PARTICULAR ORDER - found := false - for _, pk := range peeked { - if pk.ID() == tx.ID() { - found = true - break - } - } - require.True(found) + got, ok := mpool.Get(tx.ID()) + require.True(ok) + require.Equal(tx, got) // once removed it cannot be there - mpool.Remove([]*txs.Tx{tx}) + mpool.Remove(tx) - require.False(mpool.Has(tx.ID())) - require.Equal((*txs.Tx)(nil), mpool.Get(tx.ID())) + _, ok = mpool.Get(tx.ID()) + require.False(ok) // we can reinsert it again to grow the mempool require.NoError(mpool.Add(tx)) @@ -113,41 +100,23 @@ func TestProposalTxsInMempool(t *testing.T) { proposalTxs, err := createTestProposalTxs(2) require.NoError(err) - for i, tx := range proposalTxs { - require.False(mpool.Has(tx.ID())) + for _, tx := range proposalTxs { + _, ok := mpool.Get(tx.ID()) + require.False(ok) // we can insert require.NoError(mpool.Add(tx)) // we can get it - require.True(mpool.Has(tx.ID())) - - retrieved := mpool.Get(tx.ID()) - require.NotNil(retrieved) - require.Equal(tx, retrieved) - - { - // we can peek it - peeked := mpool.PeekTxs(math.MaxInt) - require.Len(peeked, i+1) - - // tx will be among those peeked, - // in NO PARTICULAR ORDER - found := false - for _, pk := range peeked { - if pk.ID() == tx.ID() { - found = true - break - } - } - require.True(found) - } + got, ok := mpool.Get(tx.ID()) + require.Equal(tx, got) + require.True(ok) // once removed it cannot be there - mpool.Remove([]*txs.Tx{tx}) + mpool.Remove(tx) - require.False(mpool.Has(tx.ID())) - require.Equal((*txs.Tx)(nil), mpool.Get(tx.ID())) + _, ok = mpool.Get(tx.ID()) + require.False(ok) // we can reinsert it again to grow the mempool require.NoError(mpool.Add(tx)) @@ -233,25 +202,99 @@ func generateAddValidatorTx(startTime uint64, endTime uint64) (*txs.Tx, error) { return txs.NewSigned(utx, txs.Codec, nil) } -func TestDropExpiredStakerTxs(t *testing.T) { +func TestPeekTxs(t *testing.T) { + require := require.New(t) + + registerer := prometheus.NewRegistry() + toEngine := make(chan common.Message, 100) + mempool, err := New("mempool", registerer, toEngine) + require.NoError(err) + + testDecisionTxs, err := createTestDecisionTxs(1) + require.NoError(err) + testProposalTxs, err := createTestProposalTxs(1) + require.NoError(err) + + tx, exists := mempool.Peek() + require.False(exists) + require.Nil(tx) + + require.NoError(mempool.Add(testDecisionTxs[0])) + require.NoError(mempool.Add(testProposalTxs[0])) + + tx, exists = mempool.Peek() + require.True(exists) + require.Equal(tx, testDecisionTxs[0]) + require.NotEqual(tx, testProposalTxs[0]) + + mempool.Remove(testDecisionTxs[0]) + + tx, exists = mempool.Peek() + require.True(exists) + require.NotEqual(tx, testDecisionTxs[0]) + require.Equal(tx, testProposalTxs[0]) + + mempool.Remove(testProposalTxs[0]) + + tx, exists = mempool.Peek() + require.False(exists) + require.Nil(tx) +} + +func TestRemoveConflicts(t *testing.T) { require := require.New(t) registerer := prometheus.NewRegistry() - mempool, err := New("mempool", registerer, nil) + toEngine := make(chan common.Message, 100) + mempool, err := New("mempool", registerer, toEngine) require.NoError(err) - tx1, err := generateAddValidatorTx(10, 20) + txs, err := createTestDecisionTxs(1) require.NoError(err) - require.NoError(mempool.Add(tx1)) + conflictTxs, err := createTestDecisionTxs(1) + require.NoError(err) + + require.NoError(mempool.Add(txs[0])) + + tx, exists := mempool.Peek() + require.True(exists) + require.Equal(tx, txs[0]) + + mempool.Remove(conflictTxs[0]) + + _, exists = mempool.Peek() + require.False(exists) +} - tx2, err := generateAddValidatorTx(8, 20) +func TestIterate(t *testing.T) { + require := require.New(t) + + registerer := prometheus.NewRegistry() + toEngine := make(chan common.Message, 100) + mempool, err := New("mempool", registerer, toEngine) + require.NoError(err) + + testDecisionTxs, err := createTestDecisionTxs(1) require.NoError(err) - require.NoError(mempool.Add(tx2)) + decisionTx := testDecisionTxs[0] - tx3, err := generateAddValidatorTx(15, 20) + testProposalTxs, err := createTestProposalTxs(1) require.NoError(err) - require.NoError(mempool.Add(tx3)) + proposalTx := testProposalTxs[0] + + require.NoError(mempool.Add(decisionTx)) + require.NoError(mempool.Add(proposalTx)) + + expectedSet := set.Of( + decisionTx.ID(), + proposalTx.ID(), + ) + + set := set.NewSet[ids.ID](2) + mempool.Iterate(func(tx *txs.Tx) bool { + set.Add(tx.ID()) + return true + }) - minStartTime := time.Unix(9, 0) - require.Len(mempool.DropExpiredStakerTxs(minStartTime), 1) + require.Equal(expectedSet, set) } diff --git a/vms/platformvm/txs/mempool/mock_mempool.go b/vms/platformvm/txs/mempool/mock_mempool.go index edc134a42ddf..c47f42e92718 100644 --- a/vms/platformvm/txs/mempool/mock_mempool.go +++ b/vms/platformvm/txs/mempool/mock_mempool.go @@ -1,15 +1,16 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool (interfaces: Mempool) +// +// Generated by this command: +// +// mockgen -package=mempool -destination=vms/platformvm/txs/mempool/mock_mempool.go github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool Mempool +// // Package mempool is a generated GoMock package. package mempool import ( reflect "reflect" - time "time" ids "github.com/ava-labs/avalanchego/ids" txs "github.com/ava-labs/avalanchego/vms/platformvm/txs" @@ -48,59 +49,22 @@ func (m *MockMempool) Add(arg0 *txs.Tx) error { } // Add indicates an expected call of Add. -func (mr *MockMempoolMockRecorder) Add(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) Add(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockMempool)(nil).Add), arg0) } -// DisableAdding mocks base method. -func (m *MockMempool) DisableAdding() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "DisableAdding") -} - -// DisableAdding indicates an expected call of DisableAdding. -func (mr *MockMempoolMockRecorder) DisableAdding() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableAdding", reflect.TypeOf((*MockMempool)(nil).DisableAdding)) -} - -// DropExpiredStakerTxs mocks base method. -func (m *MockMempool) DropExpiredStakerTxs(arg0 time.Time) []ids.ID { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DropExpiredStakerTxs", arg0) - ret0, _ := ret[0].([]ids.ID) - return ret0 -} - -// DropExpiredStakerTxs indicates an expected call of DropExpiredStakerTxs. -func (mr *MockMempoolMockRecorder) DropExpiredStakerTxs(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropExpiredStakerTxs", reflect.TypeOf((*MockMempool)(nil).DropExpiredStakerTxs), arg0) -} - -// EnableAdding mocks base method. -func (m *MockMempool) EnableAdding() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "EnableAdding") -} - -// EnableAdding indicates an expected call of EnableAdding. -func (mr *MockMempoolMockRecorder) EnableAdding() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableAdding", reflect.TypeOf((*MockMempool)(nil).EnableAdding)) -} - // Get mocks base method. -func (m *MockMempool) Get(arg0 ids.ID) *txs.Tx { +func (m *MockMempool) Get(arg0 ids.ID) (*txs.Tx, bool) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", arg0) ret0, _ := ret[0].(*txs.Tx) - return ret0 + ret1, _ := ret[1].(bool) + return ret0, ret1 } // Get indicates an expected call of Get. -func (mr *MockMempoolMockRecorder) Get(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) Get(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMempool)(nil).Get), arg0) } @@ -114,37 +78,35 @@ func (m *MockMempool) GetDropReason(arg0 ids.ID) error { } // GetDropReason indicates an expected call of GetDropReason. -func (mr *MockMempoolMockRecorder) GetDropReason(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) GetDropReason(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDropReason", reflect.TypeOf((*MockMempool)(nil).GetDropReason), arg0) } -// Has mocks base method. -func (m *MockMempool) Has(arg0 ids.ID) bool { +// Iterate mocks base method. +func (m *MockMempool) Iterate(arg0 func(*txs.Tx) bool) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Has", arg0) - ret0, _ := ret[0].(bool) - return ret0 + m.ctrl.Call(m, "Iterate", arg0) } -// Has indicates an expected call of Has. -func (mr *MockMempoolMockRecorder) Has(arg0 interface{}) *gomock.Call { +// Iterate indicates an expected call of Iterate. +func (mr *MockMempoolMockRecorder) Iterate(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockMempool)(nil).Has), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockMempool)(nil).Iterate), arg0) } -// HasTxs mocks base method. -func (m *MockMempool) HasTxs() bool { +// Len mocks base method. +func (m *MockMempool) Len() int { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HasTxs") - ret0, _ := ret[0].(bool) + ret := m.ctrl.Call(m, "Len") + ret0, _ := ret[0].(int) return ret0 } -// HasTxs indicates an expected call of HasTxs. -func (mr *MockMempoolMockRecorder) HasTxs() *gomock.Call { +// Len indicates an expected call of Len. +func (mr *MockMempoolMockRecorder) Len() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasTxs", reflect.TypeOf((*MockMempool)(nil).HasTxs)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockMempool)(nil).Len)) } // MarkDropped mocks base method. @@ -154,35 +116,40 @@ func (m *MockMempool) MarkDropped(arg0 ids.ID, arg1 error) { } // MarkDropped indicates an expected call of MarkDropped. -func (mr *MockMempoolMockRecorder) MarkDropped(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) MarkDropped(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkDropped", reflect.TypeOf((*MockMempool)(nil).MarkDropped), arg0, arg1) } -// PeekTxs mocks base method. -func (m *MockMempool) PeekTxs(arg0 int) []*txs.Tx { +// Peek mocks base method. +func (m *MockMempool) Peek() (*txs.Tx, bool) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PeekTxs", arg0) - ret0, _ := ret[0].([]*txs.Tx) - return ret0 + ret := m.ctrl.Call(m, "Peek") + ret0, _ := ret[0].(*txs.Tx) + ret1, _ := ret[1].(bool) + return ret0, ret1 } -// PeekTxs indicates an expected call of PeekTxs. -func (mr *MockMempoolMockRecorder) PeekTxs(arg0 interface{}) *gomock.Call { +// Peek indicates an expected call of Peek. +func (mr *MockMempoolMockRecorder) Peek() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PeekTxs", reflect.TypeOf((*MockMempool)(nil).PeekTxs), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Peek", reflect.TypeOf((*MockMempool)(nil).Peek)) } // Remove mocks base method. -func (m *MockMempool) Remove(arg0 []*txs.Tx) { +func (m *MockMempool) Remove(arg0 ...*txs.Tx) { m.ctrl.T.Helper() - m.ctrl.Call(m, "Remove", arg0) + varargs := []any{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Remove", varargs...) } // Remove indicates an expected call of Remove. -func (mr *MockMempoolMockRecorder) Remove(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) Remove(arg0 ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockMempool)(nil).Remove), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockMempool)(nil).Remove), arg0...) } // RequestBuildBlock mocks base method. @@ -192,7 +159,7 @@ func (m *MockMempool) RequestBuildBlock(arg0 bool) { } // RequestBuildBlock indicates an expected call of RequestBuildBlock. -func (mr *MockMempoolMockRecorder) RequestBuildBlock(arg0 interface{}) *gomock.Call { +func (mr *MockMempoolMockRecorder) RequestBuildBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestBuildBlock", reflect.TypeOf((*MockMempool)(nil).RequestBuildBlock), arg0) } diff --git a/vms/platformvm/txs/mock_staker.go b/vms/platformvm/txs/mock_staker.go deleted file mode 100644 index e01ca66cf9e3..000000000000 --- a/vms/platformvm/txs/mock_staker.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/vms/platformvm/txs (interfaces: Staker) - -// Package txs is a generated GoMock package. -package txs - -import ( - reflect "reflect" - time "time" - - ids "github.com/ava-labs/avalanchego/ids" - bls "github.com/ava-labs/avalanchego/utils/crypto/bls" - gomock "go.uber.org/mock/gomock" -) - -// MockStaker is a mock of Staker interface. -type MockStaker struct { - ctrl *gomock.Controller - recorder *MockStakerMockRecorder -} - -// MockStakerMockRecorder is the mock recorder for MockStaker. -type MockStakerMockRecorder struct { - mock *MockStaker -} - -// NewMockStaker creates a new mock instance. -func NewMockStaker(ctrl *gomock.Controller) *MockStaker { - mock := &MockStaker{ctrl: ctrl} - mock.recorder = &MockStakerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockStaker) EXPECT() *MockStakerMockRecorder { - return m.recorder -} - -// CurrentPriority mocks base method. -func (m *MockStaker) CurrentPriority() Priority { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CurrentPriority") - ret0, _ := ret[0].(Priority) - return ret0 -} - -// CurrentPriority indicates an expected call of CurrentPriority. -func (mr *MockStakerMockRecorder) CurrentPriority() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentPriority", reflect.TypeOf((*MockStaker)(nil).CurrentPriority)) -} - -// EndTime mocks base method. -func (m *MockStaker) EndTime() time.Time { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EndTime") - ret0, _ := ret[0].(time.Time) - return ret0 -} - -// EndTime indicates an expected call of EndTime. -func (mr *MockStakerMockRecorder) EndTime() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EndTime", reflect.TypeOf((*MockStaker)(nil).EndTime)) -} - -// NodeID mocks base method. -func (m *MockStaker) NodeID() ids.NodeID { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NodeID") - ret0, _ := ret[0].(ids.NodeID) - return ret0 -} - -// NodeID indicates an expected call of NodeID. -func (mr *MockStakerMockRecorder) NodeID() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeID", reflect.TypeOf((*MockStaker)(nil).NodeID)) -} - -// PendingPriority mocks base method. -func (m *MockStaker) PendingPriority() Priority { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PendingPriority") - ret0, _ := ret[0].(Priority) - return ret0 -} - -// PendingPriority indicates an expected call of PendingPriority. -func (mr *MockStakerMockRecorder) PendingPriority() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PendingPriority", reflect.TypeOf((*MockStaker)(nil).PendingPriority)) -} - -// PublicKey mocks base method. -func (m *MockStaker) PublicKey() (*bls.PublicKey, bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PublicKey") - ret0, _ := ret[0].(*bls.PublicKey) - ret1, _ := ret[1].(bool) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// PublicKey indicates an expected call of PublicKey. -func (mr *MockStakerMockRecorder) PublicKey() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublicKey", reflect.TypeOf((*MockStaker)(nil).PublicKey)) -} - -// StartTime mocks base method. -func (m *MockStaker) StartTime() time.Time { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StartTime") - ret0, _ := ret[0].(time.Time) - return ret0 -} - -// StartTime indicates an expected call of StartTime. -func (mr *MockStakerMockRecorder) StartTime() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTime", reflect.TypeOf((*MockStaker)(nil).StartTime)) -} - -// SubnetID mocks base method. -func (m *MockStaker) SubnetID() ids.ID { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubnetID") - ret0, _ := ret[0].(ids.ID) - return ret0 -} - -// SubnetID indicates an expected call of SubnetID. -func (mr *MockStakerMockRecorder) SubnetID() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetID", reflect.TypeOf((*MockStaker)(nil).SubnetID)) -} - -// Weight mocks base method. -func (m *MockStaker) Weight() uint64 { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Weight") - ret0, _ := ret[0].(uint64) - return ret0 -} - -// Weight indicates an expected call of Weight. -func (mr *MockStakerMockRecorder) Weight() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Weight", reflect.TypeOf((*MockStaker)(nil).Weight)) -} diff --git a/vms/platformvm/txs/mock_staker_tx.go b/vms/platformvm/txs/mock_staker_tx.go new file mode 100644 index 000000000000..2e01b15b3813 --- /dev/null +++ b/vms/platformvm/txs/mock_staker_tx.go @@ -0,0 +1,265 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: vms/platformvm/txs/staker_tx.go +// +// Generated by this command: +// +// mockgen -source=vms/platformvm/txs/staker_tx.go -destination=vms/platformvm/txs/mock_staker_tx.go -package=txs -exclude_interfaces=ValidatorTx,DelegatorTx,StakerTx,PermissionlessStaker +// + +// Package txs is a generated GoMock package. +package txs + +import ( + reflect "reflect" + time "time" + + ids "github.com/ava-labs/avalanchego/ids" + bls "github.com/ava-labs/avalanchego/utils/crypto/bls" + gomock "go.uber.org/mock/gomock" +) + +// MockStaker is a mock of Staker interface. +type MockStaker struct { + ctrl *gomock.Controller + recorder *MockStakerMockRecorder +} + +// MockStakerMockRecorder is the mock recorder for MockStaker. +type MockStakerMockRecorder struct { + mock *MockStaker +} + +// NewMockStaker creates a new mock instance. +func NewMockStaker(ctrl *gomock.Controller) *MockStaker { + mock := &MockStaker{ctrl: ctrl} + mock.recorder = &MockStakerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStaker) EXPECT() *MockStakerMockRecorder { + return m.recorder +} + +// CurrentPriority mocks base method. +func (m *MockStaker) CurrentPriority() Priority { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CurrentPriority") + ret0, _ := ret[0].(Priority) + return ret0 +} + +// CurrentPriority indicates an expected call of CurrentPriority. +func (mr *MockStakerMockRecorder) CurrentPriority() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentPriority", reflect.TypeOf((*MockStaker)(nil).CurrentPriority)) +} + +// EndTime mocks base method. +func (m *MockStaker) EndTime() time.Time { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EndTime") + ret0, _ := ret[0].(time.Time) + return ret0 +} + +// EndTime indicates an expected call of EndTime. +func (mr *MockStakerMockRecorder) EndTime() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EndTime", reflect.TypeOf((*MockStaker)(nil).EndTime)) +} + +// NodeID mocks base method. +func (m *MockStaker) NodeID() ids.NodeID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NodeID") + ret0, _ := ret[0].(ids.NodeID) + return ret0 +} + +// NodeID indicates an expected call of NodeID. +func (mr *MockStakerMockRecorder) NodeID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeID", reflect.TypeOf((*MockStaker)(nil).NodeID)) +} + +// PublicKey mocks base method. +func (m *MockStaker) PublicKey() (*bls.PublicKey, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PublicKey") + ret0, _ := ret[0].(*bls.PublicKey) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// PublicKey indicates an expected call of PublicKey. +func (mr *MockStakerMockRecorder) PublicKey() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublicKey", reflect.TypeOf((*MockStaker)(nil).PublicKey)) +} + +// SubnetID mocks base method. +func (m *MockStaker) SubnetID() ids.ID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetID") + ret0, _ := ret[0].(ids.ID) + return ret0 +} + +// SubnetID indicates an expected call of SubnetID. +func (mr *MockStakerMockRecorder) SubnetID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetID", reflect.TypeOf((*MockStaker)(nil).SubnetID)) +} + +// Weight mocks base method. +func (m *MockStaker) Weight() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Weight") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// Weight indicates an expected call of Weight. +func (mr *MockStakerMockRecorder) Weight() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Weight", reflect.TypeOf((*MockStaker)(nil).Weight)) +} + +// MockScheduledStaker is a mock of ScheduledStaker interface. +type MockScheduledStaker struct { + ctrl *gomock.Controller + recorder *MockScheduledStakerMockRecorder +} + +// MockScheduledStakerMockRecorder is the mock recorder for MockScheduledStaker. +type MockScheduledStakerMockRecorder struct { + mock *MockScheduledStaker +} + +// NewMockScheduledStaker creates a new mock instance. +func NewMockScheduledStaker(ctrl *gomock.Controller) *MockScheduledStaker { + mock := &MockScheduledStaker{ctrl: ctrl} + mock.recorder = &MockScheduledStakerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockScheduledStaker) EXPECT() *MockScheduledStakerMockRecorder { + return m.recorder +} + +// CurrentPriority mocks base method. +func (m *MockScheduledStaker) CurrentPriority() Priority { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CurrentPriority") + ret0, _ := ret[0].(Priority) + return ret0 +} + +// CurrentPriority indicates an expected call of CurrentPriority. +func (mr *MockScheduledStakerMockRecorder) CurrentPriority() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentPriority", reflect.TypeOf((*MockScheduledStaker)(nil).CurrentPriority)) +} + +// EndTime mocks base method. +func (m *MockScheduledStaker) EndTime() time.Time { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EndTime") + ret0, _ := ret[0].(time.Time) + return ret0 +} + +// EndTime indicates an expected call of EndTime. +func (mr *MockScheduledStakerMockRecorder) EndTime() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EndTime", reflect.TypeOf((*MockScheduledStaker)(nil).EndTime)) +} + +// NodeID mocks base method. +func (m *MockScheduledStaker) NodeID() ids.NodeID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NodeID") + ret0, _ := ret[0].(ids.NodeID) + return ret0 +} + +// NodeID indicates an expected call of NodeID. +func (mr *MockScheduledStakerMockRecorder) NodeID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeID", reflect.TypeOf((*MockScheduledStaker)(nil).NodeID)) +} + +// PendingPriority mocks base method. +func (m *MockScheduledStaker) PendingPriority() Priority { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PendingPriority") + ret0, _ := ret[0].(Priority) + return ret0 +} + +// PendingPriority indicates an expected call of PendingPriority. +func (mr *MockScheduledStakerMockRecorder) PendingPriority() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PendingPriority", reflect.TypeOf((*MockScheduledStaker)(nil).PendingPriority)) +} + +// PublicKey mocks base method. +func (m *MockScheduledStaker) PublicKey() (*bls.PublicKey, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PublicKey") + ret0, _ := ret[0].(*bls.PublicKey) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// PublicKey indicates an expected call of PublicKey. +func (mr *MockScheduledStakerMockRecorder) PublicKey() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublicKey", reflect.TypeOf((*MockScheduledStaker)(nil).PublicKey)) +} + +// StartTime mocks base method. +func (m *MockScheduledStaker) StartTime() time.Time { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartTime") + ret0, _ := ret[0].(time.Time) + return ret0 +} + +// StartTime indicates an expected call of StartTime. +func (mr *MockScheduledStakerMockRecorder) StartTime() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTime", reflect.TypeOf((*MockScheduledStaker)(nil).StartTime)) +} + +// SubnetID mocks base method. +func (m *MockScheduledStaker) SubnetID() ids.ID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubnetID") + ret0, _ := ret[0].(ids.ID) + return ret0 +} + +// SubnetID indicates an expected call of SubnetID. +func (mr *MockScheduledStakerMockRecorder) SubnetID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubnetID", reflect.TypeOf((*MockScheduledStaker)(nil).SubnetID)) +} + +// Weight mocks base method. +func (m *MockScheduledStaker) Weight() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Weight") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// Weight indicates an expected call of Weight. +func (mr *MockScheduledStakerMockRecorder) Weight() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Weight", reflect.TypeOf((*MockScheduledStaker)(nil).Weight)) +} diff --git a/vms/platformvm/txs/mock_unsigned_tx.go b/vms/platformvm/txs/mock_unsigned_tx.go index 9d9ec6c94dd4..f775c5203a4f 100644 --- a/vms/platformvm/txs/mock_unsigned_tx.go +++ b/vms/platformvm/txs/mock_unsigned_tx.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/vms/platformvm/txs (interfaces: UnsignedTx) +// Source: vms/platformvm/txs/unsigned_tx.go +// +// Generated by this command: +// +// mockgen -source=vms/platformvm/txs/unsigned_tx.go -destination=vms/platformvm/txs/mock_unsigned_tx.go -package=txs -exclude_interfaces= +// // Package txs is a generated GoMock package. package txs @@ -55,15 +57,15 @@ func (mr *MockUnsignedTxMockRecorder) Bytes() *gomock.Call { } // InitCtx mocks base method. -func (m *MockUnsignedTx) InitCtx(arg0 *snow.Context) { +func (m *MockUnsignedTx) InitCtx(ctx *snow.Context) { m.ctrl.T.Helper() - m.ctrl.Call(m, "InitCtx", arg0) + m.ctrl.Call(m, "InitCtx", ctx) } // InitCtx indicates an expected call of InitCtx. -func (mr *MockUnsignedTxMockRecorder) InitCtx(arg0 interface{}) *gomock.Call { +func (mr *MockUnsignedTxMockRecorder) InitCtx(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*MockUnsignedTx)(nil).InitCtx), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitCtx", reflect.TypeOf((*MockUnsignedTx)(nil).InitCtx), ctx) } // InputIDs mocks base method. @@ -95,41 +97,41 @@ func (mr *MockUnsignedTxMockRecorder) Outputs() *gomock.Call { } // SetBytes mocks base method. -func (m *MockUnsignedTx) SetBytes(arg0 []byte) { +func (m *MockUnsignedTx) SetBytes(unsignedBytes []byte) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetBytes", arg0) + m.ctrl.Call(m, "SetBytes", unsignedBytes) } // SetBytes indicates an expected call of SetBytes. -func (mr *MockUnsignedTxMockRecorder) SetBytes(arg0 interface{}) *gomock.Call { +func (mr *MockUnsignedTxMockRecorder) SetBytes(unsignedBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBytes", reflect.TypeOf((*MockUnsignedTx)(nil).SetBytes), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBytes", reflect.TypeOf((*MockUnsignedTx)(nil).SetBytes), unsignedBytes) } // SyntacticVerify mocks base method. -func (m *MockUnsignedTx) SyntacticVerify(arg0 *snow.Context) error { +func (m *MockUnsignedTx) SyntacticVerify(ctx *snow.Context) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SyntacticVerify", arg0) + ret := m.ctrl.Call(m, "SyntacticVerify", ctx) ret0, _ := ret[0].(error) return ret0 } // SyntacticVerify indicates an expected call of SyntacticVerify. -func (mr *MockUnsignedTxMockRecorder) SyntacticVerify(arg0 interface{}) *gomock.Call { +func (mr *MockUnsignedTxMockRecorder) SyntacticVerify(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyntacticVerify", reflect.TypeOf((*MockUnsignedTx)(nil).SyntacticVerify), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyntacticVerify", reflect.TypeOf((*MockUnsignedTx)(nil).SyntacticVerify), ctx) } // Visit mocks base method. -func (m *MockUnsignedTx) Visit(arg0 Visitor) error { +func (m *MockUnsignedTx) Visit(visitor Visitor) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Visit", arg0) + ret := m.ctrl.Call(m, "Visit", visitor) ret0, _ := ret[0].(error) return ret0 } // Visit indicates an expected call of Visit. -func (mr *MockUnsignedTxMockRecorder) Visit(arg0 interface{}) *gomock.Call { +func (mr *MockUnsignedTxMockRecorder) Visit(visitor any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Visit", reflect.TypeOf((*MockUnsignedTx)(nil).Visit), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Visit", reflect.TypeOf((*MockUnsignedTx)(nil).Visit), visitor) } diff --git a/vms/platformvm/txs/priorities.go b/vms/platformvm/txs/priorities.go index 6a4fb4dc10a9..a324bdae8e1c 100644 --- a/vms/platformvm/txs/priorities.go +++ b/vms/platformvm/txs/priorities.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/priorities_test.go b/vms/platformvm/txs/priorities_test.go index ce266d5d7adb..5e629a853ca1 100644 --- a/vms/platformvm/txs/priorities_test.go +++ b/vms/platformvm/txs/priorities_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/remove_subnet_validator_tx.go b/vms/platformvm/txs/remove_subnet_validator_tx.go index 2221c2f345ca..ef55cccea290 100644 --- a/vms/platformvm/txs/remove_subnet_validator_tx.go +++ b/vms/platformvm/txs/remove_subnet_validator_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/remove_subnet_validator_tx_test.go b/vms/platformvm/txs/remove_subnet_validator_tx_test.go index 4b1f381c039b..2890b6d8d103 100644 --- a/vms/platformvm/txs/remove_subnet_validator_tx_test.go +++ b/vms/platformvm/txs/remove_subnet_validator_tx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -157,7 +157,7 @@ func TestRemoveSubnetValidatorTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x03, } var unsignedSimpleRemoveValidatorTx UnsignedTx = simpleRemoveValidatorTx - unsignedSimpleRemoveValidatorTxBytes, err := Codec.Marshal(Version, &unsignedSimpleRemoveValidatorTx) + unsignedSimpleRemoveValidatorTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleRemoveValidatorTx) require.NoError(err) require.Equal(expectedUnsignedSimpleRemoveValidatorTxBytes, unsignedSimpleRemoveValidatorTxBytes) @@ -417,7 +417,7 @@ func TestRemoveSubnetValidatorTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, } var unsignedComplexRemoveValidatorTx UnsignedTx = complexRemoveValidatorTx - unsignedComplexRemoveValidatorTxBytes, err := Codec.Marshal(Version, &unsignedComplexRemoveValidatorTx) + unsignedComplexRemoveValidatorTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexRemoveValidatorTx) require.NoError(err) require.Equal(expectedUnsignedComplexRemoveValidatorTxBytes, unsignedComplexRemoveValidatorTxBytes) diff --git a/vms/platformvm/txs/reward_validator_tx.go b/vms/platformvm/txs/reward_validator_tx.go index d4b579f1b95b..01b1e34bde46 100644 --- a/vms/platformvm/txs/reward_validator_tx.go +++ b/vms/platformvm/txs/reward_validator_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/staker_tx.go b/vms/platformvm/txs/staker_tx.go index 049d3519375f..8adb1ac23f7d 100644 --- a/vms/platformvm/txs/staker_tx.go +++ b/vms/platformvm/txs/staker_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -48,9 +48,13 @@ type Staker interface { // PublicKey returns the BLS public key registered by this transaction. If // there was no key registered by this transaction, it will return false. PublicKey() (*bls.PublicKey, bool, error) - StartTime() time.Time EndTime() time.Time Weight() uint64 - PendingPriority() Priority CurrentPriority() Priority } + +type ScheduledStaker interface { + Staker + StartTime() time.Time + PendingPriority() Priority +} diff --git a/vms/platformvm/txs/subnet_validator.go b/vms/platformvm/txs/subnet_validator.go index d9da9d31b739..a7c683f35a8f 100644 --- a/vms/platformvm/txs/subnet_validator.go +++ b/vms/platformvm/txs/subnet_validator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/subnet_validator_test.go b/vms/platformvm/txs/subnet_validator_test.go index 7fcbf3e44e4e..cdfbeaf159a5 100644 --- a/vms/platformvm/txs/subnet_validator_test.go +++ b/vms/platformvm/txs/subnet_validator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/transfer_subnet_ownership_tx.go b/vms/platformvm/txs/transfer_subnet_ownership_tx.go index 78dbf28b48b4..4fa2807809ce 100644 --- a/vms/platformvm/txs/transfer_subnet_ownership_tx.go +++ b/vms/platformvm/txs/transfer_subnet_ownership_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/transfer_subnet_ownership_tx_test.go b/vms/platformvm/txs/transfer_subnet_ownership_tx_test.go index e8cddeb3e1d0..39866c138e2b 100644 --- a/vms/platformvm/txs/transfer_subnet_ownership_tx_test.go +++ b/vms/platformvm/txs/transfer_subnet_ownership_tx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -164,7 +164,7 @@ func TestTransferSubnetOwnershipTxSerialization(t *testing.T) { 0x44, 0x55, 0x66, 0x77, } var unsignedSimpleTransferSubnetOwnershipTx UnsignedTx = simpleTransferSubnetOwnershipTx - unsignedSimpleTransferSubnetOwnershipTxBytes, err := Codec.Marshal(Version, &unsignedSimpleTransferSubnetOwnershipTx) + unsignedSimpleTransferSubnetOwnershipTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleTransferSubnetOwnershipTx) require.NoError(err) require.Equal(expectedUnsignedSimpleTransferSubnetOwnershipTxBytes, unsignedSimpleTransferSubnetOwnershipTxBytes) @@ -438,7 +438,7 @@ func TestTransferSubnetOwnershipTxSerialization(t *testing.T) { 0x44, 0x55, 0x66, 0x77, } var unsignedComplexTransferSubnetOwnershipTx UnsignedTx = complexTransferSubnetOwnershipTx - unsignedComplexTransferSubnetOwnershipTxBytes, err := Codec.Marshal(Version, &unsignedComplexTransferSubnetOwnershipTx) + unsignedComplexTransferSubnetOwnershipTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexTransferSubnetOwnershipTx) require.NoError(err) require.Equal(expectedUnsignedComplexTransferSubnetOwnershipTxBytes, unsignedComplexTransferSubnetOwnershipTxBytes) diff --git a/vms/platformvm/txs/transform_subnet_tx.go b/vms/platformvm/txs/transform_subnet_tx.go index f540ea674f4c..1ba543e1aa05 100644 --- a/vms/platformvm/txs/transform_subnet_tx.go +++ b/vms/platformvm/txs/transform_subnet_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/transform_subnet_tx_test.go b/vms/platformvm/txs/transform_subnet_tx_test.go index 4b88fd60ef3a..d5a237667ded 100644 --- a/vms/platformvm/txs/transform_subnet_tx_test.go +++ b/vms/platformvm/txs/transform_subnet_tx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -223,7 +223,7 @@ func TestTransformSubnetTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x03, } var unsignedSimpleTransformTx UnsignedTx = simpleTransformTx - unsignedSimpleTransformTxBytes, err := Codec.Marshal(Version, &unsignedSimpleTransformTx) + unsignedSimpleTransformTxBytes, err := Codec.Marshal(CodecVersion, &unsignedSimpleTransformTx) require.NoError(err) require.Equal(expectedUnsignedSimpleTransformTxBytes, unsignedSimpleTransformTxBytes) @@ -520,7 +520,7 @@ func TestTransformSubnetTxSerialization(t *testing.T) { 0x00, 0x00, 0x00, 0x00, } var unsignedComplexTransformTx UnsignedTx = complexTransformTx - unsignedComplexTransformTxBytes, err := Codec.Marshal(Version, &unsignedComplexTransformTx) + unsignedComplexTransformTxBytes, err := Codec.Marshal(CodecVersion, &unsignedComplexTransformTx) require.NoError(err) require.Equal(expectedUnsignedComplexTransformTxBytes, unsignedComplexTransformTxBytes) diff --git a/vms/platformvm/txs/tx.go b/vms/platformvm/txs/tx.go index 27cc812e5a79..9874f66e0468 100644 --- a/vms/platformvm/txs/tx.go +++ b/vms/platformvm/txs/tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/p2p/gossip" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/hashing" @@ -18,6 +19,8 @@ import ( ) var ( + _ gossip.Gossipable = (*Tx)(nil) + ErrNilSignedTx = errors.New("nil signed tx is not valid") errSignedTxNotInitialized = errors.New("signed tx was never initialized and is not valid") @@ -45,12 +48,12 @@ func NewSigned( } func (tx *Tx) Initialize(c codec.Manager) error { - signedBytes, err := c.Marshal(Version, tx) + signedBytes, err := c.Marshal(CodecVersion, tx) if err != nil { return fmt.Errorf("couldn't marshal ProposalTx: %w", err) } - unsignedBytesLen, err := c.Size(Version, &tx.Unsigned) + unsignedBytesLen, err := c.Size(CodecVersion, &tx.Unsigned) if err != nil { return fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err) } @@ -75,7 +78,7 @@ func Parse(c codec.Manager, signedBytes []byte) (*Tx, error) { return nil, fmt.Errorf("couldn't parse tx: %w", err) } - unsignedBytesLen, err := c.Size(Version, &tx.Unsigned) + unsignedBytesLen, err := c.Size(CodecVersion, &tx.Unsigned) if err != nil { return nil, fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err) } @@ -93,6 +96,10 @@ func (tx *Tx) ID() ids.ID { return tx.TxID } +func (tx *Tx) GossipID() ids.ID { + return tx.TxID +} + // UTXOs returns the UTXOs transaction is producing. func (tx *Tx) UTXOs() []*avax.UTXO { outs := tx.Unsigned.Outputs() @@ -125,7 +132,7 @@ func (tx *Tx) SyntacticVerify(ctx *snow.Context) error { // Note: We explicitly pass the codec in Sign since we may need to sign P-Chain // genesis txs whose length exceed the max length of txs.Codec. func (tx *Tx) Sign(c codec.Manager, signers [][]*secp256k1.PrivateKey) error { - unsignedBytes, err := c.Marshal(Version, &tx.Unsigned) + unsignedBytes, err := c.Marshal(CodecVersion, &tx.Unsigned) if err != nil { return fmt.Errorf("couldn't marshal UnsignedTx: %w", err) } @@ -146,7 +153,7 @@ func (tx *Tx) Sign(c codec.Manager, signers [][]*secp256k1.PrivateKey) error { tx.Creds = append(tx.Creds, cred) // Attach credential } - signedBytes, err := c.Marshal(Version, tx) + signedBytes, err := c.Marshal(CodecVersion, tx) if err != nil { return fmt.Errorf("couldn't marshal ProposalTx: %w", err) } diff --git a/vms/platformvm/txs/txheap/by_end_time.go b/vms/platformvm/txs/txheap/by_end_time.go index 2499ce971bc9..9cbba82cef07 100644 --- a/vms/platformvm/txs/txheap/by_end_time.go +++ b/vms/platformvm/txs/txheap/by_end_time.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txheap diff --git a/vms/platformvm/txs/txheap/by_end_time_test.go b/vms/platformvm/txs/txheap/by_end_time_test.go index 5d95a2c66ad7..a629b7b1c3bd 100644 --- a/vms/platformvm/txs/txheap/by_end_time_test.go +++ b/vms/platformvm/txs/txheap/by_end_time_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txheap diff --git a/vms/platformvm/txs/txheap/heap.go b/vms/platformvm/txs/txheap/heap.go index 3727bb891d92..7c9e33d3f989 100644 --- a/vms/platformvm/txs/txheap/heap.go +++ b/vms/platformvm/txs/txheap/heap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txheap diff --git a/vms/platformvm/txs/unsigned_tx.go b/vms/platformvm/txs/unsigned_tx.go index 7fe1702b0197..5b3e62dd1c33 100644 --- a/vms/platformvm/txs/unsigned_tx.go +++ b/vms/platformvm/txs/unsigned_tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/txs/validator.go b/vms/platformvm/txs/validator.go index ea7d048f5074..726ba23b1c5d 100644 --- a/vms/platformvm/txs/validator.go +++ b/vms/platformvm/txs/validator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -40,11 +40,6 @@ func (v *Validator) EndTime() time.Time { return time.Unix(int64(v.End), 0) } -// Duration is the amount of time that this validator will be in the validator set -func (v *Validator) Duration() time.Duration { - return v.EndTime().Sub(v.StartTime()) -} - // Weight is this validator's weight when sampling func (v *Validator) Weight() uint64 { return v.Wght diff --git a/vms/platformvm/txs/validator_test.go b/vms/platformvm/txs/validator_test.go index fbef50981a14..0b9e749ca009 100644 --- a/vms/platformvm/txs/validator_test.go +++ b/vms/platformvm/txs/validator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs @@ -36,7 +36,7 @@ func TestBoundedBy(t *testing.T) { End: bEndTime, Wght: defaultWeight, } - require.False(BoundedBy(a.StartTime(), b.EndTime(), b.StartTime(), b.EndTime())) + require.False(BoundedBy(a.StartTime(), a.EndTime(), b.StartTime(), b.EndTime())) require.False(BoundedBy(b.StartTime(), b.EndTime(), a.StartTime(), a.EndTime())) // case 2: a starts, b starts, a finishes, b finishes diff --git a/vms/platformvm/txs/visitor.go b/vms/platformvm/txs/visitor.go index 05a21c355801..b3fc55af4cf3 100644 --- a/vms/platformvm/txs/visitor.go +++ b/vms/platformvm/txs/visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package txs diff --git a/vms/platformvm/utxo/handler.go b/vms/platformvm/utxo/handler.go index 2d652103fee2..f22a76fd0b8f 100644 --- a/vms/platformvm/utxo/handler.go +++ b/vms/platformvm/utxo/handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utxo @@ -557,7 +557,7 @@ func (h *handler) VerifySpendUTXOs( return fmt.Errorf("expected fx.Owned but got %T", out) } owner := owned.Owners() - ownerBytes, err := txs.Codec.Marshal(txs.Version, owner) + ownerBytes, err := txs.Codec.Marshal(txs.CodecVersion, owner) if err != nil { return fmt.Errorf("couldn't marshal owner: %w", err) } @@ -606,7 +606,7 @@ func (h *handler) VerifySpendUTXOs( return fmt.Errorf("expected fx.Owned but got %T", out) } owner := owned.Owners() - ownerBytes, err := txs.Codec.Marshal(txs.Version, owner) + ownerBytes, err := txs.Codec.Marshal(txs.CodecVersion, owner) if err != nil { return fmt.Errorf("couldn't marshal owner: %w", err) } diff --git a/vms/platformvm/utxo/handler_test.go b/vms/platformvm/utxo/handler_test.go index f74286987d32..d0224ed4666a 100644 --- a/vms/platformvm/utxo/handler_test.go +++ b/vms/platformvm/utxo/handler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utxo @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -39,8 +39,10 @@ func TestVerifySpendUTXOs(t *testing.T) { require.NoError(t, fx.InitializeVM(&secp256k1fx.TestVM{})) require.NoError(t, fx.Bootstrapped()) + ctx := snowtest.Context(t, snowtest.PChainID) + h := &handler{ - ctx: snow.DefaultContextTest(), + ctx: ctx, clk: &mockable.Clock{}, fx: fx, } diff --git a/vms/platformvm/utxo/mock_verifier.go b/vms/platformvm/utxo/mock_verifier.go index 1c1d9c9c75f9..0447806cb7ae 100644 --- a/vms/platformvm/utxo/mock_verifier.go +++ b/vms/platformvm/utxo/mock_verifier.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/platformvm/utxo (interfaces: Verifier) +// +// Generated by this command: +// +// mockgen -package=utxo -destination=vms/platformvm/utxo/mock_verifier.go github.com/ava-labs/avalanchego/vms/platformvm/utxo Verifier +// // Package utxo is a generated GoMock package. package utxo @@ -49,7 +51,7 @@ func (m *MockVerifier) VerifySpend(arg0 txs.UnsignedTx, arg1 avax.UTXOGetter, ar } // VerifySpend indicates an expected call of VerifySpend. -func (mr *MockVerifierMockRecorder) VerifySpend(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockVerifierMockRecorder) VerifySpend(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifySpend", reflect.TypeOf((*MockVerifier)(nil).VerifySpend), arg0, arg1, arg2, arg3, arg4, arg5) } @@ -63,7 +65,7 @@ func (m *MockVerifier) VerifySpendUTXOs(arg0 txs.UnsignedTx, arg1 []*avax.UTXO, } // VerifySpendUTXOs indicates an expected call of VerifySpendUTXOs. -func (mr *MockVerifierMockRecorder) VerifySpendUTXOs(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockVerifierMockRecorder) VerifySpendUTXOs(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifySpendUTXOs", reflect.TypeOf((*MockVerifier)(nil).VerifySpendUTXOs), arg0, arg1, arg2, arg3, arg4, arg5) } diff --git a/vms/platformvm/validator_set_property_test.go b/vms/platformvm/validator_set_property_test.go index 2ac0d4358d7d..5ca5bfd6c241 100644 --- a/vms/platformvm/validator_set_property_test.go +++ b/vms/platformvm/validator_set_property_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm @@ -26,6 +26,7 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" @@ -373,31 +374,15 @@ func addPrimaryValidatorWithoutBLSKey(vm *VM, data *validatorInputData) (*state. } func internalAddValidator(vm *VM, signedTx *txs.Tx) (*state.Staker, error) { - stakerTx := signedTx.Unsigned.(txs.StakerTx) - if err := vm.Network.IssueTx(context.Background(), signedTx); err != nil { - return nil, fmt.Errorf("could not add tx to mempool: %w", err) - } + vm.ctx.Lock.Unlock() + err := vm.issueTx(context.Background(), signedTx) + vm.ctx.Lock.Lock() - blk, err := vm.Builder.BuildBlock(context.Background()) if err != nil { - return nil, fmt.Errorf("failed building block: %w", err) - } - if err := blk.Verify(context.Background()); err != nil { - return nil, fmt.Errorf("failed verifying block: %w", err) - } - if err := blk.Accept(context.Background()); err != nil { - return nil, fmt.Errorf("failed accepting block: %w", err) - } - if err := vm.SetPreference(context.Background(), vm.manager.LastAccepted()); err != nil { - return nil, fmt.Errorf("failed setting preference: %w", err) + return nil, fmt.Errorf("could not add tx to mempool: %w", err) } - // move time ahead, promoting the validator to current - currentTime := stakerTx.StartTime() - vm.clock.Set(currentTime) - vm.state.SetTimestamp(currentTime) - - blk, err = vm.Builder.BuildBlock(context.Background()) + blk, err := vm.Builder.BuildBlock(context.Background()) if err != nil { return nil, fmt.Errorf("failed building block: %w", err) } @@ -411,6 +396,7 @@ func internalAddValidator(vm *VM, signedTx *txs.Tx) (*state.Staker, error) { return nil, fmt.Errorf("failed setting preference: %w", err) } + stakerTx := signedTx.Unsigned.(txs.Staker) return vm.state.GetCurrentValidator(stakerTx.SubnetID(), stakerTx.NodeID()) } @@ -752,7 +738,7 @@ func buildVM(t *testing.T) (*VM, ids.ID, error) { atomicDB := prefixdb.New([]byte{1}, baseDB) msgChan := make(chan common.Message, 1) - ctx := defaultContext(t) + ctx := snowtest.Context(t, snowtest.PChainID) m := atomic.NewMemory(atomicDB) ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) @@ -765,7 +751,7 @@ func buildVM(t *testing.T) (*VM, ids.ID, error) { return nil } - genesisBytes, err := buildCustomGenesis() + genesisBytes, err := buildCustomGenesis(ctx.AVAXAssetID) if err != nil { return nil, ids.Empty, err } @@ -802,7 +788,10 @@ func buildVM(t *testing.T) (*VM, ids.ID, error) { if err != nil { return nil, ids.Empty, err } - if err := vm.Network.IssueTx(context.Background(), testSubnet1); err != nil { + vm.ctx.Lock.Unlock() + err = vm.issueTx(context.Background(), testSubnet1) + vm.ctx.Lock.Lock() + if err != nil { return nil, ids.Empty, err } @@ -823,7 +812,7 @@ func buildVM(t *testing.T) (*VM, ids.ID, error) { return vm, testSubnet1.ID(), nil } -func buildCustomGenesis() ([]byte, error) { +func buildCustomGenesis(avaxAssetID ids.ID) ([]byte, error) { genesisUTXOs := make([]api.UTXO, len(keys)) for i, key := range keys { id := key.PublicKey().Address() diff --git a/vms/platformvm/validators/manager.go b/vms/platformvm/validators/manager.go index a4c5c87a3040..2c8b025a128b 100644 --- a/vms/platformvm/validators/manager.go +++ b/vms/platformvm/validators/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/vms/platformvm/validators/manager_benchmark_test.go b/vms/platformvm/validators/manager_benchmark_test.go index 155811d988ad..7c84589574df 100644 --- a/vms/platformvm/validators/manager_benchmark_test.go +++ b/vms/platformvm/validators/manager_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators @@ -112,7 +112,9 @@ func BenchmarkGetValidatorSet(b *testing.B) { db, genesisBytes, prometheus.NewRegistry(), - vdrs, + &config.Config{ + Validators: vdrs, + }, execConfig, &snow.Context{ NetworkID: constants.UnitTestID, diff --git a/vms/platformvm/validators/test_manager.go b/vms/platformvm/validators/test_manager.go index d7ffe993248e..e04742f265c7 100644 --- a/vms/platformvm/validators/test_manager.go +++ b/vms/platformvm/validators/test_manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package validators diff --git a/vms/platformvm/vm.go b/vms/platformvm/vm.go index d9898b873137..8c4b527e4539 100644 --- a/vms/platformvm/vm.go +++ b/vms/platformvm/vm.go @@ -1,12 +1,16 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm import ( "context" + "errors" "fmt" + "math" "net/http" + "sync" + "time" "github.com/gorilla/rpc/v2" @@ -31,7 +35,6 @@ import ( "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/components/avax" - "github.com/ava-labs/avalanchego/vms/platformvm/api" "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/config" "github.com/ava-labs/avalanchego/vms/platformvm/fx" @@ -88,6 +91,12 @@ type VM struct { txBuilder txbuilder.Builder manager blockexecutor.Manager + // Cancelled on shutdown + onShutdownCtx context.Context + // Call [onShutdownCtxCancel] to cancel [onShutdownCtx] during Shutdown() + onShutdownCtxCancel context.CancelFunc + awaitShutdown sync.WaitGroup + // TODO: Remove after v1.11.x is activated pruned utils.Atomic[bool] } @@ -127,7 +136,8 @@ func (vm *VM) Initialize( vm.ctx = chainCtx vm.db = db - vm.codecRegistry = linearcodec.NewDefault() + // Note: this codec is never used to serialize anything + vm.codecRegistry = linearcodec.NewDefault(time.Time{}) vm.fx = &secp256k1fx.Fx{} if err := vm.fx.Initialize(vm); err != nil { return err @@ -139,7 +149,7 @@ func (vm *VM) Initialize( vm.db, genesisBytes, registerer, - vm.Config.Validators, + &vm.Config, execConfig, vm.ctx, vm.metrics, @@ -189,13 +199,33 @@ func (vm *VM) Initialize( txExecutorBackend, validatorManager, ) - vm.Network = network.New( - txExecutorBackend.Ctx, - vm.manager, + + txVerifier := network.NewLockedTxVerifier(&txExecutorBackend.Ctx.Lock, vm.manager) + vm.Network, err = network.New( + chainCtx.Log, + chainCtx.NodeID, + chainCtx.SubnetID, + chainCtx.ValidatorState, + txVerifier, mempool, txExecutorBackend.Config.PartialSyncPrimaryNetwork, appSender, + registerer, + execConfig.Network, ) + if err != nil { + return fmt.Errorf("failed to initialize network: %w", err) + } + + vm.onShutdownCtx, vm.onShutdownCtxCancel = context.WithCancel(context.Background()) + vm.awaitShutdown.Add(1) + go func() { + defer vm.awaitShutdown.Done() + + // Invariant: Gossip must never grab the context lock. + vm.Network.Gossip(vm.onShutdownCtx) + }() + vm.Builder = blockbuilder.New( mempool, vm.txBuilder, @@ -219,6 +249,10 @@ func (vm *VM) Initialize( return err } + // Incrementing [awaitShutdown] would cause a deadlock since + // [periodicallyPruneMempool] grabs the context lock. + go vm.periodicallyPruneMempool(execConfig.MempoolPruneFrequency) + shouldPrune, err := vm.state.ShouldPrune() if err != nil { return fmt.Errorf( @@ -246,6 +280,49 @@ func (vm *VM) Initialize( return nil } +func (vm *VM) periodicallyPruneMempool(frequency time.Duration) { + ticker := time.NewTicker(frequency) + defer ticker.Stop() + + for { + select { + case <-vm.onShutdownCtx.Done(): + return + case <-ticker.C: + if err := vm.pruneMempool(); err != nil { + vm.ctx.Log.Debug("pruning mempool failed", + zap.Error(err), + ) + } + } + } +} + +func (vm *VM) pruneMempool() error { + vm.ctx.Lock.Lock() + defer vm.ctx.Lock.Unlock() + + // Packing all of the transactions in order performs additional checks that + // the MempoolTxVerifier doesn't include. So, evicting transactions from + // here is expected to happen occasionally. + blockTxs, err := vm.Builder.PackBlockTxs(math.MaxInt) + if err != nil { + return err + } + + for _, tx := range blockTxs { + if err := vm.Builder.Add(tx); err != nil { + vm.ctx.Log.Debug( + "failed to reissue tx", + zap.Stringer("txID", tx.ID()), + zap.Error(err), + ) + } + } + + return nil +} + // Create all chains that exist that this node validates. func (vm *VM) initBlockchains() error { if vm.Config.PartialSyncPrimaryNetwork { @@ -330,7 +407,7 @@ func (vm *VM) onNormalOperationsStarted() error { } // Start the block builder - vm.Builder.ResetBlockTimer() + vm.Builder.StartBlockTimer() return nil } @@ -351,7 +428,10 @@ func (vm *VM) Shutdown(context.Context) error { return nil } - vm.Builder.Shutdown() + vm.onShutdownCtxCancel() + vm.awaitShutdown.Wait() + + vm.Builder.ShutdownBlockTimer() if vm.bootstrapped.Get() { primaryVdrIDs := vm.Validators.GetValidatorIDs(constants.PrimaryNetworkID) @@ -430,18 +510,6 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { }, err } -// CreateStaticHandlers returns a map where: -// * keys are API endpoint extensions -// * values are API handlers -func (*VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, error) { - server := rpc.NewServer() - server.RegisterCodec(json.NewCodec(), "application/json") - server.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8") - return map[string]http.Handler{ - "": server, - }, server.RegisterService(&api.StaticService{}, "platform") -} - func (vm *VM) Connected(_ context.Context, nodeID ids.NodeID, _ *version.Application) error { return vm.uptimeManager.Connect(nodeID, constants.PrimaryNetworkID) } @@ -480,3 +548,16 @@ func (vm *VM) VerifyHeightIndex(_ context.Context) error { func (vm *VM) GetBlockIDAtHeight(_ context.Context, height uint64) (ids.ID, error) { return vm.state.GetBlockIDAtHeight(height) } + +func (vm *VM) issueTx(ctx context.Context, tx *txs.Tx) error { + err := vm.Network.IssueTx(ctx, tx) + if err != nil && !errors.Is(err, mempool.ErrDuplicateTx) { + vm.ctx.Log.Debug("failed to add tx to mempool", + zap.Stringer("txID", tx.ID()), + zap.Error(err), + ) + return err + } + + return nil +} diff --git a/vms/platformvm/vm_regression_test.go b/vms/platformvm/vm_regression_test.go index 80b38a06c234..36186ec32ae0 100644 --- a/vms/platformvm/vm_regression_test.go +++ b/vms/platformvm/vm_regression_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm @@ -23,11 +23,13 @@ import ( "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/platformvm/block" "github.com/ava-labs/avalanchego/vms/platformvm/config" @@ -45,9 +47,10 @@ import ( func TestAddDelegatorTxOverDelegatedRegression(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { + vm.ctx.Lock.Lock() require.NoError(vm.Shutdown(context.Background())) vm.ctx.Lock.Unlock() }() @@ -72,7 +75,9 @@ func TestAddDelegatorTxOverDelegatedRegression(t *testing.T) { require.NoError(err) // trigger block creation - require.NoError(vm.Network.IssueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Lock() addValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -104,7 +109,9 @@ func TestAddDelegatorTxOverDelegatedRegression(t *testing.T) { require.NoError(err) // trigger block creation - require.NoError(vm.Network.IssueTx(context.Background(), addFirstDelegatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addFirstDelegatorTx)) + vm.ctx.Lock.Lock() addFirstDelegatorBlock, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -138,7 +145,9 @@ func TestAddDelegatorTxOverDelegatedRegression(t *testing.T) { require.NoError(err) // trigger block creation - require.NoError(vm.Network.IssueTx(context.Background(), addSecondDelegatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addSecondDelegatorTx)) + vm.ctx.Lock.Lock() addSecondDelegatorBlock, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -162,12 +171,13 @@ func TestAddDelegatorTxOverDelegatedRegression(t *testing.T) { require.NoError(err) // trigger block creation - err = vm.Network.IssueTx(context.Background(), addThirdDelegatorTx) + vm.ctx.Lock.Unlock() + err = vm.issueTx(context.Background(), addThirdDelegatorTx) require.ErrorIs(err, executor.ErrOverDelegated) } func TestAddDelegatorTxHeapCorruption(t *testing.T) { - validatorStartTime := banffForkTime.Add(executor.SyncBound).Add(1 * time.Second) + validatorStartTime := latestForkTime.Add(executor.SyncBound).Add(1 * time.Second) validatorEndTime := validatorStartTime.Add(360 * 24 * time.Hour) validatorStake := defaultMaxValidatorStake / 5 @@ -205,13 +215,12 @@ func TestAddDelegatorTxHeapCorruption(t *testing.T) { t.Run(test.name, func(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, apricotPhase3) vm.ApricotPhase3Time = test.ap3Time vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) - vm.ctx.Lock.Unlock() }() @@ -236,7 +245,9 @@ func TestAddDelegatorTxHeapCorruption(t *testing.T) { require.NoError(err) // issue the add validator tx - require.NoError(vm.Network.IssueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the validator tx addValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -258,7 +269,9 @@ func TestAddDelegatorTxHeapCorruption(t *testing.T) { require.NoError(err) // issue the first add delegator tx - require.NoError(vm.Network.IssueTx(context.Background(), addFirstDelegatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addFirstDelegatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the first add delegator tx addFirstDelegatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -280,7 +293,9 @@ func TestAddDelegatorTxHeapCorruption(t *testing.T) { require.NoError(err) // issue the second add delegator tx - require.NoError(vm.Network.IssueTx(context.Background(), addSecondDelegatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addSecondDelegatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the second add delegator tx addSecondDelegatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -302,7 +317,9 @@ func TestAddDelegatorTxHeapCorruption(t *testing.T) { require.NoError(err) // issue the third add delegator tx - require.NoError(vm.Network.IssueTx(context.Background(), addThirdDelegatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addThirdDelegatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the third add delegator tx addThirdDelegatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -324,7 +341,9 @@ func TestAddDelegatorTxHeapCorruption(t *testing.T) { require.NoError(err) // issue the fourth add delegator tx - require.NoError(vm.Network.IssueTx(context.Background(), addFourthDelegatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addFourthDelegatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the fourth add delegator tx addFourthDelegatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -340,7 +359,6 @@ func TestAddDelegatorTxHeapCorruption(t *testing.T) { // panic. func TestUnverifiedParentPanicRegression(t *testing.T) { require := require.New(t) - _, genesisBytes := defaultGenesis(t) baseDB := memdb.New() atomicDB := prefixdb.New([]byte{1}, baseDB) @@ -352,16 +370,20 @@ func TestUnverifiedParentPanicRegression(t *testing.T) { MinStakeDuration: defaultMinStakingDuration, MaxStakeDuration: defaultMaxStakingDuration, RewardConfig: defaultRewardConfig, - BanffTime: banffForkTime, + BanffTime: latestForkTime, + CortinaTime: mockable.MaxTime, + DurangoTime: mockable.MaxTime, }} - ctx := defaultContext(t) + ctx := snowtest.Context(t, snowtest.PChainID) ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) ctx.Lock.Unlock() }() + _, genesisBytes := defaultGenesis(t, ctx.AVAXAssetID) + msgChan := make(chan common.Message, 1) require.NoError(vm.Initialize( context.Background(), @@ -379,8 +401,8 @@ func TestUnverifiedParentPanicRegression(t *testing.T) { vm.ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) // set time to post Banff fork - vm.clock.Set(banffForkTime.Add(time.Second)) - vm.state.SetTimestamp(banffForkTime.Add(time.Second)) + vm.clock.Set(latestForkTime.Add(time.Second)) + vm.state.SetTimestamp(latestForkTime.Add(time.Second)) key0 := keys[0] key1 := keys[1] @@ -463,11 +485,10 @@ func TestUnverifiedParentPanicRegression(t *testing.T) { func TestRejectedStateRegressionInvalidValidatorTimestamp(t *testing.T) { require := require.New(t) - vm, baseDB, mutableSharedMemory := defaultVM(t) + vm, baseDB, mutableSharedMemory := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) - vm.ctx.Lock.Unlock() }() @@ -583,7 +604,7 @@ func TestRejectedStateRegressionInvalidValidatorTimestamp(t *testing.T) { mutableSharedMemory.SharedMemory = m.NewSharedMemory(vm.ctx.ChainID) peerSharedMemory := m.NewSharedMemory(vm.ctx.XChainID) - utxoBytes, err := txs.Codec.Marshal(txs.Version, utxo) + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) require.NoError(err) inputID := utxo.InputID() @@ -648,7 +669,7 @@ func TestRejectedStateRegressionInvalidValidatorTimestamp(t *testing.T) { vm.db, nil, prometheus.NewRegistry(), - vm.Config.Validators, + &vm.Config, execCfg, vm.ctx, metrics.Noop, @@ -672,11 +693,10 @@ func TestRejectedStateRegressionInvalidValidatorTimestamp(t *testing.T) { func TestRejectedStateRegressionInvalidValidatorReward(t *testing.T) { require := require.New(t) - vm, baseDB, mutableSharedMemory := defaultVM(t) + vm, baseDB, mutableSharedMemory := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) - vm.ctx.Lock.Unlock() }() @@ -829,7 +849,7 @@ func TestRejectedStateRegressionInvalidValidatorReward(t *testing.T) { mutableSharedMemory.SharedMemory = m.NewSharedMemory(vm.ctx.ChainID) peerSharedMemory := m.NewSharedMemory(vm.ctx.XChainID) - utxoBytes, err := txs.Codec.Marshal(txs.Version, utxo) + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) require.NoError(err) inputID := utxo.InputID() @@ -955,7 +975,7 @@ func TestRejectedStateRegressionInvalidValidatorReward(t *testing.T) { vm.db, nil, prometheus.NewRegistry(), - vm.Config.Validators, + &vm.Config, execCfg, vm.ctx, metrics.Noop, @@ -988,11 +1008,10 @@ func TestRejectedStateRegressionInvalidValidatorReward(t *testing.T) { func TestValidatorSetAtCacheOverwriteRegression(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) - vm.ctx.Lock.Unlock() }() @@ -1116,7 +1135,7 @@ func TestValidatorSetAtCacheOverwriteRegression(t *testing.T) { func TestAddDelegatorTxAddBeforeRemove(t *testing.T) { require := require.New(t) - validatorStartTime := banffForkTime.Add(executor.SyncBound).Add(1 * time.Second) + validatorStartTime := latestForkTime.Add(executor.SyncBound).Add(1 * time.Second) validatorEndTime := validatorStartTime.Add(360 * 24 * time.Hour) validatorStake := defaultMaxValidatorStake / 5 @@ -1128,10 +1147,11 @@ func TestAddDelegatorTxAddBeforeRemove(t *testing.T) { delegator2EndTime := delegator2StartTime.Add(3 * defaultMinStakingDuration) delegator2Stake := defaultMaxValidatorStake - validatorStake - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { + vm.ctx.Lock.Lock() require.NoError(vm.Shutdown(context.Background())) vm.ctx.Lock.Unlock() @@ -1158,7 +1178,9 @@ func TestAddDelegatorTxAddBeforeRemove(t *testing.T) { require.NoError(err) // issue the add validator tx - require.NoError(vm.Network.IssueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the validator tx addValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1180,7 +1202,9 @@ func TestAddDelegatorTxAddBeforeRemove(t *testing.T) { require.NoError(err) // issue the first add delegator tx - require.NoError(vm.Network.IssueTx(context.Background(), addFirstDelegatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addFirstDelegatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the first add delegator tx addFirstDelegatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1203,22 +1227,22 @@ func TestAddDelegatorTxAddBeforeRemove(t *testing.T) { // attempting to issue the second add delegator tx should fail because the // total stake weight would go over the limit. - err = vm.Network.IssueTx(context.Background(), addSecondDelegatorTx) + vm.ctx.Lock.Unlock() + err = vm.issueTx(context.Background(), addSecondDelegatorTx) require.ErrorIs(err, executor.ErrOverDelegated) } func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionNotTracked(t *testing.T) { require := require.New(t) - validatorStartTime := banffForkTime.Add(executor.SyncBound).Add(1 * time.Second) + validatorStartTime := latestForkTime.Add(executor.SyncBound).Add(1 * time.Second) validatorEndTime := validatorStartTime.Add(360 * 24 * time.Hour) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) - vm.ctx.Lock.Unlock() }() @@ -1241,7 +1265,9 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionNotTracked(t ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the validator tx addValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1258,7 +1284,9 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionNotTracked(t ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Lock() // trigger block creation for the subnet tx createSubnetBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1278,7 +1306,9 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionNotTracked(t ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), addSubnetValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addSubnetValidatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the validator tx addSubnetValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1307,7 +1337,9 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionNotTracked(t // validator set into the current validator set. vm.clock.Set(validatorStartTime) - require.NoError(vm.Network.IssueTx(context.Background(), removeSubnetValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), removeSubnetValidatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the validator tx removeSubnetValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1328,10 +1360,10 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionNotTracked(t func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionTracked(t *testing.T) { require := require.New(t) - validatorStartTime := banffForkTime.Add(executor.SyncBound).Add(1 * time.Second) + validatorStartTime := latestForkTime.Add(executor.SyncBound).Add(1 * time.Second) validatorEndTime := validatorStartTime.Add(360 * 24 * time.Hour) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { @@ -1359,7 +1391,9 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionTracked(t *t ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the validator tx addValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1376,7 +1410,9 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionTracked(t *t ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Lock() // trigger block creation for the subnet tx createSubnetBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1396,7 +1432,9 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionTracked(t *t ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), addSubnetValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addSubnetValidatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the validator tx addSubnetValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1417,7 +1455,9 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionTracked(t *t // validator set into the current validator set. vm.clock.Set(validatorStartTime) - require.NoError(vm.Network.IssueTx(context.Background(), removeSubnetValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), removeSubnetValidatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the validator tx removeSubnetValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -1432,13 +1472,13 @@ func TestRemovePermissionedValidatorDuringPendingToCurrentTransitionTracked(t *t func TestSubnetValidatorBLSKeyDiffAfterExpiry(t *testing.T) { // setup require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) - vm.ctx.Lock.Unlock() }() + subnetID := testSubnet1.TxID // setup time @@ -1511,7 +1551,9 @@ func TestSubnetValidatorBLSKeyDiffAfterExpiry(t *testing.T) { require.NoError(err) require.NoError(primaryTx.SyntacticVerify(vm.ctx)) - require.NoError(vm.Network.IssueTx(context.Background(), primaryTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), primaryTx)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting primary validator to current @@ -1538,7 +1580,9 @@ func TestSubnetValidatorBLSKeyDiffAfterExpiry(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), subnetTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), subnetTx)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting the subnet validator to current @@ -1642,7 +1686,9 @@ func TestSubnetValidatorBLSKeyDiffAfterExpiry(t *testing.T) { require.NoError(err) require.NoError(uPrimaryRestartTx.SyntacticVerify(vm.ctx)) - require.NoError(vm.Network.IssueTx(context.Background(), primaryRestartTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), primaryRestartTx)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting restarted primary validator to current @@ -1714,11 +1760,10 @@ func TestPrimaryNetworkValidatorPopulatedToEmptyBLSKeyDiff(t *testing.T) { // setup require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) - vm.ctx.Lock.Unlock() }() @@ -1750,7 +1795,9 @@ func TestPrimaryNetworkValidatorPopulatedToEmptyBLSKeyDiff(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), primaryTx1)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), primaryTx1)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting primary validator to current @@ -1842,7 +1889,9 @@ func TestPrimaryNetworkValidatorPopulatedToEmptyBLSKeyDiff(t *testing.T) { require.NoError(err) require.NoError(uPrimaryRestartTx.SyntacticVerify(vm.ctx)) - require.NoError(vm.Network.IssueTx(context.Background(), primaryRestartTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), primaryRestartTx)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting restarted primary validator to current @@ -1874,13 +1923,13 @@ func TestSubnetValidatorPopulatedToEmptyBLSKeyDiff(t *testing.T) { // setup require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) - vm.ctx.Lock.Unlock() }() + subnetID := testSubnet1.TxID // setup time @@ -1913,7 +1962,9 @@ func TestSubnetValidatorPopulatedToEmptyBLSKeyDiff(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), primaryTx1)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), primaryTx1)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting primary validator to current @@ -1940,7 +1991,9 @@ func TestSubnetValidatorPopulatedToEmptyBLSKeyDiff(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), subnetTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), subnetTx)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting the subnet validator to current @@ -2044,7 +2097,9 @@ func TestSubnetValidatorPopulatedToEmptyBLSKeyDiff(t *testing.T) { require.NoError(err) require.NoError(uPrimaryRestartTx.SyntacticVerify(vm.ctx)) - require.NoError(vm.Network.IssueTx(context.Background(), primaryRestartTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), primaryRestartTx)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting restarted primary validator to current @@ -2085,13 +2140,13 @@ func TestSubnetValidatorSetAfterPrimaryNetworkValidatorRemoval(t *testing.T) { // setup require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) - vm.ctx.Lock.Unlock() }() + subnetID := testSubnet1.TxID // setup time @@ -2122,7 +2177,9 @@ func TestSubnetValidatorSetAfterPrimaryNetworkValidatorRemoval(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), primaryTx1)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), primaryTx1)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting primary validator to current @@ -2146,7 +2203,9 @@ func TestSubnetValidatorSetAfterPrimaryNetworkValidatorRemoval(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), subnetTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), subnetTx)) + vm.ctx.Lock.Lock() require.NoError(buildAndAcceptStandardBlock(vm)) // move time ahead, promoting the subnet validator to current diff --git a/vms/platformvm/vm_test.go b/vms/platformvm/vm_test.go index 045ccf85b6ac..23e88a646368 100644 --- a/vms/platformvm/vm_test.go +++ b/vms/platformvm/vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package platformvm @@ -6,7 +6,7 @@ package platformvm import ( "bytes" "context" - "errors" + "fmt" "testing" "time" @@ -34,6 +34,7 @@ import ( "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/snow/networking/sender" "github.com/ava-labs/avalanchego/snow/networking/timeout" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/subnets" @@ -47,6 +48,7 @@ import ( "github.com/ava-labs/avalanchego/utils/resource" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/timer" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/components/avax" @@ -68,7 +70,19 @@ import ( txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" ) -const defaultWeight uint64 = 10000 +type activeFork uint8 + +const ( + apricotPhase3 activeFork = iota + apricotPhase5 + banffFork + cortinaFork + durangoFork + + latestFork activeFork = durangoFork + + defaultWeight uint64 = 10000 +) var ( defaultMinStakingDuration = 24 * time.Hour @@ -81,9 +95,6 @@ var ( SupplyCap: 720 * units.MegaAvax, } - // AVAX asset ID in tests - avaxAssetID = ids.ID{'y', 'e', 'e', 't'} - defaultTxFee = uint64(100) // chain timestamp at genesis @@ -95,31 +106,23 @@ var ( // time that genesis validators stop validating defaultValidateEndTime = defaultValidateStartTime.Add(10 * defaultMinStakingDuration) - banffForkTime = defaultValidateEndTime.Add(-5 * defaultMinStakingDuration) + latestForkTime = defaultGenesisTime.Add(time.Second) // each key controls an address that has [defaultBalance] AVAX at genesis keys = secp256k1.TestKeys() // Node IDs of genesis validators. Initialized in init function - genesisNodeIDs []ids.NodeID - - defaultMinValidatorStake = 5 * units.MilliAvax - defaultMaxValidatorStake = 500 * units.MilliAvax + genesisNodeIDs []ids.NodeID defaultMinDelegatorStake = 1 * units.MilliAvax - - // amount all genesis validators have in defaultVM - defaultBalance = 100 * defaultMinValidatorStake + defaultMinValidatorStake = 5 * defaultMinDelegatorStake + defaultMaxValidatorStake = 100 * defaultMinValidatorStake + defaultBalance = 2 * defaultMaxValidatorStake // amount all genesis validators have in defaultVM // subnet that exists at genesis in defaultVM // Its controlKeys are keys[0], keys[1], keys[2] // Its threshold is 2 testSubnet1 *txs.Tx testSubnet1ControlKeys = keys[0:3] - - xChainID = ids.Empty.Prefix(0) - cChainID = ids.Empty.Prefix(1) - - errMissing = errors.New("missing") ) func init() { @@ -137,45 +140,10 @@ type mutableSharedMemory struct { atomic.SharedMemory } -func defaultContext(t *testing.T) *snow.Context { - require := require.New(t) - - ctx := snow.DefaultContextTest() - ctx.NetworkID = constants.UnitTestID - ctx.XChainID = xChainID - ctx.CChainID = cChainID - ctx.AVAXAssetID = avaxAssetID - aliaser := ids.NewAliaser() - - require.NoError(aliaser.Alias(constants.PlatformChainID, "P")) - require.NoError(aliaser.Alias(constants.PlatformChainID, constants.PlatformChainID.String())) - require.NoError(aliaser.Alias(xChainID, "X")) - require.NoError(aliaser.Alias(xChainID, xChainID.String())) - require.NoError(aliaser.Alias(cChainID, "C")) - require.NoError(aliaser.Alias(cChainID, cChainID.String())) - - ctx.BCLookup = aliaser - - ctx.ValidatorState = &validators.TestState{ - GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { - subnetID, ok := map[ids.ID]ids.ID{ - constants.PlatformChainID: constants.PrimaryNetworkID, - xChainID: constants.PrimaryNetworkID, - cChainID: constants.PrimaryNetworkID, - }[chainID] - if !ok { - return ids.Empty, errMissing - } - return subnetID, nil - }, - } - return ctx -} - // Returns: // 1) The genesis state // 2) The byte representation of the default genesis for tests -func defaultGenesis(t *testing.T) (*api.BuildGenesisArgs, []byte) { +func defaultGenesis(t *testing.T, avaxAssetID ids.ID) (*api.BuildGenesisArgs, []byte) { require := require.New(t) genesisUTXOs := make([]api.UTXO, len(keys)) @@ -232,81 +200,38 @@ func defaultGenesis(t *testing.T) (*api.BuildGenesisArgs, []byte) { return &buildGenesisArgs, genesisBytes } -// Returns: -// 1) The genesis state -// 2) The byte representation of the default genesis for tests -func BuildGenesisTest(t *testing.T) (*api.BuildGenesisArgs, []byte) { - return BuildGenesisTestWithArgs(t, nil) -} - -// Returns: -// 1) The genesis state -// 2) The byte representation of the default genesis for tests -func BuildGenesisTestWithArgs(t *testing.T, args *api.BuildGenesisArgs) (*api.BuildGenesisArgs, []byte) { +func defaultVM(t *testing.T, fork activeFork) (*VM, database.Database, *mutableSharedMemory) { require := require.New(t) - genesisUTXOs := make([]api.UTXO, len(keys)) - for i, key := range keys { - id := key.PublicKey().Address() - addr, err := address.FormatBech32(constants.UnitTestHRP, id.Bytes()) - require.NoError(err) - - genesisUTXOs[i] = api.UTXO{ - Amount: json.Uint64(defaultBalance), - Address: addr, - } - } - - genesisValidators := make([]api.GenesisPermissionlessValidator, len(genesisNodeIDs)) - for i, nodeID := range genesisNodeIDs { - addr, err := address.FormatBech32(constants.UnitTestHRP, nodeID.Bytes()) - require.NoError(err) - - genesisValidators[i] = api.GenesisPermissionlessValidator{ - GenesisValidator: api.GenesisValidator{ - StartTime: json.Uint64(defaultValidateStartTime.Unix()), - EndTime: json.Uint64(defaultValidateEndTime.Unix()), - NodeID: nodeID, - }, - RewardOwner: &api.Owner{ - Threshold: 1, - Addresses: []string{addr}, - }, - Staked: []api.UTXO{{ - Amount: json.Uint64(defaultWeight), - Address: addr, - }}, - DelegationFee: reward.PercentDenominator, - } - } - - buildGenesisArgs := api.BuildGenesisArgs{ - NetworkID: json.Uint32(constants.UnitTestID), - AvaxAssetID: avaxAssetID, - UTXOs: genesisUTXOs, - Validators: genesisValidators, - Chains: nil, - Time: json.Uint64(defaultGenesisTime.Unix()), - InitialSupply: json.Uint64(360 * units.MegaAvax), - Encoding: formatting.Hex, - } + var ( + apricotPhase3Time = mockable.MaxTime + apricotPhase5Time = mockable.MaxTime + banffTime = mockable.MaxTime + cortinaTime = mockable.MaxTime + durangoTime = mockable.MaxTime + ) - if args != nil { - buildGenesisArgs = *args + // always reset latestForkTime (a package level variable) + // to ensure test independence + latestForkTime = defaultGenesisTime.Add(time.Second) + switch fork { + case durangoFork: + durangoTime = latestForkTime + fallthrough + case cortinaFork: + cortinaTime = latestForkTime + fallthrough + case banffFork: + banffTime = latestForkTime + fallthrough + case apricotPhase5: + apricotPhase5Time = latestForkTime + fallthrough + case apricotPhase3: + apricotPhase3Time = latestForkTime + default: + require.NoError(fmt.Errorf("unhandled fork %d", fork)) } - buildGenesisResponse := api.BuildGenesisReply{} - platformvmSS := api.StaticService{} - require.NoError(platformvmSS.BuildGenesis(nil, &buildGenesisArgs, &buildGenesisResponse)) - - genesisBytes, err := formatting.Decode(buildGenesisResponse.Encoding, buildGenesisResponse.Bytes) - require.NoError(err) - - return &buildGenesisArgs, genesisBytes -} - -func defaultVM(t *testing.T) (*VM, database.Database, *mutableSharedMemory) { - require := require.New(t) - vm := &VM{Config: config.Config{ Chains: chains.TestManager, UptimeLockedCalculator: uptime.NewLockedCalculator(), @@ -322,18 +247,20 @@ func defaultVM(t *testing.T) (*VM, database.Database, *mutableSharedMemory) { MinStakeDuration: defaultMinStakingDuration, MaxStakeDuration: defaultMaxStakingDuration, RewardConfig: defaultRewardConfig, - ApricotPhase3Time: defaultValidateEndTime, - ApricotPhase5Time: defaultValidateEndTime, - BanffTime: banffForkTime, + ApricotPhase3Time: apricotPhase3Time, + ApricotPhase5Time: apricotPhase5Time, + BanffTime: banffTime, + CortinaTime: cortinaTime, + DurangoTime: durangoTime, }} db := memdb.New() chainDB := prefixdb.New([]byte{0}, db) atomicDB := prefixdb.New([]byte{1}, db) - vm.clock.Set(banffForkTime.Add(time.Second)) + vm.clock.Set(latestForkTime) msgChan := make(chan common.Message, 1) - ctx := defaultContext(t) + ctx := snowtest.Context(t, snowtest.PChainID) m := atomic.NewMemory(atomicDB) msm := &mutableSharedMemory{ @@ -343,7 +270,7 @@ func defaultVM(t *testing.T) (*VM, database.Database, *mutableSharedMemory) { ctx.Lock.Lock() defer ctx.Lock.Unlock() - _, genesisBytes := defaultGenesis(t) + _, genesisBytes := defaultGenesis(t, ctx.AVAXAssetID) appSender := &common.SenderTest{} appSender.CantSendAppGossip = true appSender.SendAppGossipF = func(context.Context, []byte) error { @@ -362,6 +289,9 @@ func defaultVM(t *testing.T) (*VM, database.Database, *mutableSharedMemory) { appSender, )) + // align chain time and local clock + vm.state.SetTimestamp(vm.clock.Time()) + require.NoError(vm.SetState(context.Background(), snow.NormalOp)) // Create a subnet and store it in testSubnet1 @@ -376,7 +306,9 @@ func defaultVM(t *testing.T) (*VM, database.Database, *mutableSharedMemory) { keys[0].PublicKey().Address(), // change addr ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), testSubnet1)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), testSubnet1)) + vm.ctx.Lock.Lock() blk, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) require.NoError(blk.Verify(context.Background())) @@ -389,7 +321,7 @@ func defaultVM(t *testing.T) (*VM, database.Database, *mutableSharedMemory) { // Ensure genesis state is parsed from bytes and stored correctly func TestGenesis(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -404,7 +336,7 @@ func TestGenesis(t *testing.T) { require.NoError(err) require.Equal(choices.Accepted, genesisBlock.Status()) - genesisState, _ := defaultGenesis(t) + genesisState, _ := defaultGenesis(t, vm.ctx.AVAXAssetID) // Ensure all the genesis UTXOs are there for _, utxo := range genesisState.UTXOs { _, addrBytes, err := address.ParseBech32(utxo.Address) @@ -425,7 +357,7 @@ func TestGenesis(t *testing.T) { require.NoError(err) require.Equal(utxo.Address, addr) - require.Equal(uint64(utxo.Amount)-vm.TxFee, out.Amount()) + require.Equal(uint64(utxo.Amount)-vm.CreateSubnetTxFee, out.Amount()) } } @@ -445,17 +377,19 @@ func TestGenesis(t *testing.T) { // accept proposal to add validator to primary network func TestAddValidatorCommit(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) vm.ctx.Lock.Unlock() }() - startTime := vm.clock.Time().Add(txexecutor.SyncBound).Add(1 * time.Second) - endTime := startTime.Add(defaultMinStakingDuration) - nodeID := ids.GenerateTestNodeID() - rewardAddress := ids.GenerateTestShortID() + var ( + startTime = vm.clock.Time().Add(txexecutor.SyncBound).Add(1 * time.Second) + endTime = startTime.Add(defaultMinStakingDuration) + nodeID = ids.GenerateTestNodeID() + rewardAddress = ids.GenerateTestShortID() + ) // create valid tx tx, err := vm.txBuilder.NewAddValidatorTx( @@ -471,7 +405,9 @@ func TestAddValidatorCommit(t *testing.T) { require.NoError(err) // trigger block creation - require.NoError(vm.Network.IssueTx(context.Background(), tx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), tx)) + vm.ctx.Lock.Lock() blk, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -483,15 +419,15 @@ func TestAddValidatorCommit(t *testing.T) { require.NoError(err) require.Equal(status.Committed, txStatus) - // Verify that new validator now in pending validator set - _, err = vm.state.GetPendingValidator(constants.PrimaryNetworkID, nodeID) + // Verify that new validator now in current validator set + _, err = vm.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID) require.NoError(err) } // verify invalid attempt to add validator to primary network func TestInvalidAddValidatorCommit(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -544,17 +480,19 @@ func TestInvalidAddValidatorCommit(t *testing.T) { // Reject attempt to add validator to primary network func TestAddValidatorReject(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, cortinaFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) vm.ctx.Lock.Unlock() }() - startTime := vm.clock.Time().Add(txexecutor.SyncBound).Add(1 * time.Second) - endTime := startTime.Add(defaultMinStakingDuration) - nodeID := ids.GenerateTestNodeID() - rewardAddress := ids.GenerateTestShortID() + var ( + startTime = vm.clock.Time().Add(txexecutor.SyncBound).Add(1 * time.Second) + endTime = startTime.Add(defaultMinStakingDuration) + nodeID = ids.GenerateTestNodeID() + rewardAddress = ids.GenerateTestShortID() + ) // create valid tx tx, err := vm.txBuilder.NewAddValidatorTx( @@ -570,7 +508,9 @@ func TestAddValidatorReject(t *testing.T) { require.NoError(err) // trigger block creation - require.NoError(vm.Network.IssueTx(context.Background(), tx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), tx)) + vm.ctx.Lock.Lock() blk, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -588,9 +528,10 @@ func TestAddValidatorReject(t *testing.T) { // Reject proposal to add validator to primary network func TestAddValidatorInvalidNotReissued(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { + vm.ctx.Lock.Lock() require.NoError(vm.Shutdown(context.Background())) vm.ctx.Lock.Unlock() }() @@ -598,7 +539,7 @@ func TestAddValidatorInvalidNotReissued(t *testing.T) { // Use nodeID that is already in the genesis repeatNodeID := genesisNodeIDs[0] - startTime := banffForkTime.Add(txexecutor.SyncBound).Add(1 * time.Second) + startTime := latestForkTime.Add(txexecutor.SyncBound).Add(1 * time.Second) endTime := startTime.Add(defaultMinStakingDuration) // create valid tx @@ -615,23 +556,26 @@ func TestAddValidatorInvalidNotReissued(t *testing.T) { require.NoError(err) // trigger block creation - err = vm.Network.IssueTx(context.Background(), tx) + vm.ctx.Lock.Unlock() + err = vm.issueTx(context.Background(), tx) require.ErrorIs(err, txexecutor.ErrAlreadyValidator) } // Accept proposal to add validator to subnet func TestAddSubnetValidatorAccept(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) vm.ctx.Lock.Unlock() }() - startTime := vm.clock.Time().Add(txexecutor.SyncBound).Add(1 * time.Second) - endTime := startTime.Add(defaultMinStakingDuration) - nodeID := genesisNodeIDs[0] + var ( + startTime = vm.clock.Time().Add(txexecutor.SyncBound).Add(1 * time.Second) + endTime = startTime.Add(defaultMinStakingDuration) + nodeID = genesisNodeIDs[0] + ) // create valid tx // note that [startTime, endTime] is a subset of time that keys[0] @@ -648,7 +592,9 @@ func TestAddSubnetValidatorAccept(t *testing.T) { require.NoError(err) // trigger block creation - require.NoError(vm.Network.IssueTx(context.Background(), tx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), tx)) + vm.ctx.Lock.Lock() blk, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -660,24 +606,26 @@ func TestAddSubnetValidatorAccept(t *testing.T) { require.NoError(err) require.Equal(status.Committed, txStatus) - // Verify that new validator is in pending validator set - _, err = vm.state.GetPendingValidator(testSubnet1.ID(), nodeID) + // Verify that new validator is in current validator set + _, err = vm.state.GetCurrentValidator(testSubnet1.ID(), nodeID) require.NoError(err) } // Reject proposal to add validator to subnet func TestAddSubnetValidatorReject(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) vm.ctx.Lock.Unlock() }() - startTime := vm.clock.Time().Add(txexecutor.SyncBound).Add(1 * time.Second) - endTime := startTime.Add(defaultMinStakingDuration) - nodeID := genesisNodeIDs[0] + var ( + startTime = vm.clock.Time().Add(txexecutor.SyncBound).Add(1 * time.Second) + endTime = startTime.Add(defaultMinStakingDuration) + nodeID = genesisNodeIDs[0] + ) // create valid tx // note that [startTime, endTime] is a subset of time that keys[0] @@ -694,7 +642,9 @@ func TestAddSubnetValidatorReject(t *testing.T) { require.NoError(err) // trigger block creation - require.NoError(vm.Network.IssueTx(context.Background(), tx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), tx)) + vm.ctx.Lock.Lock() blk, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -705,15 +655,15 @@ func TestAddSubnetValidatorReject(t *testing.T) { _, _, err = vm.state.GetTx(tx.ID()) require.ErrorIs(err, database.ErrNotFound) - // Verify that new validator NOT in pending validator set - _, err = vm.state.GetPendingValidator(testSubnet1.ID(), nodeID) + // Verify that new validator NOT in validator set + _, err = vm.state.GetCurrentValidator(testSubnet1.ID(), nodeID) require.ErrorIs(err, database.ErrNotFound) } // Test case where primary network validator rewarded func TestRewardValidatorAccept(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -784,7 +734,7 @@ func TestRewardValidatorAccept(t *testing.T) { // Test case where primary network validator not rewarded func TestRewardValidatorReject(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -857,7 +807,7 @@ func TestRewardValidatorReject(t *testing.T) { // Ensure BuildBlock errors when there is no block to build func TestUnneededBuildBlock(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -870,7 +820,7 @@ func TestUnneededBuildBlock(t *testing.T) { // test acceptance of proposal to create a new chain func TestCreateChain(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -888,7 +838,9 @@ func TestCreateChain(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), tx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), tx)) + vm.ctx.Lock.Lock() blk, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) // should contain proposal to create chain @@ -916,12 +868,11 @@ func TestCreateChain(t *testing.T) { // test where we: // 1) Create a subnet -// 2) Add a validator to the subnet's pending validator set -// 3) Advance timestamp to validator's start time (moving the validator from pending to current) -// 4) Advance timestamp to validator's end time (removing validator from current) +// 2) Add a validator to the subnet's current validator set +// 3) Advance timestamp to validator's end time (removing validator from current) func TestCreateSubnet(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -940,9 +891,11 @@ func TestCreateSubnet(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Lock() - // should contain proposal to create subnet + // should contain the CreateSubnetTx blk, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -981,13 +934,15 @@ func TestCreateSubnet(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Lock() blk, err = vm.Builder.BuildBlock(context.Background()) // should add validator to the new subnet require.NoError(err) require.NoError(blk.Verify(context.Background())) - require.NoError(blk.Accept(context.Background())) // add the validator to pending validator set + require.NoError(blk.Accept(context.Background())) // add the validator to current validator set require.NoError(vm.SetPreference(context.Background(), vm.manager.LastAccepted())) txID := blk.(block.Block).Txs()[0].ID() @@ -995,19 +950,6 @@ func TestCreateSubnet(t *testing.T) { require.NoError(err) require.Equal(status.Committed, txStatus) - _, err = vm.state.GetPendingValidator(createSubnetTx.ID(), nodeID) - require.NoError(err) - - // Advance time to when new validator should start validating - // Create a block with an advance time tx that moves validator - // from pending to current validator set - vm.clock.Set(startTime) - blk, err = vm.Builder.BuildBlock(context.Background()) // should be advance time tx - require.NoError(err) - require.NoError(blk.Verify(context.Background())) - require.NoError(blk.Accept(context.Background())) // move validator addValidatorTx from pending to current - require.NoError(vm.SetPreference(context.Background(), vm.manager.LastAccepted())) - _, err = vm.state.GetPendingValidator(createSubnetTx.ID(), nodeID) require.ErrorIs(err, database.ErrNotFound) @@ -1031,7 +973,7 @@ func TestCreateSubnet(t *testing.T) { // test asset import func TestAtomicImport(t *testing.T) { require := require.New(t) - vm, baseDB, mutableSharedMemory := defaultVM(t) + vm, baseDB, mutableSharedMemory := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -1062,7 +1004,7 @@ func TestAtomicImport(t *testing.T) { utxo := &avax.UTXO{ UTXOID: utxoID, - Asset: avax.Asset{ID: avaxAssetID}, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, Out: &secp256k1fx.TransferOutput{ Amt: amount, OutputOwners: secp256k1fx.OutputOwners{ @@ -1071,7 +1013,7 @@ func TestAtomicImport(t *testing.T) { }, }, } - utxoBytes, err := txs.Codec.Marshal(txs.Version, utxo) + utxoBytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo) require.NoError(err) inputID := utxo.InputID() @@ -1097,7 +1039,9 @@ func TestAtomicImport(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), tx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), tx)) + vm.ctx.Lock.Lock() blk, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -1118,7 +1062,7 @@ func TestAtomicImport(t *testing.T) { // test optimistic asset import func TestOptimisticAtomicImport(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, apricotPhase3) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -1178,7 +1122,6 @@ func TestOptimisticAtomicImport(t *testing.T) { // test restarting the node func TestRestartFullyAccepted(t *testing.T) { require := require.New(t) - _, genesisBytes := defaultGenesis(t) db := memdb.New() firstDB := prefixdb.New([]byte{}, db) @@ -1189,17 +1132,21 @@ func TestRestartFullyAccepted(t *testing.T) { MinStakeDuration: defaultMinStakingDuration, MaxStakeDuration: defaultMaxStakingDuration, RewardConfig: defaultRewardConfig, - BanffTime: banffForkTime, + BanffTime: latestForkTime, + CortinaTime: latestForkTime, + DurangoTime: latestForkTime, }} - firstCtx := defaultContext(t) + firstCtx := snowtest.Context(t, snowtest.PChainID) + + _, genesisBytes := defaultGenesis(t, firstCtx.AVAXAssetID) baseDB := memdb.New() atomicDB := prefixdb.New([]byte{1}, baseDB) m := atomic.NewMemory(atomicDB) firstCtx.SharedMemory = m.NewSharedMemory(firstCtx.ChainID) - initialClkTime := banffForkTime.Add(time.Second) + initialClkTime := latestForkTime.Add(time.Second) firstVM.clock.Set(initialClkTime) firstCtx.Lock.Lock() @@ -1272,10 +1219,12 @@ func TestRestartFullyAccepted(t *testing.T) { MinStakeDuration: defaultMinStakingDuration, MaxStakeDuration: defaultMaxStakingDuration, RewardConfig: defaultRewardConfig, - BanffTime: banffForkTime, + BanffTime: latestForkTime, + CortinaTime: latestForkTime, + DurangoTime: latestForkTime, }} - secondCtx := defaultContext(t) + secondCtx := snowtest.Context(t, snowtest.PChainID) secondCtx.SharedMemory = firstCtx.SharedMemory secondVM.clock.Set(initialClkTime) secondCtx.Lock.Lock() @@ -1307,8 +1256,6 @@ func TestRestartFullyAccepted(t *testing.T) { func TestBootstrapPartiallyAccepted(t *testing.T) { require := require.New(t) - _, genesisBytes := defaultGenesis(t) - baseDB := memdb.New() vmDB := prefixdb.New([]byte("vm"), baseDB) bootstrappingDB := prefixdb.New([]byte("bootstrapping"), baseDB) @@ -1322,19 +1269,22 @@ func TestBootstrapPartiallyAccepted(t *testing.T) { MinStakeDuration: defaultMinStakingDuration, MaxStakeDuration: defaultMaxStakingDuration, RewardConfig: defaultRewardConfig, - BanffTime: banffForkTime, + BanffTime: latestForkTime, + CortinaTime: latestForkTime, + DurangoTime: latestForkTime, }} - initialClkTime := banffForkTime.Add(time.Second) + initialClkTime := latestForkTime.Add(time.Second) vm.clock.Set(initialClkTime) - ctx := defaultContext(t) + ctx := snowtest.Context(t, snowtest.PChainID) + + _, genesisBytes := defaultGenesis(t, ctx.AVAXAssetID) atomicDB := prefixdb.New([]byte{1}, baseDB) m := atomic.NewMemory(atomicDB) ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) - consensusCtx := snow.DefaultConsensusContextTest() - consensusCtx.Context = ctx + consensusCtx := snowtest.ConsensusContext(ctx) ctx.Lock.Lock() msgChan := make(chan common.Message, 1) @@ -1652,7 +1602,6 @@ func TestBootstrapPartiallyAccepted(t *testing.T) { func TestUnverifiedParent(t *testing.T) { require := require.New(t) - _, genesisBytes := defaultGenesis(t) vm := &VM{Config: config.Config{ Chains: chains.TestManager, @@ -1661,18 +1610,22 @@ func TestUnverifiedParent(t *testing.T) { MinStakeDuration: defaultMinStakingDuration, MaxStakeDuration: defaultMaxStakingDuration, RewardConfig: defaultRewardConfig, - BanffTime: banffForkTime, + BanffTime: latestForkTime, + CortinaTime: latestForkTime, + DurangoTime: latestForkTime, }} - initialClkTime := banffForkTime.Add(time.Second) + initialClkTime := latestForkTime.Add(time.Second) vm.clock.Set(initialClkTime) - ctx := defaultContext(t) + ctx := snowtest.Context(t, snowtest.PChainID) ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) ctx.Lock.Unlock() }() + _, genesisBytes := defaultGenesis(t, ctx.AVAXAssetID) + msgChan := make(chan common.Message, 1) require.NoError(vm.Initialize( context.Background(), @@ -1723,7 +1676,7 @@ func TestUnverifiedParent(t *testing.T) { firstAdvanceTimeBlk := vm.manager.NewBlock(statelessBlk) require.NoError(firstAdvanceTimeBlk.Verify(context.Background())) - // include a tx1 to make the block be accepted + // include a tx2 to make the block be accepted tx2 := &txs.Tx{Unsigned: &txs.ImportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: vm.ctx.NetworkID, @@ -1741,7 +1694,7 @@ func TestUnverifiedParent(t *testing.T) { }, }}, }} - require.NoError(tx1.Initialize(txs.Codec)) + require.NoError(tx2.Initialize(txs.Codec)) nextChainTime = nextChainTime.Add(time.Second) vm.clock.Set(nextChainTime) statelessSecondAdvanceTimeBlk, err := block.NewBanffStandardBlock( @@ -1758,7 +1711,7 @@ func TestUnverifiedParent(t *testing.T) { } func TestMaxStakeAmount(t *testing.T) { - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(t, vm.Shutdown(context.Background())) @@ -1809,7 +1762,7 @@ func TestMaxStakeAmount(t *testing.T) { func TestUptimeDisallowedWithRestart(t *testing.T) { require := require.New(t) - _, genesisBytes := defaultGenesis(t) + latestForkTime = defaultValidateStartTime.Add(defaultMinStakingDuration) db := memdb.New() firstDB := prefixdb.New([]byte{}, db) @@ -1820,12 +1773,16 @@ func TestUptimeDisallowedWithRestart(t *testing.T) { RewardConfig: defaultRewardConfig, Validators: validators.NewManager(), UptimeLockedCalculator: uptime.NewLockedCalculator(), - BanffTime: banffForkTime, + BanffTime: latestForkTime, + CortinaTime: latestForkTime, + DurangoTime: latestForkTime, }} - firstCtx := defaultContext(t) + firstCtx := snowtest.Context(t, snowtest.PChainID) firstCtx.Lock.Lock() + _, genesisBytes := defaultGenesis(t, firstCtx.AVAXAssetID) + firstMsgChan := make(chan common.Message, 1) require.NoError(firstVM.Initialize( context.Background(), @@ -1839,7 +1796,7 @@ func TestUptimeDisallowedWithRestart(t *testing.T) { nil, )) - initialClkTime := defaultValidateStartTime + initialClkTime := latestForkTime.Add(time.Second) firstVM.clock.Set(initialClkTime) // Set VM state to NormalOp, to start tracking validators' uptime @@ -1848,7 +1805,8 @@ func TestUptimeDisallowedWithRestart(t *testing.T) { // Fast forward clock so that validators meet 20% uptime required for reward durationForReward := defaultValidateEndTime.Sub(defaultValidateStartTime) * firstUptimePercentage / 100 - firstVM.clock.Set(defaultValidateStartTime.Add(durationForReward)) + vmStopTime := defaultValidateStartTime.Add(durationForReward) + firstVM.clock.Set(vmStopTime) // Shutdown VM to stop all genesis validator uptime. // At this point they have been validating for the 20% uptime needed to be rewarded @@ -1863,16 +1821,22 @@ func TestUptimeDisallowedWithRestart(t *testing.T) { UptimePercentage: secondUptimePercentage / 100., Validators: validators.NewManager(), UptimeLockedCalculator: uptime.NewLockedCalculator(), - BanffTime: banffForkTime, + BanffTime: latestForkTime, + CortinaTime: latestForkTime, + DurangoTime: latestForkTime, }} - secondCtx := defaultContext(t) + secondCtx := snowtest.Context(t, snowtest.PChainID) secondCtx.Lock.Lock() defer func() { require.NoError(secondVM.Shutdown(context.Background())) secondCtx.Lock.Unlock() }() + atomicDB := prefixdb.New([]byte{1}, db) + m := atomic.NewMemory(atomicDB) + secondCtx.SharedMemory = m.NewSharedMemory(secondCtx.ChainID) + secondMsgChan := make(chan common.Message, 1) require.NoError(secondVM.Initialize( context.Background(), @@ -1886,8 +1850,7 @@ func TestUptimeDisallowedWithRestart(t *testing.T) { nil, )) - // set clock to the time we switched firstVM off - secondVM.clock.Set(defaultValidateStartTime.Add(durationForReward)) + secondVM.clock.Set(vmStopTime) // Set VM state to NormalOp, to start tracking validators' uptime require.NoError(secondVM.SetState(context.Background(), snow.Bootstrapping)) @@ -1947,7 +1910,8 @@ func TestUptimeDisallowedWithRestart(t *testing.T) { func TestUptimeDisallowedAfterNeverConnecting(t *testing.T) { require := require.New(t) - _, genesisBytes := defaultGenesis(t) + latestForkTime = defaultValidateStartTime.Add(defaultMinStakingDuration) + db := memdb.New() vm := &VM{Config: config.Config{ @@ -1956,12 +1920,20 @@ func TestUptimeDisallowedAfterNeverConnecting(t *testing.T) { RewardConfig: defaultRewardConfig, Validators: validators.NewManager(), UptimeLockedCalculator: uptime.NewLockedCalculator(), - BanffTime: banffForkTime, + BanffTime: latestForkTime, + CortinaTime: latestForkTime, + DurangoTime: latestForkTime, }} - ctx := defaultContext(t) + ctx := snowtest.Context(t, snowtest.PChainID) ctx.Lock.Lock() + _, genesisBytes := defaultGenesis(t, ctx.AVAXAssetID) + + atomicDB := prefixdb.New([]byte{1}, db) + m := atomic.NewMemory(atomicDB) + ctx.SharedMemory = m.NewSharedMemory(ctx.ChainID) + msgChan := make(chan common.Message, 1) appSender := &common.SenderTest{T: t} require.NoError(vm.Initialize( @@ -1981,7 +1953,7 @@ func TestUptimeDisallowedAfterNeverConnecting(t *testing.T) { ctx.Lock.Unlock() }() - initialClkTime := defaultValidateStartTime + initialClkTime := latestForkTime.Add(time.Second) vm.clock.Set(initialClkTime) // Set VM state to NormalOp, to start tracking validators' uptime @@ -2043,10 +2015,10 @@ func TestUptimeDisallowedAfterNeverConnecting(t *testing.T) { func TestRemovePermissionedValidatorDuringAddPending(t *testing.T) { require := require.New(t) - validatorStartTime := banffForkTime.Add(txexecutor.SyncBound).Add(1 * time.Second) + validatorStartTime := latestForkTime.Add(txexecutor.SyncBound).Add(1 * time.Second) validatorEndTime := validatorStartTime.Add(360 * 24 * time.Hour) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { @@ -2073,7 +2045,9 @@ func TestRemovePermissionedValidatorDuringAddPending(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Lock() // trigger block creation for the validator tx addValidatorBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -2090,7 +2064,9 @@ func TestRemovePermissionedValidatorDuringAddPending(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Lock() // trigger block creation for the subnet tx createSubnetBlock, err := vm.Builder.BuildBlock(context.Background()) @@ -2142,7 +2118,7 @@ func TestRemovePermissionedValidatorDuringAddPending(t *testing.T) { func TestTransferSubnetOwnershipTx(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -2159,7 +2135,9 @@ func TestTransferSubnetOwnershipTx(t *testing.T) { require.NoError(err) subnetID := createSubnetTx.ID() - require.NoError(vm.Network.IssueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), createSubnetTx)) + vm.ctx.Lock.Lock() createSubnetBlock, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -2191,7 +2169,9 @@ func TestTransferSubnetOwnershipTx(t *testing.T) { ) require.NoError(err) - require.NoError(vm.Network.IssueTx(context.Background(), transferSubnetOwnershipTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), transferSubnetOwnershipTx)) + vm.ctx.Lock.Lock() transferSubnetOwnershipBlock, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -2217,7 +2197,7 @@ func TestTransferSubnetOwnershipTx(t *testing.T) { func TestBaseTx(t *testing.T) { require := require.New(t) - vm, _, _ := defaultVM(t) + vm, _, _ := defaultVM(t, latestFork) vm.ctx.Lock.Lock() defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -2277,7 +2257,9 @@ func TestBaseTx(t *testing.T) { require.Equal(vm.TxFee, totalInputAmt-totalOutputAmt) require.Equal(sendAmt, key1OutputAmt) - require.NoError(vm.Network.IssueTx(context.Background(), baseTx)) + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), baseTx)) + vm.ctx.Lock.Lock() baseTxBlock, err := vm.Builder.BuildBlock(context.Background()) require.NoError(err) @@ -2289,3 +2271,82 @@ func TestBaseTx(t *testing.T) { require.NoError(baseTxBlock.Accept(context.Background())) require.NoError(vm.SetPreference(context.Background(), vm.manager.LastAccepted())) } + +func TestPruneMempool(t *testing.T) { + require := require.New(t) + vm, _, _ := defaultVM(t, latestFork) + vm.ctx.Lock.Lock() + defer func() { + require.NoError(vm.Shutdown(context.Background())) + vm.ctx.Lock.Unlock() + }() + + // Create a tx that will be valid regardless of timestamp. + sendAmt := uint64(100000) + changeAddr := ids.ShortEmpty + + baseTx, err := vm.txBuilder.NewBaseTx( + sendAmt, + secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + keys[1].Address(), + }, + }, + []*secp256k1.PrivateKey{keys[0]}, + changeAddr, + ) + require.NoError(err) + + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), baseTx)) + vm.ctx.Lock.Lock() + + // [baseTx] should be in the mempool. + baseTxID := baseTx.ID() + _, ok := vm.Builder.Get(baseTxID) + require.True(ok) + + // Create a tx that will be invalid after time advancement. + var ( + startTime = vm.clock.Time() + endTime = startTime.Add(vm.MinStakeDuration) + ) + + addValidatorTx, err := vm.txBuilder.NewAddValidatorTx( + defaultMinValidatorStake, + uint64(startTime.Unix()), + uint64(endTime.Unix()), + ids.GenerateTestNodeID(), + keys[2].Address(), + 20000, + []*secp256k1.PrivateKey{keys[1]}, + ids.ShortEmpty, + ) + require.NoError(err) + + vm.ctx.Lock.Unlock() + require.NoError(vm.issueTx(context.Background(), addValidatorTx)) + vm.ctx.Lock.Lock() + + // Advance clock to [endTime], making [addValidatorTx] invalid. + vm.clock.Set(endTime) + + // [addValidatorTx] and [baseTx] should still be in the mempool. + addValidatorTxID := addValidatorTx.ID() + _, ok = vm.Builder.Get(addValidatorTxID) + require.True(ok) + _, ok = vm.Builder.Get(baseTxID) + require.True(ok) + + vm.ctx.Lock.Unlock() + require.NoError(vm.pruneMempool()) + vm.ctx.Lock.Lock() + + // [addValidatorTx] should be ejected from the mempool. + // [baseTx] should still be in the mempool. + _, ok = vm.Builder.Get(addValidatorTxID) + require.False(ok) + _, ok = vm.Builder.Get(baseTxID) + require.True(ok) +} diff --git a/vms/platformvm/warp/codec.go b/vms/platformvm/warp/codec.go index cf4587224751..6ef6e526bdc1 100644 --- a/vms/platformvm/warp/codec.go +++ b/vms/platformvm/warp/codec.go @@ -1,28 +1,28 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp import ( "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/utils" ) -const codecVersion = 0 +const CodecVersion = 0 -// Codec does serialization and deserialization for Warp messages. -var c codec.Manager +var Codec codec.Manager func init() { - c = codec.NewManager(math.MaxInt) - lc := linearcodec.NewCustomMaxLength(math.MaxInt32) + Codec = codec.NewManager(math.MaxInt) + lc := linearcodec.NewDefault(time.Time{}) err := utils.Err( lc.RegisterType(&BitSetSignature{}), - c.RegisterCodec(codecVersion, lc), + Codec.RegisterCodec(CodecVersion, lc), ) if err != nil { panic(err) diff --git a/vms/platformvm/warp/constants.go b/vms/platformvm/warp/constants.go index a91f5f39394f..723cdf50bc82 100644 --- a/vms/platformvm/warp/constants.go +++ b/vms/platformvm/warp/constants.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp diff --git a/vms/platformvm/warp/gwarp/client.go b/vms/platformvm/warp/gwarp/client.go index 6619b4ff6ab7..0b51a54971f7 100644 --- a/vms/platformvm/warp/gwarp/client.go +++ b/vms/platformvm/warp/gwarp/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gwarp diff --git a/vms/platformvm/warp/gwarp/server.go b/vms/platformvm/warp/gwarp/server.go index 4fbee3a3e736..7857f4e0ee70 100644 --- a/vms/platformvm/warp/gwarp/server.go +++ b/vms/platformvm/warp/gwarp/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gwarp diff --git a/vms/platformvm/warp/gwarp/signer_test.go b/vms/platformvm/warp/gwarp/signer_test.go index d1d6e8f7147d..306067dc883d 100644 --- a/vms/platformvm/warp/gwarp/signer_test.go +++ b/vms/platformvm/warp/gwarp/signer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gwarp diff --git a/vms/platformvm/warp/message.go b/vms/platformvm/warp/message.go index 34850aed98ad..76383bdafa11 100644 --- a/vms/platformvm/warp/message.go +++ b/vms/platformvm/warp/message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp @@ -28,7 +28,7 @@ func ParseMessage(b []byte) (*Message, error) { msg := &Message{ bytes: b, } - _, err := c.Unmarshal(b, msg) + _, err := Codec.Unmarshal(b, msg) if err != nil { return nil, err } @@ -38,7 +38,7 @@ func ParseMessage(b []byte) (*Message, error) { // Initialize recalculates the result of Bytes(). It does not call Initialize() // on the UnsignedMessage. func (m *Message) Initialize() error { - bytes, err := c.Marshal(codecVersion, m) + bytes, err := Codec.Marshal(CodecVersion, m) m.bytes = bytes return err } diff --git a/vms/platformvm/warp/message_test.go b/vms/platformvm/warp/message_test.go index 910abd3403c9..99a50b366d95 100644 --- a/vms/platformvm/warp/message_test.go +++ b/vms/platformvm/warp/message_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp diff --git a/vms/platformvm/warp/payload/addressed_call.go b/vms/platformvm/warp/payload/addressed_call.go index afdecd9e9f01..b3617ce487da 100644 --- a/vms/platformvm/warp/payload/addressed_call.go +++ b/vms/platformvm/warp/payload/addressed_call.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package payload diff --git a/vms/platformvm/warp/payload/addressed_call_test.go b/vms/platformvm/warp/payload/addressed_call_test.go index 0e60ef294c4b..77a885d836d5 100644 --- a/vms/platformvm/warp/payload/addressed_call_test.go +++ b/vms/platformvm/warp/payload/addressed_call_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package payload diff --git a/vms/platformvm/warp/payload/codec.go b/vms/platformvm/warp/payload/codec.go index e2e8ddd7a7f5..d188029abfed 100644 --- a/vms/platformvm/warp/payload/codec.go +++ b/vms/platformvm/warp/payload/codec.go @@ -1,9 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package payload import ( + "time" + "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/utils" @@ -11,26 +13,21 @@ import ( ) const ( - codecVersion = 0 + CodecVersion = 0 MaxMessageSize = 24 * units.KiB - - // Note: Modifying this variable can have subtle implications on memory - // usage when parsing malformed payloads. - MaxSliceLen = 24 * 1024 ) -// Codec does serialization and deserialization for Warp messages. -var c codec.Manager +var Codec codec.Manager func init() { - c = codec.NewManager(MaxMessageSize) - lc := linearcodec.NewCustomMaxLength(MaxSliceLen) + Codec = codec.NewManager(MaxMessageSize) + lc := linearcodec.NewDefault(time.Time{}) err := utils.Err( lc.RegisterType(&Hash{}), lc.RegisterType(&AddressedCall{}), - c.RegisterCodec(codecVersion, lc), + Codec.RegisterCodec(CodecVersion, lc), ) if err != nil { panic(err) diff --git a/vms/platformvm/warp/payload/hash.go b/vms/platformvm/warp/payload/hash.go index f3a0eb0c09d3..330f74fd869d 100644 --- a/vms/platformvm/warp/payload/hash.go +++ b/vms/platformvm/warp/payload/hash.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package payload diff --git a/vms/platformvm/warp/payload/hash_test.go b/vms/platformvm/warp/payload/hash_test.go index 1d890a8bd551..d58fe5e6a0c0 100644 --- a/vms/platformvm/warp/payload/hash_test.go +++ b/vms/platformvm/warp/payload/hash_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package payload diff --git a/vms/platformvm/warp/payload/payload.go b/vms/platformvm/warp/payload/payload.go index e4601945be98..c5c09464803e 100644 --- a/vms/platformvm/warp/payload/payload.go +++ b/vms/platformvm/warp/payload/payload.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package payload @@ -22,7 +22,7 @@ type Payload interface { func Parse(bytes []byte) (Payload, error) { var payload Payload - if _, err := c.Unmarshal(bytes, &payload); err != nil { + if _, err := Codec.Unmarshal(bytes, &payload); err != nil { return nil, err } payload.initialize(bytes) @@ -30,7 +30,7 @@ func Parse(bytes []byte) (Payload, error) { } func initialize(p Payload) error { - bytes, err := c.Marshal(codecVersion, &p) + bytes, err := Codec.Marshal(CodecVersion, &p) if err != nil { return fmt.Errorf("couldn't marshal %T payload: %w", p, err) } diff --git a/vms/platformvm/warp/payload/payload_test.go b/vms/platformvm/warp/payload/payload_test.go index da14b8de0dbb..86b584ae33db 100644 --- a/vms/platformvm/warp/payload/payload_test.go +++ b/vms/platformvm/warp/payload/payload_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package payload diff --git a/vms/platformvm/warp/signature.go b/vms/platformvm/warp/signature.go index 2f2b0cae985b..667383376501 100644 --- a/vms/platformvm/warp/signature.go +++ b/vms/platformvm/warp/signature.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp diff --git a/vms/platformvm/warp/signature_test.go b/vms/platformvm/warp/signature_test.go index b3eaa88bbfe8..b50891ede5d4 100644 --- a/vms/platformvm/warp/signature_test.go +++ b/vms/platformvm/warp/signature_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp @@ -39,8 +39,8 @@ type testValidator struct { vdr *Validator } -func (v *testValidator) Less(o *testValidator) bool { - return v.vdr.Less(o.vdr) +func (v *testValidator) Compare(o *testValidator) int { + return v.vdr.Compare(o.vdr) } func newTestValidator() *testValidator { diff --git a/vms/platformvm/warp/signer.go b/vms/platformvm/warp/signer.go index 76f8ec02b4cd..8372aef0a728 100644 --- a/vms/platformvm/warp/signer.go +++ b/vms/platformvm/warp/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp diff --git a/vms/platformvm/warp/signer_test.go b/vms/platformvm/warp/signer_test.go index d4e83c24a850..1bc177872d87 100644 --- a/vms/platformvm/warp/signer_test.go +++ b/vms/platformvm/warp/signer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp diff --git a/vms/platformvm/warp/test_signer.go b/vms/platformvm/warp/test_signer.go index aef578f78ae2..c17b15b215e2 100644 --- a/vms/platformvm/warp/test_signer.go +++ b/vms/platformvm/warp/test_signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp diff --git a/vms/platformvm/warp/unsigned_message.go b/vms/platformvm/warp/unsigned_message.go index 95ef0d2d07f0..1e66f552c9fa 100644 --- a/vms/platformvm/warp/unsigned_message.go +++ b/vms/platformvm/warp/unsigned_message.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp @@ -41,13 +41,13 @@ func ParseUnsignedMessage(b []byte) (*UnsignedMessage, error) { bytes: b, id: hashing.ComputeHash256Array(b), } - _, err := c.Unmarshal(b, msg) + _, err := Codec.Unmarshal(b, msg) return msg, err } // Initialize recalculates the result of Bytes(). func (m *UnsignedMessage) Initialize() error { - bytes, err := c.Marshal(codecVersion, m) + bytes, err := Codec.Marshal(CodecVersion, m) if err != nil { return fmt.Errorf("couldn't marshal warp unsigned message: %w", err) } diff --git a/vms/platformvm/warp/unsigned_message_test.go b/vms/platformvm/warp/unsigned_message_test.go index f3be73ef6c77..03a140d14c27 100644 --- a/vms/platformvm/warp/unsigned_message_test.go +++ b/vms/platformvm/warp/unsigned_message_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp diff --git a/vms/platformvm/warp/validator.go b/vms/platformvm/warp/validator.go index 42ff34e7cb5e..2ada068adc76 100644 --- a/vms/platformvm/warp/validator.go +++ b/vms/platformvm/warp/validator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp @@ -39,8 +39,8 @@ type Validator struct { NodeIDs []ids.NodeID } -func (v *Validator) Less(o *Validator) bool { - return bytes.Compare(v.PublicKeyBytes, o.PublicKeyBytes) < 0 +func (v *Validator) Compare(o *Validator) int { + return bytes.Compare(v.PublicKeyBytes, o.PublicKeyBytes) } // GetCanonicalValidatorSet returns the validator set of [subnetID] at diff --git a/vms/platformvm/warp/validator_test.go b/vms/platformvm/warp/validator_test.go index 9af37aed81f6..3fbaf8860dbe 100644 --- a/vms/platformvm/warp/validator_test.go +++ b/vms/platformvm/warp/validator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package warp diff --git a/vms/propertyfx/burn_operation.go b/vms/propertyfx/burn_operation.go index 4217420b3b62..1dedb4c2f448 100644 --- a/vms/propertyfx/burn_operation.go +++ b/vms/propertyfx/burn_operation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/burn_operation_test.go b/vms/propertyfx/burn_operation_test.go index e9e9735efd3c..b6a995b0307c 100644 --- a/vms/propertyfx/burn_operation_test.go +++ b/vms/propertyfx/burn_operation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/credential.go b/vms/propertyfx/credential.go index 0a67c182a995..3a464cc29dfe 100644 --- a/vms/propertyfx/credential.go +++ b/vms/propertyfx/credential.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/credential_test.go b/vms/propertyfx/credential_test.go index 4be34acd3247..3ce9bc97f3c3 100644 --- a/vms/propertyfx/credential_test.go +++ b/vms/propertyfx/credential_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/factory.go b/vms/propertyfx/factory.go index 21c69c97cd98..c42b92c84c5f 100644 --- a/vms/propertyfx/factory.go +++ b/vms/propertyfx/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/factory_test.go b/vms/propertyfx/factory_test.go index ec921aef3f69..f40cb2610a80 100644 --- a/vms/propertyfx/factory_test.go +++ b/vms/propertyfx/factory_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/fx.go b/vms/propertyfx/fx.go index 28d211a9b5ad..24a3dff171cb 100644 --- a/vms/propertyfx/fx.go +++ b/vms/propertyfx/fx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/fx_test.go b/vms/propertyfx/fx_test.go index fdab69bb5bf4..0cd995ba5282 100644 --- a/vms/propertyfx/fx_test.go +++ b/vms/propertyfx/fx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx @@ -39,7 +39,7 @@ var ( func TestFxInitialize(t *testing.T) { vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } fx := Fx{} @@ -56,7 +56,7 @@ func TestFxVerifyMintOperation(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -98,7 +98,7 @@ func TestFxVerifyMintOperationWrongTx(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -132,7 +132,7 @@ func TestFxVerifyMintOperationWrongNumberUTXOs(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -163,7 +163,7 @@ func TestFxVerifyMintOperationWrongCredential(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -195,7 +195,7 @@ func TestFxVerifyMintOperationInvalidUTXO(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -226,7 +226,7 @@ func TestFxVerifyMintOperationFailingVerification(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -264,7 +264,7 @@ func TestFxVerifyMintOperationInvalidGroupID(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -301,7 +301,7 @@ func TestFxVerifyTransferOperation(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -335,7 +335,7 @@ func TestFxVerifyTransferOperationWrongUTXO(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -364,7 +364,7 @@ func TestFxVerifyTransferOperationFailedVerify(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -399,7 +399,7 @@ func TestFxVerifyOperationUnknownOperation(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -431,7 +431,7 @@ func TestFxVerifyTransfer(t *testing.T) { require := require.New(t) vm := secp256k1fx.TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) diff --git a/vms/propertyfx/mint_operation.go b/vms/propertyfx/mint_operation.go index 535ea1359010..7eecf5de27ad 100644 --- a/vms/propertyfx/mint_operation.go +++ b/vms/propertyfx/mint_operation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/mint_operation_test.go b/vms/propertyfx/mint_operation_test.go index 138d989d3296..abcc552ace4c 100644 --- a/vms/propertyfx/mint_operation_test.go +++ b/vms/propertyfx/mint_operation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/mint_output.go b/vms/propertyfx/mint_output.go index 3aebd115a404..7ff60375721c 100644 --- a/vms/propertyfx/mint_output.go +++ b/vms/propertyfx/mint_output.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/mint_output_test.go b/vms/propertyfx/mint_output_test.go index 0b4b76c55f88..4cfa1da038d8 100644 --- a/vms/propertyfx/mint_output_test.go +++ b/vms/propertyfx/mint_output_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/owned_output.go b/vms/propertyfx/owned_output.go index 30e32ca3ddf2..cbe2f4376753 100644 --- a/vms/propertyfx/owned_output.go +++ b/vms/propertyfx/owned_output.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/propertyfx/owned_output_test.go b/vms/propertyfx/owned_output_test.go index dbc7bea63698..a9c9adc57643 100644 --- a/vms/propertyfx/owned_output_test.go +++ b/vms/propertyfx/owned_output_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package propertyfx diff --git a/vms/proposervm/README.md b/vms/proposervm/README.md index e01a014f1156..6dec4fe9d932 100644 --- a/vms/proposervm/README.md +++ b/vms/proposervm/README.md @@ -47,7 +47,7 @@ A proposer in position `i` in the proposers list has its submission windows star The following validation rules are enforced: - Given a `proposervm.Block` **C** and its parent block **P**, **P**'s inner block must be **C**'s inner block's parent. -- A block must have a `PChainHeight` is larger or equal to its parent's `PChainHeight` (`PChainHeight` is monotonic). +- A block must have a `PChainHeight` that is larger or equal to its parent's `PChainHeight` (`PChainHeight` is monotonic). - A block must have a `PChainHeight` that is less or equal to current P-Chain height. - A block must have a `Timestamp` larger or equal to its parent's `Timestamp` (`Timestamp` is monotonic) - A block received by a node at time `t_local` must have a `Timestamp` such that `Timestamp < t_local + maxSkew` (a block too far in the future is invalid). `maxSkew` is currently set to `10 seconds`. diff --git a/vms/proposervm/batched_vm.go b/vms/proposervm/batched_vm.go index fd104318cea7..0bf514827193 100644 --- a/vms/proposervm/batched_vm.go +++ b/vms/proposervm/batched_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -101,7 +101,7 @@ func (vm *VM) BatchedParseBlock(ctx context.Context, blks [][]byte) ([]snowman.B ) for ; blocksIndex < len(blks); blocksIndex++ { blkBytes := blks[blocksIndex] - statelessBlock, err := statelessblock.Parse(blkBytes) + statelessBlock, err := statelessblock.Parse(blkBytes, vm.DurangoTime) if err != nil { break } diff --git a/vms/proposervm/batched_vm_test.go b/vms/proposervm/batched_vm_test.go index 684d91ceb3df..27cdaba0493f 100644 --- a/vms/proposervm/batched_vm_test.go +++ b/vms/proposervm/batched_vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -20,16 +20,20 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/avalanchego/vms/proposervm/proposer" ) func TestCoreVMNotRemote(t *testing.T) { // if coreVM is not remote VM, a specific error is returned require := require.New(t) - _, _, proVM, _, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + _, _, proVM, _, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -54,7 +58,11 @@ func TestCoreVMNotRemote(t *testing.T) { func TestGetAncestorsPreForkOnly(t *testing.T) { require := require.New(t) - coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, mockable.MaxTime) // disable ProBlks + var ( + activationTime = mockable.MaxTime + durangoTime = activationTime + ) + coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, activationTime, durangoTime) defer func() { require.NoError(proRemoteVM.Shutdown(context.Background())) }() @@ -200,7 +208,11 @@ func TestGetAncestorsPreForkOnly(t *testing.T) { func TestGetAncestorsPostForkOnly(t *testing.T) { require := require.New(t) - coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, time.Time{}) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, activationTime, durangoTime) defer func() { require.NoError(proRemoteVM.Shutdown(context.Background())) }() @@ -211,10 +223,9 @@ func TestGetAncestorsPostForkOnly(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk1, nil @@ -225,17 +236,16 @@ func TestGetAncestorsPostForkOnly(t *testing.T) { // prepare build of next block require.NoError(builtBlk1.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk1.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) + require.NoError(waitForProposerWindow(proRemoteVM, builtBlk1, 0)) coreBlk2 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreBlk1.ID(), - HeightV: coreBlk1.Height() + 1, - TimestampV: coreBlk1.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{2}, + ParentV: coreBlk1.ID(), + HeightV: coreBlk1.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk2, nil @@ -246,17 +256,16 @@ func TestGetAncestorsPostForkOnly(t *testing.T) { // prepare build of next block require.NoError(builtBlk2.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk2.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) + require.NoError(waitForProposerWindow(proRemoteVM, builtBlk2, 0)) coreBlk3 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: coreBlk2.ID(), - HeightV: coreBlk2.Height() + 1, - TimestampV: coreBlk2.Timestamp(), + BytesV: []byte{3}, + ParentV: coreBlk2.ID(), + HeightV: coreBlk2.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk3, nil @@ -352,13 +361,18 @@ func TestGetAncestorsPostForkOnly(t *testing.T) { func TestGetAncestorsAtSnomanPlusPlusFork(t *testing.T) { require := require.New(t) - currentTime := time.Now().Truncate(time.Second) - preForkTime := currentTime.Add(5 * time.Minute) - forkTime := currentTime.Add(10 * time.Minute) - postForkTime := currentTime.Add(15 * time.Minute) + + var ( + currentTime = time.Now().Truncate(time.Second) + preForkTime = currentTime.Add(5 * time.Minute) + forkTime = currentTime.Add(10 * time.Minute) + postForkTime = currentTime.Add(15 * time.Minute) + + durangoTime = forkTime + ) // enable ProBlks in next future - coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, forkTime) + coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, forkTime, durangoTime) defer func() { require.NoError(proRemoteVM.Shutdown(context.Background())) }() @@ -428,10 +442,9 @@ func TestGetAncestorsAtSnomanPlusPlusFork(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: coreBlk2.ID(), - HeightV: coreBlk2.Height() + 1, - TimestampV: postForkTime.Add(proposer.MaxVerifyDelay), + BytesV: []byte{3}, + ParentV: coreBlk2.ID(), + HeightV: coreBlk2.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk3, nil @@ -443,17 +456,16 @@ func TestGetAncestorsAtSnomanPlusPlusFork(t *testing.T) { // prepare build of next block require.NoError(builtBlk3.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk3.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) + require.NoError(waitForProposerWindow(proRemoteVM, builtBlk3, builtBlk3.(*postForkBlock).PChainHeight())) coreBlk4 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{4}, - ParentV: coreBlk3.ID(), - HeightV: coreBlk3.Height() + 1, - TimestampV: postForkTime, + BytesV: []byte{4}, + ParentV: coreBlk3.ID(), + HeightV: coreBlk3.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk4, nil @@ -558,7 +570,11 @@ func TestGetAncestorsAtSnomanPlusPlusFork(t *testing.T) { func TestBatchedParseBlockPreForkOnly(t *testing.T) { require := require.New(t) - coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, mockable.MaxTime) // disable ProBlks + var ( + activationTime = mockable.MaxTime + durangoTime = activationTime + ) + coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, activationTime, durangoTime) defer func() { require.NoError(proRemoteVM.Shutdown(context.Background())) }() @@ -569,10 +585,9 @@ func TestBatchedParseBlockPreForkOnly(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk1, nil @@ -596,10 +611,9 @@ func TestBatchedParseBlockPreForkOnly(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreBlk1.ID(), - HeightV: coreBlk1.Height() + 1, - TimestampV: coreBlk1.Timestamp(), + BytesV: []byte{2}, + ParentV: coreBlk1.ID(), + HeightV: coreBlk1.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk2, nil @@ -623,10 +637,9 @@ func TestBatchedParseBlockPreForkOnly(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: coreBlk2.ID(), - HeightV: coreBlk2.Height() + 1, - TimestampV: coreBlk2.Timestamp(), + BytesV: []byte{3}, + ParentV: coreBlk2.ID(), + HeightV: coreBlk2.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk3, nil @@ -679,7 +692,11 @@ func TestBatchedParseBlockPreForkOnly(t *testing.T) { func TestBatchedParseBlockPostForkOnly(t *testing.T) { require := require.New(t) - coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, time.Time{}) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, activationTime, durangoTime) defer func() { require.NoError(proRemoteVM.Shutdown(context.Background())) }() @@ -690,10 +707,9 @@ func TestBatchedParseBlockPostForkOnly(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk1, nil @@ -704,17 +720,16 @@ func TestBatchedParseBlockPostForkOnly(t *testing.T) { // prepare build of next block require.NoError(builtBlk1.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk1.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) + require.NoError(waitForProposerWindow(proRemoteVM, builtBlk1, 0)) coreBlk2 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreBlk1.ID(), - HeightV: coreBlk1.Height() + 1, - TimestampV: coreBlk1.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{2}, + ParentV: coreBlk1.ID(), + HeightV: coreBlk1.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk2, nil @@ -725,17 +740,16 @@ func TestBatchedParseBlockPostForkOnly(t *testing.T) { // prepare build of next block require.NoError(builtBlk2.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk2.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) + require.NoError(waitForProposerWindow(proRemoteVM, builtBlk2, builtBlk2.(*postForkBlock).PChainHeight())) coreBlk3 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: coreBlk2.ID(), - HeightV: coreBlk2.Height() + 1, - TimestampV: coreBlk2.Timestamp(), + BytesV: []byte{3}, + ParentV: coreBlk2.ID(), + HeightV: coreBlk2.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk3, nil @@ -788,13 +802,18 @@ func TestBatchedParseBlockPostForkOnly(t *testing.T) { func TestBatchedParseBlockAtSnomanPlusPlusFork(t *testing.T) { require := require.New(t) - currentTime := time.Now().Truncate(time.Second) - preForkTime := currentTime.Add(5 * time.Minute) - forkTime := currentTime.Add(10 * time.Minute) - postForkTime := currentTime.Add(15 * time.Minute) + + var ( + currentTime = time.Now().Truncate(time.Second) + preForkTime = currentTime.Add(5 * time.Minute) + forkTime = currentTime.Add(10 * time.Minute) + postForkTime = currentTime.Add(15 * time.Minute) + + durangoTime = forkTime + ) // enable ProBlks in next future - coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, forkTime) + coreVM, proRemoteVM, coreGenBlk := initTestRemoteProposerVM(t, forkTime, durangoTime) defer func() { require.NoError(proRemoteVM.Shutdown(context.Background())) }() @@ -864,10 +883,9 @@ func TestBatchedParseBlockAtSnomanPlusPlusFork(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: coreBlk2.ID(), - HeightV: coreBlk2.Height() + 1, - TimestampV: postForkTime.Add(proposer.MaxVerifyDelay), + BytesV: []byte{3}, + ParentV: coreBlk2.ID(), + HeightV: coreBlk2.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk3, nil @@ -879,17 +897,16 @@ func TestBatchedParseBlockAtSnomanPlusPlusFork(t *testing.T) { // prepare build of next block require.NoError(builtBlk3.Verify(context.Background())) require.NoError(proRemoteVM.SetPreference(context.Background(), builtBlk3.ID())) - proRemoteVM.Set(proRemoteVM.Time().Add(proposer.MaxBuildDelay)) + require.NoError(waitForProposerWindow(proRemoteVM, builtBlk3, builtBlk3.(*postForkBlock).PChainHeight())) coreBlk4 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{4}, - ParentV: coreBlk3.ID(), - HeightV: coreBlk3.Height() + 1, - TimestampV: postForkTime, + BytesV: []byte{4}, + ParentV: coreBlk3.ID(), + HeightV: coreBlk3.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk4, nil @@ -956,7 +973,8 @@ type TestRemoteProposerVM struct { func initTestRemoteProposerVM( t *testing.T, - proBlkStartTime time.Time, + activationTime, + durangoTime time.Time, ) ( TestRemoteProposerVM, *VM, @@ -1020,12 +1038,15 @@ func initTestRemoteProposerVM( proVM := New( coreVM, - proBlkStartTime, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: activationTime, + DurangoTime: durangoTime, + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) valState := &validators.TestState{ @@ -1064,7 +1085,7 @@ func initTestRemoteProposerVM( }, nil } - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) ctx.NodeID = ids.NodeIDFromCert(pTestCert) ctx.ValidatorState = valState diff --git a/vms/proposervm/block.go b/vms/proposervm/block.go index 489d325f8f70..10a151952a10 100644 --- a/vms/proposervm/block.go +++ b/vms/proposervm/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -35,6 +35,7 @@ var ( errPChainHeightNotReached = errors.New("block P-chain height larger than current P-chain height") errTimeTooAdvanced = errors.New("time is too far advanced") errProposerWindowNotStarted = errors.New("proposer window hasn't started") + errUnexpectedProposer = errors.New("unexpected proposer for current window") errProposersNotActivated = errors.New("proposers haven't been activated yet") errPChainHeightTooLow = errors.New("block P-chain height is too low") ) @@ -124,12 +125,11 @@ func (p *postForkCommonComponents) Verify( // If the node is currently syncing - we don't assume that the P-chain has // been synced up to this point yet. if p.vm.consensusState == snow.NormalOp { - childID := child.ID() currentPChainHeight, err := p.vm.ctx.ValidatorState.GetCurrentHeight(ctx) if err != nil { p.vm.ctx.Log.Error("block verification failed", zap.String("reason", "failed to get current P-Chain height"), - zap.Stringer("blkID", childID), + zap.Stringer("blkID", child.ID()), zap.Error(err), ) return err @@ -142,28 +142,24 @@ func (p *postForkCommonComponents) Verify( ) } - childHeight := child.Height() - proposerID := child.Proposer() - minDelay, err := p.vm.Windower.Delay(ctx, childHeight, parentPChainHeight, proposerID, proposer.MaxVerifyWindows) + var shouldHaveProposer bool + if p.vm.IsDurangoActivated(parentTimestamp) { + shouldHaveProposer, err = p.verifyPostDurangoBlockDelay(ctx, parentTimestamp, parentPChainHeight, child) + } else { + shouldHaveProposer, err = p.verifyPreDurangoBlockDelay(ctx, parentTimestamp, parentPChainHeight, child) + } if err != nil { return err } - delay := childTimestamp.Sub(parentTimestamp) - if delay < minDelay { - return errProposerWindowNotStarted - } - // Verify the signature of the node - shouldHaveProposer := delay < proposer.MaxVerifyDelay if err := child.SignedBlock.Verify(shouldHaveProposer, p.vm.ctx.ChainID); err != nil { return err } p.vm.ctx.Log.Debug("verified post-fork block", - zap.Stringer("blkID", childID), + zap.Stringer("blkID", child.ID()), zap.Time("parentTimestamp", parentTimestamp), - zap.Duration("minDelay", minDelay), zap.Time("blockTimestamp", childTimestamp), ) } @@ -202,37 +198,26 @@ func (p *postForkCommonComponents) buildChild( return nil, err } - delay := newTimestamp.Sub(parentTimestamp) - if delay < proposer.MaxBuildDelay { - parentHeight := p.innerBlk.Height() - proposerID := p.vm.ctx.NodeID - minDelay, err := p.vm.Windower.Delay(ctx, parentHeight+1, parentPChainHeight, proposerID, proposer.MaxBuildWindows) - if err != nil { - p.vm.ctx.Log.Error("unexpected build block failure", - zap.String("reason", "failed to calculate required timestamp delay"), - zap.Stringer("parentID", parentID), - zap.Error(err), - ) - return nil, err - } - - if delay < minDelay { - // It's not our turn to propose a block yet. This is likely caused - // by having previously notified the consensus engine to attempt to - // build a block on top of a block that is no longer the preferred - // block. - p.vm.ctx.Log.Debug("build block dropped", - zap.Time("parentTimestamp", parentTimestamp), - zap.Duration("minDelay", minDelay), - zap.Time("blockTimestamp", newTimestamp), - ) - - // In case the inner VM only issued one pendingTxs message, we - // should attempt to re-handle that once it is our turn to build the - // block. - p.vm.notifyInnerBlockReady() - return nil, errProposerWindowNotStarted - } + var shouldBuildSignedBlock bool + if p.vm.IsDurangoActivated(parentTimestamp) { + shouldBuildSignedBlock, err = p.shouldBuildSignedBlockPostDurango( + ctx, + parentID, + parentTimestamp, + parentPChainHeight, + newTimestamp, + ) + } else { + shouldBuildSignedBlock, err = p.shouldBuildSignedBlockPreDurango( + ctx, + parentID, + parentTimestamp, + parentPChainHeight, + newTimestamp, + ) + } + if err != nil { + return nil, err } var innerBlock snowman.Block @@ -249,22 +234,22 @@ func (p *postForkCommonComponents) buildChild( // Build the child var statelessChild block.SignedBlock - if delay >= proposer.MaxVerifyDelay { - statelessChild, err = block.BuildUnsigned( + if shouldBuildSignedBlock { + statelessChild, err = block.Build( parentID, newTimestamp, pChainHeight, + p.vm.StakingCertLeaf, innerBlock.Bytes(), + p.vm.ctx.ChainID, + p.vm.StakingLeafSigner, ) } else { - statelessChild, err = block.Build( + statelessChild, err = block.BuildUnsigned( parentID, newTimestamp, pChainHeight, - p.vm.stakingCertLeaf, innerBlock.Bytes(), - p.vm.ctx.ChainID, - p.vm.stakingLeafSigner, ) } if err != nil { @@ -334,3 +319,185 @@ func verifyIsNotOracleBlock(ctx context.Context, b snowman.Block) error { return err } } + +func (p *postForkCommonComponents) verifyPreDurangoBlockDelay( + ctx context.Context, + parentTimestamp time.Time, + parentPChainHeight uint64, + blk *postForkBlock, +) (bool, error) { + var ( + blkTimestamp = blk.Timestamp() + childHeight = blk.Height() + proposerID = blk.Proposer() + ) + minDelay, err := p.vm.Windower.Delay( + ctx, + childHeight, + parentPChainHeight, + proposerID, + proposer.MaxVerifyWindows, + ) + if err != nil { + p.vm.ctx.Log.Error("unexpected block verification failure", + zap.String("reason", "failed to calculate required timestamp delay"), + zap.Stringer("blkID", blk.ID()), + zap.Error(err), + ) + return false, err + } + + delay := blkTimestamp.Sub(parentTimestamp) + if delay < minDelay { + return false, errProposerWindowNotStarted + } + + return delay < proposer.MaxVerifyDelay, nil +} + +func (p *postForkCommonComponents) verifyPostDurangoBlockDelay( + ctx context.Context, + parentTimestamp time.Time, + parentPChainHeight uint64, + blk *postForkBlock, +) (bool, error) { + var ( + blkTimestamp = blk.Timestamp() + blkHeight = blk.Height() + proposerID = blk.Proposer() + ) + + expectedProposerID, err := p.vm.Windower.ExpectedProposer( + ctx, + blkHeight, + parentPChainHeight, + proposer.TimeToSlot(parentTimestamp, blkTimestamp), + ) + switch { + case errors.Is(err, proposer.ErrAnyoneCanPropose): + return false, nil // block should be unsigned + case err != nil: + p.vm.ctx.Log.Error("unexpected block verification failure", + zap.String("reason", "failed to calculate expected proposer"), + zap.Stringer("blkID", blk.ID()), + zap.Error(err), + ) + return false, err + case expectedProposerID == proposerID: + return true, nil // block should be signed + default: + return false, errUnexpectedProposer + } +} + +func (p *postForkCommonComponents) shouldBuildSignedBlockPostDurango( + ctx context.Context, + parentID ids.ID, + parentTimestamp time.Time, + parentPChainHeight uint64, + newTimestamp time.Time, +) (bool, error) { + parentHeight := p.innerBlk.Height() + currentSlot := proposer.TimeToSlot(parentTimestamp, newTimestamp) + expectedProposerID, err := p.vm.Windower.ExpectedProposer( + ctx, + parentHeight+1, + parentPChainHeight, + currentSlot, + ) + switch { + case errors.Is(err, proposer.ErrAnyoneCanPropose): + return false, nil // build an unsigned block + case err != nil: + p.vm.ctx.Log.Error("unexpected build block failure", + zap.String("reason", "failed to calculate expected proposer"), + zap.Stringer("parentID", parentID), + zap.Error(err), + ) + return false, err + case expectedProposerID == p.vm.ctx.NodeID: + return true, nil // build a signed block + } + + // It's not our turn to propose a block yet. This is likely caused by having + // previously notified the consensus engine to attempt to build a block on + // top of a block that is no longer the preferred block. + p.vm.ctx.Log.Debug("build block dropped", + zap.Time("parentTimestamp", parentTimestamp), + zap.Time("blockTimestamp", newTimestamp), + zap.Uint64("slot", currentSlot), + zap.Stringer("expectedProposer", expectedProposerID), + ) + + // We need to reschedule the block builder to the next time we can try to + // build a block. + // + // TODO: After Durango activates, restructure this logic to separate + // updating the scheduler from verifying the proposerID. + nextStartTime, err := p.vm.getPostDurangoSlotTime( + ctx, + parentHeight+1, + parentPChainHeight, + currentSlot+1, // We know we aren't the proposer for the current slot + parentTimestamp, + ) + if err != nil { + p.vm.ctx.Log.Error("failed to reset block builder scheduler", + zap.String("reason", "failed to calculate expected proposer"), + zap.Stringer("parentID", parentID), + zap.Error(err), + ) + return false, err + } + p.vm.Scheduler.SetBuildBlockTime(nextStartTime) + + // In case the inner VM only issued one pendingTxs message, we should + // attempt to re-handle that once it is our turn to build the block. + p.vm.notifyInnerBlockReady() + return false, errProposerWindowNotStarted +} + +func (p *postForkCommonComponents) shouldBuildSignedBlockPreDurango( + ctx context.Context, + parentID ids.ID, + parentTimestamp time.Time, + parentPChainHeight uint64, + newTimestamp time.Time, +) (bool, error) { + delay := newTimestamp.Sub(parentTimestamp) + if delay >= proposer.MaxBuildDelay { + return false, nil // time for any node to build an unsigned block + } + + parentHeight := p.innerBlk.Height() + proposerID := p.vm.ctx.NodeID + minDelay, err := p.vm.Windower.Delay(ctx, parentHeight+1, parentPChainHeight, proposerID, proposer.MaxBuildWindows) + if err != nil { + p.vm.ctx.Log.Error("unexpected build block failure", + zap.String("reason", "failed to calculate required timestamp delay"), + zap.Stringer("parentID", parentID), + zap.Error(err), + ) + return false, err + } + + if delay >= minDelay { + // it's time for this node to propose a block. It'll be signed or + // unsigned depending on the delay + return delay < proposer.MaxVerifyDelay, nil + } + + // It's not our turn to propose a block yet. This is likely caused by having + // previously notified the consensus engine to attempt to build a block on + // top of a block that is no longer the preferred block. + p.vm.ctx.Log.Debug("build block dropped", + zap.Time("parentTimestamp", parentTimestamp), + zap.Duration("minDelay", minDelay), + zap.Time("blockTimestamp", newTimestamp), + ) + + // In case the inner VM only issued one pendingTxs message, we should + // attempt to re-handle that once it is our turn to build the block. + p.vm.notifyInnerBlockReady() + return false, errProposerWindowNotStarted +} diff --git a/vms/proposervm/block/block.go b/vms/proposervm/block/block.go index c4abe5a9f96d..0f5b374391f7 100644 --- a/vms/proposervm/block/block.go +++ b/vms/proposervm/block/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -28,7 +28,7 @@ type Block interface { Block() []byte Bytes() []byte - initialize(bytes []byte) error + initialize(bytes []byte, durangoTime time.Time) error } type SignedBlock interface { @@ -76,7 +76,7 @@ func (b *statelessBlock) Bytes() []byte { return b.bytes } -func (b *statelessBlock) initialize(bytes []byte) error { +func (b *statelessBlock) initialize(bytes []byte, durangoTime time.Time) error { b.bytes = bytes // The serialized form of the block is the unsignedBytes followed by the @@ -91,13 +91,18 @@ func (b *statelessBlock) initialize(bytes []byte) error { return nil } - cert, err := staking.ParseCertificate(b.StatelessBlock.Certificate) + // TODO: Remove durangoTime after v1.11.x has activated. + var err error + if b.timestamp.Before(durangoTime) { + b.cert, err = staking.ParseCertificate(b.StatelessBlock.Certificate) + } else { + b.cert, err = staking.ParseCertificatePermissive(b.StatelessBlock.Certificate) + } if err != nil { return fmt.Errorf("%w: %w", errInvalidCertificate, err) } - b.cert = cert - b.proposer = ids.NodeIDFromCert(cert) + b.proposer = ids.NodeIDFromCert(b.cert) return nil } diff --git a/vms/proposervm/block/block_test.go b/vms/proposervm/block/block_test.go index d0b1a817b941..8a8a57ae3b9d 100644 --- a/vms/proposervm/block/block_test.go +++ b/vms/proposervm/block/block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/proposervm/block/build.go b/vms/proposervm/block/build.go index cdf943318fcc..b13255c91dd1 100644 --- a/vms/proposervm/block/build.go +++ b/vms/proposervm/block/build.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -31,11 +31,14 @@ func BuildUnsigned( timestamp: timestamp, } - bytes, err := c.Marshal(codecVersion, &block) + bytes, err := Codec.Marshal(CodecVersion, &block) if err != nil { return nil, err } - return block, block.initialize(bytes) + + // Invariant: The durango timestamp isn't used here because the certificate + // is empty. + return block, block.initialize(bytes, time.Time{}) } func Build( @@ -61,7 +64,7 @@ func Build( } var blockIntf SignedBlock = block - unsignedBytesWithEmptySignature, err := c.Marshal(codecVersion, &blockIntf) + unsignedBytesWithEmptySignature, err := Codec.Marshal(CodecVersion, &blockIntf) if err != nil { return nil, err } @@ -85,7 +88,7 @@ func Build( return nil, err } - block.bytes, err = c.Marshal(codecVersion, &blockIntf) + block.bytes, err = Codec.Marshal(CodecVersion, &blockIntf) return block, err } @@ -100,7 +103,7 @@ func BuildHeader( Body: bodyID, } - bytes, err := c.Marshal(codecVersion, &header) + bytes, err := Codec.Marshal(CodecVersion, &header) header.bytes = bytes return &header, err } @@ -117,9 +120,11 @@ func BuildOption( InnerBytes: innerBytes, } - bytes, err := c.Marshal(codecVersion, &block) + bytes, err := Codec.Marshal(CodecVersion, &block) if err != nil { return nil, err } - return block, block.initialize(bytes) + + // Invariant: The durango timestamp isn't used. + return block, block.initialize(bytes, time.Time{}) } diff --git a/vms/proposervm/block/build_test.go b/vms/proposervm/block/build_test.go index 30bdd79460f8..8388e8a434f8 100644 --- a/vms/proposervm/block/build_test.go +++ b/vms/proposervm/block/build_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/proposervm/block/codec.go b/vms/proposervm/block/codec.go index 6d68a4cc2fe7..ca2318002093 100644 --- a/vms/proposervm/block/codec.go +++ b/vms/proposervm/block/codec.go @@ -1,33 +1,31 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block import ( "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/utils" ) -const codecVersion = 0 +const CodecVersion = 0 -// The maximum block size is enforced by the p2p message size limit. -// See: [constants.DefaultMaxMessageSize] -// -// Invariant: This codec must never be used to unmarshal a slice unless it is a -// `[]byte`. Otherwise a malicious payload could cause an OOM. -var c codec.Manager +var Codec codec.Manager func init() { - linearCodec := linearcodec.NewCustomMaxLength(math.MaxUint32) - c = codec.NewManager(math.MaxInt) + lc := linearcodec.NewDefault(time.Time{}) + // The maximum block size is enforced by the p2p message size limit. + // See: [constants.DefaultMaxMessageSize] + Codec = codec.NewManager(math.MaxInt) err := utils.Err( - linearCodec.RegisterType(&statelessBlock{}), - linearCodec.RegisterType(&option{}), - c.RegisterCodec(codecVersion, linearCodec), + lc.RegisterType(&statelessBlock{}), + lc.RegisterType(&option{}), + Codec.RegisterCodec(CodecVersion, lc), ) if err != nil { panic(err) diff --git a/vms/proposervm/block/header.go b/vms/proposervm/block/header.go index 0098ab7e1932..83c4e813c806 100644 --- a/vms/proposervm/block/header.go +++ b/vms/proposervm/block/header.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/proposervm/block/header_test.go b/vms/proposervm/block/header_test.go index bdbfaf3be0fd..a4db59385f01 100644 --- a/vms/proposervm/block/header_test.go +++ b/vms/proposervm/block/header_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/proposervm/block/option.go b/vms/proposervm/block/option.go index 180b90e31fd6..7edb39bd429f 100644 --- a/vms/proposervm/block/option.go +++ b/vms/proposervm/block/option.go @@ -1,9 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block import ( + "time" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/hashing" ) @@ -32,7 +34,7 @@ func (b *option) Bytes() []byte { return b.bytes } -func (b *option) initialize(bytes []byte) error { +func (b *option) initialize(bytes []byte, _ time.Time) error { b.id = hashing.ComputeHash256Array(bytes) b.bytes = bytes return nil diff --git a/vms/proposervm/block/option_test.go b/vms/proposervm/block/option_test.go index f6d4f409650d..d5af9c100079 100644 --- a/vms/proposervm/block/option_test.go +++ b/vms/proposervm/block/option_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block diff --git a/vms/proposervm/block/parse.go b/vms/proposervm/block/parse.go index aff15bde9d2b..bf9b44adf1f4 100644 --- a/vms/proposervm/block/parse.go +++ b/vms/proposervm/block/parse.go @@ -1,30 +1,33 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block -import "fmt" +import ( + "fmt" + "time" +) -func Parse(bytes []byte) (Block, error) { +func Parse(bytes []byte, durangoTime time.Time) (Block, error) { var block Block - parsedVersion, err := c.Unmarshal(bytes, &block) + parsedVersion, err := Codec.Unmarshal(bytes, &block) if err != nil { return nil, err } - if parsedVersion != codecVersion { - return nil, fmt.Errorf("expected codec version %d but got %d", codecVersion, parsedVersion) + if parsedVersion != CodecVersion { + return nil, fmt.Errorf("expected codec version %d but got %d", CodecVersion, parsedVersion) } - return block, block.initialize(bytes) + return block, block.initialize(bytes, durangoTime) } func ParseHeader(bytes []byte) (Header, error) { header := statelessHeader{} - parsedVersion, err := c.Unmarshal(bytes, &header) + parsedVersion, err := Codec.Unmarshal(bytes, &header) if err != nil { return nil, err } - if parsedVersion != codecVersion { - return nil, fmt.Errorf("expected codec version %d but got %d", codecVersion, parsedVersion) + if parsedVersion != CodecVersion { + return nil, fmt.Errorf("expected codec version %d but got %d", CodecVersion, parsedVersion) } header.bytes = bytes return &header, nil diff --git a/vms/proposervm/block/parse_test.go b/vms/proposervm/block/parse_test.go index 43a10671e7f5..148bac82c0a6 100644 --- a/vms/proposervm/block/parse_test.go +++ b/vms/proposervm/block/parse_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package block @@ -43,14 +43,19 @@ func TestParse(t *testing.T) { require.NoError(err) builtBlockBytes := builtBlock.Bytes() - - parsedBlockIntf, err := Parse(builtBlockBytes) - require.NoError(err) - - parsedBlock, ok := parsedBlockIntf.(SignedBlock) - require.True(ok) - - equal(require, chainID, builtBlock, parsedBlock) + durangoTimes := []time.Time{ + timestamp.Add(time.Second), // Durango not activated yet + timestamp.Add(-time.Second), // Durango activated + } + for _, durangoTime := range durangoTimes { + parsedBlockIntf, err := Parse(builtBlockBytes, durangoTime) + require.NoError(err) + + parsedBlock, ok := parsedBlockIntf.(SignedBlock) + require.True(ok) + + equal(require, chainID, builtBlock, parsedBlock) + } } func TestParseDuplicateExtension(t *testing.T) { @@ -60,8 +65,16 @@ func TestParseDuplicateExtension(t *testing.T) { blockBytes, err := hex.DecodeString(blockHex) require.NoError(err) - _, err = Parse(blockBytes) + // Note: The above blockHex specifies 123 as the block's timestamp. + timestamp := time.Unix(123, 0) + durangoNotYetActivatedTime := timestamp.Add(time.Second) + durangoAlreadyActivatedTime := timestamp.Add(-time.Second) + + _, err = Parse(blockBytes, durangoNotYetActivatedTime) require.ErrorIs(err, errInvalidCertificate) + + _, err = Parse(blockBytes, durangoAlreadyActivatedTime) + require.NoError(err) } func TestParseHeader(t *testing.T) { @@ -97,7 +110,7 @@ func TestParseOption(t *testing.T) { builtOptionBytes := builtOption.Bytes() - parsedOption, err := Parse(builtOptionBytes) + parsedOption, err := Parse(builtOptionBytes, time.Time{}) require.NoError(err) equalOption(require, builtOption, parsedOption) @@ -115,14 +128,19 @@ func TestParseUnsigned(t *testing.T) { require.NoError(err) builtBlockBytes := builtBlock.Bytes() - - parsedBlockIntf, err := Parse(builtBlockBytes) - require.NoError(err) - - parsedBlock, ok := parsedBlockIntf.(SignedBlock) - require.True(ok) - - equal(require, ids.Empty, builtBlock, parsedBlock) + durangoTimes := []time.Time{ + timestamp.Add(time.Second), // Durango not activated yet + timestamp.Add(-time.Second), // Durango activated + } + for _, durangoTime := range durangoTimes { + parsedBlockIntf, err := Parse(builtBlockBytes, durangoTime) + require.NoError(err) + + parsedBlock, ok := parsedBlockIntf.(SignedBlock) + require.True(ok) + + equal(require, ids.Empty, builtBlock, parsedBlock) + } } func TestParseGibberish(t *testing.T) { @@ -130,6 +148,6 @@ func TestParseGibberish(t *testing.T) { bytes := []byte{0, 1, 2, 3, 4, 5} - _, err := Parse(bytes) + _, err := Parse(bytes, time.Time{}) require.ErrorIs(err, codec.ErrUnknownVersion) } diff --git a/vms/proposervm/block_server.go b/vms/proposervm/block_server.go index e9e2e192a4e5..6a056c8bc827 100644 --- a/vms/proposervm/block_server.go +++ b/vms/proposervm/block_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm diff --git a/vms/proposervm/block_test.go b/vms/proposervm/block_test.go index 04ac66ecf34e..fea216120811 100644 --- a/vms/proposervm/block_test.go +++ b/vms/proposervm/block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -21,11 +21,12 @@ import ( "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/proposervm/proposer" + "github.com/ava-labs/avalanchego/vms/proposervm/scheduler" ) // Assert that when the underlying VM implements ChainVMWithBuildBlockContext @@ -36,39 +37,53 @@ func TestPostForkCommonComponents_buildChild(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - pChainHeight := uint64(1337) - parentID := ids.GenerateTestID() - parentTimestamp := time.Now() - blkID := ids.GenerateTestID() + var ( + nodeID = ids.GenerateTestNodeID() + pChainHeight uint64 = 1337 + parentID = ids.GenerateTestID() + parentTimestamp = time.Now().Truncate(time.Second) + parentHeight uint64 = 1234 + blkID = ids.GenerateTestID() + ) + innerBlk := snowman.NewMockBlock(ctrl) innerBlk.EXPECT().ID().Return(blkID).AnyTimes() - innerBlk.EXPECT().Height().Return(pChainHeight - 1).AnyTimes() + innerBlk.EXPECT().Height().Return(parentHeight + 1).AnyTimes() + builtBlk := snowman.NewMockBlock(ctrl) builtBlk.EXPECT().Bytes().Return([]byte{1, 2, 3}).AnyTimes() builtBlk.EXPECT().ID().Return(ids.GenerateTestID()).AnyTimes() builtBlk.EXPECT().Height().Return(pChainHeight).AnyTimes() - innerVM := mocks.NewMockChainVM(ctrl) - innerBlockBuilderVM := mocks.NewMockBuildBlockWithContextChainVM(ctrl) + + innerVM := block.NewMockChainVM(ctrl) + innerBlockBuilderVM := block.NewMockBuildBlockWithContextChainVM(ctrl) innerBlockBuilderVM.EXPECT().BuildBlockWithContext(gomock.Any(), &block.Context{ PChainHeight: pChainHeight - 1, }).Return(builtBlk, nil).AnyTimes() + vdrState := validators.NewMockState(ctrl) vdrState.EXPECT().GetMinimumHeight(context.Background()).Return(pChainHeight, nil).AnyTimes() + windower := proposer.NewMockWindower(ctrl) - windower.EXPECT().Delay(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(time.Duration(0), nil).AnyTimes() + windower.EXPECT().ExpectedProposer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nodeID, nil).AnyTimes() pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(err) vm := &VM{ + Config: Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: time.Unix(0, 0), + StakingCertLeaf: &staking.Certificate{}, + StakingLeafSigner: pk, + }, ChainVM: innerVM, blockBuilderVM: innerBlockBuilderVM, ctx: &snow.Context{ + NodeID: nodeID, ValidatorState: vdrState, Log: logging.NoLog{}, }, - Windower: windower, - stakingCertLeaf: &staking.Certificate{}, - stakingLeafSigner: pk, + Windower: windower, } blk := &postForkCommonComponents{ @@ -87,11 +102,15 @@ func TestPostForkCommonComponents_buildChild(t *testing.T) { require.Equal(builtBlk, gotChild.(*postForkBlock).innerBlk) } -func TestValidatorNodeBlockBuiltDelaysTests(t *testing.T) { +func TestPreDurangoValidatorNodeBlockBuiltDelaysTests(t *testing.T) { require := require.New(t) ctx := context.Background() - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = mockable.MaxTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(ctx)) }() @@ -220,11 +239,15 @@ func TestValidatorNodeBlockBuiltDelaysTests(t *testing.T) { } } -func TestNonValidatorNodeBlockBuiltDelaysTests(t *testing.T) { +func TestPreDurangoNonValidatorNodeBlockBuiltDelaysTests(t *testing.T) { require := require.New(t) ctx := context.Background() - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = mockable.MaxTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(ctx)) }() @@ -348,3 +371,81 @@ func TestNonValidatorNodeBlockBuiltDelaysTests(t *testing.T) { require.Equal(ids.EmptyNodeID, childBlk.(*postForkBlock).Proposer()) // unsigned so no proposer } } + +// We consider cases where this node is not current proposer (may be scheduled in the next future or not). +// We check that scheduler is called nonetheless, to be able to process innerVM block requests +func TestPostDurangoBuildChildResetScheduler(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + + var ( + thisNodeID = ids.GenerateTestNodeID() + selectedProposer = ids.GenerateTestNodeID() + pChainHeight uint64 = 1337 + parentID = ids.GenerateTestID() + parentTimestamp = time.Now().Truncate(time.Second) + now = parentTimestamp.Add(12 * time.Second) + parentHeight uint64 = 1234 + ) + + innerBlk := snowman.NewMockBlock(ctrl) + innerBlk.EXPECT().Height().Return(parentHeight + 1).AnyTimes() + + vdrState := validators.NewMockState(ctrl) + vdrState.EXPECT().GetMinimumHeight(context.Background()).Return(pChainHeight, nil).AnyTimes() + + windower := proposer.NewMockWindower(ctrl) + windower.EXPECT().ExpectedProposer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(selectedProposer, nil).AnyTimes() // return a proposer different from thisNode, to check whether scheduler is reset + + scheduler := scheduler.NewMockScheduler(ctrl) + + pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(err) + vm := &VM{ + Config: Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: time.Unix(0, 0), + StakingCertLeaf: &staking.Certificate{}, + StakingLeafSigner: pk, + }, + ChainVM: block.NewMockChainVM(ctrl), + ctx: &snow.Context{ + NodeID: thisNodeID, + ValidatorState: vdrState, + Log: logging.NoLog{}, + }, + Windower: windower, + Scheduler: scheduler, + } + vm.Clock.Set(now) + + blk := &postForkCommonComponents{ + innerBlk: innerBlk, + vm: vm, + } + + delays := []time.Duration{ + proposer.MaxLookAheadWindow - time.Minute, + proposer.MaxLookAheadWindow, + proposer.MaxLookAheadWindow + time.Minute, + } + + for _, delay := range delays { + windower.EXPECT().MinDelayForProposer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(delay, nil).Times(1) + + // we mock the scheduler setting the exact time we expect it to be reset + // to + expectedSchedulerTime := parentTimestamp.Add(delay) + scheduler.EXPECT().SetBuildBlockTime(expectedSchedulerTime).Times(1) + + _, err = blk.buildChild( + context.Background(), + parentID, + parentTimestamp, + pChainHeight-1, + ) + require.ErrorIs(err, errProposerWindowNotStarted) + } +} diff --git a/vms/proposervm/config.go b/vms/proposervm/config.go new file mode 100644 index 000000000000..a7eb4ff0db9b --- /dev/null +++ b/vms/proposervm/config.go @@ -0,0 +1,39 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package proposervm + +import ( + "crypto" + "time" + + "github.com/ava-labs/avalanchego/staking" +) + +type Config struct { + // Time at which proposerVM activates its congestion control mechanism + ActivationTime time.Time + + // Durango fork activation time + DurangoTime time.Time + + // Minimal P-chain height referenced upon block building + MinimumPChainHeight uint64 + + // Configurable minimal delay among blocks issued consecutively + MinBlkDelay time.Duration + + // Maximal number of block indexed. + // Zero signals all blocks are indexed. + NumHistoricalBlocks uint64 + + // Block signer + StakingLeafSigner crypto.Signer + + // Block certificate + StakingCertLeaf *staking.Certificate +} + +func (c *Config) IsDurangoActivated(timestamp time.Time) bool { + return !timestamp.Before(c.DurangoTime) +} diff --git a/vms/proposervm/height_indexed_vm.go b/vms/proposervm/height_indexed_vm.go index 99b911c5be64..a29334f6d8dd 100644 --- a/vms/proposervm/height_indexed_vm.go +++ b/vms/proposervm/height_indexed_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -136,7 +136,7 @@ func (vm *VM) storeHeightEntry(height uint64, blkID ids.ID) error { zap.Uint64("height", height), ) - if vm.numHistoricalBlocks == 0 { + if vm.NumHistoricalBlocks == 0 { return nil } @@ -145,13 +145,13 @@ func (vm *VM) storeHeightEntry(height uint64, blkID ids.ID) error { // is why <= is used rather than <. This prevents the user from only storing // the last accepted block, which can never be safe due to the non-atomic // commits between the proposervm database and the innerVM's database. - if blocksSinceFork <= vm.numHistoricalBlocks { + if blocksSinceFork <= vm.NumHistoricalBlocks { return nil } // Note: heightToDelete is >= forkHeight, so it is guaranteed not to // underflow. - heightToDelete := height - vm.numHistoricalBlocks - 1 + heightToDelete := height - vm.NumHistoricalBlocks - 1 blockToDelete, err := vm.State.GetBlockIDAtHeight(heightToDelete) if err == database.ErrNotFound { // Block may have already been deleted. This can happen due to a @@ -180,7 +180,7 @@ func (vm *VM) storeHeightEntry(height uint64, blkID ids.ID) error { // TODO: Support async deletion of old blocks. func (vm *VM) pruneOldBlocks() error { - if vm.numHistoricalBlocks == 0 { + if vm.NumHistoricalBlocks == 0 { return nil } @@ -194,7 +194,7 @@ func (vm *VM) pruneOldBlocks() error { // // Note: vm.lastAcceptedHeight is guaranteed to be >= height, so the // subtraction can never underflow. - for vm.lastAcceptedHeight-height > vm.numHistoricalBlocks { + for vm.lastAcceptedHeight-height > vm.NumHistoricalBlocks { blockToDelete, err := vm.State.GetBlockIDAtHeight(height) if err != nil { return err diff --git a/vms/proposervm/indexer/block_server.go b/vms/proposervm/indexer/block_server.go index e817b9bad830..fcecaf9e9fcf 100644 --- a/vms/proposervm/indexer/block_server.go +++ b/vms/proposervm/indexer/block_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer diff --git a/vms/proposervm/indexer/block_server_test.go b/vms/proposervm/indexer/block_server_test.go index e132926c811c..a973d66a05a9 100644 --- a/vms/proposervm/indexer/block_server_test.go +++ b/vms/proposervm/indexer/block_server_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer diff --git a/vms/proposervm/indexer/height_indexer.go b/vms/proposervm/indexer/height_indexer.go index 697570306f6b..c0a1e4155b3b 100644 --- a/vms/proposervm/indexer/height_indexer.go +++ b/vms/proposervm/indexer/height_indexer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer diff --git a/vms/proposervm/indexer/height_indexer_test.go b/vms/proposervm/indexer/height_indexer_test.go index 0ff9ebcdbb59..2a093530048a 100644 --- a/vms/proposervm/indexer/height_indexer_test.go +++ b/vms/proposervm/indexer/height_indexer_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package indexer diff --git a/vms/proposervm/main_test.go b/vms/proposervm/main_test.go index 913e29613f1c..72165ddb6e78 100644 --- a/vms/proposervm/main_test.go +++ b/vms/proposervm/main_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm diff --git a/vms/proposervm/mock_post_fork_block.go b/vms/proposervm/mock_post_fork_block.go index 4f0847424253..ab449b6363bf 100644 --- a/vms/proposervm/mock_post_fork_block.go +++ b/vms/proposervm/mock_post_fork_block.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/proposervm (interfaces: PostForkBlock) +// +// Generated by this command: +// +// mockgen -package=proposervm -destination=vms/proposervm/mock_post_fork_block.go github.com/ava-labs/avalanchego/vms/proposervm PostForkBlock +// // Package proposervm is a generated GoMock package. package proposervm @@ -51,7 +53,7 @@ func (m *MockPostForkBlock) Accept(arg0 context.Context) error { } // Accept indicates an expected call of Accept. -func (mr *MockPostForkBlockMockRecorder) Accept(arg0 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) Accept(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockPostForkBlock)(nil).Accept), arg0) } @@ -121,7 +123,7 @@ func (m *MockPostForkBlock) Reject(arg0 context.Context) error { } // Reject indicates an expected call of Reject. -func (mr *MockPostForkBlockMockRecorder) Reject(arg0 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) Reject(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reject", reflect.TypeOf((*MockPostForkBlock)(nil).Reject), arg0) } @@ -163,7 +165,7 @@ func (m *MockPostForkBlock) Verify(arg0 context.Context) error { } // Verify indicates an expected call of Verify. -func (mr *MockPostForkBlockMockRecorder) Verify(arg0 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) Verify(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockPostForkBlock)(nil).Verify), arg0) } @@ -177,7 +179,7 @@ func (m *MockPostForkBlock) acceptInnerBlk(arg0 context.Context) error { } // acceptInnerBlk indicates an expected call of acceptInnerBlk. -func (mr *MockPostForkBlockMockRecorder) acceptInnerBlk(arg0 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) acceptInnerBlk(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "acceptInnerBlk", reflect.TypeOf((*MockPostForkBlock)(nil).acceptInnerBlk), arg0) } @@ -206,7 +208,7 @@ func (m *MockPostForkBlock) buildChild(arg0 context.Context) (Block, error) { } // buildChild indicates an expected call of buildChild. -func (mr *MockPostForkBlockMockRecorder) buildChild(arg0 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) buildChild(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "buildChild", reflect.TypeOf((*MockPostForkBlock)(nil).buildChild), arg0) } @@ -249,7 +251,7 @@ func (m *MockPostForkBlock) pChainHeight(arg0 context.Context) (uint64, error) { } // pChainHeight indicates an expected call of pChainHeight. -func (mr *MockPostForkBlockMockRecorder) pChainHeight(arg0 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) pChainHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "pChainHeight", reflect.TypeOf((*MockPostForkBlock)(nil).pChainHeight), arg0) } @@ -261,7 +263,7 @@ func (m *MockPostForkBlock) setInnerBlk(arg0 snowman.Block) { } // setInnerBlk indicates an expected call of setInnerBlk. -func (mr *MockPostForkBlockMockRecorder) setInnerBlk(arg0 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) setInnerBlk(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setInnerBlk", reflect.TypeOf((*MockPostForkBlock)(nil).setInnerBlk), arg0) } @@ -273,7 +275,7 @@ func (m *MockPostForkBlock) setStatus(arg0 choices.Status) { } // setStatus indicates an expected call of setStatus. -func (mr *MockPostForkBlockMockRecorder) setStatus(arg0 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) setStatus(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "setStatus", reflect.TypeOf((*MockPostForkBlock)(nil).setStatus), arg0) } @@ -287,7 +289,7 @@ func (m *MockPostForkBlock) verifyPostForkChild(arg0 context.Context, arg1 *post } // verifyPostForkChild indicates an expected call of verifyPostForkChild. -func (mr *MockPostForkBlockMockRecorder) verifyPostForkChild(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) verifyPostForkChild(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "verifyPostForkChild", reflect.TypeOf((*MockPostForkBlock)(nil).verifyPostForkChild), arg0, arg1) } @@ -301,7 +303,7 @@ func (m *MockPostForkBlock) verifyPostForkOption(arg0 context.Context, arg1 *pos } // verifyPostForkOption indicates an expected call of verifyPostForkOption. -func (mr *MockPostForkBlockMockRecorder) verifyPostForkOption(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) verifyPostForkOption(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "verifyPostForkOption", reflect.TypeOf((*MockPostForkBlock)(nil).verifyPostForkOption), arg0, arg1) } @@ -315,7 +317,7 @@ func (m *MockPostForkBlock) verifyPreForkChild(arg0 context.Context, arg1 *preFo } // verifyPreForkChild indicates an expected call of verifyPreForkChild. -func (mr *MockPostForkBlockMockRecorder) verifyPreForkChild(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPostForkBlockMockRecorder) verifyPreForkChild(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "verifyPreForkChild", reflect.TypeOf((*MockPostForkBlock)(nil).verifyPreForkChild), arg0, arg1) } diff --git a/vms/proposervm/post_fork_block.go b/vms/proposervm/post_fork_block.go index 28d127d33f70..707b6dc327c7 100644 --- a/vms/proposervm/post_fork_block.go +++ b/vms/proposervm/post_fork_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm diff --git a/vms/proposervm/post_fork_block_test.go b/vms/proposervm/post_fork_block_test.go index 659bdd1e5fd3..a16d4a7d6219 100644 --- a/vms/proposervm/post_fork_block_test.go +++ b/vms/proposervm/post_fork_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -16,6 +16,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowman" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/vms/proposervm/block" "github.com/ava-labs/avalanchego/vms/proposervm/proposer" ) @@ -38,7 +40,11 @@ func TestOracle_PostForkBlock_ImplementsInterface(t *testing.T) { require.Equal(snowman.ErrNotOracle, err) // setup - _, _, proVM, _, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + _, _, proVM, _, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -70,10 +76,10 @@ func TestOracle_PostForkBlock_ImplementsInterface(t *testing.T) { ids.Empty, // refer unknown parent time.Time{}, 0, // pChainHeight, - proVM.stakingCertLeaf, + proVM.StakingCertLeaf, innerOracleBlk.Bytes(), proVM.ctx.ChainID, - proVM.stakingLeafSigner, + proVM.StakingLeafSigner, ) require.NoError(err) proBlk = postForkBlock{ @@ -91,10 +97,14 @@ func TestOracle_PostForkBlock_ImplementsInterface(t *testing.T) { } // ProposerBlock.Verify tests section -func TestBlockVerify_PostForkBlock_ParentChecks(t *testing.T) { +func TestBlockVerify_PostForkBlock_PreDurango_ParentChecks(t *testing.T) { require := require.New(t) - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = mockable.MaxTime // pre Durango + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -105,24 +115,24 @@ func TestBlockVerify_PostForkBlock_ParentChecks(t *testing.T) { } // create parent block ... - prntCoreBlk := &snowman.TestBlock{ + parentCoreBlk := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { - return prntCoreBlk, nil + return parentCoreBlk, nil } coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { switch blkID { case coreGenBlk.ID(): return coreGenBlk, nil - case prntCoreBlk.ID(): - return prntCoreBlk, nil + case parentCoreBlk.ID(): + return parentCoreBlk, nil default: return nil, database.ErrNotFound } @@ -131,38 +141,26 @@ func TestBlockVerify_PostForkBlock_ParentChecks(t *testing.T) { switch { case bytes.Equal(b, coreGenBlk.Bytes()): return coreGenBlk, nil - case bytes.Equal(b, prntCoreBlk.Bytes()): - return prntCoreBlk, nil + case bytes.Equal(b, parentCoreBlk.Bytes()): + return parentCoreBlk, nil default: return nil, errUnknownBlock } } - proVM.Set(proVM.Time().Add(proposer.MaxBuildDelay)) - prntProBlk, err := proVM.BuildBlock(context.Background()) + parentBlk, err := proVM.BuildBlock(context.Background()) require.NoError(err) - require.NoError(prntProBlk.Verify(context.Background())) - require.NoError(proVM.SetPreference(context.Background(), prntProBlk.ID())) + require.NoError(parentBlk.Verify(context.Background())) + require.NoError(proVM.SetPreference(context.Background(), parentBlk.ID())) // .. create child block ... childCoreBlk := &snowman.TestBlock{ - ParentV: prntCoreBlk.ID(), - BytesV: []byte{2}, - TimestampV: prntCoreBlk.Timestamp(), + ParentV: parentCoreBlk.ID(), + BytesV: []byte{2}, + HeightV: parentCoreBlk.Height() + 1, } - childSlb, err := block.Build( - ids.Empty, // refer unknown parent - childCoreBlk.Timestamp(), - pChainHeight, - proVM.stakingCertLeaf, - childCoreBlk.Bytes(), - proVM.ctx.ChainID, - proVM.stakingLeafSigner, - ) - require.NoError(err) - childProBlk := postForkBlock{ - SignedBlock: childSlb, + childBlk := postForkBlock{ postForkCommonComponents: postForkCommonComponents{ vm: proVM, innerBlk: childCoreBlk, @@ -170,57 +168,203 @@ func TestBlockVerify_PostForkBlock_ParentChecks(t *testing.T) { }, } - // child block referring unknown parent does not verify - err = childProBlk.Verify(context.Background()) - require.ErrorIs(err, database.ErrNotFound) + // set proVM to be able to build unsigned blocks + proVM.Set(proVM.Time().Add(proposer.MaxVerifyDelay)) - // child block referring known parent does verify - childSlb, err = block.BuildUnsigned( - prntProBlk.ID(), // refer known parent - prntProBlk.Timestamp().Add(proposer.MaxVerifyDelay), - pChainHeight, - childCoreBlk.Bytes(), + { + // child block referring unknown parent does not verify + childSlb, err := block.BuildUnsigned( + ids.Empty, // refer unknown parent + proVM.Time(), + pChainHeight, + childCoreBlk.Bytes(), + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, database.ErrNotFound) + } + + { + // child block referring known parent does verify + childSlb, err := block.BuildUnsigned( + parentBlk.ID(), // refer known parent + proVM.Time(), + pChainHeight, + childCoreBlk.Bytes(), + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } +} + +func TestBlockVerify_PostForkBlock_PostDurango_ParentChecks(t *testing.T) { + require := require.New(t) + + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime // post Durango ) - require.NoError(err) - childProBlk.SignedBlock = childSlb + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) + defer func() { + require.NoError(proVM.Shutdown(context.Background())) + }() + + pChainHeight := uint64(100) + valState.GetCurrentHeightF = func(context.Context) (uint64, error) { + return pChainHeight, nil + } + + parentCoreBlk := &snowman.TestBlock{ + TestDecidable: choices.TestDecidable{ + IDV: ids.Empty.Prefix(1111), + StatusV: choices.Processing, + }, + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, + } + coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { + return parentCoreBlk, nil + } + coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { + switch blkID { + case coreGenBlk.ID(): + return coreGenBlk, nil + case parentCoreBlk.ID(): + return parentCoreBlk, nil + default: + return nil, database.ErrNotFound + } + } + coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { + switch { + case bytes.Equal(b, coreGenBlk.Bytes()): + return coreGenBlk, nil + case bytes.Equal(b, parentCoreBlk.Bytes()): + return parentCoreBlk, nil + default: + return nil, errUnknownBlock + } + } + + parentBlk, err := proVM.BuildBlock(context.Background()) require.NoError(err) - proVM.Set(proVM.Time().Add(proposer.MaxVerifyDelay)) - require.NoError(childProBlk.Verify(context.Background())) + require.NoError(parentBlk.Verify(context.Background())) + require.NoError(proVM.SetPreference(context.Background(), parentBlk.ID())) + + childCoreBlk := &snowman.TestBlock{ + ParentV: parentCoreBlk.ID(), + BytesV: []byte{2}, + HeightV: parentCoreBlk.Height() + 1, + } + childBlk := postForkBlock{ + postForkCommonComponents: postForkCommonComponents{ + vm: proVM, + innerBlk: childCoreBlk, + status: choices.Processing, + }, + } + + require.NoError(waitForProposerWindow(proVM, parentBlk, parentBlk.(*postForkBlock).PChainHeight())) + + { + // child block referring unknown parent does not verify + childSlb, err := block.Build( + ids.Empty, // refer unknown parent + proVM.Time(), + pChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, database.ErrNotFound) + } + + { + // child block referring known parent does verify + childSlb, err := block.Build( + parentBlk.ID(), + proVM.Time(), + pChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + + require.NoError(err) + childBlk.SignedBlock = childSlb + + proVM.Set(childSlb.Timestamp()) + require.NoError(childBlk.Verify(context.Background())) + } } func TestBlockVerify_PostForkBlock_TimestampChecks(t *testing.T) { require := require.New(t) - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = mockable.MaxTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() + // reduce validator state to allow proVM.ctx.NodeID to be easily selected as proposer + valState.GetValidatorSetF = func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + var ( + thisNode = proVM.ctx.NodeID + nodeID1 = ids.BuildTestNodeID([]byte{1}) + ) + return map[ids.NodeID]*validators.GetValidatorOutput{ + thisNode: { + NodeID: thisNode, + Weight: 5, + }, + nodeID1: { + NodeID: nodeID1, + Weight: 100, + }, + }, nil + } + proVM.ctx.ValidatorState = valState + pChainHeight := uint64(100) valState.GetCurrentHeightF = func(context.Context) (uint64, error) { return pChainHeight, nil } // create parent block ... - prntCoreBlk := &snowman.TestBlock{ + parentCoreBlk := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { - return prntCoreBlk, nil + return parentCoreBlk, nil } coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { switch blkID { case coreGenBlk.ID(): return coreGenBlk, nil - case prntCoreBlk.ID(): - return prntCoreBlk, nil + case parentCoreBlk.ID(): + return parentCoreBlk, nil default: return nil, database.ErrNotFound } @@ -229,45 +373,34 @@ func TestBlockVerify_PostForkBlock_TimestampChecks(t *testing.T) { switch { case bytes.Equal(b, coreGenBlk.Bytes()): return coreGenBlk, nil - case bytes.Equal(b, prntCoreBlk.Bytes()): - return prntCoreBlk, nil + case bytes.Equal(b, parentCoreBlk.Bytes()): + return parentCoreBlk, nil default: return nil, errUnknownBlock } } - prntProBlk, err := proVM.BuildBlock(context.Background()) + parentBlk, err := proVM.BuildBlock(context.Background()) require.NoError(err) - require.NoError(prntProBlk.Verify(context.Background())) - require.NoError(proVM.SetPreference(context.Background(), prntProBlk.ID())) + require.NoError(parentBlk.Verify(context.Background())) + require.NoError(proVM.SetPreference(context.Background(), parentBlk.ID())) - prntTimestamp := prntProBlk.Timestamp() + var ( + parentTimestamp = parentBlk.Timestamp() + parentPChainHeight = parentBlk.(*postForkBlock).PChainHeight() + ) childCoreBlk := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - ParentV: prntCoreBlk.ID(), + ParentV: parentCoreBlk.ID(), + HeightV: parentCoreBlk.Height() + 1, BytesV: []byte{2}, } - - // child block timestamp cannot be lower than parent timestamp - childCoreBlk.TimestampV = prntTimestamp.Add(-1 * time.Second) - proVM.Clock.Set(childCoreBlk.TimestampV) - childSlb, err := block.Build( - prntProBlk.ID(), - childCoreBlk.Timestamp(), - pChainHeight, - proVM.stakingCertLeaf, - childCoreBlk.Bytes(), - proVM.ctx.ChainID, - proVM.stakingLeafSigner, - ) - require.NoError(err) - childProBlk := postForkBlock{ - SignedBlock: childSlb, + childBlk := postForkBlock{ postForkCommonComponents: postForkCommonComponents{ vm: proVM, innerBlk: childCoreBlk, @@ -275,96 +408,137 @@ func TestBlockVerify_PostForkBlock_TimestampChecks(t *testing.T) { }, } - err = childProBlk.Verify(context.Background()) - require.ErrorIs(err, errTimeNotMonotonic) + { + // child block timestamp cannot be lower than parent timestamp + newTime := parentTimestamp.Add(-1 * time.Second) + proVM.Clock.Set(newTime) + + childSlb, err := block.Build( + parentBlk.ID(), + newTime, + pChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, errTimeNotMonotonic) + } - // block cannot arrive before its creator window starts - blkWinDelay, err := proVM.Delay(context.Background(), childCoreBlk.Height(), pChainHeight, proVM.ctx.NodeID, proposer.MaxVerifyWindows) - require.NoError(err) - beforeWinStart := prntTimestamp.Add(blkWinDelay).Add(-1 * time.Second) - proVM.Clock.Set(beforeWinStart) - childSlb, err = block.Build( - prntProBlk.ID(), - beforeWinStart, - pChainHeight, - proVM.stakingCertLeaf, - childCoreBlk.Bytes(), - proVM.ctx.ChainID, - proVM.stakingLeafSigner, - ) + blkWinDelay, err := proVM.Delay(context.Background(), childCoreBlk.Height(), parentPChainHeight, proVM.ctx.NodeID, proposer.MaxVerifyWindows) require.NoError(err) - childProBlk.SignedBlock = childSlb - - err = childProBlk.Verify(context.Background()) - require.ErrorIs(err, errProposerWindowNotStarted) - - // block can arrive at its creator window starts - atWindowStart := prntTimestamp.Add(blkWinDelay) - proVM.Clock.Set(atWindowStart) - childSlb, err = block.Build( - prntProBlk.ID(), - atWindowStart, - pChainHeight, - proVM.stakingCertLeaf, - childCoreBlk.Bytes(), - proVM.ctx.ChainID, - proVM.stakingLeafSigner, - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - - require.NoError(childProBlk.Verify(context.Background())) - - // block can arrive after its creator window starts - afterWindowStart := prntTimestamp.Add(blkWinDelay).Add(5 * time.Second) - proVM.Clock.Set(afterWindowStart) - childSlb, err = block.Build( - prntProBlk.ID(), - afterWindowStart, - pChainHeight, - proVM.stakingCertLeaf, - childCoreBlk.Bytes(), - proVM.ctx.ChainID, - proVM.stakingLeafSigner, - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - require.NoError(childProBlk.Verify(context.Background())) - - // block can arrive within submission window - atSubWindowEnd := proVM.Time().Add(proposer.MaxVerifyDelay) - proVM.Clock.Set(atSubWindowEnd) - childSlb, err = block.BuildUnsigned( - prntProBlk.ID(), - atSubWindowEnd, - pChainHeight, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - require.NoError(childProBlk.Verify(context.Background())) - - // block timestamp cannot be too much in the future - afterSubWinEnd := proVM.Time().Add(maxSkew).Add(time.Second) - childSlb, err = block.Build( - prntProBlk.ID(), - afterSubWinEnd, - pChainHeight, - proVM.stakingCertLeaf, - childCoreBlk.Bytes(), - proVM.ctx.ChainID, - proVM.stakingLeafSigner, - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - err = childProBlk.Verify(context.Background()) - require.ErrorIs(err, errTimeTooAdvanced) + + { + // block cannot arrive before its creator window starts + beforeWinStart := parentTimestamp.Add(blkWinDelay).Add(-1 * time.Second) + proVM.Clock.Set(beforeWinStart) + + childSlb, err := block.Build( + parentBlk.ID(), + beforeWinStart, + pChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, errProposerWindowNotStarted) + } + + { + // block can arrive at its creator window starts + atWindowStart := parentTimestamp.Add(blkWinDelay) + proVM.Clock.Set(atWindowStart) + + childSlb, err := block.Build( + parentBlk.ID(), + atWindowStart, + pChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } + + { + // block can arrive after its creator window starts + afterWindowStart := parentTimestamp.Add(blkWinDelay).Add(5 * time.Second) + proVM.Clock.Set(afterWindowStart) + + childSlb, err := block.Build( + parentBlk.ID(), + afterWindowStart, + pChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } + + { + // block can arrive within submission window + atSubWindowEnd := proVM.Time().Add(proposer.MaxVerifyDelay) + proVM.Clock.Set(atSubWindowEnd) + + childSlb, err := block.BuildUnsigned( + parentBlk.ID(), + atSubWindowEnd, + pChainHeight, + childCoreBlk.Bytes(), + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } + + { + // block timestamp cannot be too much in the future + afterSubWinEnd := proVM.Time().Add(maxSkew).Add(time.Second) + + childSlb, err := block.Build( + parentBlk.ID(), + afterSubWinEnd, + pChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, errTimeTooAdvanced) + } } func TestBlockVerify_PostForkBlock_PChainHeightChecks(t *testing.T) { require := require.New(t) - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -373,26 +547,29 @@ func TestBlockVerify_PostForkBlock_PChainHeightChecks(t *testing.T) { valState.GetCurrentHeightF = func(context.Context) (uint64, error) { return pChainHeight, nil } + valState.GetMinimumHeightF = func(context.Context) (uint64, error) { + return pChainHeight / 50, nil + } // create parent block ... - prntCoreBlk := &snowman.TestBlock{ + parentCoreBlk := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { - return prntCoreBlk, nil + return parentCoreBlk, nil } coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { switch blkID { case coreGenBlk.ID(): return coreGenBlk, nil - case prntCoreBlk.ID(): - return prntCoreBlk, nil + case parentCoreBlk.ID(): + return parentCoreBlk, nil default: return nil, database.ErrNotFound } @@ -401,44 +578,34 @@ func TestBlockVerify_PostForkBlock_PChainHeightChecks(t *testing.T) { switch { case bytes.Equal(b, coreGenBlk.Bytes()): return coreGenBlk, nil - case bytes.Equal(b, prntCoreBlk.Bytes()): - return prntCoreBlk, nil + case bytes.Equal(b, parentCoreBlk.Bytes()): + return parentCoreBlk, nil default: return nil, errUnknownBlock } } - prntProBlk, err := proVM.BuildBlock(context.Background()) + parentBlk, err := proVM.BuildBlock(context.Background()) require.NoError(err) - require.NoError(prntProBlk.Verify(context.Background())) - require.NoError(proVM.SetPreference(context.Background(), prntProBlk.ID())) + require.NoError(parentBlk.Verify(context.Background())) + require.NoError(proVM.SetPreference(context.Background(), parentBlk.ID())) - prntBlkPChainHeight := pChainHeight + // set VM to be ready to build next block. We set it to generate unsigned blocks + // for simplicity. + parentBlkPChainHeight := parentBlk.(*postForkBlock).PChainHeight() + require.NoError(waitForProposerWindow(proVM, parentBlk, parentBlkPChainHeight)) childCoreBlk := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - ParentV: prntCoreBlk.ID(), - BytesV: []byte{2}, - TimestampV: prntProBlk.Timestamp().Add(proposer.MaxVerifyDelay), - } - - // child P-Chain height must not precede parent P-Chain height - childSlb, err := block.Build( - prntProBlk.ID(), - childCoreBlk.Timestamp(), - prntBlkPChainHeight-1, - proVM.stakingCertLeaf, - childCoreBlk.Bytes(), - proVM.ctx.ChainID, - proVM.stakingLeafSigner, - ) - require.NoError(err) - childProBlk := postForkBlock{ - SignedBlock: childSlb, + ParentV: parentCoreBlk.ID(), + HeightV: parentBlk.Height() + 1, + BytesV: []byte{2}, + } + childBlk := postForkBlock{ postForkCommonComponents: postForkCommonComponents{ vm: proVM, innerBlk: childCoreBlk, @@ -446,63 +613,103 @@ func TestBlockVerify_PostForkBlock_PChainHeightChecks(t *testing.T) { }, } - err = childProBlk.Verify(context.Background()) - require.ErrorIs(err, errTimeTooAdvanced) + { + // child P-Chain height must not precede parent P-Chain height + childSlb, err := block.Build( + parentBlk.ID(), + proVM.Time(), + parentBlkPChainHeight-1, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, errPChainHeightNotMonotonic) + } - // child P-Chain height can be equal to parent P-Chain height - childSlb, err = block.BuildUnsigned( - prntProBlk.ID(), - childCoreBlk.Timestamp(), - prntBlkPChainHeight, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - - proVM.Set(childCoreBlk.Timestamp()) - require.NoError(childProBlk.Verify(context.Background())) - - // child P-Chain height may follow parent P-Chain height - pChainHeight = prntBlkPChainHeight * 2 // move ahead pChainHeight - childSlb, err = block.BuildUnsigned( - prntProBlk.ID(), - childCoreBlk.Timestamp(), - prntBlkPChainHeight+1, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - require.NoError(childProBlk.Verify(context.Background())) + { + // child P-Chain height can be equal to parent P-Chain height + childSlb, err := block.Build( + parentBlk.ID(), + proVM.Time(), + parentBlkPChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } + + { + // child P-Chain height may follow parent P-Chain height + childSlb, err := block.Build( + parentBlk.ID(), + proVM.Time(), + parentBlkPChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } - // block P-Chain height can be equal to current P-Chain height currPChainHeight, _ := proVM.ctx.ValidatorState.GetCurrentHeight(context.Background()) - childSlb, err = block.BuildUnsigned( - prntProBlk.ID(), - childCoreBlk.Timestamp(), - currPChainHeight, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - require.NoError(childProBlk.Verify(context.Background())) - - // block P-Chain height cannot be at higher than current P-Chain height - childSlb, err = block.BuildUnsigned( - prntProBlk.ID(), - childCoreBlk.Timestamp(), - currPChainHeight*2, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - err = childProBlk.Verify(context.Background()) - require.ErrorIs(err, errPChainHeightNotReached) + { + // block P-Chain height can be equal to current P-Chain height + childSlb, err := block.Build( + parentBlk.ID(), + proVM.Time(), + currPChainHeight, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } + + { + // block P-Chain height cannot be at higher than current P-Chain height + childSlb, err := block.Build( + parentBlk.ID(), + proVM.Time(), + currPChainHeight*2, + proVM.StakingCertLeaf, + childCoreBlk.Bytes(), + proVM.ctx.ChainID, + proVM.StakingLeafSigner, + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, errPChainHeightNotReached) + } } func TestBlockVerify_PostForkBlockBuiltOnOption_PChainHeightChecks(t *testing.T) { require := require.New(t) - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = mockable.MaxTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -511,7 +718,9 @@ func TestBlockVerify_PostForkBlockBuiltOnOption_PChainHeightChecks(t *testing.T) valState.GetCurrentHeightF = func(context.Context) (uint64, error) { return pChainHeight, nil } - // proVM.SetStartTime(timer.MaxTime) // switch off scheduler for current test + valState.GetMinimumHeightF = func(context.Context) (uint64, error) { + return pChainHeight / 50, nil + } // create post fork oracle block ... oracleCoreBlk := &TestOptionsBlock{ @@ -520,9 +729,9 @@ func TestBlockVerify_PostForkBlockBuiltOnOption_PChainHeightChecks(t *testing.T) IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, }, } oracleCoreBlk.opts = [2]snowman.Block{ @@ -531,18 +740,18 @@ func TestBlockVerify_PostForkBlockBuiltOnOption_PChainHeightChecks(t *testing.T) IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, }, &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(3333), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, }, } @@ -594,31 +803,23 @@ func TestBlockVerify_PostForkBlockBuiltOnOption_PChainHeightChecks(t *testing.T) require.NoError(parentBlk.Verify(context.Background())) require.NoError(proVM.SetPreference(context.Background(), parentBlk.ID())) - prntBlkPChainHeight := pChainHeight + // set VM to be ready to build next block. We set it to generate unsigned blocks + // for simplicity. + nextTime := parentBlk.Timestamp().Add(proposer.MaxVerifyDelay) + proVM.Set(nextTime) + + parentBlkPChainHeight := postForkOracleBlk.PChainHeight() // option takes proposal blocks' Pchain height childCoreBlk := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - ParentV: oracleCoreBlk.opts[0].ID(), - BytesV: []byte{2}, - TimestampV: parentBlk.Timestamp().Add(proposer.MaxVerifyDelay), - } - - // child P-Chain height must not precede parent P-Chain height - childSlb, err := block.Build( - parentBlk.ID(), - childCoreBlk.Timestamp(), - prntBlkPChainHeight-1, - proVM.stakingCertLeaf, - childCoreBlk.Bytes(), - proVM.ctx.ChainID, - proVM.stakingLeafSigner, - ) - require.NoError(err) - childProBlk := postForkBlock{ - SignedBlock: childSlb, + ParentV: oracleCoreBlk.opts[0].ID(), + BytesV: []byte{2}, + HeightV: oracleCoreBlk.opts[0].Height() + 1, + } + childBlk := postForkBlock{ postForkCommonComponents: postForkCommonComponents{ vm: proVM, innerBlk: childCoreBlk, @@ -626,57 +827,77 @@ func TestBlockVerify_PostForkBlockBuiltOnOption_PChainHeightChecks(t *testing.T) }, } - err = childProBlk.Verify(context.Background()) - require.ErrorIs(err, errTimeTooAdvanced) + { + // child P-Chain height must not precede parent P-Chain height + childSlb, err := block.BuildUnsigned( + parentBlk.ID(), + nextTime, + parentBlkPChainHeight-1, + childCoreBlk.Bytes(), + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, errPChainHeightNotMonotonic) + } - // child P-Chain height can be equal to parent P-Chain height - childSlb, err = block.BuildUnsigned( - parentBlk.ID(), - childCoreBlk.Timestamp(), - prntBlkPChainHeight, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - - proVM.Set(childCoreBlk.Timestamp()) - require.NoError(childProBlk.Verify(context.Background())) - - // child P-Chain height may follow parent P-Chain height - pChainHeight = prntBlkPChainHeight * 2 // move ahead pChainHeight - childSlb, err = block.BuildUnsigned( - parentBlk.ID(), - childCoreBlk.Timestamp(), - prntBlkPChainHeight+1, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - require.NoError(childProBlk.Verify(context.Background())) + { + // child P-Chain height can be equal to parent P-Chain height + childSlb, err := block.BuildUnsigned( + parentBlk.ID(), + nextTime, + parentBlkPChainHeight, + childCoreBlk.Bytes(), + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } + + { + // child P-Chain height may follow parent P-Chain height + childSlb, err := block.BuildUnsigned( + parentBlk.ID(), + nextTime, + parentBlkPChainHeight+1, + childCoreBlk.Bytes(), + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } - // block P-Chain height can be equal to current P-Chain height currPChainHeight, _ := proVM.ctx.ValidatorState.GetCurrentHeight(context.Background()) - childSlb, err = block.BuildUnsigned( - parentBlk.ID(), - childCoreBlk.Timestamp(), - currPChainHeight, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - require.NoError(childProBlk.Verify(context.Background())) - - // block P-Chain height cannot be at higher than current P-Chain height - childSlb, err = block.BuildUnsigned( - parentBlk.ID(), - childCoreBlk.Timestamp(), - currPChainHeight*2, - childCoreBlk.Bytes(), - ) - require.NoError(err) - childProBlk.SignedBlock = childSlb - err = childProBlk.Verify(context.Background()) - require.ErrorIs(err, errPChainHeightNotReached) + { + // block P-Chain height can be equal to current P-Chain height + childSlb, err := block.BuildUnsigned( + parentBlk.ID(), + nextTime, + currPChainHeight, + childCoreBlk.Bytes(), + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + + require.NoError(childBlk.Verify(context.Background())) + } + + { + // block P-Chain height cannot be at higher than current P-Chain height + childSlb, err := block.BuildUnsigned( + parentBlk.ID(), + nextTime, + currPChainHeight*2, + childCoreBlk.Bytes(), + ) + require.NoError(err) + childBlk.SignedBlock = childSlb + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, errPChainHeightNotReached) + } } func TestBlockVerify_PostForkBlock_CoreBlockVerifyIsCalledOnce(t *testing.T) { @@ -684,7 +905,11 @@ func TestBlockVerify_PostForkBlock_CoreBlockVerifyIsCalledOnce(t *testing.T) { // Verify a block once (in this test by building it). // Show that other verify call would not call coreBlk.Verify() - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -699,9 +924,9 @@ func TestBlockVerify_PostForkBlock_CoreBlockVerifyIsCalledOnce(t *testing.T) { IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -747,7 +972,11 @@ func TestBlockAccept_PostForkBlock_SetsLastAcceptedBlock(t *testing.T) { require := require.New(t) // setup - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -762,9 +991,9 @@ func TestBlockAccept_PostForkBlock_SetsLastAcceptedBlock(t *testing.T) { IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -810,7 +1039,11 @@ func TestBlockAccept_PostForkBlock_SetsLastAcceptedBlock(t *testing.T) { func TestBlockAccept_PostForkBlock_TwoProBlocksWithSameCoreBlock_OneIsAccepted(t *testing.T) { require := require.New(t) - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -826,10 +1059,9 @@ func TestBlockAccept_PostForkBlock_TwoProBlocksWithSameCoreBlock_OneIsAccepted(t IDV: ids.Empty.Prefix(111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -858,7 +1090,11 @@ func TestBlockAccept_PostForkBlock_TwoProBlocksWithSameCoreBlock_OneIsAccepted(t func TestBlockReject_PostForkBlock_InnerBlockIsNotRejected(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -868,10 +1104,9 @@ func TestBlockReject_PostForkBlock_InnerBlockIsNotRejected(t *testing.T) { IDV: ids.Empty.Prefix(111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -891,8 +1126,11 @@ func TestBlockReject_PostForkBlock_InnerBlockIsNotRejected(t *testing.T) { func TestBlockVerify_PostForkBlock_ShouldBePostForkOption(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) - proVM.Set(coreGenBlk.Timestamp()) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -904,9 +1142,9 @@ func TestBlockVerify_PostForkBlock_ShouldBePostForkOption(t *testing.T) { IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, }, } coreOpt0 := &snowman.TestBlock{ @@ -914,18 +1152,18 @@ func TestBlockVerify_PostForkBlock_ShouldBePostForkOption(t *testing.T) { IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, } coreOpt1 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(3333), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, } oracleCoreBlk.opts = [2]snowman.Block{ coreOpt0, @@ -986,10 +1224,10 @@ func TestBlockVerify_PostForkBlock_ShouldBePostForkOption(t *testing.T) { postForkOracleBlk.ID(), postForkOracleBlk.Timestamp().Add(proposer.WindowDuration), postForkOracleBlk.PChainHeight(), - proVM.stakingCertLeaf, + proVM.StakingCertLeaf, oracleCoreBlk.opts[0].Bytes(), proVM.ctx.ChainID, - proVM.stakingLeafSigner, + proVM.StakingLeafSigner, ) require.NoError(err) @@ -1006,8 +1244,11 @@ func TestBlockVerify_PostForkBlock_ShouldBePostForkOption(t *testing.T) { func TestBlockVerify_PostForkBlock_PChainTooLow(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 5) - proVM.Set(coreGenBlk.Timestamp()) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 5) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -1017,9 +1258,9 @@ func TestBlockVerify_PostForkBlock_PChainTooLow(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { diff --git a/vms/proposervm/post_fork_option.go b/vms/proposervm/post_fork_option.go index 047a01c477fd..93cfd2550ca6 100644 --- a/vms/proposervm/post_fork_option.go +++ b/vms/proposervm/post_fork_option.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm diff --git a/vms/proposervm/post_fork_option_test.go b/vms/proposervm/post_fork_option_test.go index 8e93e2aad05f..dd16f8cdb518 100644 --- a/vms/proposervm/post_fork_option_test.go +++ b/vms/proposervm/post_fork_option_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -18,7 +18,6 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/vms/proposervm/block" - "github.com/ava-labs/avalanchego/vms/proposervm/proposer" ) var _ snowman.OracleBlock = (*TestOptionsBlock)(nil) @@ -37,8 +36,11 @@ func (tob TestOptionsBlock) Options(context.Context) ([2]snowman.Block, error) { func TestBlockVerify_PostForkOption_ParentChecks(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) - proVM.Set(coreGenBlk.Timestamp()) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -50,9 +52,9 @@ func TestBlockVerify_PostForkOption_ParentChecks(t *testing.T) { IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, }, } oracleCoreBlk.opts = [2]snowman.Block{ @@ -61,18 +63,18 @@ func TestBlockVerify_PostForkOption_ParentChecks(t *testing.T) { IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, }, &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(3333), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, }, } @@ -133,14 +135,14 @@ func TestBlockVerify_PostForkOption_ParentChecks(t *testing.T) { IDV: ids.Empty.Prefix(4444), StatusV: choices.Processing, }, - ParentV: oracleCoreBlk.opts[0].ID(), - BytesV: []byte{4}, - TimestampV: oracleCoreBlk.opts[0].Timestamp().Add(proposer.MaxVerifyDelay), + ParentV: oracleCoreBlk.opts[0].ID(), + BytesV: []byte{4}, + HeightV: oracleCoreBlk.opts[0].Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return childCoreBlk, nil } - proVM.Set(childCoreBlk.Timestamp()) + require.NoError(waitForProposerWindow(proVM, opts[0], postForkOracleBlk.PChainHeight())) proChild, err := proVM.BuildBlock(context.Background()) require.NoError(err) @@ -153,8 +155,11 @@ func TestBlockVerify_PostForkOption_CoreBlockVerifyIsCalledOnce(t *testing.T) { require := require.New(t) // Verify an option once; then show that another verify call would not call coreBlk.Verify() - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) - proVM.Set(coreGenBlk.Timestamp()) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -166,9 +171,9 @@ func TestBlockVerify_PostForkOption_CoreBlockVerifyIsCalledOnce(t *testing.T) { IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, }, } coreOpt0 := &snowman.TestBlock{ @@ -176,18 +181,18 @@ func TestBlockVerify_PostForkOption_CoreBlockVerifyIsCalledOnce(t *testing.T) { IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, } coreOpt1 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(3333), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, } oracleCoreBlk.opts = [2]snowman.Block{ coreOpt0, @@ -255,8 +260,11 @@ func TestBlockVerify_PostForkOption_CoreBlockVerifyIsCalledOnce(t *testing.T) { func TestBlockAccept_PostForkOption_SetsLastAcceptedBlock(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) - proVM.Set(coreGenBlk.Timestamp()) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -268,9 +276,9 @@ func TestBlockAccept_PostForkOption_SetsLastAcceptedBlock(t *testing.T) { IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, }, } oracleCoreBlk.opts = [2]snowman.Block{ @@ -279,18 +287,18 @@ func TestBlockAccept_PostForkOption_SetsLastAcceptedBlock(t *testing.T) { IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, }, &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(3333), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, }, } @@ -365,8 +373,11 @@ func TestBlockAccept_PostForkOption_SetsLastAcceptedBlock(t *testing.T) { func TestBlockReject_InnerBlockIsNotRejected(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) - proVM.Set(coreGenBlk.Timestamp()) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -378,9 +389,9 @@ func TestBlockReject_InnerBlockIsNotRejected(t *testing.T) { IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, }, } oracleCoreBlk.opts = [2]snowman.Block{ @@ -389,18 +400,18 @@ func TestBlockReject_InnerBlockIsNotRejected(t *testing.T) { IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, }, &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(3333), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: oracleCoreBlk.ID(), - TimestampV: oracleCoreBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: oracleCoreBlk.ID(), + HeightV: oracleCoreBlk.Height() + 1, }, } @@ -467,8 +478,11 @@ func TestBlockVerify_PostForkOption_ParentIsNotOracleWithError(t *testing.T) { require := require.New(t) // Verify an option once; then show that another verify call would not call coreBlk.Verify() - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) - proVM.Set(coreGenBlk.Timestamp()) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -479,9 +493,9 @@ func TestBlockVerify_PostForkOption_ParentIsNotOracleWithError(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, }, optsErr: snowman.ErrNotOracle, } @@ -491,10 +505,9 @@ func TestBlockVerify_PostForkOption_ParentIsNotOracleWithError(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreBlk.ID(), - HeightV: coreBlk.Height() + 1, - TimestampV: coreBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: coreBlk.ID(), + HeightV: coreBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { @@ -553,7 +566,11 @@ func TestBlockVerify_PostForkOption_ParentIsNotOracleWithError(t *testing.T) { func TestOptionTimestampValidity(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, db := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, db := initTestProposerVM(t, activationTime, durangoTime, 0) coreOracleBlkID := ids.GenerateTestID() coreOracleBlk := &TestOptionsBlock{ @@ -562,10 +579,9 @@ func TestOptionTimestampValidity(t *testing.T) { IDV: coreOracleBlkID, StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(time.Second), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, }, opts: [2]snowman.Block{ &snowman.TestBlock{ @@ -573,24 +589,26 @@ func TestOptionTimestampValidity(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreOracleBlkID, - TimestampV: coreGenBlk.Timestamp().Add(time.Second), + BytesV: []byte{2}, + ParentV: coreOracleBlkID, + HeightV: coreGenBlk.Height() + 2, }, &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: coreOracleBlkID, - TimestampV: coreGenBlk.Timestamp().Add(time.Second), + BytesV: []byte{3}, + ParentV: coreOracleBlkID, + HeightV: coreGenBlk.Height() + 2, }, }, } + + oracleBlkTime := proVM.Time().Truncate(time.Second) statelessBlock, err := block.BuildUnsigned( coreGenBlk.ID(), - coreGenBlk.Timestamp(), + oracleBlkTime, 0, coreOracleBlk.Bytes(), ) @@ -650,8 +668,7 @@ func TestOptionTimestampValidity(t *testing.T) { return nil, nil } - expectedTime := coreGenBlk.Timestamp() - require.Equal(expectedTime, option.Timestamp()) + require.Equal(oracleBlkTime, option.Timestamp()) require.NoError(option.Accept(context.Background())) require.NoError(proVM.Shutdown(context.Background())) @@ -660,12 +677,15 @@ func TestOptionTimestampValidity(t *testing.T) { ctx := proVM.ctx proVM = New( coreVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: time.Unix(0, 0), + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) coreVM.InitializeF = func( @@ -743,5 +763,5 @@ func TestOptionTimestampValidity(t *testing.T) { return nil, nil } - require.Equal(expectedTime, statefulOptionBlock.Timestamp()) + require.Equal(oracleBlkTime, statefulOptionBlock.Timestamp()) } diff --git a/vms/proposervm/pre_fork_block.go b/vms/proposervm/pre_fork_block.go index ed665e473910..199c1c98db7d 100644 --- a/vms/proposervm/pre_fork_block.go +++ b/vms/proposervm/pre_fork_block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -97,7 +97,7 @@ func (b *preForkBlock) getInnerBlk() snowman.Block { func (b *preForkBlock) verifyPreForkChild(ctx context.Context, child *preForkBlock) error { parentTimestamp := b.Timestamp() - if !parentTimestamp.Before(b.vm.activationTime) { + if !parentTimestamp.Before(b.vm.ActivationTime) { if err := verifyIsOracleBlock(ctx, b.Block); err != nil { return err } @@ -135,7 +135,7 @@ func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkB currentPChainHeight, ) } - if childPChainHeight < b.vm.minimumPChainHeight { + if childPChainHeight < b.vm.MinimumPChainHeight { return errPChainHeightTooLow } @@ -150,7 +150,7 @@ func (b *preForkBlock) verifyPostForkChild(ctx context.Context, child *postForkB // if the *preForkBlock is the last *preForkBlock before activation takes effect // (its timestamp is at or after the activation time) parentTimestamp := b.Timestamp() - if parentTimestamp.Before(b.vm.activationTime) { + if parentTimestamp.Before(b.vm.ActivationTime) { return errProposersNotActivated } @@ -181,7 +181,7 @@ func (*preForkBlock) verifyPostForkOption(context.Context, *postForkOption) erro func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) { parentTimestamp := b.Timestamp() - if parentTimestamp.Before(b.vm.activationTime) { + if parentTimestamp.Before(b.vm.ActivationTime) { // The chain hasn't forked yet innerBlock, err := b.vm.ChainVM.BuildBlock(ctx) if err != nil { @@ -210,7 +210,7 @@ func (b *preForkBlock) buildChild(ctx context.Context) (Block, error) { // The child's P-Chain height is proposed as the optimal P-Chain height that // is at least the minimum height - pChainHeight, err := b.vm.optimalPChainHeight(ctx, b.vm.minimumPChainHeight) + pChainHeight, err := b.vm.optimalPChainHeight(ctx, b.vm.MinimumPChainHeight) if err != nil { b.vm.ctx.Log.Error("unexpected build block failure", zap.String("reason", "failed to calculate optimal P-chain height"), diff --git a/vms/proposervm/pre_fork_block_test.go b/vms/proposervm/pre_fork_block_test.go index 1366482a0d9b..e6ff5346ea73 100644 --- a/vms/proposervm/pre_fork_block_test.go +++ b/vms/proposervm/pre_fork_block_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -18,12 +18,12 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowman" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" + "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/avalanchego/vms/proposervm/block" - "github.com/ava-labs/avalanchego/vms/proposervm/proposer" + + statelessblock "github.com/ava-labs/avalanchego/vms/proposervm/block" ) func TestOracle_PreForkBlkImplementsInterface(t *testing.T) { @@ -51,7 +51,11 @@ func TestOracle_PreForkBlkImplementsInterface(t *testing.T) { func TestOracle_PreForkBlkCanBuiltOnPreForkOption(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, mockable.MaxTime, 0) + var ( + activationTime = mockable.MaxTime + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -139,8 +143,11 @@ func TestOracle_PreForkBlkCanBuiltOnPreForkOption(t *testing.T) { func TestOracle_PostForkBlkCanBuiltOnPreForkOption(t *testing.T) { require := require.New(t) - activationTime := genesisTimestamp.Add(10 * time.Second) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, 0) + var ( + activationTime = genesisTimestamp.Add(10 * time.Second) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -233,8 +240,11 @@ func TestOracle_PostForkBlkCanBuiltOnPreForkOption(t *testing.T) { func TestBlockVerify_PreFork_ParentChecks(t *testing.T) { require := require.New(t) - activationTime := genesisTimestamp.Add(10 * time.Second) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, 0) + var ( + activationTime = genesisTimestamp.Add(10 * time.Second) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -242,7 +252,7 @@ func TestBlockVerify_PreFork_ParentChecks(t *testing.T) { require.True(coreGenBlk.Timestamp().Before(activationTime)) // create parent block ... - prntCoreBlk := &snowman.TestBlock{ + parentCoreBlk := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, @@ -252,14 +262,14 @@ func TestBlockVerify_PreFork_ParentChecks(t *testing.T) { TimestampV: coreGenBlk.Timestamp(), } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { - return prntCoreBlk, nil + return parentCoreBlk, nil } coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { switch blkID { case coreGenBlk.ID(): return coreGenBlk, nil - case prntCoreBlk.ID(): - return prntCoreBlk, nil + case parentCoreBlk.ID(): + return parentCoreBlk, nil default: return nil, database.ErrNotFound } @@ -268,15 +278,14 @@ func TestBlockVerify_PreFork_ParentChecks(t *testing.T) { switch { case bytes.Equal(b, coreGenBlk.Bytes()): return coreGenBlk, nil - case bytes.Equal(b, prntCoreBlk.Bytes()): - return prntCoreBlk, nil + case bytes.Equal(b, parentCoreBlk.Bytes()): + return parentCoreBlk, nil default: return nil, database.ErrNotFound } } - proVM.Set(proVM.Time().Add(proposer.MaxBuildDelay)) - prntProBlk, err := proVM.BuildBlock(context.Background()) + parentBlk, err := proVM.BuildBlock(context.Background()) require.NoError(err) // .. create child block ... @@ -286,28 +295,35 @@ func TestBlockVerify_PreFork_ParentChecks(t *testing.T) { StatusV: choices.Processing, }, BytesV: []byte{2}, - TimestampV: prntCoreBlk.Timestamp().Add(proposer.MaxVerifyDelay), + TimestampV: parentCoreBlk.Timestamp(), } - childProBlk := preForkBlock{ + childBlk := preForkBlock{ Block: childCoreBlk, vm: proVM, } - // child block referring unknown parent does not verify - childCoreBlk.ParentV = ids.Empty - err = childProBlk.Verify(context.Background()) - require.ErrorIs(err, database.ErrNotFound) + { + // child block referring unknown parent does not verify + childCoreBlk.ParentV = ids.Empty + err = childBlk.Verify(context.Background()) + require.ErrorIs(err, database.ErrNotFound) + } - // child block referring known parent does verify - childCoreBlk.ParentV = prntProBlk.ID() - require.NoError(childProBlk.Verify(context.Background())) + { + // child block referring known parent does verify + childCoreBlk.ParentV = parentBlk.ID() + require.NoError(childBlk.Verify(context.Background())) + } } func TestBlockVerify_BlocksBuiltOnPreForkGenesis(t *testing.T) { require := require.New(t) - activationTime := genesisTimestamp.Add(10 * time.Second) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, 0) + var ( + activationTime = genesisTimestamp.Add(10 * time.Second) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -338,14 +354,14 @@ func TestBlockVerify_BlocksBuiltOnPreForkGenesis(t *testing.T) { require.NoError(preForkChild.Verify(context.Background())) // postFork block does NOT verify if parent is before fork activation time - postForkStatelessChild, err := block.Build( + postForkStatelessChild, err := statelessblock.Build( coreGenBlk.ID(), coreBlk.Timestamp(), 0, // pChainHeight - proVM.stakingCertLeaf, + proVM.StakingCertLeaf, coreBlk.Bytes(), proVM.ctx.ChainID, - proVM.stakingLeafSigner, + proVM.StakingLeafSigner, ) require.NoError(err) postForkChild := &postForkBlock{ @@ -437,8 +453,11 @@ func TestBlockVerify_BlocksBuiltOnPreForkGenesis(t *testing.T) { func TestBlockVerify_BlocksBuiltOnPostForkGenesis(t *testing.T) { require := require.New(t) - activationTime := genesisTimestamp.Add(-1 * time.Second) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, 0) + var ( + activationTime = genesisTimestamp.Add(-1 * time.Second) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) proVM.Set(activationTime) defer func() { require.NoError(proVM.Shutdown(context.Background())) @@ -479,7 +498,11 @@ func TestBlockAccept_PreFork_SetsLastAcceptedBlock(t *testing.T) { require := require.New(t) // setup - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, mockable.MaxTime, 0) + var ( + activationTime = mockable.MaxTime + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -537,7 +560,11 @@ func TestBlockAccept_PreFork_SetsLastAcceptedBlock(t *testing.T) { func TestBlockReject_PreForkBlock_InnerBlockIsRejected(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, mockable.MaxTime, 0) // disable ProBlks + var ( + activationTime = mockable.MaxTime + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -568,8 +595,11 @@ func TestBlockReject_PreForkBlock_InnerBlockIsRejected(t *testing.T) { func TestBlockVerify_ForkBlockIsOracleBlock(t *testing.T) { require := require.New(t) - activationTime := genesisTimestamp.Add(10 * time.Second) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, 0) + var ( + activationTime = genesisTimestamp.Add(10 * time.Second) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -659,8 +689,11 @@ func TestBlockVerify_ForkBlockIsOracleBlock(t *testing.T) { func TestBlockVerify_ForkBlockIsOracleBlockButChildrenAreSigned(t *testing.T) { require := require.New(t) - activationTime := genesisTimestamp.Add(10 * time.Second) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, 0) + var ( + activationTime = genesisTimestamp.Add(10 * time.Second) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -736,14 +769,14 @@ func TestBlockVerify_ForkBlockIsOracleBlockButChildrenAreSigned(t *testing.T) { require.NoError(firstBlock.Verify(context.Background())) - slb, err := block.Build( + slb, err := statelessblock.Build( firstBlock.ID(), // refer unknown parent firstBlock.Timestamp(), 0, // pChainHeight, - proVM.stakingCertLeaf, + proVM.StakingCertLeaf, coreBlk.opts[0].Bytes(), proVM.ctx.ChainID, - proVM.stakingLeafSigner, + proVM.StakingLeafSigner, ) require.NoError(err) @@ -773,7 +806,7 @@ func TestPreForkBlock_BuildBlockWithContext(t *testing.T) { builtBlk.EXPECT().Bytes().Return([]byte{1, 2, 3}).AnyTimes() builtBlk.EXPECT().ID().Return(ids.GenerateTestID()).AnyTimes() builtBlk.EXPECT().Height().Return(pChainHeight).AnyTimes() - innerVM := mocks.NewMockChainVM(ctrl) + innerVM := block.NewMockChainVM(ctrl) innerVM.EXPECT().BuildBlock(gomock.Any()).Return(builtBlk, nil).AnyTimes() vdrState := validators.NewMockState(ctrl) vdrState.EXPECT().GetMinimumHeight(context.Background()).Return(pChainHeight, nil).AnyTimes() @@ -798,7 +831,7 @@ func TestPreForkBlock_BuildBlockWithContext(t *testing.T) { // Should call BuildBlock since proposervm is not activated innerBlk.EXPECT().Timestamp().Return(time.Time{}) - vm.activationTime = mockable.MaxTime + vm.ActivationTime = mockable.MaxTime gotChild, err = blk.buildChild(context.Background()) require.NoError(err) diff --git a/vms/proposervm/proposer/mock_windower.go b/vms/proposervm/proposer/mock_windower.go index 3e7375326429..5384da8ccf80 100644 --- a/vms/proposervm/proposer/mock_windower.go +++ b/vms/proposervm/proposer/mock_windower.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/proposervm/proposer (interfaces: Windower) +// +// Generated by this command: +// +// mockgen -package=proposer -destination=vms/proposervm/proposer/mock_windower.go github.com/ava-labs/avalanchego/vms/proposervm/proposer Windower +// // Package proposer is a generated GoMock package. package proposer @@ -49,11 +51,41 @@ func (m *MockWindower) Delay(arg0 context.Context, arg1, arg2 uint64, arg3 ids.N } // Delay indicates an expected call of Delay. -func (mr *MockWindowerMockRecorder) Delay(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockWindowerMockRecorder) Delay(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delay", reflect.TypeOf((*MockWindower)(nil).Delay), arg0, arg1, arg2, arg3, arg4) } +// ExpectedProposer mocks base method. +func (m *MockWindower) ExpectedProposer(arg0 context.Context, arg1, arg2, arg3 uint64) (ids.NodeID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExpectedProposer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(ids.NodeID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExpectedProposer indicates an expected call of ExpectedProposer. +func (mr *MockWindowerMockRecorder) ExpectedProposer(arg0, arg1, arg2, arg3 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExpectedProposer", reflect.TypeOf((*MockWindower)(nil).ExpectedProposer), arg0, arg1, arg2, arg3) +} + +// MinDelayForProposer mocks base method. +func (m *MockWindower) MinDelayForProposer(arg0 context.Context, arg1, arg2 uint64, arg3 ids.NodeID, arg4 uint64) (time.Duration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MinDelayForProposer", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(time.Duration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MinDelayForProposer indicates an expected call of MinDelayForProposer. +func (mr *MockWindowerMockRecorder) MinDelayForProposer(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MinDelayForProposer", reflect.TypeOf((*MockWindower)(nil).MinDelayForProposer), arg0, arg1, arg2, arg3, arg4) +} + // Proposers mocks base method. func (m *MockWindower) Proposers(arg0 context.Context, arg1, arg2 uint64, arg3 int) ([]ids.NodeID, error) { m.ctrl.T.Helper() @@ -64,7 +96,7 @@ func (m *MockWindower) Proposers(arg0 context.Context, arg1, arg2 uint64, arg3 i } // Proposers indicates an expected call of Proposers. -func (mr *MockWindowerMockRecorder) Proposers(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockWindowerMockRecorder) Proposers(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Proposers", reflect.TypeOf((*MockWindower)(nil).Proposers), arg0, arg1, arg2, arg3) } diff --git a/vms/proposervm/proposer/validators.go b/vms/proposervm/proposer/validators.go index ba60a088003a..6af996187b69 100644 --- a/vms/proposervm/proposer/validators.go +++ b/vms/proposervm/proposer/validators.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposer @@ -15,6 +15,6 @@ type validatorData struct { weight uint64 } -func (d validatorData) Less(other validatorData) bool { - return d.id.Less(other.id) +func (d validatorData) Compare(other validatorData) int { + return d.id.Compare(other.id) } diff --git a/vms/proposervm/proposer/validators_test.go b/vms/proposervm/proposer/validators_test.go index 2f7913d01e2e..e86f2c806a14 100644 --- a/vms/proposervm/proposer/validators_test.go +++ b/vms/proposervm/proposer/validators_test.go @@ -1,9 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposer import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -11,16 +12,31 @@ import ( "github.com/ava-labs/avalanchego/ids" ) -func TestValidatorDataLess(t *testing.T) { - require := require.New(t) - - var v1, v2 validatorData - require.False(v1.Less(v2)) - require.False(v2.Less(v1)) +func TestValidatorDataCompare(t *testing.T) { + tests := []struct { + a validatorData + b validatorData + expected int + }{ + { + a: validatorData{}, + b: validatorData{}, + expected: 0, + }, + { + a: validatorData{ + id: ids.BuildTestNodeID([]byte{1}), + }, + b: validatorData{}, + expected: 1, + }, + } + for _, test := range tests { + t.Run(fmt.Sprintf("%s_%s_%d", test.a.id, test.b.id, test.expected), func(t *testing.T) { + require := require.New(t) - v1 = validatorData{ - id: ids.BuildTestNodeID([]byte{1}), + require.Equal(test.expected, test.a.Compare(test.b)) + require.Equal(-test.expected, test.b.Compare(test.a)) + }) } - require.False(v1.Less(v2)) - require.True(v2.Less(v1)) } diff --git a/vms/proposervm/proposer/windower.go b/vms/proposervm/proposer/windower.go index 8cb7bc43b24d..6d1d958dd04a 100644 --- a/vms/proposervm/proposer/windower.go +++ b/vms/proposervm/proposer/windower.go @@ -1,12 +1,17 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposer import ( "context" + "errors" + "fmt" + "math/bits" "time" + "gonum.org/v1/gonum/mathext/prng" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils" @@ -24,31 +29,70 @@ const ( MaxBuildWindows = 60 MaxBuildDelay = MaxBuildWindows * WindowDuration // 5 minutes + + MaxLookAheadSlots = 720 + MaxLookAheadWindow = MaxLookAheadSlots * WindowDuration // 1 hour ) -var _ Windower = (*windower)(nil) +var ( + _ Windower = (*windower)(nil) + + ErrAnyoneCanPropose = errors.New("anyone can propose") +) type Windower interface { - // Proposers returns the proposer list for building a block at [chainHeight] + // Proposers returns the proposer list for building a block at [blockHeight] // when the validator set is defined at [pChainHeight]. The list is returned // in order. The minimum delay of a validator is the index they appear times // [WindowDuration]. Proposers( ctx context.Context, - chainHeight, + blockHeight, pChainHeight uint64, maxWindows int, ) ([]ids.NodeID, error) + // Delay returns the amount of time that [validatorID] must wait before - // building a block at [chainHeight] when the validator set is defined at + // building a block at [blockHeight] when the validator set is defined at // [pChainHeight]. Delay( ctx context.Context, - chainHeight, + blockHeight, pChainHeight uint64, validatorID ids.NodeID, maxWindows int, ) (time.Duration, error) + + // In the Post-Durango windowing scheme, every validator active at + // [pChainHeight] gets specific slots it can propose in (instead of being + // able to propose from a given time on as it happens Pre-Durango). + // [ExpectedProposer] calculates which nodeID is scheduled to propose a + // block of height [blockHeight] at [slot]. + // If no validators are currently available, [ErrAnyoneCanPropose] is + // returned. + ExpectedProposer( + ctx context.Context, + blockHeight, + pChainHeight, + slot uint64, + ) (ids.NodeID, error) + + // In the Post-Durango windowing scheme, every validator active at + // [pChainHeight] gets specific slots it can propose in (instead of being + // able to propose from a given time on as it happens Pre-Durango). + // [MinDelayForProposer] specifies how long [nodeID] needs to wait for its + // slot to start. Delay is specified as starting from slot zero start. + // (which is parent timestamp). For efficiency reasons, we cap the slot + // search to [MaxLookAheadSlots]. + // If no validators are currently available, [ErrAnyoneCanPropose] is + // returned. + MinDelayForProposer( + ctx context.Context, + blockHeight, + pChainHeight uint64, + nodeID ids.NodeID, + startSlot uint64, + ) (time.Duration, error) } // windower interfaces with P-Chain and it is responsible for calculating the @@ -57,7 +101,6 @@ type windower struct { state validators.State subnetID ids.ID chainSource uint64 - sampler sampler.WeightedWithoutReplacement } func New(state validators.State, subnetID, chainID ids.ID) Windower { @@ -66,56 +109,30 @@ func New(state validators.State, subnetID, chainID ids.ID) Windower { state: state, subnetID: subnetID, chainSource: w.UnpackLong(), - sampler: sampler.NewDeterministicWeightedWithoutReplacement(), } } -func (w *windower) Proposers(ctx context.Context, chainHeight, pChainHeight uint64, maxWindows int) ([]ids.NodeID, error) { - // get the validator set by the p-chain height - validatorsMap, err := w.state.GetValidatorSet(ctx, pChainHeight, w.subnetID) +func (w *windower) Proposers(ctx context.Context, blockHeight, pChainHeight uint64, maxWindows int) ([]ids.NodeID, error) { + // Note: The 32-bit prng is used here for legacy reasons. All other usages + // of a prng in this file should use the 64-bit version. + source := prng.NewMT19937() + sampler, validators, err := w.makeSampler(ctx, pChainHeight, source) if err != nil { return nil, err } - // convert the map of validators to a slice - validators := make([]validatorData, 0, len(validatorsMap)) - weight := uint64(0) - for k, v := range validatorsMap { - validators = append(validators, validatorData{ - id: k, - weight: v.Weight, - }) - newWeight, err := math.Add64(weight, v.Weight) + var totalWeight uint64 + for _, validator := range validators { + totalWeight, err = math.Add64(totalWeight, validator.weight) if err != nil { return nil, err } - weight = newWeight } - // canonically sort validators - // Note: validators are sorted by ID, sorting by weight would not create a - // canonically sorted list - utils.Sort(validators) - - // convert the slice of validators to a slice of weights - validatorWeights := make([]uint64, len(validators)) - for i, v := range validators { - validatorWeights[i] = v.weight - } - - if err := w.sampler.Initialize(validatorWeights); err != nil { - return nil, err - } - - numToSample := maxWindows - if weight < uint64(numToSample) { - numToSample = int(weight) - } - - seed := chainHeight ^ w.chainSource - w.sampler.Seed(int64(seed)) + source.Seed(w.chainSource ^ blockHeight) - indices, err := w.sampler.Sample(numToSample) + numToSample := int(math.Min(uint64(maxWindows), totalWeight)) + indices, err := sampler.Sample(numToSample) if err != nil { return nil, err } @@ -127,12 +144,12 @@ func (w *windower) Proposers(ctx context.Context, chainHeight, pChainHeight uint return nodeIDs, nil } -func (w *windower) Delay(ctx context.Context, chainHeight, pChainHeight uint64, validatorID ids.NodeID, maxWindows int) (time.Duration, error) { +func (w *windower) Delay(ctx context.Context, blockHeight, pChainHeight uint64, validatorID ids.NodeID, maxWindows int) (time.Duration, error) { if validatorID == ids.EmptyNodeID { return time.Duration(maxWindows) * WindowDuration, nil } - proposers, err := w.Proposers(ctx, chainHeight, pChainHeight, maxWindows) + proposers, err := w.Proposers(ctx, blockHeight, pChainHeight, maxWindows) if err != nil { return 0, err } @@ -146,3 +163,124 @@ func (w *windower) Delay(ctx context.Context, chainHeight, pChainHeight uint64, } return delay, nil } + +func (w *windower) ExpectedProposer( + ctx context.Context, + blockHeight, + pChainHeight, + slot uint64, +) (ids.NodeID, error) { + source := prng.NewMT19937_64() + sampler, validators, err := w.makeSampler(ctx, pChainHeight, source) + if err != nil { + return ids.EmptyNodeID, err + } + if len(validators) == 0 { + return ids.EmptyNodeID, ErrAnyoneCanPropose + } + + return w.expectedProposer( + validators, + source, + sampler, + blockHeight, + slot, + ) +} + +func (w *windower) MinDelayForProposer( + ctx context.Context, + blockHeight, + pChainHeight uint64, + nodeID ids.NodeID, + startSlot uint64, +) (time.Duration, error) { + source := prng.NewMT19937_64() + sampler, validators, err := w.makeSampler(ctx, pChainHeight, source) + if err != nil { + return 0, err + } + if len(validators) == 0 { + return 0, ErrAnyoneCanPropose + } + + maxSlot := startSlot + MaxLookAheadSlots + for slot := startSlot; slot < maxSlot; slot++ { + expectedNodeID, err := w.expectedProposer( + validators, + source, + sampler, + blockHeight, + slot, + ) + if err != nil { + return 0, err + } + + if expectedNodeID == nodeID { + return time.Duration(slot) * WindowDuration, nil + } + } + + // no slots scheduled for the max window we inspect. Return max delay + return time.Duration(maxSlot) * WindowDuration, nil +} + +func (w *windower) makeSampler( + ctx context.Context, + pChainHeight uint64, + source sampler.Source, +) (sampler.WeightedWithoutReplacement, []validatorData, error) { + // Get the canconical representation of the validator set at the provided + // p-chain height. + validatorsMap, err := w.state.GetValidatorSet(ctx, pChainHeight, w.subnetID) + if err != nil { + return nil, nil, err + } + + validators := make([]validatorData, 0, len(validatorsMap)) + for k, v := range validatorsMap { + validators = append(validators, validatorData{ + id: k, + weight: v.Weight, + }) + } + + // Note: validators are sorted by ID. Sorting by weight would not create a + // canonically sorted list. + utils.Sort(validators) + + weights := make([]uint64, len(validators)) + for i, validator := range validators { + weights[i] = validator.weight + } + + sampler := sampler.NewDeterministicWeightedWithoutReplacement(source) + return sampler, validators, sampler.Initialize(weights) +} + +func (w *windower) expectedProposer( + validators []validatorData, + source *prng.MT19937_64, + sampler sampler.WeightedWithoutReplacement, + blockHeight, + slot uint64, +) (ids.NodeID, error) { + // Slot is reversed to utilize a different state space in the seed than the + // height. If the slot was not reversed the state space would collide; + // biasing the seed generation. For example, without reversing the slot + // height=0 and slot=1 would equal height=1 and slot=0. + source.Seed(w.chainSource ^ blockHeight ^ bits.Reverse64(slot)) + indices, err := sampler.Sample(1) + if err != nil { + return ids.EmptyNodeID, fmt.Errorf("failed sampling proposers: %w", err) + } + return validators[indices[0]].id, nil +} + +func TimeToSlot(start, now time.Time) uint64 { + if now.Before(start) { + return 0 + } + return uint64(now.Sub(start) / WindowDuration) +} diff --git a/vms/proposervm/proposer/windower_test.go b/vms/proposervm/proposer/windower_test.go index 2a141a361ea2..d3e2ac68817a 100644 --- a/vms/proposervm/proposer/windower_test.go +++ b/vms/proposervm/proposer/windower_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposer import ( "context" + "math" "math/rand" "testing" "time" @@ -13,35 +14,49 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" + + safemath "github.com/ava-labs/avalanchego/utils/math" +) + +var ( + subnetID = ids.GenerateTestID() + randomChainID = ids.GenerateTestID() + fixedChainID = ids.ID{0, 2} ) func TestWindowerNoValidators(t *testing.T) { require := require.New(t) - subnetID := ids.GenerateTestID() - chainID := ids.GenerateTestID() - nodeID := ids.GenerateTestNodeID() - vdrState := &validators.TestState{ - T: t, - GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return nil, nil - }, - } - - w := New(vdrState, subnetID, chainID) + _, vdrState := makeValidators(t, 0) + w := New(vdrState, subnetID, randomChainID) - delay, err := w.Delay(context.Background(), 1, 0, nodeID, MaxVerifyWindows) + var ( + chainHeight uint64 = 1 + pChainHeight uint64 = 0 + nodeID = ids.GenerateTestNodeID() + slot uint64 = 1 + ) + delay, err := w.Delay(context.Background(), chainHeight, pChainHeight, nodeID, MaxVerifyWindows) require.NoError(err) require.Zero(delay) + + expectedProposer, err := w.ExpectedProposer(context.Background(), chainHeight, pChainHeight, slot) + require.ErrorIs(err, ErrAnyoneCanPropose) + require.Equal(ids.EmptyNodeID, expectedProposer) + + delay, err = w.MinDelayForProposer(context.Background(), chainHeight, pChainHeight, nodeID, slot) + require.ErrorIs(err, ErrAnyoneCanPropose) + require.Zero(delay) } func TestWindowerRepeatedValidator(t *testing.T) { require := require.New(t) - subnetID := ids.GenerateTestID() - chainID := ids.GenerateTestID() - validatorID := ids.GenerateTestNodeID() - nonValidatorID := ids.GenerateTestNodeID() + var ( + validatorID = ids.GenerateTestNodeID() + nonValidatorID = ids.GenerateTestNodeID() + ) + vdrState := &validators.TestState{ T: t, GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { @@ -54,7 +69,7 @@ func TestWindowerRepeatedValidator(t *testing.T) { }, } - w := New(vdrState, subnetID, chainID) + w := New(vdrState, subnetID, randomChainID) validatorDelay, err := w.Delay(context.Background(), 1, 0, validatorID, MaxVerifyWindows) require.NoError(err) @@ -65,30 +80,11 @@ func TestWindowerRepeatedValidator(t *testing.T) { require.Equal(MaxVerifyDelay, nonValidatorDelay) } -func TestWindowerChangeByHeight(t *testing.T) { +func TestDelayChangeByHeight(t *testing.T) { require := require.New(t) - subnetID := ids.ID{0, 1} - chainID := ids.ID{0, 2} - validatorIDs := make([]ids.NodeID, MaxVerifyWindows) - for i := range validatorIDs { - validatorIDs[i] = ids.BuildTestNodeID([]byte{byte(i) + 1}) - } - vdrState := &validators.TestState{ - T: t, - GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - vdrs := make(map[ids.NodeID]*validators.GetValidatorOutput, MaxVerifyWindows) - for _, id := range validatorIDs { - vdrs[id] = &validators.GetValidatorOutput{ - NodeID: id, - Weight: 1, - } - } - return vdrs, nil - }, - } - - w := New(vdrState, subnetID, chainID) + validatorIDs, vdrState := makeValidators(t, MaxVerifyWindows) + w := New(vdrState, subnetID, fixedChainID) expectedDelays1 := []time.Duration{ 2 * WindowDuration, @@ -121,35 +117,21 @@ func TestWindowerChangeByHeight(t *testing.T) { } } -func TestWindowerChangeByChain(t *testing.T) { +func TestDelayChangeByChain(t *testing.T) { require := require.New(t) - subnetID := ids.ID{0, 1} + source := rand.NewSource(int64(0)) + rng := rand.New(source) // #nosec G404 - rand.Seed(0) chainID0 := ids.ID{} - _, _ = rand.Read(chainID0[:]) // #nosec G404 - chainID1 := ids.ID{} - _, _ = rand.Read(chainID1[:]) // #nosec G404 + _, err := rng.Read(chainID0[:]) + require.NoError(err) - validatorIDs := make([]ids.NodeID, MaxVerifyWindows) - for i := range validatorIDs { - validatorIDs[i] = ids.BuildTestNodeID([]byte{byte(i) + 1}) - } - vdrState := &validators.TestState{ - T: t, - GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - vdrs := make(map[ids.NodeID]*validators.GetValidatorOutput, MaxVerifyWindows) - for _, id := range validatorIDs { - vdrs[id] = &validators.GetValidatorOutput{ - NodeID: id, - Weight: 1, - } - } - return vdrs, nil - }, - } + chainID1 := ids.ID{} + _, err = rng.Read(chainID1[:]) + require.NoError(err) + validatorIDs, vdrState := makeValidators(t, MaxVerifyWindows) w0 := New(vdrState, subnetID, chainID0) w1 := New(vdrState, subnetID, chainID1) @@ -183,3 +165,303 @@ func TestWindowerChangeByChain(t *testing.T) { require.Equal(expectedDelay, validatorDelay) } } + +func TestExpectedProposerChangeByHeight(t *testing.T) { + require := require.New(t) + + validatorIDs, vdrState := makeValidators(t, 10) + w := New(vdrState, subnetID, fixedChainID) + + var ( + dummyCtx = context.Background() + pChainHeight uint64 = 0 + slot uint64 = 0 + ) + + expectedProposers := map[uint64]ids.NodeID{ + 1: validatorIDs[2], + 2: validatorIDs[1], + } + + for chainHeight, expectedProposerID := range expectedProposers { + proposerID, err := w.ExpectedProposer(dummyCtx, chainHeight, pChainHeight, slot) + require.NoError(err) + require.Equal(expectedProposerID, proposerID) + } +} + +func TestExpectedProposerChangeByChain(t *testing.T) { + require := require.New(t) + + source := rand.NewSource(int64(0)) + rng := rand.New(source) // #nosec G404 + + chainID0 := ids.ID{} + _, err := rng.Read(chainID0[:]) + require.NoError(err) + + chainID1 := ids.ID{} + _, err = rng.Read(chainID1[:]) + require.NoError(err) + + validatorIDs, vdrState := makeValidators(t, 10) + + var ( + dummyCtx = context.Background() + chainHeight uint64 = 1 + pChainHeight uint64 = 0 + slot uint64 = 0 + ) + + expectedProposers := map[ids.ID]ids.NodeID{ + chainID0: validatorIDs[5], + chainID1: validatorIDs[3], + } + + for chainID, expectedProposerID := range expectedProposers { + w := New(vdrState, subnetID, chainID) + proposerID, err := w.ExpectedProposer(dummyCtx, chainHeight, pChainHeight, slot) + require.NoError(err) + require.Equal(expectedProposerID, proposerID) + } +} + +func TestExpectedProposerChangeBySlot(t *testing.T) { + require := require.New(t) + + validatorIDs, vdrState := makeValidators(t, 10) + w := New(vdrState, subnetID, fixedChainID) + + var ( + dummyCtx = context.Background() + chainHeight uint64 = 1 + pChainHeight uint64 = 0 + ) + + proposers := []ids.NodeID{ + validatorIDs[2], + validatorIDs[0], + validatorIDs[9], + validatorIDs[7], + validatorIDs[0], + validatorIDs[3], + validatorIDs[3], + validatorIDs[3], + validatorIDs[3], + validatorIDs[3], + validatorIDs[4], + validatorIDs[0], + validatorIDs[6], + validatorIDs[3], + validatorIDs[2], + validatorIDs[1], + validatorIDs[6], + validatorIDs[0], + validatorIDs[5], + validatorIDs[1], + validatorIDs[9], + validatorIDs[6], + validatorIDs[0], + validatorIDs[8], + } + expectedProposers := map[uint64]ids.NodeID{ + MaxLookAheadSlots: validatorIDs[4], + MaxLookAheadSlots + 1: validatorIDs[6], + } + for slot, expectedProposerID := range proposers { + expectedProposers[uint64(slot)] = expectedProposerID + } + + for slot, expectedProposerID := range expectedProposers { + actualProposerID, err := w.ExpectedProposer(dummyCtx, chainHeight, pChainHeight, slot) + require.NoError(err) + require.Equal(expectedProposerID, actualProposerID) + } +} + +func TestCoherenceOfExpectedProposerAndMinDelayForProposer(t *testing.T) { + require := require.New(t) + + _, vdrState := makeValidators(t, 10) + w := New(vdrState, subnetID, fixedChainID) + + var ( + dummyCtx = context.Background() + chainHeight uint64 = 1 + pChainHeight uint64 = 0 + ) + + for slot := uint64(0); slot < 3*MaxLookAheadSlots; slot++ { + proposerID, err := w.ExpectedProposer(dummyCtx, chainHeight, pChainHeight, slot) + require.NoError(err) + + // proposerID is the scheduled proposer. It should start with the + // expected delay + delay, err := w.MinDelayForProposer(dummyCtx, chainHeight, pChainHeight, proposerID, slot) + require.NoError(err) + require.Equal(time.Duration(slot)*WindowDuration, delay) + } +} + +func TestMinDelayForProposer(t *testing.T) { + require := require.New(t) + + validatorIDs, vdrState := makeValidators(t, 10) + w := New(vdrState, subnetID, fixedChainID) + + var ( + dummyCtx = context.Background() + chainHeight uint64 = 1 + pChainHeight uint64 = 0 + slot uint64 = 0 + ) + + expectedDelays := map[ids.NodeID]time.Duration{ + validatorIDs[0]: 1 * WindowDuration, + validatorIDs[1]: 15 * WindowDuration, + validatorIDs[2]: 0 * WindowDuration, + validatorIDs[3]: 5 * WindowDuration, + validatorIDs[4]: 10 * WindowDuration, + validatorIDs[5]: 18 * WindowDuration, + validatorIDs[6]: 12 * WindowDuration, + validatorIDs[7]: 3 * WindowDuration, + validatorIDs[8]: 23 * WindowDuration, + validatorIDs[9]: 2 * WindowDuration, + ids.GenerateTestNodeID(): MaxLookAheadWindow, + } + + for nodeID, expectedDelay := range expectedDelays { + delay, err := w.MinDelayForProposer(dummyCtx, chainHeight, pChainHeight, nodeID, slot) + require.NoError(err) + require.Equal(expectedDelay, delay) + } +} + +func BenchmarkMinDelayForProposer(b *testing.B) { + require := require.New(b) + + _, vdrState := makeValidators(b, 10) + w := New(vdrState, subnetID, fixedChainID) + + var ( + dummyCtx = context.Background() + pChainHeight uint64 = 0 + chainHeight uint64 = 1 + nodeID = ids.GenerateTestNodeID() // Ensure to exhaust the search + slot uint64 = 0 + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := w.MinDelayForProposer(dummyCtx, chainHeight, pChainHeight, nodeID, slot) + require.NoError(err) + } +} + +func TestTimeToSlot(t *testing.T) { + parentTime := time.Now() + tests := []struct { + timeOffset time.Duration + expectedSlot uint64 + }{ + { + timeOffset: -WindowDuration, + expectedSlot: 0, + }, + { + timeOffset: -time.Second, + expectedSlot: 0, + }, + { + timeOffset: 0, + expectedSlot: 0, + }, + { + timeOffset: WindowDuration, + expectedSlot: 1, + }, + { + timeOffset: 2 * WindowDuration, + expectedSlot: 2, + }, + } + for _, test := range tests { + t.Run(test.timeOffset.String(), func(t *testing.T) { + slot := TimeToSlot(parentTime, parentTime.Add(test.timeOffset)) + require.Equal(t, test.expectedSlot, slot) + }) + } +} + +// Ensure that the proposer distribution is within 3 standard deviations of the +// expected value assuming a truly random binomial distribution. +func TestProposerDistribution(t *testing.T) { + require := require.New(t) + + validatorIDs, vdrState := makeValidators(t, 10) + w := New(vdrState, subnetID, fixedChainID) + + var ( + dummyCtx = context.Background() + pChainHeight uint64 = 0 + numChainHeights uint64 = 100 + numSlots uint64 = 100 + ) + + proposerFrequency := make(map[ids.NodeID]int) + for _, validatorID := range validatorIDs { + // Initialize the map to 0s to include validators that are never sampled + // in the analysis. + proposerFrequency[validatorID] = 0 + } + for chainHeight := uint64(0); chainHeight < numChainHeights; chainHeight++ { + for slot := uint64(0); slot < numSlots; slot++ { + proposerID, err := w.ExpectedProposer(dummyCtx, chainHeight, pChainHeight, slot) + require.NoError(err) + proposerFrequency[proposerID]++ + } + } + + var ( + totalNumberOfSamples = numChainHeights * numSlots + probabilityOfBeingSampled = 1 / float64(len(validatorIDs)) + expectedNumberOfSamples = uint64(probabilityOfBeingSampled * float64(totalNumberOfSamples)) + variance = float64(totalNumberOfSamples) * probabilityOfBeingSampled * (1 - probabilityOfBeingSampled) + stdDeviation = math.Sqrt(variance) + maxDeviation uint64 + ) + for _, sampled := range proposerFrequency { + maxDeviation = safemath.Max( + maxDeviation, + safemath.AbsDiff( + uint64(sampled), + expectedNumberOfSamples, + ), + ) + } + + maxSTDDeviation := float64(maxDeviation) / stdDeviation + require.Less(maxSTDDeviation, 3.) +} + +func makeValidators(t testing.TB, count int) ([]ids.NodeID, *validators.TestState) { + validatorIDs := make([]ids.NodeID, count) + for i := range validatorIDs { + validatorIDs[i] = ids.BuildTestNodeID([]byte{byte(i) + 1}) + } + + vdrState := &validators.TestState{ + T: t, + GetValidatorSetF: func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + vdrs := make(map[ids.NodeID]*validators.GetValidatorOutput, MaxVerifyWindows) + for _, id := range validatorIDs { + vdrs[id] = &validators.GetValidatorOutput{ + NodeID: id, + Weight: 1, + } + } + return vdrs, nil + }, + } + return validatorIDs, vdrState +} diff --git a/vms/proposervm/scheduler/mock_scheduler.go b/vms/proposervm/scheduler/mock_scheduler.go new file mode 100644 index 000000000000..f4a8f1e62197 --- /dev/null +++ b/vms/proposervm/scheduler/mock_scheduler.go @@ -0,0 +1,76 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ava-labs/avalanchego/vms/proposervm/scheduler (interfaces: Scheduler) +// +// Generated by this command: +// +// mockgen -package=scheduler -destination=vms/proposervm/scheduler/mock_scheduler.go github.com/ava-labs/avalanchego/vms/proposervm/scheduler Scheduler +// + +// Package scheduler is a generated GoMock package. +package scheduler + +import ( + reflect "reflect" + time "time" + + gomock "go.uber.org/mock/gomock" +) + +// MockScheduler is a mock of Scheduler interface. +type MockScheduler struct { + ctrl *gomock.Controller + recorder *MockSchedulerMockRecorder +} + +// MockSchedulerMockRecorder is the mock recorder for MockScheduler. +type MockSchedulerMockRecorder struct { + mock *MockScheduler +} + +// NewMockScheduler creates a new mock instance. +func NewMockScheduler(ctrl *gomock.Controller) *MockScheduler { + mock := &MockScheduler{ctrl: ctrl} + mock.recorder = &MockSchedulerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockScheduler) EXPECT() *MockSchedulerMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockScheduler) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close. +func (mr *MockSchedulerMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockScheduler)(nil).Close)) +} + +// Dispatch mocks base method. +func (m *MockScheduler) Dispatch(arg0 time.Time) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Dispatch", arg0) +} + +// Dispatch indicates an expected call of Dispatch. +func (mr *MockSchedulerMockRecorder) Dispatch(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dispatch", reflect.TypeOf((*MockScheduler)(nil).Dispatch), arg0) +} + +// SetBuildBlockTime mocks base method. +func (m *MockScheduler) SetBuildBlockTime(arg0 time.Time) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetBuildBlockTime", arg0) +} + +// SetBuildBlockTime indicates an expected call of SetBuildBlockTime. +func (mr *MockSchedulerMockRecorder) SetBuildBlockTime(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuildBlockTime", reflect.TypeOf((*MockScheduler)(nil).SetBuildBlockTime), arg0) +} diff --git a/vms/proposervm/scheduler/scheduler.go b/vms/proposervm/scheduler/scheduler.go index 5946a67b9b77..8395596a55a8 100644 --- a/vms/proposervm/scheduler/scheduler.go +++ b/vms/proposervm/scheduler/scheduler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package scheduler diff --git a/vms/proposervm/scheduler/scheduler_test.go b/vms/proposervm/scheduler/scheduler_test.go index 821a36883e90..77ed39a67330 100644 --- a/vms/proposervm/scheduler/scheduler_test.go +++ b/vms/proposervm/scheduler/scheduler_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package scheduler diff --git a/vms/proposervm/state/block_height_index.go b/vms/proposervm/state/block_height_index.go index e16100bddd89..b60fca0c363d 100644 --- a/vms/proposervm/state/block_height_index.go +++ b/vms/proposervm/state/block_height_index.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/proposervm/state/block_state.go b/vms/proposervm/state/block_state.go index fa4c67e1ecf5..862d492b925b 100644 --- a/vms/proposervm/state/block_state.go +++ b/vms/proposervm/state/block_state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/proposervm/block" ) @@ -100,16 +101,20 @@ func (s *blockState) GetBlock(blkID ids.ID) (block.Block, choices.Status, error) } blkWrapper := blockWrapper{} - parsedVersion, err := c.Unmarshal(blkWrapperBytes, &blkWrapper) + parsedVersion, err := Codec.Unmarshal(blkWrapperBytes, &blkWrapper) if err != nil { return nil, choices.Unknown, err } - if parsedVersion != version { + if parsedVersion != CodecVersion { return nil, choices.Unknown, errBlockWrongVersion } // The key was in the database - blk, err := block.Parse(blkWrapper.Block) + // + // Invariant: Blocks stored on disk were previously accepted by this node. + // Because the durango activation relaxes TLS cert parsing rules, we assume + // it is always activated here. + blk, err := block.Parse(blkWrapper.Block, version.DefaultUpgradeTime) if err != nil { return nil, choices.Unknown, err } @@ -126,7 +131,7 @@ func (s *blockState) PutBlock(blk block.Block, status choices.Status) error { block: blk, } - bytes, err := c.Marshal(version, &blkWrapper) + bytes, err := Codec.Marshal(CodecVersion, &blkWrapper) if err != nil { return err } diff --git a/vms/proposervm/state/block_state_test.go b/vms/proposervm/state/block_state_test.go index 0b50698e2d45..193f3854b4a9 100644 --- a/vms/proposervm/state/block_state_test.go +++ b/vms/proposervm/state/block_state_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/proposervm/state/chain_state.go b/vms/proposervm/state/chain_state.go index 0f1a1bfba4e8..e4ed34ddcb78 100644 --- a/vms/proposervm/state/chain_state.go +++ b/vms/proposervm/state/chain_state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/proposervm/state/chain_state_test.go b/vms/proposervm/state/chain_state_test.go index ab14f4228281..6b45585f6041 100644 --- a/vms/proposervm/state/chain_state_test.go +++ b/vms/proposervm/state/chain_state_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/proposervm/state/codec.go b/vms/proposervm/state/codec.go index f73523806e53..63727894e356 100644 --- a/vms/proposervm/state/codec.go +++ b/vms/proposervm/state/codec.go @@ -1,24 +1,25 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state import ( "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" ) -const version = 0 +const CodecVersion = 0 -var c codec.Manager +var Codec codec.Manager func init() { - lc := linearcodec.NewCustomMaxLength(math.MaxUint32) - c = codec.NewManager(math.MaxInt32) + lc := linearcodec.NewDefault(time.Time{}) + Codec = codec.NewManager(math.MaxInt32) - err := c.RegisterCodec(version, lc) + err := Codec.RegisterCodec(CodecVersion, lc) if err != nil { panic(err) } diff --git a/vms/proposervm/state/mock_state.go b/vms/proposervm/state/mock_state.go index 40ef830a1365..6384528a61dd 100644 --- a/vms/proposervm/state/mock_state.go +++ b/vms/proposervm/state/mock_state.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/proposervm/state (interfaces: State) +// +// Generated by this command: +// +// mockgen -package=state -destination=vms/proposervm/state/mock_state.go github.com/ava-labs/avalanchego/vms/proposervm/state State +// // Package state is a generated GoMock package. package state @@ -62,7 +64,7 @@ func (m *MockState) DeleteBlock(arg0 ids.ID) error { } // DeleteBlock indicates an expected call of DeleteBlock. -func (mr *MockStateMockRecorder) DeleteBlock(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) DeleteBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBlock", reflect.TypeOf((*MockState)(nil).DeleteBlock), arg0) } @@ -76,7 +78,7 @@ func (m *MockState) DeleteBlockIDAtHeight(arg0 uint64) error { } // DeleteBlockIDAtHeight indicates an expected call of DeleteBlockIDAtHeight. -func (mr *MockStateMockRecorder) DeleteBlockIDAtHeight(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) DeleteBlockIDAtHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBlockIDAtHeight", reflect.TypeOf((*MockState)(nil).DeleteBlockIDAtHeight), arg0) } @@ -120,7 +122,7 @@ func (m *MockState) GetBlock(arg0 ids.ID) (block.Block, choices.Status, error) { } // GetBlock indicates an expected call of GetBlock. -func (mr *MockStateMockRecorder) GetBlock(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetBlock(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockState)(nil).GetBlock), arg0) } @@ -135,7 +137,7 @@ func (m *MockState) GetBlockIDAtHeight(arg0 uint64) (ids.ID, error) { } // GetBlockIDAtHeight indicates an expected call of GetBlockIDAtHeight. -func (mr *MockStateMockRecorder) GetBlockIDAtHeight(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) GetBlockIDAtHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockIDAtHeight", reflect.TypeOf((*MockState)(nil).GetBlockIDAtHeight), arg0) } @@ -209,7 +211,7 @@ func (m *MockState) PutBlock(arg0 block.Block, arg1 choices.Status) error { } // PutBlock indicates an expected call of PutBlock. -func (mr *MockStateMockRecorder) PutBlock(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) PutBlock(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutBlock", reflect.TypeOf((*MockState)(nil).PutBlock), arg0, arg1) } @@ -223,7 +225,7 @@ func (m *MockState) SetBlockIDAtHeight(arg0 uint64, arg1 ids.ID) error { } // SetBlockIDAtHeight indicates an expected call of SetBlockIDAtHeight. -func (mr *MockStateMockRecorder) SetBlockIDAtHeight(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetBlockIDAtHeight(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockIDAtHeight", reflect.TypeOf((*MockState)(nil).SetBlockIDAtHeight), arg0, arg1) } @@ -237,7 +239,7 @@ func (m *MockState) SetCheckpoint(arg0 ids.ID) error { } // SetCheckpoint indicates an expected call of SetCheckpoint. -func (mr *MockStateMockRecorder) SetCheckpoint(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetCheckpoint(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCheckpoint", reflect.TypeOf((*MockState)(nil).SetCheckpoint), arg0) } @@ -251,7 +253,7 @@ func (m *MockState) SetForkHeight(arg0 uint64) error { } // SetForkHeight indicates an expected call of SetForkHeight. -func (mr *MockStateMockRecorder) SetForkHeight(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetForkHeight(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetForkHeight", reflect.TypeOf((*MockState)(nil).SetForkHeight), arg0) } @@ -265,7 +267,7 @@ func (m *MockState) SetLastAccepted(arg0 ids.ID) error { } // SetLastAccepted indicates an expected call of SetLastAccepted. -func (mr *MockStateMockRecorder) SetLastAccepted(arg0 interface{}) *gomock.Call { +func (mr *MockStateMockRecorder) SetLastAccepted(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLastAccepted", reflect.TypeOf((*MockState)(nil).SetLastAccepted), arg0) } diff --git a/vms/proposervm/state/state.go b/vms/proposervm/state/state.go index c8b80b947920..487e64f71f08 100644 --- a/vms/proposervm/state/state.go +++ b/vms/proposervm/state/state.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/proposervm/state/state_test.go b/vms/proposervm/state/state_test.go index 97980fc36b9b..9ef1e291e539 100644 --- a/vms/proposervm/state/state_test.go +++ b/vms/proposervm/state/state_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package state diff --git a/vms/proposervm/state_summary.go b/vms/proposervm/state_summary.go index 629d2c6491d1..f61c29d6f426 100644 --- a/vms/proposervm/state_summary.go +++ b/vms/proposervm/state_summary.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm diff --git a/vms/proposervm/state_syncable_vm.go b/vms/proposervm/state_syncable_vm.go index da86d8c36e5c..08a321cab7bb 100644 --- a/vms/proposervm/state_syncable_vm.go +++ b/vms/proposervm/state_syncable_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm diff --git a/vms/proposervm/state_syncable_vm_test.go b/vms/proposervm/state_syncable_vm_test.go index 826f8b877987..c9b6d8b1a0dd 100644 --- a/vms/proposervm/state_syncable_vm_test.go +++ b/vms/proposervm/state_syncable_vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -19,6 +19,7 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/vms/proposervm/summary" statelessblock "github.com/ava-labs/avalanchego/vms/proposervm/block" @@ -70,15 +71,18 @@ func helperBuildStateSyncTestObjects(t *testing.T) (*fullVM, *VM) { // create the VM vm := New( innerVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: time.Unix(0, 0), + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) ctx.NodeID = ids.NodeIDFromCert(pTestCert) require.NoError(vm.Initialize( @@ -173,9 +177,9 @@ func TestStateSyncGetOngoingSyncStateSummary(t *testing.T) { // store post fork block associated with summary innerBlk := &snowman.TestBlock{ - BytesV: []byte{1}, - TimestampV: vm.Time(), - HeightV: innerSummary.Height(), + BytesV: []byte{1}, + ParentV: ids.GenerateTestID(), + HeightV: innerSummary.Height(), } innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { require.Equal(innerBlk.Bytes(), b) @@ -186,10 +190,10 @@ func TestStateSyncGetOngoingSyncStateSummary(t *testing.T) { vm.preferred, innerBlk.Timestamp(), 100, // pChainHeight, - vm.stakingCertLeaf, + vm.StakingCertLeaf, innerBlk.Bytes(), vm.ctx.ChainID, - vm.stakingLeafSigner, + vm.StakingLeafSigner, ) require.NoError(err) proBlk := &postForkBlock{ @@ -258,9 +262,9 @@ func TestStateSyncGetLastStateSummary(t *testing.T) { // store post fork block associated with summary innerBlk := &snowman.TestBlock{ - BytesV: []byte{1}, - TimestampV: vm.Time(), - HeightV: innerSummary.Height(), + BytesV: []byte{1}, + ParentV: ids.GenerateTestID(), + HeightV: innerSummary.Height(), } innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { require.Equal(innerBlk.Bytes(), b) @@ -271,10 +275,10 @@ func TestStateSyncGetLastStateSummary(t *testing.T) { vm.preferred, innerBlk.Timestamp(), 100, // pChainHeight, - vm.stakingCertLeaf, + vm.StakingCertLeaf, innerBlk.Bytes(), vm.ctx.ChainID, - vm.stakingLeafSigner, + vm.StakingLeafSigner, ) require.NoError(err) proBlk := &postForkBlock{ @@ -346,9 +350,9 @@ func TestStateSyncGetStateSummary(t *testing.T) { // store post fork block associated with summary innerBlk := &snowman.TestBlock{ - BytesV: []byte{1}, - TimestampV: vm.Time(), - HeightV: innerSummary.Height(), + BytesV: []byte{1}, + ParentV: ids.GenerateTestID(), + HeightV: innerSummary.Height(), } innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { require.Equal(innerBlk.Bytes(), b) @@ -359,10 +363,10 @@ func TestStateSyncGetStateSummary(t *testing.T) { vm.preferred, innerBlk.Timestamp(), 100, // pChainHeight, - vm.stakingCertLeaf, + vm.StakingCertLeaf, innerBlk.Bytes(), vm.ctx.ChainID, - vm.stakingLeafSigner, + vm.StakingLeafSigner, ) require.NoError(err) proBlk := &postForkBlock{ @@ -419,9 +423,9 @@ func TestParseStateSummary(t *testing.T) { // store post fork block associated with summary innerBlk := &snowman.TestBlock{ - BytesV: []byte{1}, - TimestampV: vm.Time(), - HeightV: innerSummary.Height(), + BytesV: []byte{1}, + ParentV: ids.GenerateTestID(), + HeightV: innerSummary.Height(), } innerVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { require.Equal(innerBlk.Bytes(), b) @@ -432,10 +436,10 @@ func TestParseStateSummary(t *testing.T) { vm.preferred, innerBlk.Timestamp(), 100, // pChainHeight, - vm.stakingCertLeaf, + vm.StakingCertLeaf, innerBlk.Bytes(), vm.ctx.ChainID, - vm.stakingLeafSigner, + vm.StakingLeafSigner, ) require.NoError(err) proBlk := &postForkBlock{ @@ -478,19 +482,19 @@ func TestStateSummaryAccept(t *testing.T) { // store post fork block associated with summary innerBlk := &snowman.TestBlock{ - BytesV: []byte{1}, - TimestampV: vm.Time(), - HeightV: innerSummary.Height(), + BytesV: []byte{1}, + ParentV: ids.GenerateTestID(), + HeightV: innerSummary.Height(), } slb, err := statelessblock.Build( vm.preferred, innerBlk.Timestamp(), 100, // pChainHeight, - vm.stakingCertLeaf, + vm.StakingCertLeaf, innerBlk.Bytes(), vm.ctx.ChainID, - vm.stakingLeafSigner, + vm.StakingLeafSigner, ) require.NoError(err) @@ -550,9 +554,9 @@ func TestStateSummaryAcceptOlderBlock(t *testing.T) { // store post fork block associated with summary innerBlk := &snowman.TestBlock{ - BytesV: []byte{1}, - TimestampV: vm.Time(), - HeightV: innerSummary.Height(), + BytesV: []byte{1}, + ParentV: ids.GenerateTestID(), + HeightV: innerSummary.Height(), } innerVM.GetStateSummaryF = func(_ context.Context, h uint64) (block.StateSummary, error) { require.Equal(reqHeight, h) @@ -567,10 +571,10 @@ func TestStateSummaryAcceptOlderBlock(t *testing.T) { vm.preferred, innerBlk.Timestamp(), 100, // pChainHeight, - vm.stakingCertLeaf, + vm.StakingCertLeaf, innerBlk.Bytes(), vm.ctx.ChainID, - vm.stakingLeafSigner, + vm.StakingLeafSigner, ) require.NoError(err) proBlk := &postForkBlock{ @@ -599,7 +603,11 @@ func TestNoStateSummariesServedWhileRepairingHeightIndex(t *testing.T) { require := require.New(t) // Note: by default proVM is built such that heightIndex will be considered complete - coreVM, _, proVM, _, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, _, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() diff --git a/vms/proposervm/summary/build.go b/vms/proposervm/summary/build.go index 35e2e179f0e3..516f9d1a9e72 100644 --- a/vms/proposervm/summary/build.go +++ b/vms/proposervm/summary/build.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package summary @@ -20,7 +20,7 @@ func Build( InnerSummary: coreSummary, } - bytes, err := c.Marshal(codecVersion, &summary) + bytes, err := Codec.Marshal(CodecVersion, &summary) if err != nil { return nil, fmt.Errorf("cannot marshal proposer summary due to: %w", err) } diff --git a/vms/proposervm/summary/build_test.go b/vms/proposervm/summary/build_test.go index 0e15ac3cd1d7..ad7e5df52748 100644 --- a/vms/proposervm/summary/build_test.go +++ b/vms/proposervm/summary/build_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package summary diff --git a/vms/proposervm/summary/codec.go b/vms/proposervm/summary/codec.go index a71350f37d0f..41a9eb9a37d0 100644 --- a/vms/proposervm/summary/codec.go +++ b/vms/proposervm/summary/codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package summary @@ -6,23 +6,24 @@ package summary import ( "errors" "math" + "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/codec/linearcodec" ) -const codecVersion = 0 +const CodecVersion = 0 var ( - c codec.Manager + Codec codec.Manager errWrongCodecVersion = errors.New("wrong codec version") ) func init() { - lc := linearcodec.NewCustomMaxLength(math.MaxUint32) - c = codec.NewManager(math.MaxInt32) - if err := c.RegisterCodec(codecVersion, lc); err != nil { + lc := linearcodec.NewDefault(time.Time{}) + Codec = codec.NewManager(math.MaxInt32) + if err := Codec.RegisterCodec(CodecVersion, lc); err != nil { panic(err) } } diff --git a/vms/proposervm/summary/parse.go b/vms/proposervm/summary/parse.go index 3d9295444782..670bd43a8d77 100644 --- a/vms/proposervm/summary/parse.go +++ b/vms/proposervm/summary/parse.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package summary @@ -14,11 +14,11 @@ func Parse(bytes []byte) (StateSummary, error) { id: hashing.ComputeHash256Array(bytes), bytes: bytes, } - version, err := c.Unmarshal(bytes, &summary) + version, err := Codec.Unmarshal(bytes, &summary) if err != nil { return nil, fmt.Errorf("could not unmarshal summary due to: %w", err) } - if version != codecVersion { + if version != CodecVersion { return nil, errWrongCodecVersion } return &summary, nil diff --git a/vms/proposervm/summary/parse_test.go b/vms/proposervm/summary/parse_test.go index b22be83582e2..16fb2aec9f6b 100644 --- a/vms/proposervm/summary/parse_test.go +++ b/vms/proposervm/summary/parse_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package summary diff --git a/vms/proposervm/summary/state_summary.go b/vms/proposervm/summary/state_summary.go index 59269beb112c..14213a665fa6 100644 --- a/vms/proposervm/summary/state_summary.go +++ b/vms/proposervm/summary/state_summary.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package summary diff --git a/vms/proposervm/tree/tree.go b/vms/proposervm/tree/tree.go index 8d1e7333d32e..38125ba9d0e2 100644 --- a/vms/proposervm/tree/tree.go +++ b/vms/proposervm/tree/tree.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tree diff --git a/vms/proposervm/tree/tree_test.go b/vms/proposervm/tree/tree_test.go index 55b6e129341f..1e826e418c21 100644 --- a/vms/proposervm/tree/tree_test.go +++ b/vms/proposervm/tree/tree_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tree diff --git a/vms/proposervm/vm.go b/vms/proposervm/vm.go index a7bb897932d3..9f77a658b55d 100644 --- a/vms/proposervm/vm.go +++ b/vms/proposervm/vm.go @@ -1,11 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm import ( "context" - "crypto" "errors" "fmt" "time" @@ -26,7 +25,6 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/math" @@ -86,19 +84,11 @@ func cachedBlockSize(_ ids.ID, blk snowman.Block) int { type VM struct { block.ChainVM + Config blockBuilderVM block.BuildBlockWithContextChainVM batchedVM block.BatchedChainVM ssVM block.StateSyncableVM - activationTime time.Time - minimumPChainHeight uint64 - minBlkDelay time.Duration - numHistoricalBlocks uint64 - // block signer - stakingLeafSigner crypto.Signer - // block certificate - stakingCertLeaf *staking.Certificate - state.State hIndexer indexer.HeightIndexer @@ -138,28 +128,17 @@ type VM struct { // timestamps are only specific to the second. func New( vm block.ChainVM, - activationTime time.Time, - minimumPChainHeight uint64, - minBlkDelay time.Duration, - numHistoricalBlocks uint64, - stakingLeafSigner crypto.Signer, - stakingCertLeaf *staking.Certificate, + config Config, ) *VM { blockBuilderVM, _ := vm.(block.BuildBlockWithContextChainVM) batchedVM, _ := vm.(block.BatchedChainVM) ssVM, _ := vm.(block.StateSyncableVM) return &VM{ ChainVM: vm, + Config: config, blockBuilderVM: blockBuilderVM, batchedVM: batchedVM, ssVM: ssVM, - - activationTime: activationTime, - minimumPChainHeight: minimumPChainHeight, - minBlkDelay: minBlkDelay, - numHistoricalBlocks: numHistoricalBlocks, - stakingLeafSigner: stakingLeafSigner, - stakingCertLeaf: stakingCertLeaf, } } @@ -354,18 +333,59 @@ func (vm *VM) SetPreference(ctx context.Context, preferred ids.ID) error { return err } - // reset scheduler - minDelay, err := vm.Windower.Delay(ctx, blk.Height()+1, pChainHeight, vm.ctx.NodeID, proposer.MaxBuildWindows) + var ( + childBlockHeight = blk.Height() + 1 + parentTimestamp = blk.Timestamp() + nextStartTime time.Time + ) + if vm.IsDurangoActivated(parentTimestamp) { + currentTime := vm.Clock.Time().Truncate(time.Second) + nextStartTime, err = vm.getPostDurangoSlotTime( + ctx, + childBlockHeight, + pChainHeight, + proposer.TimeToSlot(parentTimestamp, currentTime), + parentTimestamp, + ) + } else { + nextStartTime, err = vm.getPreDurangoSlotTime( + ctx, + childBlockHeight, + pChainHeight, + parentTimestamp, + ) + } if err != nil { vm.ctx.Log.Debug("failed to fetch the expected delay", zap.Error(err), ) + // A nil error is returned here because it is possible that // bootstrapping caused the last accepted block to move past the latest // P-chain height. This will cause building blocks to return an error // until the P-chain's height has advanced. return nil } + vm.Scheduler.SetBuildBlockTime(nextStartTime) + + vm.ctx.Log.Debug("set preference", + zap.Stringer("blkID", blk.ID()), + zap.Time("blockTimestamp", parentTimestamp), + zap.Time("nextStartTime", nextStartTime), + ) + return nil +} + +func (vm *VM) getPreDurangoSlotTime( + ctx context.Context, + blkHeight, + pChainHeight uint64, + parentTimestamp time.Time, +) (time.Time, error) { + delay, err := vm.Windower.Delay(ctx, blkHeight, pChainHeight, vm.ctx.NodeID, proposer.MaxBuildWindows) + if err != nil { + return time.Time{}, err + } // Note: The P-chain does not currently try to target any block time. It // notifies the consensus engine as soon as a new block may be built. To @@ -373,20 +393,39 @@ func (vm *VM) SetPreference(ctx context.Context, preferred ids.ID) error { // validators can specify. This delay may be an issue for high performance, // custom VMs. Until the P-chain is modified to target a specific block // time, ProposerMinBlockDelay can be configured in the subnet config. - if minDelay < vm.minBlkDelay { - minDelay = vm.minBlkDelay - } - - preferredTime := blk.Timestamp() - nextStartTime := preferredTime.Add(minDelay) - vm.Scheduler.SetBuildBlockTime(nextStartTime) + delay = math.Max(delay, vm.MinBlkDelay) + return parentTimestamp.Add(delay), nil +} - vm.ctx.Log.Debug("set preference", - zap.Stringer("blkID", blk.ID()), - zap.Time("blockTimestamp", preferredTime), - zap.Time("nextStartTime", nextStartTime), +func (vm *VM) getPostDurangoSlotTime( + ctx context.Context, + blkHeight, + pChainHeight, + slot uint64, + parentTimestamp time.Time, +) (time.Time, error) { + delay, err := vm.Windower.MinDelayForProposer( + ctx, + blkHeight, + pChainHeight, + vm.ctx.NodeID, + slot, ) - return nil + // Note: The P-chain does not currently try to target any block time. It + // notifies the consensus engine as soon as a new block may be built. To + // avoid fast runs of blocks there is an additional minimum delay that + // validators can specify. This delay may be an issue for high performance, + // custom VMs. Until the P-chain is modified to target a specific block + // time, ProposerMinBlockDelay can be configured in the subnet config. + switch { + case err == nil: + delay = math.Max(delay, vm.MinBlkDelay) + return parentTimestamp.Add(delay), err + case errors.Is(err, proposer.ErrAnyoneCanPropose): + return parentTimestamp.Add(vm.MinBlkDelay), err + default: + return time.Time{}, err + } } func (vm *VM) LastAccepted(ctx context.Context) (ids.ID, error) { @@ -418,7 +457,7 @@ func (vm *VM) repair(ctx context.Context) error { return err } - if vm.numHistoricalBlocks != 0 { + if vm.NumHistoricalBlocks != 0 { vm.ctx.Log.Fatal("block height index must be valid when pruning historical blocks") return errHeightIndexInvalidWhilePruning } @@ -669,7 +708,7 @@ func (vm *VM) setLastAcceptedMetadata(ctx context.Context) error { } func (vm *VM) parsePostForkBlock(ctx context.Context, b []byte) (PostForkBlock, error) { - statelessBlock, err := statelessblock.Parse(b) + statelessBlock, err := statelessblock.Parse(b, vm.DurangoTime) if err != nil { return nil, err } diff --git a/vms/proposervm/vm_byzantine_test.go b/vms/proposervm/vm_byzantine_test.go index dcfebf847cf3..c9ad1b98c79b 100644 --- a/vms/proposervm/vm_byzantine_test.go +++ b/vms/proposervm/vm_byzantine_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -18,7 +18,6 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/vms/proposervm/block" - "github.com/ava-labs/avalanchego/vms/proposervm/proposer" ) // Ensure that a byzantine node issuing an invalid PreForkBlock (Y) when the @@ -33,8 +32,11 @@ import ( func TestInvalidByzantineProposerParent(t *testing.T) { require := require.New(t) - forkTime := time.Unix(0, 0) // enable ProBlks - coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, forkTime, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -44,10 +46,9 @@ func TestInvalidByzantineProposerParent(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: gBlock.ID(), - HeightV: gBlock.Height() + 1, - TimestampV: gBlock.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: gBlock.ID(), + HeightV: gBlock.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return xBlock, nil @@ -67,10 +68,9 @@ func TestInvalidByzantineProposerParent(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: yBlockBytes, - ParentV: xBlock.ID(), - HeightV: xBlock.Height() + 1, - TimestampV: xBlock.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: yBlockBytes, + ParentV: xBlock.ID(), + HeightV: xBlock.Height() + 1, } coreVM.ParseBlockF = func(_ context.Context, blockBytes []byte) (snowman.Block, error) { @@ -103,7 +103,11 @@ func TestInvalidByzantineProposerParent(t *testing.T) { func TestInvalidByzantineProposerOracleParent(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) proVM.Set(coreGenBlk.Timestamp()) defer func() { require.NoError(proVM.Shutdown(context.Background())) @@ -116,9 +120,8 @@ func TestInvalidByzantineProposerOracleParent(t *testing.T) { IDV: xBlockID, StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), }, opts: [2]snowman.Block{ &snowman.TestBlock{ @@ -126,18 +129,16 @@ func TestInvalidByzantineProposerOracleParent(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: xBlockID, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: xBlockID, }, &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: xBlockID, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: xBlockID, }, }, } @@ -211,8 +212,11 @@ func TestInvalidByzantineProposerOracleParent(t *testing.T) { func TestInvalidByzantineProposerPreForkParent(t *testing.T) { require := require.New(t) - forkTime := time.Unix(0, 0) // enable ProBlks - coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, forkTime, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -222,10 +226,9 @@ func TestInvalidByzantineProposerPreForkParent(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: gBlock.ID(), - HeightV: gBlock.Height() + 1, - TimestampV: gBlock.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: gBlock.ID(), + HeightV: gBlock.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return xBlock, nil @@ -237,10 +240,9 @@ func TestInvalidByzantineProposerPreForkParent(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: yBlockBytes, - ParentV: xBlock.ID(), - HeightV: xBlock.Height() + 1, - TimestampV: xBlock.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: yBlockBytes, + ParentV: xBlock.ID(), + HeightV: xBlock.Height() + 1, } coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { @@ -300,7 +302,11 @@ func TestInvalidByzantineProposerPreForkParent(t *testing.T) { func TestBlockVerify_PostForkOption_FaultyParent(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) proVM.Set(coreGenBlk.Timestamp()) defer func() { require.NoError(proVM.Shutdown(context.Background())) @@ -312,9 +318,8 @@ func TestBlockVerify_PostForkOption_FaultyParent(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), }, opts: [2]snowman.Block{ &snowman.TestBlock{ @@ -322,18 +327,16 @@ func TestBlockVerify_PostForkOption_FaultyParent(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreGenBlk.ID(), // valid block should reference xBlock - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: coreGenBlk.ID(), // valid block should reference xBlock }, &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: coreGenBlk.ID(), // valid block should reference xBlock - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: coreGenBlk.ID(), // valid block should reference xBlock }, }, } @@ -399,7 +402,11 @@ func TestBlockVerify_PostForkOption_FaultyParent(t *testing.T) { func TestBlockVerify_InvalidPostForkOption(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) proVM.Set(coreGenBlk.Timestamp()) defer func() { require.NoError(proVM.Shutdown(context.Background())) @@ -413,9 +420,8 @@ func TestBlockVerify_InvalidPostForkOption(t *testing.T) { IDV: xBlockID, StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), }, opts: [2]snowman.Block{ &snowman.TestBlock{ @@ -423,18 +429,16 @@ func TestBlockVerify_InvalidPostForkOption(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: xBlockID, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: xBlockID, }, &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: xBlockID, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: xBlockID, }, }, } @@ -449,10 +453,9 @@ func TestBlockVerify_InvalidPostForkOption(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } ySlb, err := block.BuildUnsigned( @@ -529,9 +532,8 @@ func TestBlockVerify_InvalidPostForkOption(t *testing.T) { IDV: zBlockID, StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), }, opts: [2]snowman.Block{ &snowman.TestBlock{ @@ -539,18 +541,16 @@ func TestBlockVerify_InvalidPostForkOption(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: zBlockID, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: zBlockID, }, &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: zBlockID, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: zBlockID, }, }, } @@ -586,7 +586,11 @@ func TestBlockVerify_InvalidPostForkOption(t *testing.T) { func TestGetBlock_MutatedSignature(t *testing.T) { require := require.New(t) - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -609,10 +613,9 @@ func TestGetBlock_MutatedSignature(t *testing.T) { IDV: ids.Empty.Prefix(1111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreBlk1 := &snowman.TestBlock{ @@ -620,10 +623,9 @@ func TestGetBlock_MutatedSignature(t *testing.T) { IDV: ids.Empty.Prefix(2222), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreBlk0.ID(), - HeightV: coreBlk0.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: coreBlk0.ID(), + HeightV: coreBlk0.Height() + 1, } coreVM.GetBlockF = func(_ context.Context, blkID ids.ID) (snowman.Block, error) { @@ -663,7 +665,7 @@ func TestGetBlock_MutatedSignature(t *testing.T) { require.NoError(proVM.SetPreference(context.Background(), builtBlk0.ID())) - // The second propsal block will need to be signed because the timestamp + // The second proposal block will need to be signed because the timestamp // hasn't moved forward // Craft what would be the next block, but with an invalid signature: diff --git a/vms/proposervm/vm_regression_test.go b/vms/proposervm/vm_regression_test.go index 0a27c43e112a..168dd913d22a 100644 --- a/vms/proposervm/vm_regression_test.go +++ b/vms/proposervm/vm_regression_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -17,6 +17,7 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/snow/snowtest" ) func TestProposerVMInitializeShouldFailIfInnerVMCantVerifyItsHeightIndex(t *testing.T) { @@ -46,19 +47,23 @@ func TestProposerVMInitializeShouldFailIfInnerVMCantVerifyItsHeightIndex(t *test proVM := New( innerVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: time.Unix(0, 0), + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) + defer func() { // avoids leaking goroutines require.NoError(proVM.Shutdown(context.Background())) }() - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) initialState := []byte("genesis state") err := proVM.Initialize( diff --git a/vms/proposervm/vm_test.go b/vms/proposervm/vm_test.go index fb8672d8b2f4..2e4c6c3aea16 100644 --- a/vms/proposervm/vm_test.go +++ b/vms/proposervm/vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package proposervm @@ -25,7 +25,7 @@ import ( "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/utils" @@ -74,6 +74,7 @@ func init() { func initTestProposerVM( t *testing.T, proBlkStartTime time.Time, + durangoTime time.Time, minPChainHeight uint64, ) ( *fullVM, @@ -134,12 +135,15 @@ func initTestProposerVM( proVM := New( coreVM, - proBlkStartTime, - minPChainHeight, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: proBlkStartTime, + DurangoTime: durangoTime, + MinimumPChainHeight: minPChainHeight, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) valState := &validators.TestState{ @@ -178,8 +182,7 @@ func initTestProposerVM( }, nil } - ctx := snow.DefaultContextTest() - ctx.ChainID = ids.ID{1} + ctx := snowtest.Context(t, ids.ID{1}) ctx.NodeID = ids.NodeIDFromCert(pTestCert) ctx.ValidatorState = valState @@ -208,16 +211,49 @@ func initTestProposerVM( require.NoError(proVM.SetState(context.Background(), snow.NormalOp)) require.NoError(proVM.SetPreference(context.Background(), coreGenBlk.IDV)) + proVM.Set(coreGenBlk.Timestamp()) + return coreVM, valState, proVM, coreGenBlk, db } +func waitForProposerWindow(vm *VM, chainTip snowman.Block, pchainHeight uint64) error { + var ( + ctx = context.Background() + childBlockHeight = chainTip.Height() + 1 + parentTimestamp = chainTip.Timestamp() + ) + + for { + slot := proposer.TimeToSlot(parentTimestamp, vm.Clock.Time().Truncate(time.Second)) + delay, err := vm.MinDelayForProposer( + ctx, + childBlockHeight, + pchainHeight, + vm.ctx.NodeID, + slot, + ) + if err != nil { + return err + } + + vm.Clock.Set(parentTimestamp.Add(delay)) + if delay < proposer.MaxLookAheadWindow { + return nil + } + } +} + // VM.BuildBlock tests section func TestBuildBlockTimestampAreRoundedToSeconds(t *testing.T) { require := require.New(t) // given the same core block, BuildBlock returns the same proposer block - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -230,10 +266,9 @@ func TestBuildBlockTimestampAreRoundedToSeconds(t *testing.T) { IDV: ids.Empty.Prefix(111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -250,7 +285,11 @@ func TestBuildBlockIsIdempotent(t *testing.T) { require := require.New(t) // given the same core block, BuildBlock returns the same proposer block - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -260,10 +299,9 @@ func TestBuildBlockIsIdempotent(t *testing.T) { IDV: ids.Empty.Prefix(111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -285,7 +323,11 @@ func TestFirstProposerBlockIsBuiltOnTopOfGenesis(t *testing.T) { require := require.New(t) // setup - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -295,10 +337,9 @@ func TestFirstProposerBlockIsBuiltOnTopOfGenesis(t *testing.T) { IDV: ids.Empty.Prefix(111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -319,7 +360,11 @@ func TestFirstProposerBlockIsBuiltOnTopOfGenesis(t *testing.T) { func TestProposerBlocksAreBuiltOnPreferredProBlock(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -330,10 +375,9 @@ func TestProposerBlocksAreBuiltOnPreferredProBlock(t *testing.T) { IDV: ids.Empty.Prefix(111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk1, nil @@ -346,10 +390,9 @@ func TestProposerBlocksAreBuiltOnPreferredProBlock(t *testing.T) { IDV: ids.Empty.Prefix(222), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk2, nil @@ -394,16 +437,15 @@ func TestProposerBlocksAreBuiltOnPreferredProBlock(t *testing.T) { IDV: ids.Empty.Prefix(333), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: prefcoreBlk.ID(), - HeightV: prefcoreBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: prefcoreBlk.ID(), + HeightV: prefcoreBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk3, nil } - proVM.Set(proVM.Time().Add(proposer.MaxBuildDelay)) + require.NoError(waitForProposerWindow(proVM, proBlk2, proBlk2.(*postForkBlock).PChainHeight())) builtBlk, err := proVM.BuildBlock(context.Background()) require.NoError(err) @@ -414,7 +456,11 @@ func TestProposerBlocksAreBuiltOnPreferredProBlock(t *testing.T) { func TestCoreBlocksMustBeBuiltOnPreferredCoreBlock(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -424,10 +470,9 @@ func TestCoreBlocksMustBeBuiltOnPreferredCoreBlock(t *testing.T) { IDV: ids.Empty.Prefix(111), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk1, nil @@ -440,10 +485,9 @@ func TestCoreBlocksMustBeBuiltOnPreferredCoreBlock(t *testing.T) { IDV: ids.Empty.Prefix(222), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{2}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk2, nil @@ -489,16 +533,15 @@ func TestCoreBlocksMustBeBuiltOnPreferredCoreBlock(t *testing.T) { IDV: ids.Empty.Prefix(333), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: wronglyPreferredcoreBlk.ID(), - HeightV: wronglyPreferredcoreBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{3}, + ParentV: wronglyPreferredcoreBlk.ID(), + HeightV: wronglyPreferredcoreBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk3, nil } - proVM.Set(proVM.Time().Add(proposer.MaxBuildDelay)) + require.NoError(waitForProposerWindow(proVM, proBlk2, proBlk2.(*postForkBlock).PChainHeight())) blk, err := proVM.BuildBlock(context.Background()) require.NoError(err) @@ -510,26 +553,29 @@ func TestCoreBlocksMustBeBuiltOnPreferredCoreBlock(t *testing.T) { func TestCoreBlockFailureCauseProposerBlockParseFailure(t *testing.T) { require := require.New(t) - coreVM, _, proVM, _, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, _, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() innerBlk := &snowman.TestBlock{ - BytesV: []byte{1}, - TimestampV: proVM.Time(), + BytesV: []byte{1}, } coreVM.ParseBlockF = func(context.Context, []byte) (snowman.Block, error) { return nil, errMarshallingFailed } slb, err := statelessblock.Build( proVM.preferred, - innerBlk.Timestamp(), + proVM.Time(), 100, // pChainHeight, - proVM.stakingCertLeaf, + proVM.StakingCertLeaf, innerBlk.Bytes(), proVM.ctx.ChainID, - proVM.stakingLeafSigner, + proVM.StakingLeafSigner, ) require.NoError(err) proBlk := postForkBlock{ @@ -549,31 +595,36 @@ func TestCoreBlockFailureCauseProposerBlockParseFailure(t *testing.T) { func TestTwoProBlocksWrappingSameCoreBlockCanBeParsed(t *testing.T) { require := require.New(t) - coreVM, _, proVM, gencoreBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, gencoreBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() // create two Proposer blocks at the same height innerBlk := &snowman.TestBlock{ - BytesV: []byte{1}, - ParentV: gencoreBlk.ID(), - HeightV: gencoreBlk.Height() + 1, - TimestampV: proVM.Time(), + BytesV: []byte{1}, + ParentV: gencoreBlk.ID(), + HeightV: gencoreBlk.Height() + 1, } coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { require.Equal(innerBlk.Bytes(), b) return innerBlk, nil } + blkTimestamp := proVM.Time() + slb1, err := statelessblock.Build( proVM.preferred, - innerBlk.Timestamp(), + blkTimestamp, 100, // pChainHeight, - proVM.stakingCertLeaf, + proVM.StakingCertLeaf, innerBlk.Bytes(), proVM.ctx.ChainID, - proVM.stakingLeafSigner, + proVM.StakingLeafSigner, ) require.NoError(err) proBlk1 := postForkBlock{ @@ -587,12 +638,12 @@ func TestTwoProBlocksWrappingSameCoreBlockCanBeParsed(t *testing.T) { slb2, err := statelessblock.Build( proVM.preferred, - innerBlk.Timestamp(), + blkTimestamp, 200, // pChainHeight, - proVM.stakingCertLeaf, + proVM.StakingCertLeaf, innerBlk.Bytes(), proVM.ctx.ChainID, - proVM.stakingLeafSigner, + proVM.StakingLeafSigner, ) require.NoError(err) proBlk2 := postForkBlock{ @@ -620,17 +671,20 @@ func TestTwoProBlocksWrappingSameCoreBlockCanBeParsed(t *testing.T) { func TestTwoProBlocksWithSameParentCanBothVerify(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() // one block is built from this proVM localcoreBlk := &snowman.TestBlock{ - BytesV: []byte{111}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: genesisTimestamp, + BytesV: []byte{111}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return localcoreBlk, nil @@ -642,10 +696,9 @@ func TestTwoProBlocksWithSameParentCanBothVerify(t *testing.T) { // another block with same parent comes from network and is parsed netcoreBlk := &snowman.TestBlock{ - BytesV: []byte{222}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: genesisTimestamp, + BytesV: []byte{222}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.ParseBlockF = func(_ context.Context, b []byte) (snowman.Block, error) { switch { @@ -666,7 +719,7 @@ func TestTwoProBlocksWithSameParentCanBothVerify(t *testing.T) { netSlb, err := statelessblock.BuildUnsigned( proVM.preferred, - netcoreBlk.Timestamp(), + proVM.Time(), pChainHeight, netcoreBlk.Bytes(), ) @@ -688,7 +741,11 @@ func TestTwoProBlocksWithSameParentCanBothVerify(t *testing.T) { func TestPreFork_Initialize(t *testing.T) { require := require.New(t) - _, _, proVM, coreGenBlk, _ := initTestProposerVM(t, mockable.MaxTime, 0) // disable ProBlks + var ( + activationTime = mockable.MaxTime + durangoTime = activationTime + ) + _, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -707,7 +764,11 @@ func TestPreFork_Initialize(t *testing.T) { func TestPreFork_BuildBlock(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, mockable.MaxTime, 0) // disable ProBlks + var ( + activationTime = mockable.MaxTime + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -717,10 +778,9 @@ func TestPreFork_BuildBlock(t *testing.T) { IDV: ids.Empty.Prefix(333), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp().Add(proposer.MaxVerifyDelay), + BytesV: []byte{3}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return coreBlk, nil @@ -745,8 +805,11 @@ func TestPreFork_BuildBlock(t *testing.T) { func TestPreFork_ParseBlock(t *testing.T) { require := require.New(t) - // setup - coreVM, _, proVM, _, _ := initTestProposerVM(t, mockable.MaxTime, 0) // disable ProBlks + var ( + activationTime = mockable.MaxTime + durangoTime = activationTime + ) + coreVM, _, proVM, _, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -781,7 +844,11 @@ func TestPreFork_ParseBlock(t *testing.T) { func TestPreFork_SetPreference(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, mockable.MaxTime, 0) // disable ProBlks + var ( + activationTime = mockable.MaxTime + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -880,12 +947,15 @@ func TestExpiredBuildBlock(t *testing.T) { proVM := New( coreVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Time{}, + DurangoTime: mockable.MaxTime, + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) valState := &validators.TestState{ @@ -907,7 +977,7 @@ func TestExpiredBuildBlock(t *testing.T) { }, nil } - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) ctx.NodeID = ids.NodeIDFromCert(pTestCert) ctx.ValidatorState = valState @@ -967,14 +1037,13 @@ func TestExpiredBuildBlock(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } statelessBlock, err := statelessblock.BuildUnsigned( coreGenBlk.ID(), - coreBlk.Timestamp(), + proVM.Time(), 0, coreBlk.Bytes(), ) @@ -1052,7 +1121,11 @@ func (b *wrappedBlock) Verify(ctx context.Context) error { func TestInnerBlockDeduplication(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // disable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -1199,7 +1272,7 @@ func TestInnerVMRollback(t *testing.T) { } } - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) ctx.NodeID = ids.NodeIDFromCert(pTestCert) ctx.ValidatorState = valState @@ -1224,12 +1297,15 @@ func TestInnerVMRollback(t *testing.T) { proVM := New( coreVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Time{}, + DurangoTime: mockable.MaxTime, + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) require.NoError(proVM.Initialize( @@ -1311,12 +1387,15 @@ func TestInnerVMRollback(t *testing.T) { proVM = New( coreVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Time{}, + DurangoTime: mockable.MaxTime, + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) require.NoError(proVM.Initialize( @@ -1348,7 +1427,11 @@ func TestInnerVMRollback(t *testing.T) { func TestBuildBlockDuringWindow(t *testing.T) { require := require.New(t) - coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) // enable ProBlks + var ( + activationTime = time.Unix(0, 0) + durangoTime = mockable.MaxTime + ) + coreVM, valState, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -1367,24 +1450,22 @@ func TestBuildBlockDuringWindow(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: coreGenBlk.ID(), - HeightV: coreGenBlk.Height() + 1, - TimestampV: coreGenBlk.Timestamp(), + BytesV: []byte{1}, + ParentV: coreGenBlk.ID(), + HeightV: coreGenBlk.Height() + 1, } coreBlk1 := &snowman.TestBlock{ TestDecidable: choices.TestDecidable{ IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: coreBlk0.ID(), - HeightV: coreBlk0.Height() + 1, - TimestampV: coreBlk0.Timestamp(), + BytesV: []byte{2}, + ParentV: coreBlk0.ID(), + HeightV: coreBlk0.Height() + 1, } statelessBlock0, err := statelessblock.BuildUnsigned( coreGenBlk.ID(), - coreBlk0.Timestamp(), + proVM.Time(), 0, coreBlk0.Bytes(), ) @@ -1451,8 +1532,11 @@ func TestBuildBlockDuringWindow(t *testing.T) { func TestTwoForks_OneIsAccepted(t *testing.T) { require := require.New(t) - forkTime := time.Unix(0, 0) - coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, forkTime, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = mockable.MaxTime + ) + coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -1463,10 +1547,9 @@ func TestTwoForks_OneIsAccepted(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{1}, - ParentV: gBlock.ID(), - HeightV: gBlock.Height() + 1, - TimestampV: gBlock.Timestamp(), + BytesV: []byte{1}, + ParentV: gBlock.ID(), + HeightV: gBlock.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { @@ -1483,15 +1566,14 @@ func TestTwoForks_OneIsAccepted(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{2}, - ParentV: gBlock.ID(), - HeightV: gBlock.Height() + 1, - TimestampV: gBlock.Timestamp(), + BytesV: []byte{2}, + ParentV: gBlock.ID(), + HeightV: gBlock.Height() + 1, } ySlb, err := statelessblock.BuildUnsigned( gBlock.ID(), - gBlock.Timestamp(), + proVM.Time(), defaultPChainHeight, yBlock.Bytes(), ) @@ -1514,16 +1596,16 @@ func TestTwoForks_OneIsAccepted(t *testing.T) { IDV: ids.GenerateTestID(), StatusV: choices.Processing, }, - BytesV: []byte{3}, - ParentV: yBlock.ID(), - HeightV: yBlock.Height() + 1, - TimestampV: yBlock.Timestamp(), + BytesV: []byte{3}, + ParentV: yBlock.ID(), + HeightV: yBlock.Height() + 1, } coreVM.BuildBlockF = func(context.Context) (snowman.Block, error) { return zBlock, nil } require.NoError(proVM.SetPreference(context.Background(), bBlock.ID())) + proVM.Set(proVM.Time().Add(proposer.MaxBuildDelay)) cBlock, err := proVM.BuildBlock(context.Background()) require.NoError(err) coreVM.BuildBlockF = nil @@ -1547,8 +1629,11 @@ func TestTwoForks_OneIsAccepted(t *testing.T) { func TestTooFarAdvanced(t *testing.T) { require := require.New(t) - forkTime := time.Unix(0, 0) - coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, forkTime, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = mockable.MaxTime + ) + coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -1638,8 +1723,11 @@ func TestTooFarAdvanced(t *testing.T) { func TestTwoOptions_OneIsAccepted(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) - proVM.Set(coreGenBlk.Timestamp()) + var ( + activationTime = time.Unix(0, 0) + durangoTime = mockable.MaxTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -1713,8 +1801,11 @@ func TestTwoOptions_OneIsAccepted(t *testing.T) { func TestLaggedPChainHeight(t *testing.T) { require := require.New(t) - coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, time.Time{}, 0) - proVM.Set(coreGenBlk.Timestamp()) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, coreGenBlk, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -1803,12 +1894,15 @@ func TestRejectedHeightNotIndexed(t *testing.T) { proVM := New( coreVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: time.Unix(0, 0), + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) valState := &validators.TestState{ @@ -1847,7 +1941,7 @@ func TestRejectedHeightNotIndexed(t *testing.T) { }, nil } - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) ctx.NodeID = ids.NodeIDFromCert(pTestCert) ctx.ValidatorState = valState @@ -2010,12 +2104,15 @@ func TestRejectedOptionHeightNotIndexed(t *testing.T) { proVM := New( coreVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: time.Unix(0, 0), + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) valState := &validators.TestState{ @@ -2054,7 +2151,7 @@ func TestRejectedOptionHeightNotIndexed(t *testing.T) { }, nil } - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) ctx.NodeID = ids.NodeIDFromCert(pTestCert) ctx.ValidatorState = valState @@ -2170,15 +2267,18 @@ func TestVMInnerBlkCache(t *testing.T) { ctrl := gomock.NewController(t) // Create a VM - innerVM := mocks.NewMockChainVM(ctrl) + innerVM := block.NewMockChainVM(ctrl) vm := New( innerVM, - time.Time{}, // fork is active - 0, // minimum P-Chain height - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: time.Unix(0, 0), + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) innerVM.EXPECT().Initialize( @@ -2202,7 +2302,7 @@ func TestVMInnerBlkCache(t *testing.T) { innerVM.EXPECT().GetBlock(gomock.Any(), innerBlkID).Return(innerBlk, nil) } - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) ctx.NodeID = ids.NodeIDFromCert(pTestCert) require.NoError(vm.Initialize( @@ -2229,10 +2329,10 @@ func TestVMInnerBlkCache(t *testing.T) { ids.GenerateTestID(), // parent time.Time{}, // timestamp 1, // pChainHeight, - vm.stakingCertLeaf, // cert + vm.StakingCertLeaf, // cert blkNearTipInnerBytes, // inner blk bytes vm.ctx.ChainID, // chain ID - vm.stakingLeafSigner, // key + vm.StakingLeafSigner, // key ) require.NoError(err) @@ -2272,8 +2372,11 @@ func TestVMInnerBlkCache(t *testing.T) { func TestVMInnerBlkCacheDeduplicationRegression(t *testing.T) { require := require.New(t) - forkTime := time.Unix(0, 0) - coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, forkTime, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -2348,8 +2451,11 @@ func TestVMInnerBlkCacheDeduplicationRegression(t *testing.T) { func TestVMInnerBlkMarkedAcceptedRegression(t *testing.T) { require := require.New(t) - forkTime := time.Unix(0, 0) - coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, forkTime, 0) + var ( + activationTime = time.Unix(0, 0) + durangoTime = activationTime + ) + coreVM, _, proVM, gBlock, _ := initTestProposerVM(t, activationTime, durangoTime, 0) defer func() { require.NoError(proVM.Shutdown(context.Background())) }() @@ -2388,7 +2494,7 @@ func TestVMInnerBlkMarkedAcceptedRegression(t *testing.T) { type blockWithVerifyContext struct { *snowman.MockBlock - *mocks.MockWithVerifyContext + *block.MockWithVerifyContext } // Ensures that we call [VerifyWithContext] rather than [Verify] on blocks that @@ -2399,15 +2505,18 @@ func TestVM_VerifyBlockWithContext(t *testing.T) { ctrl := gomock.NewController(t) // Create a VM - innerVM := mocks.NewMockChainVM(ctrl) + innerVM := block.NewMockChainVM(ctrl) vm := New( innerVM, - time.Time{}, // fork is active - 0, // minimum P-Chain height - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: time.Unix(0, 0), + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) // make sure that DBs are compressed correctly @@ -2434,7 +2543,7 @@ func TestVM_VerifyBlockWithContext(t *testing.T) { innerVM.EXPECT().GetBlock(gomock.Any(), innerBlkID).Return(innerBlk, nil) } - snowCtx := snow.DefaultContextTest() + snowCtx := snowtest.Context(t, snowtest.CChainID) snowCtx.NodeID = ids.NodeIDFromCert(pTestCert) require.NoError(vm.Initialize( @@ -2456,7 +2565,7 @@ func TestVM_VerifyBlockWithContext(t *testing.T) { pChainHeight := uint64(0) innerBlk := blockWithVerifyContext{ MockBlock: snowman.NewMockBlock(ctrl), - MockWithVerifyContext: mocks.NewMockWithVerifyContext(ctrl), + MockWithVerifyContext: block.NewMockWithVerifyContext(ctrl), } innerBlk.MockWithVerifyContext.EXPECT().ShouldVerifyWithContext(gomock.Any()).Return(true, nil).Times(2) innerBlk.MockWithVerifyContext.EXPECT().VerifyWithContext(context.Background(), @@ -2504,7 +2613,7 @@ func TestVM_VerifyBlockWithContext(t *testing.T) { // false for ShouldVerifyWithContext innerBlk := blockWithVerifyContext{ MockBlock: snowman.NewMockBlock(ctrl), - MockWithVerifyContext: mocks.NewMockWithVerifyContext(ctrl), + MockWithVerifyContext: block.NewMockWithVerifyContext(ctrl), } innerBlk.MockWithVerifyContext.EXPECT().ShouldVerifyWithContext(gomock.Any()).Return(false, nil) innerBlk.MockBlock.EXPECT().Verify(gomock.Any()).Return(nil) @@ -2527,7 +2636,7 @@ func TestVM_VerifyBlockWithContext(t *testing.T) { // Ensure we call Verify on a block that doesn't have a valid context innerBlk := blockWithVerifyContext{ MockBlock: snowman.NewMockBlock(ctrl), - MockWithVerifyContext: mocks.NewMockWithVerifyContext(ctrl), + MockWithVerifyContext: block.NewMockWithVerifyContext(ctrl), } innerBlk.MockBlock.EXPECT().Verify(gomock.Any()).Return(nil) innerBlk.MockBlock.EXPECT().Parent().Return(ids.GenerateTestID()).AnyTimes() @@ -2593,7 +2702,7 @@ func TestHistoricalBlockDeletion(t *testing.T) { }, } - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) ctx.NodeID = ids.NodeIDFromCert(pTestCert) ctx.ValidatorState = &validators.TestState{ T: t, @@ -2613,12 +2722,15 @@ func TestHistoricalBlockDeletion(t *testing.T) { proVM := New( coreVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - DefaultNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Unix(0, 0), + DurangoTime: mockable.MaxTime, + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: DefaultNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) require.NoError(proVM.Initialize( @@ -2711,12 +2823,15 @@ func TestHistoricalBlockDeletion(t *testing.T) { numHistoricalBlocks := uint64(2) proVM = New( coreVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - numHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Time{}, + DurangoTime: mockable.MaxTime, + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: numHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) require.NoError(proVM.Initialize( @@ -2753,12 +2868,15 @@ func TestHistoricalBlockDeletion(t *testing.T) { newNumHistoricalBlocks := numHistoricalBlocks + 2 proVM = New( coreVM, - time.Time{}, - 0, - DefaultMinBlockDelay, - newNumHistoricalBlocks, - pTestSigner, - pTestCert, + Config{ + ActivationTime: time.Time{}, + DurangoTime: mockable.MaxTime, + MinimumPChainHeight: 0, + MinBlkDelay: DefaultMinBlockDelay, + NumHistoricalBlocks: newNumHistoricalBlocks, + StakingLeafSigner: pTestSigner, + StakingCertLeaf: pTestCert, + }, ) require.NoError(proVM.Initialize( diff --git a/vms/registry/mock_vm_getter.go b/vms/registry/mock_vm_getter.go index 52a1d67fc61a..30c38f1b6a74 100644 --- a/vms/registry/mock_vm_getter.go +++ b/vms/registry/mock_vm_getter.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/registry (interfaces: VMGetter) +// +// Generated by this command: +// +// mockgen -package=registry -destination=vms/registry/mock_vm_getter.go github.com/ava-labs/avalanchego/vms/registry VMGetter +// // Package registry is a generated GoMock package. package registry diff --git a/vms/registry/mock_vm_registerer.go b/vms/registry/mock_vm_registerer.go deleted file mode 100644 index 563893d765ed..000000000000 --- a/vms/registry/mock_vm_registerer.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/vms/registry (interfaces: VMRegisterer) - -// Package registry is a generated GoMock package. -package registry - -import ( - context "context" - reflect "reflect" - - ids "github.com/ava-labs/avalanchego/ids" - vms "github.com/ava-labs/avalanchego/vms" - gomock "go.uber.org/mock/gomock" -) - -// MockVMRegisterer is a mock of VMRegisterer interface. -type MockVMRegisterer struct { - ctrl *gomock.Controller - recorder *MockVMRegistererMockRecorder -} - -// MockVMRegistererMockRecorder is the mock recorder for MockVMRegisterer. -type MockVMRegistererMockRecorder struct { - mock *MockVMRegisterer -} - -// NewMockVMRegisterer creates a new mock instance. -func NewMockVMRegisterer(ctrl *gomock.Controller) *MockVMRegisterer { - mock := &MockVMRegisterer{ctrl: ctrl} - mock.recorder = &MockVMRegistererMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockVMRegisterer) EXPECT() *MockVMRegistererMockRecorder { - return m.recorder -} - -// Register mocks base method. -func (m *MockVMRegisterer) Register(arg0 context.Context, arg1 ids.ID, arg2 vms.Factory) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Register", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// Register indicates an expected call of Register. -func (mr *MockVMRegistererMockRecorder) Register(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockVMRegisterer)(nil).Register), arg0, arg1, arg2) -} - -// RegisterWithReadLock mocks base method. -func (m *MockVMRegisterer) RegisterWithReadLock(arg0 context.Context, arg1 ids.ID, arg2 vms.Factory) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RegisterWithReadLock", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// RegisterWithReadLock indicates an expected call of RegisterWithReadLock. -func (mr *MockVMRegistererMockRecorder) RegisterWithReadLock(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterWithReadLock", reflect.TypeOf((*MockVMRegisterer)(nil).RegisterWithReadLock), arg0, arg1, arg2) -} diff --git a/vms/registry/mock_vm_registry.go b/vms/registry/mock_vm_registry.go index 32360ab1b169..43efd85a6015 100644 --- a/vms/registry/mock_vm_registry.go +++ b/vms/registry/mock_vm_registry.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/vms/registry (interfaces: VMRegistry) +// +// Generated by this command: +// +// mockgen -package=registry -destination=vms/registry/mock_vm_registry.go github.com/ava-labs/avalanchego/vms/registry VMRegistry +// // Package registry is a generated GoMock package. package registry @@ -49,23 +51,7 @@ func (m *MockVMRegistry) Reload(arg0 context.Context) ([]ids.ID, map[ids.ID]erro } // Reload indicates an expected call of Reload. -func (mr *MockVMRegistryMockRecorder) Reload(arg0 interface{}) *gomock.Call { +func (mr *MockVMRegistryMockRecorder) Reload(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reload", reflect.TypeOf((*MockVMRegistry)(nil).Reload), arg0) } - -// ReloadWithReadLock mocks base method. -func (m *MockVMRegistry) ReloadWithReadLock(arg0 context.Context) ([]ids.ID, map[ids.ID]error, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReloadWithReadLock", arg0) - ret0, _ := ret[0].([]ids.ID) - ret1, _ := ret[1].(map[ids.ID]error) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// ReloadWithReadLock indicates an expected call of ReloadWithReadLock. -func (mr *MockVMRegistryMockRecorder) ReloadWithReadLock(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReloadWithReadLock", reflect.TypeOf((*MockVMRegistry)(nil).ReloadWithReadLock), arg0) -} diff --git a/vms/registry/vm_getter.go b/vms/registry/vm_getter.go index 5115af9e635f..826624744e38 100644 --- a/vms/registry/vm_getter.go +++ b/vms/registry/vm_getter.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package registry diff --git a/vms/registry/vm_getter_test.go b/vms/registry/vm_getter_test.go index 9cea55b2c82f..30bab4232be9 100644 --- a/vms/registry/vm_getter_test.go +++ b/vms/registry/vm_getter_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package registry diff --git a/vms/registry/vm_registerer.go b/vms/registry/vm_registerer.go deleted file mode 100644 index 785b2b8cf828..000000000000 --- a/vms/registry/vm_registerer.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package registry - -import ( - "context" - "errors" - "fmt" - "net/http" - "path" - - "go.uber.org/zap" - - "github.com/ava-labs/avalanchego/api/server" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/vms" -) - -var ( - _ VMRegisterer = (*vmRegisterer)(nil) - - errNotVM = errors.New("not a VM") -) - -// VMRegisterer defines functionality to install a virtual machine. -type VMRegisterer interface { - registerer - // RegisterWithReadLock installs the VM assuming that the http read-lock is - // held. - RegisterWithReadLock(context.Context, ids.ID, vms.Factory) error -} - -type registerer interface { - // Register installs the VM. - Register(context.Context, ids.ID, vms.Factory) error -} - -// VMRegistererConfig configures settings for VMRegisterer. -type VMRegistererConfig struct { - APIServer server.Server - Log logging.Logger - VMFactoryLog logging.Logger - VMManager vms.Manager -} - -type vmRegisterer struct { - config VMRegistererConfig -} - -// NewVMRegisterer returns an instance of VMRegisterer -func NewVMRegisterer(config VMRegistererConfig) VMRegisterer { - return &vmRegisterer{ - config: config, - } -} - -func (r *vmRegisterer) Register(ctx context.Context, vmID ids.ID, factory vms.Factory) error { - return r.register(ctx, r.config.APIServer, vmID, factory) -} - -func (r *vmRegisterer) RegisterWithReadLock(ctx context.Context, vmID ids.ID, factory vms.Factory) error { - return r.register(ctx, server.PathWriterFromWithReadLock(r.config.APIServer), vmID, factory) -} - -func (r *vmRegisterer) register(ctx context.Context, pathAdder server.PathAdder, vmID ids.ID, factory vms.Factory) error { - if err := r.config.VMManager.RegisterFactory(ctx, vmID, factory); err != nil { - return err - } - handlers, err := r.createStaticHandlers(ctx, vmID, factory) - if err != nil { - return err - } - - // all static endpoints go to the vm endpoint, defaulting to the vm id - defaultEndpoint := path.Join(constants.VMAliasPrefix, vmID.String()) - - if err := r.createStaticEndpoints(pathAdder, handlers, defaultEndpoint); err != nil { - return err - } - urlAliases, err := r.getURLAliases(vmID, defaultEndpoint) - if err != nil { - return err - } - return pathAdder.AddAliases(defaultEndpoint, urlAliases...) -} - -// Creates a dedicated VM instance for the sole purpose of serving the static -// handlers. -func (r *vmRegisterer) createStaticHandlers( - ctx context.Context, - vmID ids.ID, - factory vms.Factory, -) (map[string]http.Handler, error) { - vm, err := factory.New(r.config.VMFactoryLog) - if err != nil { - return nil, err - } - - commonVM, ok := vm.(common.VM) - if !ok { - return nil, fmt.Errorf("%s is %w", vmID, errNotVM) - } - - handlers, err := commonVM.CreateStaticHandlers(ctx) - if err != nil { - r.config.Log.Error("failed to create static API endpoints", - zap.Stringer("vmID", vmID), - zap.Error(err), - ) - - if err := commonVM.Shutdown(ctx); err != nil { - return nil, fmt.Errorf("shutting down VM errored with: %w", err) - } - return nil, err - } - return handlers, nil -} - -func (r *vmRegisterer) createStaticEndpoints(pathAdder server.PathAdder, handlers map[string]http.Handler, defaultEndpoint string) error { - // register the static endpoints - for extension, service := range handlers { - r.config.Log.Verbo("adding static API endpoint", - zap.String("endpoint", defaultEndpoint), - zap.String("extension", extension), - ) - if err := pathAdder.AddRoute(service, defaultEndpoint, extension); err != nil { - return fmt.Errorf( - "failed to add static API endpoint %s%s: %w", - defaultEndpoint, - extension, - err, - ) - } - } - return nil -} - -func (r vmRegisterer) getURLAliases(vmID ids.ID, defaultEndpoint string) ([]string, error) { - aliases, err := r.config.VMManager.Aliases(vmID) - if err != nil { - return nil, err - } - - var urlAliases []string - for _, alias := range aliases { - urlAlias := path.Join(constants.VMAliasPrefix, alias) - if urlAlias != defaultEndpoint { - urlAliases = append(urlAliases, urlAlias) - } - } - return urlAliases, err -} - -type readRegisterer struct { - registerer VMRegisterer -} - -func (r readRegisterer) Register(ctx context.Context, vmID ids.ID, factory vms.Factory) error { - return r.registerer.RegisterWithReadLock(ctx, vmID, factory) -} diff --git a/vms/registry/vm_registerer_test.go b/vms/registry/vm_registerer_test.go deleted file mode 100644 index baaa569a89ba..000000000000 --- a/vms/registry/vm_registerer_test.go +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package registry - -import ( - "context" - "net/http" - "path" - "testing" - - "github.com/stretchr/testify/require" - - "go.uber.org/mock/gomock" - - "github.com/ava-labs/avalanchego/api/server" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/vms" -) - -var id = ids.GenerateTestID() - -// Register should succeed even if we can't register a VM -func TestRegisterRegisterVMFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - - // We fail to register the VM - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(errTest) - - err := resources.registerer.Register(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests Register if a VM doesn't actually implement VM. -func TestRegisterBadVM(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := "this is not a vm..." - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - // Since this factory produces a bad vm, we should get an error. - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - - err := resources.registerer.Register(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errNotVM) -} - -// Tests Register if creating endpoints for a VM fails + shutdown fails -func TestRegisterCreateHandlersAndShutdownFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - // We fail to create handlers + fail to shutdown - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(nil, errTest).Times(1) - vm.EXPECT().Shutdown(gomock.Any()).Return(errTest).Times(1) - - err := resources.registerer.Register(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests Register if creating endpoints for a VM fails + shutdown succeeds -func TestRegisterCreateHandlersFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - // We fail to create handlers + but succeed our shutdown - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(nil, errTest).Times(1) - vm.EXPECT().Shutdown(gomock.Any()).Return(nil).Times(1) - - err := resources.registerer.Register(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests Register if we fail to register the new endpoint on the server. -func TestRegisterAddRouteFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - handlers := map[string]http.Handler{ - "foo": nil, - } - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(handlers, nil).Times(1) - // We fail to create an endpoint for the handler - resources.mockServer.EXPECT(). - AddRoute( - handlers["foo"], - path.Join(constants.VMAliasPrefix, id.String()), - "foo", - ). - Times(1). - Return(errTest) - - err := resources.registerer.Register(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests Register we can't find the alias for the newly registered vm -func TestRegisterAliasLookupFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - handlers := map[string]http.Handler{ - "foo": nil, - } - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(handlers, nil).Times(1) - // Registering the route fails - resources.mockServer.EXPECT(). - AddRoute( - handlers["foo"], - path.Join(constants.VMAliasPrefix, id.String()), - "foo", - ). - Times(1). - Return(nil) - resources.mockManager.EXPECT().Aliases(id).Times(1).Return(nil, errTest) - - err := resources.registerer.Register(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests Register if adding aliases for the newly registered vm fails -func TestRegisterAddAliasesFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - handlers := map[string]http.Handler{ - "foo": nil, - } - aliases := []string{"alias-1", "alias-2"} - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(handlers, nil).Times(1) - resources.mockServer.EXPECT(). - AddRoute( - handlers["foo"], - path.Join(constants.VMAliasPrefix, id.String()), - "foo", - ). - Times(1). - Return(nil) - resources.mockManager.EXPECT().Aliases(id).Times(1).Return(aliases, nil) - // Adding aliases fails - resources.mockServer.EXPECT(). - AddAliases( - path.Join(constants.VMAliasPrefix, id.String()), - path.Join(constants.VMAliasPrefix, aliases[0]), - path.Join(constants.VMAliasPrefix, aliases[1]), - ). - Return(errTest) - - err := resources.registerer.Register(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests Register if no errors are thrown -func TestRegisterHappyCase(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - handlers := map[string]http.Handler{ - "foo": nil, - } - aliases := []string{"alias-1", "alias-2"} - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(handlers, nil).Times(1) - resources.mockServer.EXPECT(). - AddRoute( - handlers["foo"], - path.Join(constants.VMAliasPrefix, id.String()), - "foo", - ). - Times(1). - Return(nil) - resources.mockManager.EXPECT().Aliases(id).Times(1).Return(aliases, nil) - resources.mockServer.EXPECT(). - AddAliases( - path.Join(constants.VMAliasPrefix, id.String()), - path.Join(constants.VMAliasPrefix, aliases[0]), - path.Join(constants.VMAliasPrefix, aliases[1]), - ). - Times(1). - Return(nil) - - require.NoError(t, resources.registerer.Register(context.Background(), id, vmFactory)) -} - -// RegisterWithReadLock should succeed even if we can't register a VM -func TestRegisterWithReadLockRegisterVMFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - - // We fail to register the VM - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(errTest) - - err := resources.registerer.RegisterWithReadLock(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests RegisterWithReadLock if a VM doesn't actually implement VM. -func TestRegisterWithReadLockBadVM(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := "this is not a vm..." - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - // Since this factory produces a bad vm, we should get an error. - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - - err := resources.registerer.RegisterWithReadLock(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errNotVM) -} - -// Tests RegisterWithReadLock if creating endpoints for a VM fails + shutdown fails -func TestRegisterWithReadLockCreateHandlersAndShutdownFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - // We fail to create handlers + fail to shutdown - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(nil, errTest).Times(1) - vm.EXPECT().Shutdown(gomock.Any()).Return(errTest).Times(1) - - err := resources.registerer.RegisterWithReadLock(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests RegisterWithReadLock if creating endpoints for a VM fails + shutdown succeeds -func TestRegisterWithReadLockCreateHandlersFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - // We fail to create handlers + but succeed our shutdown - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(nil, errTest).Times(1) - vm.EXPECT().Shutdown(gomock.Any()).Return(nil).Times(1) - - err := resources.registerer.RegisterWithReadLock(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests RegisterWithReadLock if we fail to register the new endpoint on the server. -func TestRegisterWithReadLockAddRouteWithReadLockFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - handlers := map[string]http.Handler{ - "foo": nil, - } - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(handlers, nil).Times(1) - // We fail to create an endpoint for the handler - resources.mockServer.EXPECT(). - AddRouteWithReadLock( - handlers["foo"], - path.Join(constants.VMAliasPrefix, id.String()), - "foo", - ). - Times(1). - Return(errTest) - - err := resources.registerer.RegisterWithReadLock(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests RegisterWithReadLock we can't find the alias for the newly registered vm -func TestRegisterWithReadLockAliasLookupFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - handlers := map[string]http.Handler{ - "foo": nil, - } - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(handlers, nil).Times(1) - // RegisterWithReadLocking the route fails - resources.mockServer.EXPECT(). - AddRouteWithReadLock( - handlers["foo"], - path.Join(constants.VMAliasPrefix, id.String()), - "foo", - ). - Times(1). - Return(nil) - resources.mockManager.EXPECT().Aliases(id).Times(1).Return(nil, errTest) - - err := resources.registerer.RegisterWithReadLock(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests RegisterWithReadLock if adding aliases for the newly registered vm fails -func TestRegisterWithReadLockAddAliasesFails(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - handlers := map[string]http.Handler{ - "foo": nil, - } - aliases := []string{"alias-1", "alias-2"} - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(handlers, nil).Times(1) - resources.mockServer.EXPECT(). - AddRouteWithReadLock( - handlers["foo"], - path.Join(constants.VMAliasPrefix, id.String()), - "foo", - ). - Times(1). - Return(nil) - resources.mockManager.EXPECT().Aliases(id).Times(1).Return(aliases, nil) - // Adding aliases fails - resources.mockServer.EXPECT(). - AddAliasesWithReadLock( - path.Join(constants.VMAliasPrefix, id.String()), - path.Join(constants.VMAliasPrefix, aliases[0]), - path.Join(constants.VMAliasPrefix, aliases[1]), - ). - Return(errTest) - - err := resources.registerer.RegisterWithReadLock(context.Background(), id, vmFactory) - require.ErrorIs(t, err, errTest) -} - -// Tests RegisterWithReadLock if no errors are thrown -func TestRegisterWithReadLockHappyCase(t *testing.T) { - resources := initRegistererTest(t) - - vmFactory := vms.NewMockFactory(resources.ctrl) - vm := mocks.NewMockChainVM(resources.ctrl) - - handlers := map[string]http.Handler{ - "foo": nil, - } - aliases := []string{"alias-1", "alias-2"} - - resources.mockManager.EXPECT().RegisterFactory(gomock.Any(), id, vmFactory).Times(1).Return(nil) - vmFactory.EXPECT().New(logging.NoLog{}).Times(1).Return(vm, nil) - vm.EXPECT().CreateStaticHandlers(gomock.Any()).Return(handlers, nil).Times(1) - resources.mockServer.EXPECT(). - AddRouteWithReadLock( - handlers["foo"], - path.Join(constants.VMAliasPrefix, id.String()), - "foo", - ). - Times(1). - Return(nil) - resources.mockManager.EXPECT().Aliases(id).Times(1).Return(aliases, nil) - resources.mockServer.EXPECT(). - AddAliasesWithReadLock( - path.Join(constants.VMAliasPrefix, id.String()), - path.Join(constants.VMAliasPrefix, aliases[0]), - path.Join(constants.VMAliasPrefix, aliases[1]), - ). - Times(1). - Return(nil) - - require.NoError(t, resources.registerer.RegisterWithReadLock(context.Background(), id, vmFactory)) -} - -type vmRegistererTestResources struct { - ctrl *gomock.Controller - mockManager *vms.MockManager - mockServer *server.MockServer - mockLogger *logging.MockLogger - registerer VMRegisterer -} - -func initRegistererTest(t *testing.T) *vmRegistererTestResources { - ctrl := gomock.NewController(t) - - mockManager := vms.NewMockManager(ctrl) - mockServer := server.NewMockServer(ctrl) - mockLog := logging.NewMockLogger(ctrl) - - registerer := NewVMRegisterer(VMRegistererConfig{ - APIServer: mockServer, - Log: mockLog, - VMFactoryLog: logging.NoLog{}, - VMManager: mockManager, - }) - - mockLog.EXPECT().Error(gomock.Any(), gomock.Any()).AnyTimes() - mockLog.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes() - mockLog.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes() - mockLog.EXPECT().Debug(gomock.Any(), gomock.Any()).AnyTimes() - mockLog.EXPECT().Trace(gomock.Any(), gomock.Any()).AnyTimes() - mockLog.EXPECT().Verbo(gomock.Any(), gomock.Any()).AnyTimes() - - return &vmRegistererTestResources{ - ctrl: ctrl, - mockManager: mockManager, - mockServer: mockServer, - mockLogger: mockLog, - registerer: registerer, - } -} diff --git a/vms/registry/vm_registry.go b/vms/registry/vm_registry.go index dd6f96d4e719..1374c4d46b8e 100644 --- a/vms/registry/vm_registry.go +++ b/vms/registry/vm_registry.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package registry @@ -7,6 +7,7 @@ import ( "context" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms" ) var _ VMRegistry = (*vmRegistry)(nil) @@ -16,15 +17,12 @@ var _ VMRegistry = (*vmRegistry)(nil) type VMRegistry interface { // Reload installs all non-installed vms on the node. Reload(ctx context.Context) ([]ids.ID, map[ids.ID]error, error) - // ReloadWithReadLock installs all non-installed vms on the node assuming - // the http read lock is currently held. - ReloadWithReadLock(ctx context.Context) ([]ids.ID, map[ids.ID]error, error) } // VMRegistryConfig defines configurations for VMRegistry type VMRegistryConfig struct { - VMGetter VMGetter - VMRegisterer VMRegisterer + VMGetter VMGetter + VMManager vms.Manager } type vmRegistry struct { @@ -39,16 +37,6 @@ func NewVMRegistry(config VMRegistryConfig) VMRegistry { } func (r *vmRegistry) Reload(ctx context.Context) ([]ids.ID, map[ids.ID]error, error) { - return r.reload(ctx, r.config.VMRegisterer) -} - -func (r *vmRegistry) ReloadWithReadLock(ctx context.Context) ([]ids.ID, map[ids.ID]error, error) { - return r.reload(ctx, readRegisterer{ - registerer: r.config.VMRegisterer, - }) -} - -func (r *vmRegistry) reload(ctx context.Context, registerer registerer) ([]ids.ID, map[ids.ID]error, error) { _, unregisteredVMs, err := r.config.VMGetter.Get() if err != nil { return nil, nil, err @@ -58,7 +46,7 @@ func (r *vmRegistry) reload(ctx context.Context, registerer registerer) ([]ids.I failedVMs := make(map[ids.ID]error) for vmID, factory := range unregisteredVMs { - if err := registerer.Register(ctx, vmID, factory); err != nil { + if err := r.config.VMManager.RegisterFactory(ctx, vmID, factory); err != nil { failedVMs[vmID] = err continue } diff --git a/vms/registry/vm_registry_test.go b/vms/registry/vm_registry_test.go index ecda1c4f5546..12e39a7c29c9 100644 --- a/vms/registry/vm_registry_test.go +++ b/vms/registry/vm_registry_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package registry @@ -47,12 +47,12 @@ func TestReload_Success(t *testing.T) { Get(). Times(1). Return(registeredVms, unregisteredVms, nil) - resources.mockVMRegisterer.EXPECT(). - Register(gomock.Any(), id3, factory3). + resources.mockVMManager.EXPECT(). + RegisterFactory(gomock.Any(), id3, factory3). Times(1). Return(nil) - resources.mockVMRegisterer.EXPECT(). - Register(gomock.Any(), id4, factory4). + resources.mockVMManager.EXPECT(). + RegisterFactory(gomock.Any(), id4, factory4). Times(1). Return(nil) @@ -101,12 +101,12 @@ func TestReload_PartialRegisterFailure(t *testing.T) { Get(). Times(1). Return(registeredVms, unregisteredVms, nil) - resources.mockVMRegisterer.EXPECT(). - Register(gomock.Any(), id3, factory3). + resources.mockVMManager.EXPECT(). + RegisterFactory(gomock.Any(), id3, factory3). Times(1). Return(errTest) - resources.mockVMRegisterer.EXPECT(). - Register(gomock.Any(), id4, factory4). + resources.mockVMManager.EXPECT(). + RegisterFactory(gomock.Any(), id4, factory4). Times(1). Return(nil) @@ -118,126 +118,30 @@ func TestReload_PartialRegisterFailure(t *testing.T) { require.Equal(id4, installedVMs[0]) } -// Tests the happy case where Reload succeeds. -func TestReloadWithReadLock_Success(t *testing.T) { - require := require.New(t) - - resources := initVMRegistryTest(t) - - factory1 := vms.NewMockFactory(resources.ctrl) - factory2 := vms.NewMockFactory(resources.ctrl) - factory3 := vms.NewMockFactory(resources.ctrl) - factory4 := vms.NewMockFactory(resources.ctrl) - - registeredVms := map[ids.ID]vms.Factory{ - id1: factory1, - id2: factory2, - } - - unregisteredVms := map[ids.ID]vms.Factory{ - id3: factory3, - id4: factory4, - } - - resources.mockVMGetter.EXPECT(). - Get(). - Times(1). - Return(registeredVms, unregisteredVms, nil) - resources.mockVMRegisterer.EXPECT(). - RegisterWithReadLock(gomock.Any(), id3, factory3). - Times(1). - Return(nil) - resources.mockVMRegisterer.EXPECT(). - RegisterWithReadLock(gomock.Any(), id4, factory4). - Times(1). - Return(nil) - - installedVMs, failedVMs, err := resources.vmRegistry.ReloadWithReadLock(context.Background()) - require.NoError(err) - require.ElementsMatch([]ids.ID{id3, id4}, installedVMs) - require.Empty(failedVMs) -} - -// Tests that we fail if we're not able to get the vms on disk -func TestReloadWithReadLock_GetNewVMsFails(t *testing.T) { - require := require.New(t) - - resources := initVMRegistryTest(t) - - resources.mockVMGetter.EXPECT().Get().Times(1).Return(nil, nil, errTest) - - installedVMs, failedVMs, err := resources.vmRegistry.ReloadWithReadLock(context.Background()) - require.ErrorIs(err, errTest) - require.Empty(installedVMs) - require.Empty(failedVMs) -} - -// Tests that if we fail to register a VM, we fail. -func TestReloadWithReadLock_PartialRegisterFailure(t *testing.T) { - require := require.New(t) - - resources := initVMRegistryTest(t) - - factory1 := vms.NewMockFactory(resources.ctrl) - factory2 := vms.NewMockFactory(resources.ctrl) - factory3 := vms.NewMockFactory(resources.ctrl) - factory4 := vms.NewMockFactory(resources.ctrl) - - registeredVms := map[ids.ID]vms.Factory{ - id1: factory1, - id2: factory2, - } - - unregisteredVms := map[ids.ID]vms.Factory{ - id3: factory3, - id4: factory4, - } - - resources.mockVMGetter.EXPECT(). - Get(). - Times(1). - Return(registeredVms, unregisteredVms, nil) - resources.mockVMRegisterer.EXPECT(). - RegisterWithReadLock(gomock.Any(), id3, factory3). - Times(1). - Return(errTest) - resources.mockVMRegisterer.EXPECT(). - RegisterWithReadLock(gomock.Any(), id4, factory4). - Times(1). - Return(nil) - - installedVMs, failedVMs, err := resources.vmRegistry.ReloadWithReadLock(context.Background()) - require.NoError(err) - require.Len(failedVMs, 1) - require.ErrorIs(failedVMs[id3], errTest) - require.Len(installedVMs, 1) - require.Equal(id4, installedVMs[0]) -} - type registryTestResources struct { - ctrl *gomock.Controller - mockVMGetter *MockVMGetter - mockVMRegisterer *MockVMRegisterer - vmRegistry VMRegistry + ctrl *gomock.Controller + mockVMGetter *MockVMGetter + mockVMManager *vms.MockManager + vmRegistry VMRegistry } func initVMRegistryTest(t *testing.T) *registryTestResources { ctrl := gomock.NewController(t) mockVMGetter := NewMockVMGetter(ctrl) - mockVMRegisterer := NewMockVMRegisterer(ctrl) + mockVMManager := vms.NewMockManager(ctrl) vmRegistry := NewVMRegistry( VMRegistryConfig{ - VMGetter: mockVMGetter, - VMRegisterer: mockVMRegisterer, + VMGetter: mockVMGetter, + VMManager: mockVMManager, }, ) return ®istryTestResources{ - ctrl: ctrl, - mockVMGetter: mockVMGetter, - mockVMRegisterer: mockVMRegisterer, - vmRegistry: vmRegistry, + ctrl: ctrl, + mockVMGetter: mockVMGetter, + mockVMManager: mockVMManager, + vmRegistry: vmRegistry, } } diff --git a/vms/rpcchainvm/batched_vm_test.go b/vms/rpcchainvm/batched_vm_test.go index 817037dc6e3e..f74785ebc5bc 100644 --- a/vms/rpcchainvm/batched_vm_test.go +++ b/vms/rpcchainvm/batched_vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcchainvm @@ -14,11 +14,10 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/vms/components/chain" ) @@ -42,7 +41,7 @@ func batchedParseBlockCachingTestPlugin(t *testing.T, loadExpectations bool) blo // create mock ctrl := gomock.NewController(t) - vm := mocks.NewMockChainVM(ctrl) + vm := block.NewMockChainVM(ctrl) if loadExpectations { blk1 := snowman.NewMockBlock(ctrl) @@ -86,7 +85,7 @@ func TestBatchedParseBlockCaching(t *testing.T) { vm, stopper := buildClientHelper(require, testKey) defer stopper.Stop(context.Background()) - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) require.NoError(vm.Initialize(context.Background(), ctx, memdb.New(), nil, nil, nil, nil, nil, nil)) diff --git a/vms/rpcchainvm/errors.go b/vms/rpcchainvm/errors.go index 3795024378c4..4b434b51d425 100644 --- a/vms/rpcchainvm/errors.go +++ b/vms/rpcchainvm/errors.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcchainvm diff --git a/vms/rpcchainvm/factory.go b/vms/rpcchainvm/factory.go index f7ef19749ad1..d61c41d11af8 100644 --- a/vms/rpcchainvm/factory.go +++ b/vms/rpcchainvm/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcchainvm diff --git a/vms/rpcchainvm/ghttp/gconn/conn_client.go b/vms/rpcchainvm/ghttp/gconn/conn_client.go index b4bc5a5a4de0..cfa3094bfefe 100644 --- a/vms/rpcchainvm/ghttp/gconn/conn_client.go +++ b/vms/rpcchainvm/ghttp/gconn/conn_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gconn diff --git a/vms/rpcchainvm/ghttp/gconn/conn_server.go b/vms/rpcchainvm/ghttp/gconn/conn_server.go index 07ca0f5a2b3a..57f1cfdb064b 100644 --- a/vms/rpcchainvm/ghttp/gconn/conn_server.go +++ b/vms/rpcchainvm/ghttp/gconn/conn_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gconn diff --git a/vms/rpcchainvm/ghttp/greader/reader_client.go b/vms/rpcchainvm/ghttp/greader/reader_client.go index c06bdce9ba01..be0f2a1a7ee6 100644 --- a/vms/rpcchainvm/ghttp/greader/reader_client.go +++ b/vms/rpcchainvm/ghttp/greader/reader_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package greader diff --git a/vms/rpcchainvm/ghttp/greader/reader_server.go b/vms/rpcchainvm/ghttp/greader/reader_server.go index a5f8f5d76f30..4d85f674ffc6 100644 --- a/vms/rpcchainvm/ghttp/greader/reader_server.go +++ b/vms/rpcchainvm/ghttp/greader/reader_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package greader diff --git a/vms/rpcchainvm/ghttp/gresponsewriter/locked_writer.go b/vms/rpcchainvm/ghttp/gresponsewriter/locked_writer.go index c89eb5099cc6..40528dc79d2c 100644 --- a/vms/rpcchainvm/ghttp/gresponsewriter/locked_writer.go +++ b/vms/rpcchainvm/ghttp/gresponsewriter/locked_writer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gresponsewriter diff --git a/vms/rpcchainvm/ghttp/gresponsewriter/writer_client.go b/vms/rpcchainvm/ghttp/gresponsewriter/writer_client.go index 769d8edce555..1c45567097cf 100644 --- a/vms/rpcchainvm/ghttp/gresponsewriter/writer_client.go +++ b/vms/rpcchainvm/ghttp/gresponsewriter/writer_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gresponsewriter diff --git a/vms/rpcchainvm/ghttp/gresponsewriter/writer_server.go b/vms/rpcchainvm/ghttp/gresponsewriter/writer_server.go index a78e6b002913..b73d24f21024 100644 --- a/vms/rpcchainvm/ghttp/gresponsewriter/writer_server.go +++ b/vms/rpcchainvm/ghttp/gresponsewriter/writer_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gresponsewriter diff --git a/vms/rpcchainvm/ghttp/gwriter/writer_client.go b/vms/rpcchainvm/ghttp/gwriter/writer_client.go index d9a561f2dd4e..f68cefa7c2a6 100644 --- a/vms/rpcchainvm/ghttp/gwriter/writer_client.go +++ b/vms/rpcchainvm/ghttp/gwriter/writer_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gwriter diff --git a/vms/rpcchainvm/ghttp/gwriter/writer_server.go b/vms/rpcchainvm/ghttp/gwriter/writer_server.go index ce85aaced16e..1b216dc2a4ee 100644 --- a/vms/rpcchainvm/ghttp/gwriter/writer_server.go +++ b/vms/rpcchainvm/ghttp/gwriter/writer_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gwriter diff --git a/vms/rpcchainvm/ghttp/http_client.go b/vms/rpcchainvm/ghttp/http_client.go index 62a6b705a338..cd06c46ca156 100644 --- a/vms/rpcchainvm/ghttp/http_client.go +++ b/vms/rpcchainvm/ghttp/http_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ghttp diff --git a/vms/rpcchainvm/ghttp/http_server.go b/vms/rpcchainvm/ghttp/http_server.go index adece6f93679..c602965323fd 100644 --- a/vms/rpcchainvm/ghttp/http_server.go +++ b/vms/rpcchainvm/ghttp/http_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ghttp diff --git a/vms/rpcchainvm/ghttp/http_test.go b/vms/rpcchainvm/ghttp/http_test.go index 2bcf5f3150d8..22d5095d6c6d 100644 --- a/vms/rpcchainvm/ghttp/http_test.go +++ b/vms/rpcchainvm/ghttp/http_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package ghttp diff --git a/vms/rpcchainvm/grpcutils/client.go b/vms/rpcchainvm/grpcutils/client.go index 0a9dfcffef6c..eb9501019768 100644 --- a/vms/rpcchainvm/grpcutils/client.go +++ b/vms/rpcchainvm/grpcutils/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package grpcutils diff --git a/vms/rpcchainvm/grpcutils/client_test.go b/vms/rpcchainvm/grpcutils/client_test.go index 9ef2fa1d6731..e02552995295 100644 --- a/vms/rpcchainvm/grpcutils/client_test.go +++ b/vms/rpcchainvm/grpcutils/client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package grpcutils diff --git a/vms/rpcchainvm/grpcutils/server.go b/vms/rpcchainvm/grpcutils/server.go index a6746207e427..dbcc439c9ef1 100644 --- a/vms/rpcchainvm/grpcutils/server.go +++ b/vms/rpcchainvm/grpcutils/server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package grpcutils diff --git a/vms/rpcchainvm/grpcutils/server_closer.go b/vms/rpcchainvm/grpcutils/server_closer.go index 35ca2b735a86..67a4141ddfc3 100644 --- a/vms/rpcchainvm/grpcutils/server_closer.go +++ b/vms/rpcchainvm/grpcutils/server_closer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package grpcutils diff --git a/vms/rpcchainvm/grpcutils/util.go b/vms/rpcchainvm/grpcutils/util.go index 8ad042ea55a9..880faf4d2a63 100644 --- a/vms/rpcchainvm/grpcutils/util.go +++ b/vms/rpcchainvm/grpcutils/util.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package grpcutils diff --git a/vms/rpcchainvm/gruntime/runtime_client.go b/vms/rpcchainvm/gruntime/runtime_client.go index 67a1e9864908..8db4adbeb204 100644 --- a/vms/rpcchainvm/gruntime/runtime_client.go +++ b/vms/rpcchainvm/gruntime/runtime_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gruntime diff --git a/vms/rpcchainvm/gruntime/runtime_server.go b/vms/rpcchainvm/gruntime/runtime_server.go index 882c62f5ca02..09be6c121eef 100644 --- a/vms/rpcchainvm/gruntime/runtime_server.go +++ b/vms/rpcchainvm/gruntime/runtime_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gruntime diff --git a/vms/rpcchainvm/messenger/messenger_client.go b/vms/rpcchainvm/messenger/messenger_client.go index e7910eb05d2e..d392b9af79c6 100644 --- a/vms/rpcchainvm/messenger/messenger_client.go +++ b/vms/rpcchainvm/messenger/messenger_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package messenger diff --git a/vms/rpcchainvm/messenger/messenger_server.go b/vms/rpcchainvm/messenger/messenger_server.go index 273ffdfd25b0..fc28a0757bb2 100644 --- a/vms/rpcchainvm/messenger/messenger_server.go +++ b/vms/rpcchainvm/messenger/messenger_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package messenger diff --git a/vms/rpcchainvm/runtime/README.md b/vms/rpcchainvm/runtime/README.md index 6e09e41f8ace..1a2fe264bc1c 100644 --- a/vms/rpcchainvm/runtime/README.md +++ b/vms/rpcchainvm/runtime/README.md @@ -17,7 +17,7 @@ It works by starting the VM's as a subprocess of AvalancheGo by `os.Exec`. ## Workflow - `VMRegistry` calls the RPC Chain VM `Factory`. -- Factory Starts an instanace of a `VMRE` server that consumes a `runtime.Initializer` interface implementation. +- Factory Starts an instance of a `VMRE` server that consumes a `runtime.Initializer` interface implementation. - The address of this server is passed as a ENV variable `AVALANCHE_VM_RUNTIME_ENGINE_ADDR` via `os.Exec` which starts the VM binary. - The VM uses the address of the `VMRE` server to create a client. - Client sends a `Initialize` RPC informing the server of the `Protocol Version` and future `Address` of the RPC Chain VM server allowing it to perform a validation `Handshake`. diff --git a/vms/rpcchainvm/runtime/manager.go b/vms/rpcchainvm/runtime/manager.go index 3e1a9eaac903..425faa731850 100644 --- a/vms/rpcchainvm/runtime/manager.go +++ b/vms/rpcchainvm/runtime/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package runtime diff --git a/vms/rpcchainvm/runtime/runtime.go b/vms/rpcchainvm/runtime/runtime.go index d5be95d96471..1a1a198acbda 100644 --- a/vms/rpcchainvm/runtime/runtime.go +++ b/vms/rpcchainvm/runtime/runtime.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package runtime diff --git a/vms/rpcchainvm/runtime/subprocess/initializer.go b/vms/rpcchainvm/runtime/subprocess/initializer.go index 5ade5f619bd1..bc8d4e41c63a 100644 --- a/vms/rpcchainvm/runtime/subprocess/initializer.go +++ b/vms/rpcchainvm/runtime/subprocess/initializer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package subprocess diff --git a/vms/rpcchainvm/runtime/subprocess/linux_stopper.go b/vms/rpcchainvm/runtime/subprocess/linux_stopper.go index 80a47fa7da3c..5205ea40596f 100644 --- a/vms/rpcchainvm/runtime/subprocess/linux_stopper.go +++ b/vms/rpcchainvm/runtime/subprocess/linux_stopper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build linux diff --git a/vms/rpcchainvm/runtime/subprocess/non_linux_stopper.go b/vms/rpcchainvm/runtime/subprocess/non_linux_stopper.go index 8c3ce6a138e9..c1a590e31fe7 100644 --- a/vms/rpcchainvm/runtime/subprocess/non_linux_stopper.go +++ b/vms/rpcchainvm/runtime/subprocess/non_linux_stopper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. //go:build !linux diff --git a/vms/rpcchainvm/runtime/subprocess/runtime.go b/vms/rpcchainvm/runtime/subprocess/runtime.go index 7711d377127c..2cd92a00b04e 100644 --- a/vms/rpcchainvm/runtime/subprocess/runtime.go +++ b/vms/rpcchainvm/runtime/subprocess/runtime.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package subprocess diff --git a/vms/rpcchainvm/runtime/subprocess/stopper.go b/vms/rpcchainvm/runtime/subprocess/stopper.go index b4d026590421..4dfd33c24caa 100644 --- a/vms/rpcchainvm/runtime/subprocess/stopper.go +++ b/vms/rpcchainvm/runtime/subprocess/stopper.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package subprocess diff --git a/vms/rpcchainvm/state_syncable_vm_test.go b/vms/rpcchainvm/state_syncable_vm_test.go index 241062616c9b..45512bbec04d 100644 --- a/vms/rpcchainvm/state_syncable_vm_test.go +++ b/vms/rpcchainvm/state_syncable_vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcchainvm @@ -20,7 +20,7 @@ import ( "github.com/ava-labs/avalanchego/snow/choices" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" + "github.com/ava-labs/avalanchego/snow/snowtest" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/rpcchainvm/grpcutils" "github.com/ava-labs/avalanchego/vms/rpcchainvm/runtime" @@ -66,8 +66,8 @@ var ( ) type StateSyncEnabledMock struct { - *mocks.MockChainVM - *mocks.MockStateSyncableVM + *block.MockChainVM + *block.MockStateSyncableVM } func stateSyncEnabledTestPlugin(t *testing.T, loadExpectations bool) block.ChainVM { @@ -76,8 +76,8 @@ func stateSyncEnabledTestPlugin(t *testing.T, loadExpectations bool) block.Chain // create mock ctrl := gomock.NewController(t) ssVM := StateSyncEnabledMock{ - MockChainVM: mocks.NewMockChainVM(ctrl), - MockStateSyncableVM: mocks.NewMockStateSyncableVM(ctrl), + MockChainVM: block.NewMockChainVM(ctrl), + MockStateSyncableVM: block.NewMockStateSyncableVM(ctrl), } if loadExpectations { @@ -98,8 +98,8 @@ func getOngoingSyncStateSummaryTestPlugin(t *testing.T, loadExpectations bool) b // create mock ctrl := gomock.NewController(t) ssVM := StateSyncEnabledMock{ - MockChainVM: mocks.NewMockChainVM(ctrl), - MockStateSyncableVM: mocks.NewMockStateSyncableVM(ctrl), + MockChainVM: block.NewMockChainVM(ctrl), + MockStateSyncableVM: block.NewMockStateSyncableVM(ctrl), } if loadExpectations { @@ -119,8 +119,8 @@ func getLastStateSummaryTestPlugin(t *testing.T, loadExpectations bool) block.Ch // create mock ctrl := gomock.NewController(t) ssVM := StateSyncEnabledMock{ - MockChainVM: mocks.NewMockChainVM(ctrl), - MockStateSyncableVM: mocks.NewMockStateSyncableVM(ctrl), + MockChainVM: block.NewMockChainVM(ctrl), + MockStateSyncableVM: block.NewMockStateSyncableVM(ctrl), } if loadExpectations { @@ -140,8 +140,8 @@ func parseStateSummaryTestPlugin(t *testing.T, loadExpectations bool) block.Chai // create mock ctrl := gomock.NewController(t) ssVM := StateSyncEnabledMock{ - MockChainVM: mocks.NewMockChainVM(ctrl), - MockStateSyncableVM: mocks.NewMockStateSyncableVM(ctrl), + MockChainVM: block.NewMockChainVM(ctrl), + MockStateSyncableVM: block.NewMockStateSyncableVM(ctrl), } if loadExpectations { @@ -162,8 +162,8 @@ func getStateSummaryTestPlugin(t *testing.T, loadExpectations bool) block.ChainV // create mock ctrl := gomock.NewController(t) ssVM := StateSyncEnabledMock{ - MockChainVM: mocks.NewMockChainVM(ctrl), - MockStateSyncableVM: mocks.NewMockStateSyncableVM(ctrl), + MockChainVM: block.NewMockChainVM(ctrl), + MockStateSyncableVM: block.NewMockStateSyncableVM(ctrl), } if loadExpectations { @@ -183,8 +183,8 @@ func acceptStateSummaryTestPlugin(t *testing.T, loadExpectations bool) block.Cha // create mock ctrl := gomock.NewController(t) ssVM := StateSyncEnabledMock{ - MockChainVM: mocks.NewMockChainVM(ctrl), - MockStateSyncableVM: mocks.NewMockStateSyncableVM(ctrl), + MockChainVM: block.NewMockChainVM(ctrl), + MockStateSyncableVM: block.NewMockStateSyncableVM(ctrl), } if loadExpectations { @@ -229,8 +229,8 @@ func lastAcceptedBlockPostStateSummaryAcceptTestPlugin(t *testing.T, loadExpecta // create mock ctrl := gomock.NewController(t) ssVM := StateSyncEnabledMock{ - MockChainVM: mocks.NewMockChainVM(ctrl), - MockStateSyncableVM: mocks.NewMockStateSyncableVM(ctrl), + MockChainVM: block.NewMockChainVM(ctrl), + MockStateSyncableVM: block.NewMockStateSyncableVM(ctrl), } if loadExpectations { @@ -470,7 +470,7 @@ func TestLastAcceptedBlockPostStateSummaryAccept(t *testing.T) { defer stopper.Stop(context.Background()) // Step 1: initialize VM and check initial LastAcceptedBlock - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) require.NoError(vm.Initialize(context.Background(), ctx, prefixdb.New([]byte{}, memdb.New()), nil, nil, nil, nil, nil, nil)) diff --git a/vms/rpcchainvm/vm.go b/vms/rpcchainvm/vm.go index e2e57f0284d3..ee2869989575 100644 --- a/vms/rpcchainvm/vm.go +++ b/vms/rpcchainvm/vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcchainvm diff --git a/vms/rpcchainvm/vm_client.go b/vms/rpcchainvm/vm_client.go index d9915bfbfcd6..3ca090011dd3 100644 --- a/vms/rpcchainvm/vm_client.go +++ b/vms/rpcchainvm/vm_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcchainvm @@ -375,29 +375,13 @@ func (vm *VMClient) CreateHandlers(ctx context.Context) (map[string]http.Handler return handlers, nil } -func (vm *VMClient) CreateStaticHandlers(ctx context.Context) (map[string]http.Handler, error) { - resp, err := vm.client.CreateStaticHandlers(ctx, &emptypb.Empty{}) - if err != nil { - return nil, err - } - - handlers := make(map[string]http.Handler, len(resp.Handlers)) - for _, handler := range resp.Handlers { - clientConn, err := grpcutils.Dial(handler.ServerAddr) - if err != nil { - return nil, err - } - - vm.conns = append(vm.conns, clientConn) - handlers[handler.Prefix] = ghttp.NewClient(httppb.NewHTTPClient(clientConn)) - } - return handlers, nil -} - func (vm *VMClient) Connected(ctx context.Context, nodeID ids.NodeID, nodeVersion *version.Application) error { _, err := vm.client.Connected(ctx, &vmpb.ConnectedRequest{ - NodeId: nodeID.Bytes(), - Version: nodeVersion.String(), + NodeId: nodeID.Bytes(), + Name: nodeVersion.Name, + Major: uint32(nodeVersion.Major), + Minor: uint32(nodeVersion.Minor), + Patch: uint32(nodeVersion.Patch), }) return err } @@ -541,14 +525,15 @@ func (vm *VMClient) CrossChainAppRequest(ctx context.Context, chainID ids.ID, re return err } -func (vm *VMClient) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32) error { - _, err := vm.client.CrossChainAppRequestFailed( - ctx, - &vmpb.CrossChainAppRequestFailedMsg{ - ChainId: chainID[:], - RequestId: requestID, - }, - ) +func (vm *VMClient) CrossChainAppRequestFailed(ctx context.Context, chainID ids.ID, requestID uint32, appErr *common.AppError) error { + msg := &vmpb.CrossChainAppRequestFailedMsg{ + ChainId: chainID[:], + RequestId: requestID, + ErrorCode: appErr.Code, + ErrorMessage: appErr.Message, + } + + _, err := vm.client.CrossChainAppRequestFailed(ctx, msg) return err } @@ -589,14 +574,15 @@ func (vm *VMClient) AppResponse(ctx context.Context, nodeID ids.NodeID, requestI return err } -func (vm *VMClient) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32) error { - _, err := vm.client.AppRequestFailed( - ctx, - &vmpb.AppRequestFailedMsg{ - NodeId: nodeID.Bytes(), - RequestId: requestID, - }, - ) +func (vm *VMClient) AppRequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *common.AppError) error { + msg := &vmpb.AppRequestFailedMsg{ + NodeId: nodeID.Bytes(), + RequestId: requestID, + ErrorCode: appErr.Code, + ErrorMessage: appErr.Message, + } + + _, err := vm.client.AppRequestFailed(ctx, msg) return err } diff --git a/vms/rpcchainvm/vm_server.go b/vms/rpcchainvm/vm_server.go index 7ee82a241506..5c0c22de1ae9 100644 --- a/vms/rpcchainvm/vm_server.go +++ b/vms/rpcchainvm/vm_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcchainvm @@ -337,43 +337,18 @@ func (vm *VMServer) CreateHandlers(ctx context.Context, _ *emptypb.Empty) (*vmpb return resp, nil } -func (vm *VMServer) CreateStaticHandlers(ctx context.Context, _ *emptypb.Empty) (*vmpb.CreateStaticHandlersResponse, error) { - handlers, err := vm.vm.CreateStaticHandlers(ctx) - if err != nil { - return nil, err - } - resp := &vmpb.CreateStaticHandlersResponse{} - for prefix, handler := range handlers { - serverListener, err := grpcutils.NewListener() - if err != nil { - return nil, err - } - server := grpcutils.NewServer() - vm.serverCloser.Add(server) - httppb.RegisterHTTPServer(server, ghttp.NewServer(handler)) - - // Start HTTP service - go grpcutils.Serve(serverListener, server) - - resp.Handlers = append(resp.Handlers, &vmpb.Handler{ - Prefix: prefix, - ServerAddr: serverListener.Addr().String(), - }) - } - return resp, nil -} - func (vm *VMServer) Connected(ctx context.Context, req *vmpb.ConnectedRequest) (*emptypb.Empty, error) { nodeID, err := ids.ToNodeID(req.NodeId) if err != nil { return nil, err } - peerVersion, err := version.ParseApplication(req.Version) - if err != nil { - return nil, err + peerVersion := &version.Application{ + Name: req.Name, + Major: int(req.Major), + Minor: int(req.Minor), + Patch: int(req.Patch), } - return &emptypb.Empty{}, vm.vm.Connected(ctx, nodeID, peerVersion) } @@ -536,7 +511,12 @@ func (vm *VMServer) CrossChainAppRequestFailed(ctx context.Context, msg *vmpb.Cr if err != nil { return nil, err } - return &emptypb.Empty{}, vm.vm.CrossChainAppRequestFailed(ctx, chainID, msg.RequestId) + + appErr := &common.AppError{ + Code: msg.ErrorCode, + Message: msg.ErrorMessage, + } + return &emptypb.Empty{}, vm.vm.CrossChainAppRequestFailed(ctx, chainID, msg.RequestId, appErr) } func (vm *VMServer) CrossChainAppResponse(ctx context.Context, msg *vmpb.CrossChainAppResponseMsg) (*emptypb.Empty, error) { @@ -564,7 +544,12 @@ func (vm *VMServer) AppRequestFailed(ctx context.Context, req *vmpb.AppRequestFa if err != nil { return nil, err } - return &emptypb.Empty{}, vm.vm.AppRequestFailed(ctx, nodeID, req.RequestId) + + appErr := &common.AppError{ + Code: req.ErrorCode, + Message: req.ErrorMessage, + } + return &emptypb.Empty{}, vm.vm.AppRequestFailed(ctx, nodeID, req.RequestId, appErr) } func (vm *VMServer) AppResponse(ctx context.Context, req *vmpb.AppResponseMsg) (*emptypb.Empty, error) { diff --git a/vms/rpcchainvm/vm_test.go b/vms/rpcchainvm/vm_test.go index 1e299c35ee5e..b21cd503a74a 100644 --- a/vms/rpcchainvm/vm_test.go +++ b/vms/rpcchainvm/vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcchainvm @@ -19,7 +19,6 @@ import ( "golang.org/x/exp/slices" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/rpcchainvm/grpcutils" "github.com/ava-labs/avalanchego/vms/rpcchainvm/runtime" @@ -172,7 +171,7 @@ func TestRuntimeSubprocessBootstrap(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) - vm := mocks.NewMockChainVM(ctrl) + vm := block.NewMockChainVM(ctrl) listener, err := grpcutils.NewListener() require.NoError(err) diff --git a/vms/rpcchainvm/with_context_vm_test.go b/vms/rpcchainvm/with_context_vm_test.go index 65d1e4396964..8796ff60941b 100644 --- a/vms/rpcchainvm/with_context_vm_test.go +++ b/vms/rpcchainvm/with_context_vm_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package rpcchainvm @@ -14,10 +14,9 @@ import ( "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" - "github.com/ava-labs/avalanchego/snow/engine/snowman/block/mocks" + "github.com/ava-labs/avalanchego/snow/snowtest" ) var ( @@ -37,13 +36,13 @@ var ( ) type ContextEnabledVMMock struct { - *mocks.MockChainVM - *mocks.MockBuildBlockWithContextChainVM + *block.MockChainVM + *block.MockBuildBlockWithContextChainVM } type ContextEnabledBlockMock struct { *snowman.MockBlock - *mocks.MockWithVerifyContext + *block.MockWithVerifyContext } func contextEnabledTestPlugin(t *testing.T, loadExpectations bool) block.ChainVM { @@ -52,14 +51,14 @@ func contextEnabledTestPlugin(t *testing.T, loadExpectations bool) block.ChainVM // create mock ctrl := gomock.NewController(t) ctxVM := ContextEnabledVMMock{ - MockChainVM: mocks.NewMockChainVM(ctrl), - MockBuildBlockWithContextChainVM: mocks.NewMockBuildBlockWithContextChainVM(ctrl), + MockChainVM: block.NewMockChainVM(ctrl), + MockBuildBlockWithContextChainVM: block.NewMockBuildBlockWithContextChainVM(ctrl), } if loadExpectations { ctxBlock := ContextEnabledBlockMock{ MockBlock: snowman.NewMockBlock(ctrl), - MockWithVerifyContext: mocks.NewMockWithVerifyContext(ctrl), + MockWithVerifyContext: block.NewMockWithVerifyContext(ctrl), } gomock.InOrder( // Initialize @@ -98,7 +97,7 @@ func TestContextVMSummary(t *testing.T) { vm, stopper := buildClientHelper(require, testKey) defer stopper.Stop(context.Background()) - ctx := snow.DefaultContextTest() + ctx := snowtest.Context(t, snowtest.CChainID) require.NoError(vm.Initialize(context.Background(), ctx, memdb.New(), nil, nil, nil, nil, nil, nil)) diff --git a/vms/secp256k1fx/credential.go b/vms/secp256k1fx/credential.go index 707a6b3f43d8..0367c9af96af 100644 --- a/vms/secp256k1fx/credential.go +++ b/vms/secp256k1fx/credential.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/credential_test.go b/vms/secp256k1fx/credential_test.go index 15496e1d7fac..e69b98b286e7 100644 --- a/vms/secp256k1fx/credential_test.go +++ b/vms/secp256k1fx/credential_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -27,7 +28,7 @@ func TestCredentialVerifyNil(t *testing.T) { func TestCredentialSerialize(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) m := codec.NewDefaultManager() require.NoError(m.RegisterCodec(0, c)) diff --git a/vms/secp256k1fx/factory.go b/vms/secp256k1fx/factory.go index ae2463a19deb..fd52fe79a6fe 100644 --- a/vms/secp256k1fx/factory.go +++ b/vms/secp256k1fx/factory.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/factory_test.go b/vms/secp256k1fx/factory_test.go index 435164998581..2b1fa184474d 100644 --- a/vms/secp256k1fx/factory_test.go +++ b/vms/secp256k1fx/factory_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/fx.go b/vms/secp256k1fx/fx.go index c969c9593976..8c1cfa53bf9a 100644 --- a/vms/secp256k1fx/fx.go +++ b/vms/secp256k1fx/fx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/fx_test.go b/vms/secp256k1fx/fx_test.go index c0e2663e6a23..dcdf78385404 100644 --- a/vms/secp256k1fx/fx_test.go +++ b/vms/secp256k1fx/fx_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx @@ -53,7 +53,7 @@ func init() { func TestFxInitialize(t *testing.T) { vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } fx := Fx{} @@ -69,7 +69,7 @@ func TestFxInitializeInvalid(t *testing.T) { func TestFxVerifyTransfer(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -107,7 +107,7 @@ func TestFxVerifyTransfer(t *testing.T) { func TestFxVerifyTransferNilTx(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -143,7 +143,7 @@ func TestFxVerifyTransferNilTx(t *testing.T) { func TestFxVerifyTransferNilOutput(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -170,7 +170,7 @@ func TestFxVerifyTransferNilOutput(t *testing.T) { func TestFxVerifyTransferNilInput(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -201,7 +201,7 @@ func TestFxVerifyTransferNilInput(t *testing.T) { func TestFxVerifyTransferNilCredential(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -233,7 +233,7 @@ func TestFxVerifyTransferNilCredential(t *testing.T) { func TestFxVerifyTransferInvalidOutput(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -270,7 +270,7 @@ func TestFxVerifyTransferInvalidOutput(t *testing.T) { func TestFxVerifyTransferWrongAmounts(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -307,7 +307,7 @@ func TestFxVerifyTransferWrongAmounts(t *testing.T) { func TestFxVerifyTransferTimelocked(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -344,7 +344,7 @@ func TestFxVerifyTransferTimelocked(t *testing.T) { func TestFxVerifyTransferTooManySigners(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -382,7 +382,7 @@ func TestFxVerifyTransferTooManySigners(t *testing.T) { func TestFxVerifyTransferTooFewSigners(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -417,7 +417,7 @@ func TestFxVerifyTransferTooFewSigners(t *testing.T) { func TestFxVerifyTransferMismatchedSigners(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -455,7 +455,7 @@ func TestFxVerifyTransferMismatchedSigners(t *testing.T) { func TestFxVerifyTransferInvalidSignature(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -495,7 +495,7 @@ func TestFxVerifyTransferInvalidSignature(t *testing.T) { func TestFxVerifyTransferWrongSigner(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -535,7 +535,7 @@ func TestFxVerifyTransferWrongSigner(t *testing.T) { func TestFxVerifyTransferSigIndexOOB(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -575,7 +575,7 @@ func TestFxVerifyTransferSigIndexOOB(t *testing.T) { func TestFxVerifyOperation(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -627,7 +627,7 @@ func TestFxVerifyOperation(t *testing.T) { func TestFxVerifyOperationUnknownTx(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -679,7 +679,7 @@ func TestFxVerifyOperationUnknownTx(t *testing.T) { func TestFxVerifyOperationUnknownOperation(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -709,7 +709,7 @@ func TestFxVerifyOperationUnknownOperation(t *testing.T) { func TestFxVerifyOperationUnknownCredential(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -757,7 +757,7 @@ func TestFxVerifyOperationUnknownCredential(t *testing.T) { func TestFxVerifyOperationWrongNumberOfUTXOs(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -810,7 +810,7 @@ func TestFxVerifyOperationWrongNumberOfUTXOs(t *testing.T) { func TestFxVerifyOperationUnknownUTXOType(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -855,7 +855,7 @@ func TestFxVerifyOperationUnknownUTXOType(t *testing.T) { func TestFxVerifyOperationInvalidOperationVerify(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -905,7 +905,7 @@ func TestFxVerifyOperationInvalidOperationVerify(t *testing.T) { func TestFxVerifyOperationMismatchedMintOutputs(t *testing.T) { require := require.New(t) vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } date := time.Date(2019, time.January, 19, 16, 25, 17, 3, time.UTC) @@ -952,7 +952,7 @@ func TestFxVerifyOperationMismatchedMintOutputs(t *testing.T) { func TestVerifyPermission(t *testing.T) { vm := TestVM{ - Codec: linearcodec.NewDefault(), + Codec: linearcodec.NewDefault(time.Time{}), Log: logging.NoLog{}, } fx := Fx{} diff --git a/vms/secp256k1fx/input.go b/vms/secp256k1fx/input.go index d5943a6b686e..7e11556be582 100644 --- a/vms/secp256k1fx/input.go +++ b/vms/secp256k1fx/input.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/input_test.go b/vms/secp256k1fx/input_test.go index 632004799e91..f80b824d7711 100644 --- a/vms/secp256k1fx/input_test.go +++ b/vms/secp256k1fx/input_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/keychain.go b/vms/secp256k1fx/keychain.go index 3246ef95722d..ecb42f209970 100644 --- a/vms/secp256k1fx/keychain.go +++ b/vms/secp256k1fx/keychain.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/keychain_test.go b/vms/secp256k1fx/keychain_test.go index 46fdb1a0695c..65dd984b703f 100644 --- a/vms/secp256k1fx/keychain_test.go +++ b/vms/secp256k1fx/keychain_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/mint_operation.go b/vms/secp256k1fx/mint_operation.go index a21f3061290b..80728ca7588e 100644 --- a/vms/secp256k1fx/mint_operation.go +++ b/vms/secp256k1fx/mint_operation.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/mint_operation_test.go b/vms/secp256k1fx/mint_operation_test.go index 60b60b25f10a..3b751c8dcb33 100644 --- a/vms/secp256k1fx/mint_operation_test.go +++ b/vms/secp256k1fx/mint_operation_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/mint_output.go b/vms/secp256k1fx/mint_output.go index 996f05171bbe..e52ba47025ae 100644 --- a/vms/secp256k1fx/mint_output.go +++ b/vms/secp256k1fx/mint_output.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/mint_output_test.go b/vms/secp256k1fx/mint_output_test.go index 7d092a6772ea..60a72dfc95c4 100644 --- a/vms/secp256k1fx/mint_output_test.go +++ b/vms/secp256k1fx/mint_output_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/output_owners.go b/vms/secp256k1fx/output_owners.go index 1c252bd54e6d..0f838c69800e 100644 --- a/vms/secp256k1fx/output_owners.go +++ b/vms/secp256k1fx/output_owners.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx @@ -21,7 +21,6 @@ var ( ErrOutputUnspendable = errors.New("output is unspendable") ErrOutputUnoptimized = errors.New("output representation should be optimized") ErrAddrsNotSortedUnique = errors.New("addresses not sorted and unique") - ErrMarshal = errors.New("cannot marshal without ctx") ) type OutputOwners struct { @@ -37,8 +36,8 @@ type OutputOwners struct { ctx *snow.Context } -// InitCtx assigns the OutputOwners.ctx object to given [ctx] object -// Must be called at least once for MarshalJSON to work successfully +// InitCtx allows addresses to be formatted into their human readable format +// during json marshalling. func (out *OutputOwners) InitCtx(ctx *snow.Context) { out.ctx = ctx } @@ -59,14 +58,7 @@ func (out *OutputOwners) MarshalJSON() ([]byte, error) { // Fields returns JSON keys in a map that can be used with marshal JSON // to serialize OutputOwners struct func (out *OutputOwners) Fields() (map[string]interface{}, error) { - addrsLen := len(out.Addrs) - - // we need out.ctx to do this, if its absent, throw error - if addrsLen > 0 && out.ctx == nil { - return nil, ErrMarshal - } - - addresses := make([]string, addrsLen) + addresses := make([]string, len(out.Addrs)) for i, addr := range out.Addrs { // for each [addr] in [Addrs] we attempt to format it given // the [out.ctx] object @@ -138,8 +130,13 @@ func (out *OutputOwners) Sort() { } // formatAddress formats a given [addr] into human readable format using -// [ChainID] and [NetworkID] from the provided [ctx]. +// [ChainID] and [NetworkID] if a non-nil [ctx] is provided. If [ctx] is not +// provided, the address will be returned in cb58 format. func formatAddress(ctx *snow.Context, addr ids.ShortID) (string, error) { + if ctx == nil { + return addr.String(), nil + } + chainIDAlias, err := ctx.BCLookup.PrimaryAlias(ctx.ChainID) if err != nil { return "", err diff --git a/vms/secp256k1fx/output_owners_test.go b/vms/secp256k1fx/output_owners_test.go index b09e28bea923..e042726bce64 100644 --- a/vms/secp256k1fx/output_owners_test.go +++ b/vms/secp256k1fx/output_owners_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx @@ -149,31 +149,20 @@ func TestOutputOwnerEquals(t *testing.T) { } } -func TestMarshalJSONRequiresCtxWhenAddrsArePresent(t *testing.T) { +func TestMarshalJSONDoesNotRequireCtx(t *testing.T) { require := require.New(t) out := &OutputOwners{ Threshold: 1, + Locktime: 2, Addrs: []ids.ShortID{ {1}, {0}, }, } - _, err := out.MarshalJSON() - require.ErrorIs(err, ErrMarshal) -} - -func TestMarshalJSONDoesNotRequireCtxWhenAddrsAreAbsent(t *testing.T) { - require := require.New(t) - out := &OutputOwners{ - Threshold: 1, - Locktime: 2, - Addrs: []ids.ShortID{}, - } - b, err := out.MarshalJSON() require.NoError(err) jsonData := string(b) - require.Equal(jsonData, "{\"addresses\":[],\"locktime\":2,\"threshold\":1}") + require.Equal(jsonData, `{"addresses":["6HgC8KRBEhXYbF4riJyJFLSHt37UNuRt","111111111111111111116DBWJs"],"locktime":2,"threshold":1}`) } diff --git a/vms/secp256k1fx/transfer_input.go b/vms/secp256k1fx/transfer_input.go index 6dadd558fc49..1659820c26f7 100644 --- a/vms/secp256k1fx/transfer_input.go +++ b/vms/secp256k1fx/transfer_input.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/transfer_input_test.go b/vms/secp256k1fx/transfer_input_test.go index c3019a25a12f..c155d848e559 100644 --- a/vms/secp256k1fx/transfer_input_test.go +++ b/vms/secp256k1fx/transfer_input_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -80,7 +81,7 @@ func TestTransferInputVerifyUnsorted(t *testing.T) { func TestTransferInputSerialize(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) m := codec.NewDefaultManager() require.NoError(m.RegisterCodec(0, c)) diff --git a/vms/secp256k1fx/transfer_output.go b/vms/secp256k1fx/transfer_output.go index 234cc1f68dab..ee4c5796c413 100644 --- a/vms/secp256k1fx/transfer_output.go +++ b/vms/secp256k1fx/transfer_output.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/transfer_output_test.go b/vms/secp256k1fx/transfer_output_test.go index 767f93925b7c..864fb85b9ff0 100644 --- a/vms/secp256k1fx/transfer_output_test.go +++ b/vms/secp256k1fx/transfer_output_test.go @@ -1,10 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -135,7 +136,7 @@ func TestOutputVerifyDuplicated(t *testing.T) { func TestOutputSerialize(t *testing.T) { require := require.New(t) - c := linearcodec.NewDefault() + c := linearcodec.NewDefault(time.Time{}) m := codec.NewDefaultManager() require.NoError(m.RegisterCodec(0, c)) diff --git a/vms/secp256k1fx/tx.go b/vms/secp256k1fx/tx.go index 81f4ee4d11e0..5cc483c7c3fc 100644 --- a/vms/secp256k1fx/tx.go +++ b/vms/secp256k1fx/tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/secp256k1fx/vm.go b/vms/secp256k1fx/vm.go index ba8a9f1d6c0a..07da2d394236 100644 --- a/vms/secp256k1fx/vm.go +++ b/vms/secp256k1fx/vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package secp256k1fx diff --git a/vms/tracedvm/batched_vm.go b/vms/tracedvm/batched_vm.go index 47f81c9fa114..22dfb212ec6d 100644 --- a/vms/tracedvm/batched_vm.go +++ b/vms/tracedvm/batched_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracedvm diff --git a/vms/tracedvm/block.go b/vms/tracedvm/block.go index a90a110302fe..81949d777225 100644 --- a/vms/tracedvm/block.go +++ b/vms/tracedvm/block.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracedvm diff --git a/vms/tracedvm/block_vm.go b/vms/tracedvm/block_vm.go index 969a6bc09637..10931bf8c287 100644 --- a/vms/tracedvm/block_vm.go +++ b/vms/tracedvm/block_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracedvm diff --git a/vms/tracedvm/build_block_with_context_vm.go b/vms/tracedvm/build_block_with_context_vm.go index 1d9e9319605e..b069b471f26b 100644 --- a/vms/tracedvm/build_block_with_context_vm.go +++ b/vms/tracedvm/build_block_with_context_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracedvm diff --git a/vms/tracedvm/state_syncable_vm.go b/vms/tracedvm/state_syncable_vm.go index 75738462368b..e31507d55735 100644 --- a/vms/tracedvm/state_syncable_vm.go +++ b/vms/tracedvm/state_syncable_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracedvm diff --git a/vms/tracedvm/tx.go b/vms/tracedvm/tx.go index 7e18efcb23b4..638ecd8f5914 100644 --- a/vms/tracedvm/tx.go +++ b/vms/tracedvm/tx.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracedvm diff --git a/vms/tracedvm/vertex_vm.go b/vms/tracedvm/vertex_vm.go index 53189f5cee70..4bc162c6c6ae 100644 --- a/vms/tracedvm/vertex_vm.go +++ b/vms/tracedvm/vertex_vm.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package tracedvm diff --git a/vms/types/blob_data.go b/vms/types/blob_data.go index cf5855ad7c7d..cee4fe7ba4d8 100644 --- a/vms/types/blob_data.go +++ b/vms/types/blob_data.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package types diff --git a/wallet/chain/c/backend.go b/wallet/chain/c/backend.go index 0a735116b646..cefe6befcf6a 100644 --- a/wallet/chain/c/backend.go +++ b/wallet/chain/c/backend.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c diff --git a/wallet/chain/c/builder.go b/wallet/chain/c/builder.go index d2d088e88a53..3e387ba3c27b 100644 --- a/wallet/chain/c/builder.go +++ b/wallet/chain/c/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c @@ -201,6 +201,7 @@ func (b *builder) NewImportTx( importedInputs = append(importedInputs, &avax.TransferableInput{ UTXOID: utxo.UTXOID, Asset: utxo.Asset, + FxID: secp256k1fx.ID, In: &secp256k1fx.TransferInput{ Amt: amount, Input: secp256k1fx.Input{ @@ -267,6 +268,7 @@ func (b *builder) NewExportTx( for i, output := range outputs { exportedOutputs[i] = &avax.TransferableOutput{ Asset: avax.Asset{ID: avaxAssetID}, + FxID: secp256k1fx.ID, Out: output, } @@ -376,6 +378,14 @@ func (b *builder) NewExportTx( utils.Sort(inputs) tx.Ins = inputs + + snowCtx, err := newSnowContext(b.backend) + if err != nil { + return nil, err + } + for _, out := range tx.ExportedOutputs { + out.InitCtx(snowCtx) + } return tx, nil } diff --git a/wallet/chain/c/builder_with_options.go b/wallet/chain/c/builder_with_options.go index 8416dddf9928..fa98725450a6 100644 --- a/wallet/chain/c/builder_with_options.go +++ b/wallet/chain/c/builder_with_options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c diff --git a/wallet/chain/c/context.go b/wallet/chain/c/context.go index d506b42f81fa..9e4712b8f7c9 100644 --- a/wallet/chain/c/context.go +++ b/wallet/chain/c/context.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c @@ -8,9 +8,14 @@ import ( "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/avm" ) +const Alias = "C" + var _ Context = (*context)(nil) type Context interface { @@ -41,7 +46,7 @@ func NewContextFromClients( return nil, err } - chainID, err := infoClient.GetBlockchainID(ctx, "C") + chainID, err := infoClient.GetBlockchainID(ctx, Alias) if err != nil { return nil, err } @@ -81,3 +86,17 @@ func (c *context) BlockchainID() ids.ID { func (c *context) AVAXAssetID() ids.ID { return c.avaxAssetID } + +func newSnowContext(c Context) (*snow.Context, error) { + chainID := c.BlockchainID() + lookup := ids.NewAliaser() + return &snow.Context{ + NetworkID: c.NetworkID(), + SubnetID: constants.PrimaryNetworkID, + ChainID: chainID, + CChainID: chainID, + AVAXAssetID: c.AVAXAssetID(), + Log: logging.NoLog{}, + BCLookup: lookup, + }, lookup.Alias(chainID, Alias) +} diff --git a/wallet/chain/c/signer.go b/wallet/chain/c/signer.go index 4fd85ed3b532..7be1a149fb36 100644 --- a/wallet/chain/c/signer.go +++ b/wallet/chain/c/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c diff --git a/wallet/chain/c/wallet.go b/wallet/chain/c/wallet.go index fb1a83d53dad..304fbe4cf7c8 100644 --- a/wallet/chain/c/wallet.go +++ b/wallet/chain/c/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c diff --git a/wallet/chain/c/wallet_with_options.go b/wallet/chain/c/wallet_with_options.go index 7d6193683d49..a0a1a60d85a8 100644 --- a/wallet/chain/c/wallet_with_options.go +++ b/wallet/chain/c/wallet_with_options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package c diff --git a/wallet/chain/p/backend.go b/wallet/chain/p/backend.go index bb75692f3908..d06c7a1f9cf9 100644 --- a/wallet/chain/p/backend.go +++ b/wallet/chain/p/backend.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p diff --git a/wallet/chain/p/backend_visitor.go b/wallet/chain/p/backend_visitor.go index 57d602354428..1a0c8e39e8da 100644 --- a/wallet/chain/p/backend_visitor.go +++ b/wallet/chain/p/backend_visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p diff --git a/wallet/chain/p/builder.go b/wallet/chain/p/builder.go index f890790dad26..9a8189c4db6b 100644 --- a/wallet/chain/p/builder.go +++ b/wallet/chain/p/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p @@ -311,7 +311,7 @@ func (b *builder) NewBaseTx( outputs = append(outputs, changeOutputs...) avax.SortTransferableOutputs(outputs, txs.Codec) // sort the outputs - return &txs.CreateSubnetTx{ + tx := &txs.CreateSubnetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -320,7 +320,8 @@ func (b *builder) NewBaseTx( Memo: ops.Memo(), }}, Owner: &secp256k1fx.OutputOwners{}, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewAddValidatorTx( @@ -343,7 +344,7 @@ func (b *builder) NewAddValidatorTx( } utils.Sort(rewardsOwner.Addrs) - return &txs.AddValidatorTx{ + tx := &txs.AddValidatorTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -355,7 +356,8 @@ func (b *builder) NewAddValidatorTx( StakeOuts: stakeOutputs, RewardsOwner: rewardsOwner, DelegationShares: shares, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewAddSubnetValidatorTx( @@ -377,7 +379,7 @@ func (b *builder) NewAddSubnetValidatorTx( return nil, err } - return &txs.AddSubnetValidatorTx{ + tx := &txs.AddSubnetValidatorTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -387,7 +389,8 @@ func (b *builder) NewAddSubnetValidatorTx( }}, SubnetValidator: *vdr, SubnetAuth: subnetAuth, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewRemoveSubnetValidatorTx( @@ -410,7 +413,7 @@ func (b *builder) NewRemoveSubnetValidatorTx( return nil, err } - return &txs.RemoveSubnetValidatorTx{ + tx := &txs.RemoveSubnetValidatorTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -421,7 +424,8 @@ func (b *builder) NewRemoveSubnetValidatorTx( Subnet: subnetID, NodeID: nodeID, SubnetAuth: subnetAuth, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewAddDelegatorTx( @@ -443,7 +447,7 @@ func (b *builder) NewAddDelegatorTx( } utils.Sort(rewardsOwner.Addrs) - return &txs.AddDelegatorTx{ + tx := &txs.AddDelegatorTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -454,7 +458,8 @@ func (b *builder) NewAddDelegatorTx( Validator: *vdr, StakeOuts: stakeOutputs, DelegationRewardsOwner: rewardsOwner, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewCreateChainTx( @@ -481,7 +486,7 @@ func (b *builder) NewCreateChainTx( } utils.Sort(fxIDs) - return &txs.CreateChainTx{ + tx := &txs.CreateChainTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -495,7 +500,8 @@ func (b *builder) NewCreateChainTx( FxIDs: fxIDs, GenesisData: genesis, SubnetAuth: subnetAuth, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewCreateSubnetTx( @@ -513,7 +519,7 @@ func (b *builder) NewCreateSubnetTx( } utils.Sort(owner.Addrs) - return &txs.CreateSubnetTx{ + tx := &txs.CreateSubnetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -522,7 +528,8 @@ func (b *builder) NewCreateSubnetTx( Memo: ops.Memo(), }}, Owner: owner, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewImportTx( @@ -618,7 +625,7 @@ func (b *builder) NewImportTx( } avax.SortTransferableOutputs(outputs, txs.Codec) // sort imported outputs - return &txs.ImportTx{ + tx := &txs.ImportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -628,7 +635,8 @@ func (b *builder) NewImportTx( }}, SourceChain: sourceChainID, ImportedInputs: importedInputs, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewExportTx( @@ -656,7 +664,7 @@ func (b *builder) NewExportTx( } avax.SortTransferableOutputs(outputs, txs.Codec) // sort exported outputs - return &txs.ExportTx{ + tx := &txs.ExportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -666,7 +674,8 @@ func (b *builder) NewExportTx( }}, DestinationChain: chainID, ExportedOutputs: outputs, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewTransformSubnetTx( @@ -702,7 +711,7 @@ func (b *builder) NewTransformSubnetTx( return nil, err } - return &txs.TransformSubnetTx{ + tx := &txs.TransformSubnetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -725,7 +734,8 @@ func (b *builder) NewTransformSubnetTx( MaxValidatorWeightFactor: maxValidatorWeightFactor, UptimeRequirement: uptimeRequirement, SubnetAuth: subnetAuth, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewAddPermissionlessValidatorTx( @@ -755,7 +765,7 @@ func (b *builder) NewAddPermissionlessValidatorTx( utils.Sort(validationRewardsOwner.Addrs) utils.Sort(delegationRewardsOwner.Addrs) - return &txs.AddPermissionlessValidatorTx{ + tx := &txs.AddPermissionlessValidatorTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -770,7 +780,8 @@ func (b *builder) NewAddPermissionlessValidatorTx( ValidatorRewardsOwner: validationRewardsOwner, DelegatorRewardsOwner: delegationRewardsOwner, DelegationShares: shares, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewAddPermissionlessDelegatorTx( @@ -796,7 +807,7 @@ func (b *builder) NewAddPermissionlessDelegatorTx( } utils.Sort(rewardsOwner.Addrs) - return &txs.AddPermissionlessDelegatorTx{ + tx := &txs.AddPermissionlessDelegatorTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: constants.PlatformChainID, @@ -808,7 +819,8 @@ func (b *builder) NewAddPermissionlessDelegatorTx( Subnet: vdr.Subnet, StakeOuts: stakeOutputs, DelegationRewardsOwner: rewardsOwner, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) getBalance( @@ -1117,3 +1129,13 @@ func (b *builder) authorizeSubnet(subnetID ids.ID, options *common.Options) (*se SigIndices: inputSigIndices, }, nil } + +func (b *builder) initCtx(tx txs.UnsignedTx) error { + ctx, err := newSnowContext(b.backend) + if err != nil { + return err + } + + tx.InitCtx(ctx) + return nil +} diff --git a/wallet/chain/p/builder_with_options.go b/wallet/chain/p/builder_with_options.go index 9060d7639410..46deab976577 100644 --- a/wallet/chain/p/builder_with_options.go +++ b/wallet/chain/p/builder_with_options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p diff --git a/wallet/chain/p/context.go b/wallet/chain/p/context.go index 75bcdb19e7f2..d861dae1dccc 100644 --- a/wallet/chain/p/context.go +++ b/wallet/chain/p/context.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p @@ -8,9 +8,14 @@ import ( "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/avm" ) +const Alias = "P" + var _ Context = (*context)(nil) type Context interface { @@ -144,3 +149,15 @@ func (c *context) AddSubnetValidatorFee() uint64 { func (c *context) AddSubnetDelegatorFee() uint64 { return c.addSubnetDelegatorFee } + +func newSnowContext(c Context) (*snow.Context, error) { + lookup := ids.NewAliaser() + return &snow.Context{ + NetworkID: c.NetworkID(), + SubnetID: constants.PrimaryNetworkID, + ChainID: constants.PlatformChainID, + AVAXAssetID: c.AVAXAssetID(), + Log: logging.NoLog{}, + BCLookup: lookup, + }, lookup.Alias(constants.PlatformChainID, Alias) +} diff --git a/wallet/chain/p/signer.go b/wallet/chain/p/signer.go index a795dd63c539..be2db8ddd2c0 100644 --- a/wallet/chain/p/signer.go +++ b/wallet/chain/p/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p diff --git a/wallet/chain/p/signer_visitor.go b/wallet/chain/p/signer_visitor.go index 9dd6018ea2e3..c5c444a97f13 100644 --- a/wallet/chain/p/signer_visitor.go +++ b/wallet/chain/p/signer_visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p @@ -284,7 +284,7 @@ func (s *signerVisitor) getSubnetSigners(subnetID ids.ID, subnetAuth verify.Veri // TODO: remove [signHash] after the ledger supports signing all transactions. func sign(tx *txs.Tx, signHash bool, txSigners [][]keychain.Signer) error { - unsignedBytes, err := txs.Codec.Marshal(txs.Version, &tx.Unsigned) + unsignedBytes, err := txs.Codec.Marshal(txs.CodecVersion, &tx.Unsigned) if err != nil { return fmt.Errorf("couldn't marshal unsigned tx: %w", err) } @@ -345,7 +345,7 @@ func sign(tx *txs.Tx, signHash bool, txSigners [][]keychain.Signer) error { } } - signedBytes, err := txs.Codec.Marshal(txs.Version, tx) + signedBytes, err := txs.Codec.Marshal(txs.CodecVersion, tx) if err != nil { return fmt.Errorf("couldn't marshal tx: %w", err) } diff --git a/wallet/chain/p/wallet.go b/wallet/chain/p/wallet.go index c4d5545818ae..e982a204a9f8 100644 --- a/wallet/chain/p/wallet.go +++ b/wallet/chain/p/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p diff --git a/wallet/chain/p/wallet_with_options.go b/wallet/chain/p/wallet_with_options.go index 8135db0ecfbc..33faa1a86bb0 100644 --- a/wallet/chain/p/wallet_with_options.go +++ b/wallet/chain/p/wallet_with_options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package p diff --git a/wallet/chain/x/backend.go b/wallet/chain/x/backend.go index 56ade31be1b7..6c2f81365daf 100644 --- a/wallet/chain/x/backend.go +++ b/wallet/chain/x/backend.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x diff --git a/wallet/chain/x/backend_visitor.go b/wallet/chain/x/backend_visitor.go index d617638434c6..7ce9aa2acd00 100644 --- a/wallet/chain/x/backend_visitor.go +++ b/wallet/chain/x/backend_visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x diff --git a/wallet/chain/x/builder.go b/wallet/chain/x/builder.go index 0b639a7776ad..27932019e1f4 100644 --- a/wallet/chain/x/builder.go +++ b/wallet/chain/x/builder.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x @@ -26,6 +26,12 @@ var ( errNoChangeAddress = errors.New("no possible change address") errInsufficientFunds = errors.New("insufficient funds") + fxIndexToID = map[uint32]ids.ID{ + 0: secp256k1fx.ID, + 1: nftfx.ID, + 2: propertyfx.ID, + } + _ Builder = (*builder)(nil) ) @@ -213,13 +219,14 @@ func (b *builder) NewBaseTx( outputs = append(outputs, changeOutputs...) avax.SortTransferableOutputs(outputs, Parser.Codec()) // sort the outputs - return &txs.BaseTx{BaseTx: avax.BaseTx{ + tx := &txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: b.backend.BlockchainID(), Ins: inputs, Outs: outputs, Memo: ops.Memo(), - }}, nil + }} + return tx, b.initCtx(tx) } func (b *builder) NewCreateAssetTx( @@ -243,12 +250,14 @@ func (b *builder) NewCreateAssetTx( for fxIndex, outs := range initialState { state := &txs.InitialState{ FxIndex: fxIndex, + FxID: fxIndexToID[fxIndex], Outs: outs, } state.Sort(codec) // sort the outputs states = append(states, state) } + utils.Sort(states) // sort the initial states tx := &txs.CreateAssetTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), @@ -262,8 +271,7 @@ func (b *builder) NewCreateAssetTx( Denomination: denomination, States: states, } - utils.Sort(tx.States) // sort the initial states - return tx, nil + return tx, b.initCtx(tx) } func (b *builder) NewOperationTx( @@ -280,7 +288,7 @@ func (b *builder) NewOperationTx( } txs.SortOperations(operations, Parser.Codec()) - return &txs.OperationTx{ + tx := &txs.OperationTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: b.backend.BlockchainID(), @@ -289,7 +297,8 @@ func (b *builder) NewOperationTx( Memo: ops.Memo(), }}, Ops: operations, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewOperationTxMintFT( @@ -380,6 +389,7 @@ func (b *builder) NewImportTx( importedInputs = append(importedInputs, &avax.TransferableInput{ UTXOID: utxo.UTXOID, Asset: utxo.Asset, + FxID: secp256k1fx.ID, In: &secp256k1fx.TransferInput{ Amt: out.Amt, Input: secp256k1fx.Input{ @@ -428,6 +438,7 @@ func (b *builder) NewImportTx( for assetID, amount := range importedAmounts { outputs = append(outputs, &avax.TransferableOutput{ Asset: avax.Asset{ID: assetID}, + FxID: secp256k1fx.ID, Out: &secp256k1fx.TransferOutput{ Amt: amount, OutputOwners: *to, @@ -436,7 +447,7 @@ func (b *builder) NewImportTx( } avax.SortTransferableOutputs(outputs, Parser.Codec()) - return &txs.ImportTx{ + tx := &txs.ImportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: b.backend.BlockchainID(), @@ -446,7 +457,8 @@ func (b *builder) NewImportTx( }}, SourceChain: chainID, ImportedIns: importedInputs, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) NewExportTx( @@ -473,7 +485,7 @@ func (b *builder) NewExportTx( } avax.SortTransferableOutputs(outputs, Parser.Codec()) - return &txs.ExportTx{ + tx := &txs.ExportTx{ BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ NetworkID: b.backend.NetworkID(), BlockchainID: b.backend.BlockchainID(), @@ -483,7 +495,8 @@ func (b *builder) NewExportTx( }}, DestinationChain: chainID, ExportedOuts: outputs, - }, nil + } + return tx, b.initCtx(tx) } func (b *builder) getBalance( @@ -578,6 +591,7 @@ func (b *builder) spend( inputs = append(inputs, &avax.TransferableInput{ UTXOID: utxo.UTXOID, Asset: utxo.Asset, + FxID: secp256k1fx.ID, In: &secp256k1fx.TransferInput{ Amt: out.Amt, Input: secp256k1fx.Input{ @@ -596,6 +610,7 @@ func (b *builder) spend( // This input had extra value, so some of it must be returned outputs = append(outputs, &avax.TransferableOutput{ Asset: utxo.Asset, + FxID: secp256k1fx.ID, Out: &secp256k1fx.TransferOutput{ Amt: remainingAmount, OutputOwners: *changeOwner, @@ -656,6 +671,7 @@ func (b *builder) mintFTs( operations = append(operations, &txs.Operation{ Asset: utxo.Asset, UTXOIDs: []*avax.UTXOID{&utxo.UTXOID}, + FxID: secp256k1fx.ID, Op: &secp256k1fx.MintOperation{ MintInput: secp256k1fx.Input{ SigIndices: inputSigIndices, @@ -719,6 +735,7 @@ func (b *builder) mintNFTs( UTXOIDs: []*avax.UTXOID{ &utxo.UTXOID, }, + FxID: nftfx.ID, Op: &nftfx.MintOperation{ MintInput: secp256k1fx.Input{ SigIndices: inputSigIndices, @@ -775,6 +792,7 @@ func (b *builder) mintProperty( UTXOIDs: []*avax.UTXOID{ &utxo.UTXOID, }, + FxID: propertyfx.ID, Op: &propertyfx.MintOperation{ MintInput: secp256k1fx.Input{ SigIndices: inputSigIndices, @@ -831,6 +849,7 @@ func (b *builder) burnProperty( UTXOIDs: []*avax.UTXOID{ &utxo.UTXOID, }, + FxID: propertyfx.ID, Op: &propertyfx.BurnOperation{ Input: secp256k1fx.Input{ SigIndices: inputSigIndices, @@ -847,3 +866,13 @@ func (b *builder) burnProperty( } return operations, nil } + +func (b *builder) initCtx(tx txs.UnsignedTx) error { + ctx, err := newSnowContext(b.backend) + if err != nil { + return err + } + + tx.InitCtx(ctx) + return nil +} diff --git a/wallet/chain/x/builder_with_options.go b/wallet/chain/x/builder_with_options.go index 63d554009fff..c2b65b05a630 100644 --- a/wallet/chain/x/builder_with_options.go +++ b/wallet/chain/x/builder_with_options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x diff --git a/wallet/chain/x/constants.go b/wallet/chain/x/constants.go index ed43fc07a84d..47efbfc2ff6d 100644 --- a/wallet/chain/x/constants.go +++ b/wallet/chain/x/constants.go @@ -1,9 +1,11 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x import ( + "time" + "github.com/ava-labs/avalanchego/vms/avm/block" "github.com/ava-labs/avalanchego/vms/avm/fxs" "github.com/ava-labs/avalanchego/vms/nftfx" @@ -22,11 +24,14 @@ var Parser block.Parser func init() { var err error - Parser, err = block.NewParser([]fxs.Fx{ - &secp256k1fx.Fx{}, - &nftfx.Fx{}, - &propertyfx.Fx{}, - }) + Parser, err = block.NewParser( + time.Time{}, + []fxs.Fx{ + &secp256k1fx.Fx{}, + &nftfx.Fx{}, + &propertyfx.Fx{}, + }, + ) if err != nil { panic(err) } diff --git a/wallet/chain/x/context.go b/wallet/chain/x/context.go index bdabe2d925b5..de42f01e857b 100644 --- a/wallet/chain/x/context.go +++ b/wallet/chain/x/context.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x @@ -8,9 +8,14 @@ import ( "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/avm" ) +const Alias = "X" + var _ Context = (*context)(nil) type Context interface { @@ -31,7 +36,7 @@ type context struct { func NewContextFromURI(ctx stdcontext.Context, uri string) (Context, error) { infoClient := info.NewClient(uri) - xChainClient := avm.NewClient(uri, "X") + xChainClient := avm.NewClient(uri, Alias) return NewContextFromClients(ctx, infoClient, xChainClient) } @@ -45,7 +50,7 @@ func NewContextFromClients( return nil, err } - chainID, err := infoClient.GetBlockchainID(ctx, "X") + chainID, err := infoClient.GetBlockchainID(ctx, Alias) if err != nil { return nil, err } @@ -104,3 +109,17 @@ func (c *context) BaseTxFee() uint64 { func (c *context) CreateAssetTxFee() uint64 { return c.createAssetTxFee } + +func newSnowContext(c Context) (*snow.Context, error) { + chainID := c.BlockchainID() + lookup := ids.NewAliaser() + return &snow.Context{ + NetworkID: c.NetworkID(), + SubnetID: constants.PrimaryNetworkID, + ChainID: chainID, + XChainID: chainID, + AVAXAssetID: c.AVAXAssetID(), + Log: logging.NoLog{}, + BCLookup: lookup, + }, lookup.Alias(chainID, Alias) +} diff --git a/wallet/chain/x/signer.go b/wallet/chain/x/signer.go index 98d52d83218d..2c8268199a44 100644 --- a/wallet/chain/x/signer.go +++ b/wallet/chain/x/signer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x diff --git a/wallet/chain/x/signer_visitor.go b/wallet/chain/x/signer_visitor.go index 11e611b8134e..961463ec0256 100644 --- a/wallet/chain/x/signer_visitor.go +++ b/wallet/chain/x/signer_visitor.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x @@ -245,10 +245,13 @@ func sign(tx *txs.Tx, creds []verify.Verifiable, txSigners [][]keychain.Signer) var cred *secp256k1fx.Credential switch credImpl := credIntf.(type) { case *secp256k1fx.Credential: + fxCred.FxID = secp256k1fx.ID cred = credImpl case *nftfx.Credential: + fxCred.FxID = nftfx.ID cred = &credImpl.Credential case *propertyfx.Credential: + fxCred.FxID = propertyfx.ID cred = &credImpl.Credential default: return errUnknownCredentialType diff --git a/wallet/chain/x/wallet.go b/wallet/chain/x/wallet.go index 4e187d58220f..75b3914e199f 100644 --- a/wallet/chain/x/wallet.go +++ b/wallet/chain/x/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x diff --git a/wallet/chain/x/wallet_with_options.go b/wallet/chain/x/wallet_with_options.go index 810f4e94b32a..d62d02efdd40 100644 --- a/wallet/chain/x/wallet_with_options.go +++ b/wallet/chain/x/wallet_with_options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package x diff --git a/wallet/subnet/primary/api.go b/wallet/subnet/primary/api.go index 3ac72c217884..445c518aba25 100644 --- a/wallet/subnet/primary/api.go +++ b/wallet/subnet/primary/api.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package primary diff --git a/wallet/subnet/primary/common/options.go b/wallet/subnet/primary/common/options.go index 0c15be3c8b6b..03cc1c7b5f0a 100644 --- a/wallet/subnet/primary/common/options.go +++ b/wallet/subnet/primary/common/options.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/wallet/subnet/primary/common/spend.go b/wallet/subnet/primary/common/spend.go index d7511317c4bd..42c7fc02fc34 100644 --- a/wallet/subnet/primary/common/spend.go +++ b/wallet/subnet/primary/common/spend.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/wallet/subnet/primary/common/utxos.go b/wallet/subnet/primary/common/utxos.go index 36a86bc1f126..23762a0dd5d7 100644 --- a/wallet/subnet/primary/common/utxos.go +++ b/wallet/subnet/primary/common/utxos.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package common diff --git a/wallet/subnet/primary/example_test.go b/wallet/subnet/primary/example_test.go index 483c049d4ac0..2b8d8b8eeec8 100644 --- a/wallet/subnet/primary/example_test.go +++ b/wallet/subnet/primary/example_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package primary diff --git a/wallet/subnet/primary/examples/add-permissioned-subnet-validator/main.go b/wallet/subnet/primary/examples/add-permissioned-subnet-validator/main.go index d5e8ce422307..33695b35f649 100644 --- a/wallet/subnet/primary/examples/add-permissioned-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/add-permissioned-subnet-validator/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/add-primary-validator/main.go b/wallet/subnet/primary/examples/add-primary-validator/main.go index a56dae23db3a..987229d1ec22 100644 --- a/wallet/subnet/primary/examples/add-primary-validator/main.go +++ b/wallet/subnet/primary/examples/add-primary-validator/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/c-chain-export/main.go b/wallet/subnet/primary/examples/c-chain-export/main.go index fec55c899feb..41ecb5ca814e 100644 --- a/wallet/subnet/primary/examples/c-chain-export/main.go +++ b/wallet/subnet/primary/examples/c-chain-export/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/c-chain-import/main.go b/wallet/subnet/primary/examples/c-chain-import/main.go index b4dc4e603eb3..387d435db4df 100644 --- a/wallet/subnet/primary/examples/c-chain-import/main.go +++ b/wallet/subnet/primary/examples/c-chain-import/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/create-asset/main.go b/wallet/subnet/primary/examples/create-asset/main.go index 30804f083df6..54015dda239d 100644 --- a/wallet/subnet/primary/examples/create-asset/main.go +++ b/wallet/subnet/primary/examples/create-asset/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/create-chain/main.go b/wallet/subnet/primary/examples/create-chain/main.go index 5e6898a1b649..ea98579f6f21 100644 --- a/wallet/subnet/primary/examples/create-chain/main.go +++ b/wallet/subnet/primary/examples/create-chain/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/create-locked-stakeable/main.go b/wallet/subnet/primary/examples/create-locked-stakeable/main.go index e688968e9e8a..32cdcf983ba0 100644 --- a/wallet/subnet/primary/examples/create-locked-stakeable/main.go +++ b/wallet/subnet/primary/examples/create-locked-stakeable/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/create-subnet/main.go b/wallet/subnet/primary/examples/create-subnet/main.go index add98ea7931c..e471e68f5be9 100644 --- a/wallet/subnet/primary/examples/create-subnet/main.go +++ b/wallet/subnet/primary/examples/create-subnet/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/get-p-chain-balance/main.go b/wallet/subnet/primary/examples/get-p-chain-balance/main.go index a19b3d6eae76..fd14eb4ea588 100644 --- a/wallet/subnet/primary/examples/get-p-chain-balance/main.go +++ b/wallet/subnet/primary/examples/get-p-chain-balance/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/get-x-chain-balance/main.go b/wallet/subnet/primary/examples/get-x-chain-balance/main.go index a5474f7e1095..c43d4c9dd229 100644 --- a/wallet/subnet/primary/examples/get-x-chain-balance/main.go +++ b/wallet/subnet/primary/examples/get-x-chain-balance/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/examples/remove-subnet-validator/main.go b/wallet/subnet/primary/examples/remove-subnet-validator/main.go index 2842c7c0a790..50639943b630 100644 --- a/wallet/subnet/primary/examples/remove-subnet-validator/main.go +++ b/wallet/subnet/primary/examples/remove-subnet-validator/main.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package main diff --git a/wallet/subnet/primary/utxos.go b/wallet/subnet/primary/utxos.go index f8c9ce20a694..71f7629856e1 100644 --- a/wallet/subnet/primary/utxos.go +++ b/wallet/subnet/primary/utxos.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package primary diff --git a/wallet/subnet/primary/wallet.go b/wallet/subnet/primary/wallet.go index 54de390d029c..3bb3e9965684 100644 --- a/wallet/subnet/primary/wallet.go +++ b/wallet/subnet/primary/wallet.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package primary diff --git a/x/archivedb/batch.go b/x/archivedb/batch.go index dc7502fafd2e..720ed6f9d5d3 100644 --- a/x/archivedb/batch.go +++ b/x/archivedb/batch.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package archivedb diff --git a/x/archivedb/db.go b/x/archivedb/db.go index ca638b982cf4..74b658a31736 100644 --- a/x/archivedb/db.go +++ b/x/archivedb/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package archivedb diff --git a/x/archivedb/db_test.go b/x/archivedb/db_test.go index a22b7768c812..2b1fbea2a46f 100644 --- a/x/archivedb/db_test.go +++ b/x/archivedb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package archivedb diff --git a/x/archivedb/key.go b/x/archivedb/key.go index c90b02761402..86a884cb6c0f 100644 --- a/x/archivedb/key.go +++ b/x/archivedb/key.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package archivedb diff --git a/x/archivedb/key_test.go b/x/archivedb/key_test.go index d56dca5f37fc..e5ea0ff3ced3 100644 --- a/x/archivedb/key_test.go +++ b/x/archivedb/key_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package archivedb @@ -21,9 +21,7 @@ func TestNaturalDescSortingForSameKey(t *testing.T) { entry := [][]byte{key0, key1, key2, key3} expected := [][]byte{key3, key2, key1, key0} - slices.SortFunc(entry, func(i, j []byte) bool { - return bytes.Compare(i, j) < 0 - }) + slices.SortFunc(entry, bytes.Compare) require.Equal(t, expected, entry) } @@ -37,9 +35,7 @@ func TestSortingDifferentPrefix(t *testing.T) { entry := [][]byte{key0, key1, key2, key3} expected := [][]byte{key1, key0, key3, key2} - slices.SortFunc(entry, func(i, j []byte) bool { - return bytes.Compare(i, j) < 0 - }) + slices.SortFunc(entry, bytes.Compare) require.Equal(t, expected, entry) } diff --git a/x/archivedb/prefix_test.go b/x/archivedb/prefix_test.go index 8c6362d745f4..8558b592bf99 100644 --- a/x/archivedb/prefix_test.go +++ b/x/archivedb/prefix_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package archivedb diff --git a/x/archivedb/reader.go b/x/archivedb/reader.go index 0186cbc12712..abac3d854741 100644 --- a/x/archivedb/reader.go +++ b/x/archivedb/reader.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package archivedb diff --git a/x/archivedb/value.go b/x/archivedb/value.go index 2f7ff3e1f0f3..5f5861e23f43 100644 --- a/x/archivedb/value.go +++ b/x/archivedb/value.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package archivedb diff --git a/x/merkledb/README.md b/x/merkledb/README.md index 6c7d9d68775c..29c9f0a73247 100644 --- a/x/merkledb/README.md +++ b/x/merkledb/README.md @@ -1,44 +1,230 @@ -# Path Based Merkelized Radix Trie +# MerkleDB -## TODOs +## Structure -- [ ] Remove special casing around the root node from the physical structure of the hashed tree. -- [ ] Analyze performance of using database snapshots rather than in-memory history -- [ ] Improve intermediate node regeneration after ungraceful shutdown by reusing successfully written subtrees +A _Merkle radix trie_ is a data structure that is both a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree) and a [radix trie](https://en.wikipedia.org/wiki/Radix_tree). MerkleDB is an implementation of a persisted key-value store (sometimes just called "a store") using a Merkle radix trie. We sometimes use "Merkle radix trie" and "MerkleDB instance" interchangeably below, but the two are not the same. MerkleDB maintains data in a Merkle radix trie, but not all Merkle radix tries implement a key-value store. + +Like all tries, a MerkleDB instance is composed of nodes. Conceputally, a node has: + * A unique _key_ which identifies its position in the trie. A node's key is a prefix of its childrens' keys. + * A unique _ID_, which is the hash of the node. + * A _children_ array, where each element is the ID of the child at that index. A child at a lower index is to the "left" of children at higher indices. + * An optional value. If a node has a value, then the node's key maps to its value in the key-value store. Otherwise the key isn't present in the store. + +and looks like this: +``` +Node ++--------------------------------------------+ +| ID: 32 bytes | +| Key: ? bytes | +| Value: Some(value) | None | +| Children: | +| 0: Some(child0ID) | None | +| 1: Some(child2ID) | None | +| ... | +| BranchFactor-1: Some(child15ID) | None | ++--------------------------------------------+ +``` + +This conceptual picture differs slightly from the implementation of the `node` in MerkleDB but is still useful in understanding how MerkleDB works. + +## Root IDs and Revisions + +The ID of the root node is called the _root ID_, or sometimes just the _root_ of the trie. If any node in a MerkleDB instance changes, the root ID will change. This follows from the fact that changing a node changes its ID, which changes its parent's reference to it, which changes the parent, which changes the parent's ID, and so on until the root. + +The root ID also serves as a unique identifier of a given state; instances with the same key-value mappings always have the same root ID, and instances with different key-value mappings always have different root IDs. We call a state with a given root ID a _revision_, and we sometimes say that a MerkleDB instance is "at" a given revision or root ID. The two are equivalent. + +## Views + +A _view_ is a proposal to modify a MerkleDB. If a view is _committed_, its changes are written to the MerkleDB. It can be queried, and when it is, it returns the state that the MerkleDB will contain if the view is committed. A view is immutable after creation. Namely, none of its key-value pairs can be modified. + +A view can be built atop the MerkleDB itself, or it can be built atop another view. Views can be chained together. For example, we might have: + +``` + db + / \ +view1 view2 + | +view3 +``` + +where `view1` and `view2` are built atop MerkleDB instance `db` and `view3` is built atop `view1`. Equivalently, we say that `db` is the parent of `view1` and `view2`, and `view3` is a child of `view1`. `view1` and `view2` are _siblings_. + +`view1` contains all the key-value pairs in `db`, except those modified by `view1`. That is, if `db` has key-value pair `(k,v)`, and `view1` doesn't modify that pair, then `view1` will return `v` when queried for the value of `k`. If `db` has `(k,v)` but `view1` modifies the pair to `(k, v')` then it will return `v'` when queried for the value of `k`. Similar for `view2`. + +`view3` has all of the key-value pairs as `view1`, except those modified in `view3`. That is, it has the state after the changes in `view1` are applied to `db`, followed by those in `view3`. -## Introduction +A view can be committed only if its parent is the MerkleDB (and not another view). A view can only be committed once. In the above diagram, `view3` can't be committed until `view1` is committed. -The Merkle Trie is a data structure that allows efficient and secure verification of the contents. It is a combination of a [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) and a [Radix Trie](https://en.wikipedia.org/wiki/Radix_tree). +When a view is created, we don't apply changes to the trie's structure or calculate the new IDs of nodes because this requires expensive hashing. Instead, we lazily apply changes and calculate node IDs (including the root ID) when necessary. -The trie contains `Merkle Nodes`, which store key/value and children information. +### Validity + +When a view is committed, its siblings and all of their descendants are _invalidated_. An invalid view can't be read or committed. Method calls on it will return `ErrInvalid`. + +In the diagram above, if `view1` were committed, `view2` would be invalidated. It `view2` were committed, `view1` and `view3` would be invalidated. + +## Proofs + +### Simple Proofs + +MerkleDB instances can produce _merkle proofs_, sometimes just called "proofs." A merkle proof uses cryptography to prove that a given key-value pair is or isn't in the key-value store with a given root. That is, a MerkleDB instance with root ID `r` can create a proof that shows that it has a key-value pair `(k,v)`, or that `k` is not present. + +Proofs can be useful as a client fetching data in a Byzantine environment. Suppose there are one or more servers, which may be Byzantine, serving a distirbuted key-value store using MerkleDB, and a client that wants to retrieve key-value pairs. Suppose also that the client can learn a "trusted" root ID, perhaps because it's posted on a blockchain. The client can request a key-value pair from a server, and use the returned proof to verify that the returned key-value pair is actually in the key-value store with (or isn't, as it were.) + +```mermaid +flowchart TD + A[Client] -->|"ProofRequest(k,r)"| B(Server) + B --> |"Proof(k,r)"| C(Client) + C --> |Proof Valid| D(Client trusts key-value pair from proof) + C --> |Proof Invalid| E(Client doesn't trust key-value pair from proof) +``` + +`ProofRequest(k,r)` is a request for the value that `k` maps to in the MerkleDB instance with root `r` and a proof for that data's correctness. -Each `Merkle Node` represents a key path into the trie. It stores the key, the value (if one exists), its ID, and the IDs of its children nodes. The children have keys that contain the current node's key path as a prefix, and the index of each child indicates the next nibble in that child's key. For example, if we have two nodes, Node 1 with key path `0x91A` and Node 2 with key path `0x91A4`, Node 2 is stored in index `0x4` of Node 1's children (since 0x4 is the first value after the common prefix). +`Proof(k,r)` is a proof that purports to show either that key-value pair `(k,v)` exists in the revision at `r`, or that `k` isn't in the revision. -To reduce the depth of nodes in the trie, a `Merkle Node` utilizes path compression. Instead of having a long chain of nodes each containing only a single nibble of the key, we can "compress" the path by recording additional key information with each of a node's children. For example, if we have three nodes, Node 1 with key path `0x91A`, Node 2 with key path `0x91A4`, and Node 3 with key path `0x91A5132`, then Node 1 has a key of `0x91A`. Node 2 is stored at index `0x4` of Node 1's children since `4` is the next nibble in Node 2's key after skipping the common nibbles from Node 1's key. Node 3 is stored at index `0x5` of Node 1's children. Rather than have extra nodes for the remainder of Node 3's key, we instead store the rest of the key (`132`) in Node 1's children info. +#### Verification +A proof is represented as: + +```go +type Proof struct { + // Nodes in the proof path from root --> target key + // (or node that would be where key is if it doesn't exist). + // Always contains at least the root. + Path []ProofNode + + // This is a proof that [key] exists/doesn't exist. + Key Key + + // Nothing if [Key] isn't in the trie. + // Otherwise, the value corresponding to [Key]. + Value maybe.Maybe[[]byte] +} + +type ProofNode struct { + Key Key + // Nothing if this is an intermediate node. + // The value in this node if its length < [HashLen]. + // The hash of the value in this node otherwise. + ValueOrHash maybe.Maybe[[]byte] + Children map[byte]ids.ID +} ``` -+-----------------------------------+ -| Merkle Node | -| | -| ID: 0x0131 | an id representing the current node, derived from the node's value and all children ids -| Key: 0x91 | prefix of the key, representing the location of the node in the trie -| Value: 0x00 | the value, if one exists, that is stored at the key (keyPrefix + compressedKey) -| Children: | a map of children node ids for any nodes in the trie that have this node's key as a prefix -| 0: [:0x00542F] | child 0 represents a node with key 0x910 with ID 0x00542F -| 1: [0x432:0xA0561C] | child 1 represents a node with key 0x911432 with ID 0xA0561C -| ... | -| 15: [0x9A67B:0x02FB093] | child 15 represents a node with key 0x91F9A67B with ID 0x02FB093 -+-----------------------------------+ + +For an inclusion proof, the last node in `Path` should be the one containing `Key`. +For an exclusion proof, the last node is either: +* The node that would be the parent of `Key`, if such node has no child at the index `Key` would be at. +* The node at the same child index `Key` would be at, otherwise. + +In other words, the last node of a proof says either, "the key is in the trie, and this node contains it," or, "the key isn't in the trie, and this node's existence precludes the existence of the key." + +The prover can't simply trust that such a node exists, though. It has to verify this. The prover creates an empty trie and inserts the nodes in `Path`. If the root ID of this trie matches the `r`, the verifier can trust that the last node really does exist in the trie. If the last node _didn't_ really exist, the proof creator couldn't create `Path` such that its nodes both imply the existence of the ("fake") last node and also result in the correct root ID. This follows from the one-way property of hashing. + +### Range Proofs + +MerkleDB instances can also produce _range proofs_. A range proof proves that a contiguous set of key-value pairs is or isn't in the key-value store with a given root. This is similar to the merkle proofs described above, except for multiple key-value pairs. + +```mermaid +flowchart TD + A[Client] -->|"RangeProofRequest(start,end,r)"| B(Server) + B --> |"RangeProof(start,end,r)"| C(Client) + C --> |Proof Valid| D(Client trusts key-value pairs) + C --> |Proof Invalid| E(Client doesn't trust key-value pairs) ``` +`RangeProofRequest(start,end,r)` is a request for all of the key-value pairs, in order, between keys `start` and `end` at revision `r`. + +`RangeProof(start,end,r)` contains a list of key-value pairs `kvs`, sorted by increasing key. It purports to show that, at revision `r`: +* Each element of `kvs` is a key-value pair in the store. +* There are no keys at/after `start` but before the first key in `kvs`. +* For adjacent key-value pairs `(k1,v1)` and `(k2,v2)` in `kvs`, there doesn't exist a key-value pair `(k3,v3)` in the store such that `k1 < k3 < k2`. In other words, `kvs` is a contiguous set of key-value pairs. + +Clients can use range proofs to efficiently download many key-value pairs at a time from a MerkleDB instance, as opposed to getting a proof for each key-value pair individually. + +#### Verification + +Like simple proofs, range proofs can be verified without any additional context or knowledge of the contents of the key-value store. + +A range proof is represented as: + +```go +type RangeProof struct { + // Invariant: At least one of [StartProof], [EndProof], [KeyValues] is non-empty. + + // A proof that the smallest key in the requested range does/doesn't exist. + // Note that this may not be an entire proof -- nodes are omitted if + // they are also in [EndProof]. + StartProof []ProofNode + + // If no upper range bound was given and [KeyValues] is empty, this is empty. + // + // If no upper range bound was given and [KeyValues] is non-empty, this is + // a proof for the largest key in [KeyValues]. + // + // Otherwise this is a proof for the upper range bound. + EndProof []ProofNode + + // This proof proves that the key-value pairs in [KeyValues] are in the trie. + // Sorted by increasing key. + KeyValues []KeyValue +} +``` + +The prover creates an empty trie and adds to it all of the key-value pairs in `KeyValues`. + +Then, it inserts: +* The nodes in `StartProof` +* The nodes in `EndProof` + +For each node in `StartProof`, the prover only populates `Children` entries whose key is before `start`. +For each node in `EndProof`, it populates only `Children` entries whose key is after `end`, where `end` is the largest key proven by the range proof. + +Then, it calculates the root ID of this trie and compares it to the expected one. + +If the proof: +* Omits any key-values in the range +* Includes additional key-values that aren't really in the range +* Provides an incorrect value for a key in the range + +then the actual root ID won't match the expected root ID. + +Like simple proofs, range proof verification relies on the fact that the proof generator can't forge data such that it results in a trie with both incorrect data and the correct root ID. + +### Change Proofs + +Finally, MerkleDB instances can produce and verify _change proofs_. A change proof proves that a set of key-value changes were applied to a MerkleDB instance in the process of changing its root from `r` to `r'`. For example, suppose there's an instance with root `r` + +```mermaid +flowchart TD + A[Client] -->|"ChangeProofRequest(start,end,r,r')"| B(Server) + B --> |"ChangeProof(start,end,r,r')"| C(Client) + C --> |Proof Valid| D(Client trusts key-value pair changes) + C --> |Proof Invalid| E(Client doesn't trust key-value changes) +``` + +`ChangeProofRequest(start,end,r,r')` is a request for all key-value pairs, in order, between keys `start` and `end`, that occurred after the root of was `r` and before the root was `r'`. + +`ChangeProof(start,end,r,r')` contains a set of key-value pairs `kvs`. It purports to show that: +* Each element of `kvs` is a key-value pair in the at revision `r'` but not at revision `r`. +* There are no key-value changes between `r` and `r'` such that the key is at/after `start` but before the first key in `kvs`. +* For adjacent key-value changes `(k1,v1)` and `(k2,v2)` in `kvs`, there doesn't exist a key-value change `(k3,v3)` between `r` and `r'` such that `k1 < k3 < k2`. In other words, `kvs` is a contiguous set of key-value changes. + +Change proofs are useful for applying changes between revisions. For example, suppose a client has a MerkleDB instance at revision `r`. The client learns that the state has been updated and that the new root is `r'`. The client can request a change proof from a server at revision `r'`, and apply the changes in the change proof to change its state from `r` to `r'`. Note that `r` and `r'` need not be "consecutive" revisions. For example, it's possible that the state goes from revision `r` to `r1` to `r2` to `r'`. The client apply changes to get directly from `r` to `r'`, without ever needing to be at revision `r1` or `r2`. + +#### Verification + +Unlike simple proofs and range proofs, change proofs require additional context to verify. Namely, the prover must have the trie at the start root `r`. + +The verification algorithm is similar to range proofs, except that instead of inserting the key-value changes, start proof and end proof into an empty trie, they are added to the trie at revision `r`. + ## Serialization ### Node -Nodes are persisted in an underlying database. In order to persist nodes, we must first serialize them. -Serialization is done by the `encoder` interface defined in `codec.go`. +Nodes are persisted in an underlying database. In order to persist nodes, we must first serialize them. Serialization is done by the `encoder` interface defined in `codec.go`. -The node serialization format is as follows: +The node serialization format is: ``` +----------------------------------------------------+ @@ -91,9 +277,9 @@ For each child of the node, we have an additional: +----------------------------------------------------+ | Child index (varint) | +----------------------------------------------------+ -| Child compressed key length (varint) | +| Child compressed key length (varint) | +----------------------------------------------------+ -| Child compressed key (variable length bytes) | +| Child compressed key (variable length bytes) | +----------------------------------------------------+ | Child ID (32 bytes) | +----------------------------------------------------+ @@ -134,10 +320,10 @@ The second is at child index `14`, has compressed key `0x0F0F0F` and ID (in hex) | Child index (varint) | | 0x00 | +--------------------------------------------------------------------+ -| Child compressed key length (varint) | +| Child compressed key length (varint) | | 0x02 | +--------------------------------------------------------------------+ -| Child compressed key (variable length bytes) | +| Child compressed key (variable length bytes) | | 0x10 | +--------------------------------------------------------------------+ | Child ID (32 bytes) | @@ -146,10 +332,10 @@ The second is at child index `14`, has compressed key `0x0F0F0F` and ID (in hex) | Child index (varint) | | 0x0E | +--------------------------------------------------------------------+ -| Child compressed key length (varint) | +| Child compressed key length (varint) | | 0x06 | +--------------------------------------------------------------------+ -| Child compressed key (variable length bytes) | +| Child compressed key (variable length bytes) | | 0xFFF0 | +--------------------------------------------------------------------+ | Child ID (32 bytes) | @@ -164,6 +350,13 @@ Each node must have a unique ID that identifies it. This ID is calculated by has * The node's value digest * The node's key +The node's value digest is: +* Nothing, if the node has no value +* The node's value, if it has a value < 32 bytes +* The hash of the node's value otherwise + +We use the node's value digest rather than its value when hashing so that when we send proofs, each `ProofNode` doesn't need to contain the node's value, which could be very large. By using the value digest, we allow a proof verifier to calculate a node's ID while limiting the size of the data sent to the verifier. + Specifically, we encode these values in the following way: ``` @@ -216,39 +409,40 @@ Bytes are encoded by simply copying them onto the buffer. ## Design choices ### []byte copying -Nodes contain a []byte which represents its value. This slice should never be edited internally. This allows usage without having to make copies of it for safety. -Anytime these values leave the library, for example in `Get`, `GetValue`, `GetProof`, `GetRangeProof`, etc, they need to be copied into a new slice to prevent -edits made outside the library from being reflected in the DB/TrieViews. + +A node may contain a value, which is represented in Go as a `[]byte`. This slice is never edited, allowing it to be used without copying it first in many places. When a value leaves the library, for example when returned in `Get`, `GetValue`, `GetProof`, `GetRangeProof`, etc., the value is copied to prevent edits made outside the library from being reflected in the database. ### Split Node Storage -The nodes are stored under two different prefixes depending on if the node contains a value. -If it does contain a value it is stored within the ValueNodeDB and if it doesn't it is stored in the IntermediateNodeDB. -By splitting the nodes up by value, it allows better key/value iteration and a more compact key format. -### Single node type +Nodes with values ("value nodes") are persisted under one database prefix, while nodes without values ("intermediate nodes") are persisted under another database prefix. This separation allows for easy iteration over all key-value pairs in the database, as this is simply iterating over the database prefix containing value nodes. -A `Merkle Node` holds the IDs of its children, its value, as well as any key extension. This simplifies some logic and allows all of the data about a node to be loaded in a single database read. This trades off a small amount of storage efficiency (some fields may be `nil` but are still stored for every node). +### Single Node Type -### Validity +MerkleDB uses one type to represent nodes, rather than having multiple types (e.g. branch nodes, value nodes, extension nodes) as other Merkle Trie implementations do. -A `trieView` is built atop another trie, and there may be other `trieView`s built atop the same trie. We call these *siblings*. If one sibling is committed to database, we *invalidate* all other siblings and their descendants. Operations on an invalid trie return `ErrInvalid`. The children of the committed `trieView` are updated so that their new `parentTrie` is the database. +Not using extension nodes results in worse storage efficiency (some nodes may have mostly empty children) but simpler code. ### Locking `merkleDB` has a `RWMutex` named `lock`. Its read operations don't store data in a map, so a read lock suffices for read operations. `merkleDB` has a `Mutex` named `commitLock`. It enforces that only a single view/batch is attempting to commit to the database at one time. `lock` is insufficient because there is a period of view preparation where read access should still be allowed, followed by a period where a full write lock is needed. The `commitLock` ensures that only a single goroutine makes the transition from read => write. -A `trieView` is built atop another trie, which may be the underlying `merkleDB` or another `trieView`. +A `view` is built atop another trie, which may be the underlying `merkleDB` or another `view`. We use locking to guarantee atomicity/consistency of trie operations. -`trieView` has a `RWMutex` named `commitLock` which ensures that we don't create a view atop the `trieView` while it's being committed. -It also has a `RWMutex` named `validityTrackingLock` that is held during methods that change the view's validity, tracking of child views' validity, or of the `trieView` parent trie. This lock ensures that writing/reading from `trieView` or any of its descendants is safe. -The `CommitToDB` method grabs the `merkleDB`'s `commitLock`. This is the only `trieView` method that modifies the underlying `merkleDB`. +`view` has a `RWMutex` named `commitLock` which ensures that we don't create a view atop the `view` while it's being committed. +It also has a `RWMutex` named `validityTrackingLock` that is held during methods that change the view's validity, tracking of child views' validity, or of the `view` parent trie. This lock ensures that writing/reading from `view` or any of its descendants is safe. +The `CommitToDB` method grabs the `merkleDB`'s `commitLock`. This is the only `view` method that modifies the underlying `merkleDB`. -In some of `merkleDB`'s methods, we create a `trieView` and call unexported methods on it without locking it. +In some of `merkleDB`'s methods, we create a `view` and call unexported methods on it without locking it. We do so because the exported counterpart of the method read locks the `merkleDB`, which is already locked. This pattern is safe because the `merkleDB` is locked, so no data under the view is changing, and nobody else has a reference to the view, so there can't be any concurrent access. -To prevent deadlocks, `trieView` and `merkleDB` never acquire the `commitLock` of descendant views. +To prevent deadlocks, `view` and `merkleDB` never acquire the `commitLock` of descendant views. That is, locking is always done from a view toward to the underlying `merkleDB`, never the other way around. The `validityTrackingLock` goes the opposite way. A view can lock the `validityTrackingLock` of its children, but not its ancestors. Because of this, any function that takes the `validityTrackingLock` must not take the `commitLock` as this may cause a deadlock. Keeping `commitLock` solely in the ancestor direction and `validityTrackingLock` solely in the descendant direction prevents deadlocks from occurring. + +## TODOs + +- [ ] Analyze performance of using database snapshots rather than in-memory history +- [ ] Improve intermediate node regeneration after ungraceful shutdown by reusing successfully written subtrees diff --git a/x/merkledb/batch.go b/x/merkledb/batch.go index 82ff3533aaaf..033200409b79 100644 --- a/x/merkledb/batch.go +++ b/x/merkledb/batch.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb diff --git a/x/merkledb/cache.go b/x/merkledb/cache.go index 7b280c1208d4..ee2e7f0b2713 100644 --- a/x/merkledb/cache.go +++ b/x/merkledb/cache.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -25,6 +25,7 @@ type onEvictCache[K comparable, V any] struct { onEviction func(K, V) error } +// [size] must always return a positive number. func newOnEvictCache[K comparable, V any]( maxSize int, size func(K, V) int, diff --git a/x/merkledb/cache_test.go b/x/merkledb/cache_test.go index e0939df9451d..9883c23af38c 100644 --- a/x/merkledb/cache_test.go +++ b/x/merkledb/cache_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb diff --git a/x/merkledb/codec.go b/x/merkledb/codec.go index 973dd5888ab6..eae205631192 100644 --- a/x/merkledb/codec.go +++ b/x/merkledb/codec.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -9,6 +9,7 @@ import ( "errors" "io" "math" + "math/bits" "sync" "golang.org/x/exp/maps" @@ -29,11 +30,8 @@ const ( minDBNodeLen = minMaybeByteSliceLen + minVarIntLen minChildLen = minVarIntLen + minKeyLen + ids.IDLen + boolLen - estimatedKeyLen = 64 - estimatedValueLen = 64 - estimatedCompressedKeyLen = 8 - // Child index, child compressed key, child ID, child has value - estimatedNodeChildLen = minVarIntLen + estimatedCompressedKeyLen + ids.IDLen + boolLen + estimatedKeyLen = 64 + estimatedValueLen = 64 // Child index, child ID hashValuesChildLen = minVarIntLen + ids.IDLen ) @@ -62,15 +60,18 @@ type encoderDecoder interface { type encoder interface { // Assumes [n] is non-nil. encodeDBNode(n *dbNode) []byte + encodedDBNodeSize(n *dbNode) int // Returns the bytes that will be hashed to generate [n]'s ID. // Assumes [n] is non-nil. encodeHashValues(n *node) []byte + encodeKey(key Key) []byte } type decoder interface { // Assumes [n] is non-nil. decodeDBNode(bytes []byte, n *dbNode) error + decodeKey(bytes []byte) (Key, error) } func newCodec() encoderDecoder { @@ -91,16 +92,47 @@ type codecImpl struct { varIntPool sync.Pool } -func (c *codecImpl) encodeDBNode(n *dbNode) []byte { - var ( - numChildren = len(n.children) - // Estimate size of [n] to prevent memory allocations - estimatedLen = estimatedValueLen + minVarIntLen + estimatedNodeChildLen*numChildren - buf = bytes.NewBuffer(make([]byte, 0, estimatedLen)) - ) +func (c *codecImpl) childSize(index byte, childEntry *child) int { + // * index + // * child ID + // * child key + // * bool indicating whether the child has a value + return c.uintSize(uint64(index)) + ids.IDLen + c.keySize(childEntry.compressedKey) + boolLen +} + +// based on the current implementation of codecImpl.encodeUint which uses binary.PutUvarint +func (*codecImpl) uintSize(value uint64) int { + if value == 0 { + return 1 + } + return (bits.Len64(value) + 6) / 7 +} + +func (c *codecImpl) keySize(p Key) int { + return c.uintSize(uint64(p.length)) + bytesNeeded(p.length) +} +func (c *codecImpl) encodedDBNodeSize(n *dbNode) int { + // * number of children + // * bool indicating whether [n] has a value + // * the value (optional) + // * children + size := c.uintSize(uint64(len(n.children))) + boolLen + if n.value.HasValue() { + valueLen := len(n.value.Value()) + size += c.uintSize(uint64(valueLen)) + valueLen + } + // for each non-nil entry, we add the additional size of the child entry + for index, entry := range n.children { + size += c.childSize(index, entry) + } + return size +} + +func (c *codecImpl) encodeDBNode(n *dbNode) []byte { + buf := bytes.NewBuffer(make([]byte, 0, c.encodedDBNodeSize(n))) c.encodeMaybeByteSlice(buf, n.value) - c.encodeUint(buf, uint64(numChildren)) + c.encodeUint(buf, uint64(len(n.children))) // Note we insert children in order of increasing index // for determinism. keys := maps.Keys(n.children) @@ -108,7 +140,7 @@ func (c *codecImpl) encodeDBNode(n *dbNode) []byte { for _, index := range keys { entry := n.children[index] c.encodeUint(buf, uint64(index)) - c.encodeKey(buf, entry.compressedKey) + c.encodeKeyToBuffer(buf, entry.compressedKey) _, _ = buf.Write(entry.id[:]) c.encodeBool(buf, entry.hasValue) } @@ -134,7 +166,7 @@ func (c *codecImpl) encodeHashValues(n *node) []byte { _, _ = buf.Write(entry.id[:]) } c.encodeMaybeByteSlice(buf, n.valueDigest) - c.encodeKey(buf, n.key) + c.encodeKeyToBuffer(buf, n.key) return buf.Bytes() } @@ -172,7 +204,7 @@ func (c *codecImpl) decodeDBNode(b []byte, n *dbNode) error { } previousChild = index - compressedKey, err := c.decodeKey(src) + compressedKey, err := c.decodeKeyFromReader(src) if err != nil { return err } @@ -330,12 +362,31 @@ func (*codecImpl) decodeID(src *bytes.Reader) (ids.ID, error) { return id, err } -func (c *codecImpl) encodeKey(dst *bytes.Buffer, key Key) { +func (c *codecImpl) encodeKey(key Key) []byte { + estimatedLen := binary.MaxVarintLen64 + len(key.Bytes()) + dst := bytes.NewBuffer(make([]byte, 0, estimatedLen)) + c.encodeKeyToBuffer(dst, key) + return dst.Bytes() +} + +func (c *codecImpl) encodeKeyToBuffer(dst *bytes.Buffer, key Key) { c.encodeUint(dst, uint64(key.length)) _, _ = dst.Write(key.Bytes()) } -func (c *codecImpl) decodeKey(src *bytes.Reader) (Key, error) { +func (c *codecImpl) decodeKey(b []byte) (Key, error) { + src := bytes.NewReader(b) + key, err := c.decodeKeyFromReader(src) + if err != nil { + return Key{}, err + } + if src.Len() != 0 { + return Key{}, errExtraSpace + } + return key, err +} + +func (c *codecImpl) decodeKeyFromReader(src *bytes.Reader) (Key, error) { if minKeyLen > src.Len() { return Key{}, io.ErrUnexpectedEOF } diff --git a/x/merkledb/codec_test.go b/x/merkledb/codec_test.go index 699db9a4bd81..455b75e1bed1 100644 --- a/x/merkledb/codec_test.go +++ b/x/merkledb/codec_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -81,21 +81,14 @@ func FuzzCodecKey(f *testing.F) { ) { require := require.New(t) codec := codec.(*codecImpl) - reader := bytes.NewReader(b) - startLen := reader.Len() - got, err := codec.decodeKey(reader) + got, err := codec.decodeKey(b) if err != nil { t.SkipNow() } - endLen := reader.Len() - numRead := startLen - endLen // Encoding [got] should be the same as [b]. - var buf bytes.Buffer - codec.encodeKey(&buf, got) - bufBytes := buf.Bytes() - require.Len(bufBytes, numRead) - require.Equal(b[:numRead], bufBytes) + gotBytes := codec.encodeKey(got) + require.Equal(b, gotBytes) }, ) } @@ -165,7 +158,7 @@ func FuzzCodecDBNodeDeterministic(f *testing.F) { } nodeBytes := codec.encodeDBNode(&node) - + require.Len(nodeBytes, codec.encodedDBNodeSize(&node)) var gotNode dbNode require.NoError(codec.decodeDBNode(nodeBytes, &gotNode)) require.Equal(node, gotNode) @@ -248,7 +241,28 @@ func FuzzEncodeHashValues(f *testing.F) { func TestCodecDecodeKeyLengthOverflowRegression(t *testing.T) { codec := codec.(*codecImpl) - bytes := bytes.NewReader(binary.AppendUvarint(nil, math.MaxInt)) - _, err := codec.decodeKey(bytes) + _, err := codec.decodeKey(binary.AppendUvarint(nil, math.MaxInt)) require.ErrorIs(t, err, io.ErrUnexpectedEOF) } + +func TestUintSize(t *testing.T) { + c := codec.(*codecImpl) + + // Test lower bound + expectedSize := c.uintSize(0) + actualSize := binary.PutUvarint(make([]byte, binary.MaxVarintLen64), 0) + require.Equal(t, expectedSize, actualSize) + + // Test upper bound + expectedSize = c.uintSize(math.MaxUint64) + actualSize = binary.PutUvarint(make([]byte, binary.MaxVarintLen64), math.MaxUint64) + require.Equal(t, expectedSize, actualSize) + + // Test powers of 2 + for power := 0; power < 64; power++ { + n := uint64(1) << uint(power) + expectedSize := c.uintSize(n) + actualSize := binary.PutUvarint(make([]byte, binary.MaxVarintLen64), n) + require.Equal(t, expectedSize, actualSize, power) + } +} diff --git a/x/merkledb/db.go b/x/merkledb/db.go index c813a9478b1f..021ebc12d7d8 100644 --- a/x/merkledb/db.go +++ b/x/merkledb/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -32,13 +32,13 @@ import ( ) const ( - // TODO: name better rebuildViewSizeFractionOfCacheSize = 50 minRebuildViewSizePerCommit = 1000 clearBatchSize = units.MiB rebuildIntermediateDeletionWriteSize = units.MiB valueNodePrefixLen = 1 + cacheEntryOverHead = 8 ) var ( @@ -51,11 +51,11 @@ var ( intermediateNodePrefix = []byte{2} cleanShutdownKey = []byte(string(metadataPrefix) + "cleanShutdown") + rootDBKey = []byte(string(metadataPrefix) + "root") hadCleanShutdown = []byte{1} didNotHaveCleanShutdown = []byte{0} - errSameRoot = errors.New("start and end root are the same") - errNoNewSentinel = errors.New("there was no updated sentinel node in change list") + errSameRoot = errors.New("start and end root are the same") ) type ChangeProofer interface { @@ -64,6 +64,11 @@ type ChangeProofer interface { // Returns at most [maxLength] key/value pairs. // Returns [ErrInsufficientHistory] if this node has insufficient history // to generate the proof. + // Returns ErrEmptyProof if [endRootID] is ids.Empty. + // Note that [endRootID] == ids.Empty means the trie is empty + // (i.e. we don't need a change proof.) + // Returns [ErrNoEndRoot], which wraps [ErrInsufficientHistory], if the + // history doesn't contain the [endRootID]. GetChangeProof( ctx context.Context, startRootID ids.ID, @@ -100,6 +105,9 @@ type RangeProofer interface { // [start, end] when the root of the trie was [rootID]. // If [start] is Nothing, there's no lower bound on the range. // If [end] is Nothing, there's no upper bound on the range. + // Returns ErrEmptyProof if [rootID] is ids.Empty. + // Note that [rootID] == ids.Empty means the trie is empty + // (i.e. we don't need a range proof.) GetRangeProofAtRoot( ctx context.Context, rootID ids.ID, @@ -153,16 +161,19 @@ type Config struct { // // If 0 is specified, [runtime.NumCPU] will be used. RootGenConcurrency uint - // The number of bytes to write to disk when intermediate nodes are evicted - // from their cache and written to disk. - EvictionBatchSize uint + // The number of changes to the database that we store in memory in order to // serve change proofs. HistoryLength uint - // The number of bytes to cache nodes with values. + // The number of bytes used to cache nodes with values. ValueNodeCacheSize uint - // The number of bytes to cache nodes without values. + // The number of bytes used to cache nodes without values. IntermediateNodeCacheSize uint + // The number of bytes used to store nodes without values in memory before forcing them onto disk. + IntermediateWriteBufferSize uint + // The number of bytes to write to disk when intermediate nodes are evicted + // from the write buffer and written to disk. + IntermediateWriteBatchSize uint // If [Reg] is nil, metrics are collected locally but not exported through // Prometheus. // This may be useful for testing. @@ -171,7 +182,7 @@ type Config struct { Tracer trace.Tracer } -// merkleDB can only be edited by committing changes from a trieView. +// merkleDB can only be edited by committing changes from a view. type merkleDB struct { // Must be held when reading/writing fields. lock sync.RWMutex @@ -201,14 +212,14 @@ type merkleDB struct { debugTracer trace.Tracer infoTracer trace.Tracer - // The sentinel node of this trie. - // It is the node with a nil key and is the ancestor of all nodes in the trie. - // If it has a value or has multiple children, it is also the root of the trie. - sentinelNode *node - rootID ids.ID + // The root of this trie. + // Nothing if the trie is empty. + root maybe.Maybe[*node] + + rootID ids.ID // Valid children of this trie. - childViews []*trieView + childViews []*view // calculateNodeIDsSema controls the number of goroutines inside // [calculateNodeIDsHelper] at any given time. @@ -248,15 +259,26 @@ func newDatabase( return make([]byte, 0, defaultBufferLength) }, } + trieDB := &merkleDB{ - metrics: metrics, - baseDB: db, - valueNodeDB: newValueNodeDB(db, bufferPool, metrics, int(config.ValueNodeCacheSize)), - intermediateNodeDB: newIntermediateNodeDB(db, bufferPool, metrics, int(config.IntermediateNodeCacheSize), int(config.EvictionBatchSize), BranchFactorToTokenSize[config.BranchFactor]), + metrics: metrics, + baseDB: db, + intermediateNodeDB: newIntermediateNodeDB( + db, + bufferPool, + metrics, + int(config.IntermediateNodeCacheSize), + int(config.IntermediateWriteBufferSize), + int(config.IntermediateWriteBatchSize), + BranchFactorToTokenSize[config.BranchFactor]), + valueNodeDB: newValueNodeDB(db, + bufferPool, + metrics, + int(config.ValueNodeCacheSize)), history: newTrieHistory(int(config.HistoryLength)), debugTracer: getTracerIfEnabled(config.TraceLevel, DebugTrace, config.Tracer), infoTracer: getTracerIfEnabled(config.TraceLevel, InfoTrace, config.Tracer), - childViews: make([]*trieView, 0, defaultPreallocationSize), + childViews: make([]*view, 0, defaultPreallocationSize), calculateNodeIDsSema: semaphore.NewWeighted(int64(rootGenConcurrency)), tokenSize: BranchFactorToTokenSize[config.BranchFactor], } @@ -268,6 +290,9 @@ func newDatabase( // add current root to history (has no changes) trieDB.history.record(&changeSummary{ rootID: trieDB.rootID, + rootChange: change[maybe.Maybe[*node]]{ + after: trieDB.root, + }, values: map[Key]*change[maybe.Maybe[[]byte]]{}, nodes: map[Key]*change[*node]{}, }) @@ -295,7 +320,8 @@ func newDatabase( // Deletes every intermediate node and rebuilds them by re-adding every key/value. // TODO: make this more efficient by only clearing out the stale portions of the trie. func (db *merkleDB) rebuild(ctx context.Context, cacheSize int) error { - db.sentinelNode = newNode(Key{}) + db.root = maybe.Nothing[*node]() + db.rootID = ids.Empty // Delete intermediate nodes. if err := database.ClearPrefix(db.baseDB, intermediateNodePrefix, rebuildIntermediateDeletionWriteSize); err != nil { @@ -309,10 +335,11 @@ func (db *merkleDB) rebuild(ctx context.Context, cacheSize int) error { ) currentOps := make([]database.BatchOp, 0, opsSizeLimit) valueIt := db.NewIterator() - defer valueIt.Release() + // ensure valueIt is captured and release gets called on the latest copy of valueIt + defer func() { valueIt.Release() }() for valueIt.Next() { if len(currentOps) >= opsSizeLimit { - view, err := newTrieView(db, db, ViewChanges{BatchOps: currentOps, ConsumeBytes: true}) + view, err := newView(db, db, ViewChanges{BatchOps: currentOps, ConsumeBytes: true}) if err != nil { return err } @@ -335,7 +362,7 @@ func (db *merkleDB) rebuild(ctx context.Context, cacheSize int) error { if err := valueIt.Error(); err != nil { return err } - view, err := newTrieView(db, db, ViewChanges{BatchOps: currentOps, ConsumeBytes: true}) + view, err := newView(db, db, ViewChanges{BatchOps: currentOps, ConsumeBytes: true}) if err != nil { return err } @@ -361,7 +388,7 @@ func (db *merkleDB) CommitChangeProof(ctx context.Context, proof *ChangeProof) e } } - view, err := newTrieView(db, db, ViewChanges{BatchOps: ops}) + view, err := newView(db, db, ViewChanges{BatchOps: ops}) if err != nil { return err } @@ -402,7 +429,7 @@ func (db *merkleDB) CommitRangeProof(ctx context.Context, start, end maybe.Maybe } // Don't need to lock [view] because nobody else has a reference to it. - view, err := newTrieView(db, db, ViewChanges{BatchOps: ops}) + view, err := newView(db, db, ViewChanges{BatchOps: ops}) if err != nil { return err } @@ -428,6 +455,9 @@ func (db *merkleDB) Close() error { return database.ErrClosed } + // mark all children as no longer valid because the db has closed + db.invalidateChildrenExcept(nil) + db.closed = true db.valueNodeDB.Close() // Flush intermediary nodes to disk. @@ -447,13 +477,8 @@ func (db *merkleDB) PrefetchPaths(keys [][]byte) error { return database.ErrClosed } - // reuse the view so that it can keep repeated nodes in memory - tempView, err := newTrieView(db, db, ViewChanges{}) - if err != nil { - return err - } for _, key := range keys { - if err := db.prefetchPath(tempView, key); err != nil { + if err := db.prefetchPath(key); err != nil { return err } } @@ -468,21 +493,16 @@ func (db *merkleDB) PrefetchPath(key []byte) error { if db.closed { return database.ErrClosed } - tempView, err := newTrieView(db, db, ViewChanges{}) - if err != nil { - return err - } - - return db.prefetchPath(tempView, key) + return db.prefetchPath(key) } -func (db *merkleDB) prefetchPath(view *trieView, keyBytes []byte) error { - return view.visitPathToKey(ToKey(keyBytes), func(n *node) error { - if !n.hasValue() { - return db.intermediateNodeDB.nodeCache.Put(n.key, n) +func (db *merkleDB) prefetchPath(keyBytes []byte) error { + return visitPathToKey(db, ToKey(keyBytes), func(n *node) error { + if n.hasValue() { + db.valueNodeDB.nodeCache.Put(n.key, n) + } else { + db.intermediateNodeDB.nodeCache.Put(n.key, n) } - - db.valueNodeDB.nodeCache.Put(n.key, n) return nil }) } @@ -576,38 +596,23 @@ func (db *merkleDB) GetMerkleRoot(ctx context.Context) (ids.ID, error) { return db.getMerkleRoot(), nil } -// Assumes [db.lock] is read locked. +// Assumes [db.lock] or [db.commitLock] is read locked. func (db *merkleDB) getMerkleRoot() ids.ID { return db.rootID } -// isSentinelNodeTheRoot returns true if the passed in sentinel node has a value and or multiple child nodes -// When this is true, the root of the trie is the sentinel node -// When this is false, the root of the trie is the sentinel node's single child -func isSentinelNodeTheRoot(sentinel *node) bool { - return sentinel.valueDigest.HasValue() || len(sentinel.children) != 1 -} - func (db *merkleDB) GetProof(ctx context.Context, key []byte) (*Proof, error) { db.commitLock.RLock() defer db.commitLock.RUnlock() - return db.getProof(ctx, key) -} + _, span := db.infoTracer.Start(ctx, "MerkleDB.GetProof") + defer span.End() -// Assumes [db.commitLock] is read locked. -// Assumes [db.lock] is not held -func (db *merkleDB) getProof(ctx context.Context, key []byte) (*Proof, error) { if db.closed { return nil, database.ErrClosed } - view, err := newTrieView(db, db, ViewChanges{}) - if err != nil { - return nil, err - } - // Don't need to lock [view] because nobody else has a reference to it. - return view.getProof(ctx, key) + return getProof(db, key) } func (db *merkleDB) GetRangeProof( @@ -619,7 +624,14 @@ func (db *merkleDB) GetRangeProof( db.commitLock.RLock() defer db.commitLock.RUnlock() - return db.getRangeProofAtRoot(ctx, db.getMerkleRoot(), start, end, maxLength) + _, span := db.infoTracer.Start(ctx, "MerkleDB.GetRangeProof") + defer span.End() + + if db.closed { + return nil, database.ErrClosed + } + + return getRangeProof(db, start, end, maxLength) } func (db *merkleDB) GetRangeProofAtRoot( @@ -632,30 +644,23 @@ func (db *merkleDB) GetRangeProofAtRoot( db.commitLock.RLock() defer db.commitLock.RUnlock() - return db.getRangeProofAtRoot(ctx, rootID, start, end, maxLength) -} + _, span := db.infoTracer.Start(ctx, "MerkleDB.GetRangeProofAtRoot") + defer span.End() -// Assumes [db.commitLock] is read locked. -// Assumes [db.lock] is not held -func (db *merkleDB) getRangeProofAtRoot( - ctx context.Context, - rootID ids.ID, - start maybe.Maybe[[]byte], - end maybe.Maybe[[]byte], - maxLength int, -) (*RangeProof, error) { - if db.closed { + switch { + case db.closed: return nil, database.ErrClosed - } - if maxLength <= 0 { + case maxLength <= 0: return nil, fmt.Errorf("%w but was %d", ErrInvalidMaxLength, maxLength) + case rootID == ids.Empty: + return nil, ErrEmptyProof } - historicalView, err := db.getHistoricalViewForRange(rootID, start, end) + historicalTrie, err := db.getTrieAtRootForRange(rootID, start, end) if err != nil { return nil, err } - return historicalView.GetRangeProof(ctx, start, end, maxLength) + return getRangeProof(historicalTrie, start, end, maxLength) } func (db *merkleDB) GetChangeProof( @@ -666,11 +671,16 @@ func (db *merkleDB) GetChangeProof( end maybe.Maybe[[]byte], maxLength int, ) (*ChangeProof, error) { - if start.HasValue() && end.HasValue() && bytes.Compare(start.Value(), end.Value()) == 1 { + _, span := db.infoTracer.Start(ctx, "MerkleDB.GetChangeProof") + defer span.End() + + switch { + case start.HasValue() && end.HasValue() && bytes.Compare(start.Value(), end.Value()) == 1: return nil, ErrStartAfterEnd - } - if startRootID == endRootID { + case startRootID == endRootID: return nil, errSameRoot + case endRootID == ids.Empty: + return nil, ErrEmptyProof } db.commitLock.RLock() @@ -712,13 +722,13 @@ func (db *merkleDB) GetChangeProof( // Since we hold [db.commitlock] we must still have sufficient // history to recreate the trie at [endRootID]. - historicalView, err := db.getHistoricalViewForRange(endRootID, start, largestKey) + historicalTrie, err := db.getTrieAtRootForRange(endRootID, start, largestKey) if err != nil { return nil, err } if largestKey.HasValue() { - endProof, err := historicalView.getProof(ctx, largestKey.Value()) + endProof, err := getProof(historicalTrie, largestKey.Value()) if err != nil { return nil, err } @@ -726,7 +736,7 @@ func (db *merkleDB) GetChangeProof( } if start.HasValue() { - startProof, err := historicalView.getProof(ctx, start.Value()) + startProof, err := getProof(historicalTrie, start.Value()) if err != nil { return nil, err } @@ -763,7 +773,7 @@ func (db *merkleDB) GetChangeProof( func (db *merkleDB) NewView( _ context.Context, changes ViewChanges, -) (TrieView, error) { +) (View, error) { // ensure the db doesn't change while creating the new view db.commitLock.RLock() defer db.commitLock.RUnlock() @@ -772,7 +782,7 @@ func (db *merkleDB) NewView( return nil, database.ErrClosed } - newView, err := newTrieView(db, db, changes) + newView, err := newView(db, db, changes) if err != nil { return nil, err } @@ -845,7 +855,7 @@ func (db *merkleDB) PutContext(ctx context.Context, k, v []byte) error { return database.ErrClosed } - view, err := newTrieView(db, db, ViewChanges{BatchOps: []database.BatchOp{{Key: k, Value: v}}}) + view, err := newView(db, db, ViewChanges{BatchOps: []database.BatchOp{{Key: k, Value: v}}}) if err != nil { return err } @@ -864,7 +874,7 @@ func (db *merkleDB) DeleteContext(ctx context.Context, key []byte) error { return database.ErrClosed } - view, err := newTrieView(db, db, + view, err := newView(db, db, ViewChanges{ BatchOps: []database.BatchOp{{ Key: key, @@ -888,7 +898,7 @@ func (db *merkleDB) commitBatch(ops []database.BatchOp) error { return database.ErrClosed } - view, err := newTrieView(db, db, ViewChanges{BatchOps: ops, ConsumeBytes: true}) + view, err := newView(db, db, ViewChanges{BatchOps: ops, ConsumeBytes: true}) if err != nil { return err } @@ -897,7 +907,8 @@ func (db *merkleDB) commitBatch(ops []database.BatchOp) error { // commitChanges commits the changes in [trieToCommit] to [db]. // Assumes [trieToCommit]'s node IDs have been calculated. -func (db *merkleDB) commitChanges(ctx context.Context, trieToCommit *trieView) error { +// Assumes [db.commitLock] is held. +func (db *merkleDB) commitChanges(ctx context.Context, trieToCommit *view) error { db.lock.Lock() defer db.lock.Unlock() @@ -931,13 +942,7 @@ func (db *merkleDB) commitChanges(ctx context.Context, trieToCommit *trieView) e return nil } - sentinelChange, ok := changes.nodes[Key{}] - if !ok { - return errNoNewSentinel - } - currentValueNodeBatch := db.valueNodeDB.NewBatch() - _, nodesSpan := db.infoTracer.Start(ctx, "MerkleDB.commitChanges.writeNodes") for key, nodeChange := range changes.nodes { shouldAddIntermediate := nodeChange.after != nil && !nodeChange.after.hasValue() @@ -973,17 +978,23 @@ func (db *merkleDB) commitChanges(ctx context.Context, trieToCommit *trieView) e return err } - // Only modify in-memory state after the commit succeeds - // so that we don't need to clean up on error. - db.sentinelNode = sentinelChange.after - db.rootID = changes.rootID db.history.record(changes) - return nil + + // Update root in database. + db.root = changes.rootChange.after + db.rootID = changes.rootID + + if db.root.IsNothing() { + return db.baseDB.Delete(rootDBKey) + } + + rootKey := codec.encodeKey(db.root.Value().key) + return db.baseDB.Put(rootDBKey, rootKey) } // moveChildViewsToDB removes any child views from the trieToCommit and moves them to the db // assumes [db.lock] is held -func (db *merkleDB) moveChildViewsToDB(trieToCommit *trieView) { +func (db *merkleDB) moveChildViewsToDB(trieToCommit *view) { trieToCommit.validityTrackingLock.Lock() defer trieToCommit.validityTrackingLock.Unlock() @@ -991,11 +1002,11 @@ func (db *merkleDB) moveChildViewsToDB(trieToCommit *trieView) { childView.updateParent(db) db.childViews = append(db.childViews, childView) } - trieToCommit.childViews = make([]*trieView, 0, defaultPreallocationSize) + trieToCommit.childViews = make([]*view, 0, defaultPreallocationSize) } // CommitToDB is a no-op for db since it is already in sync with itself. -// This exists to satisfy the TrieView interface. +// This exists to satisfy the View interface. func (*merkleDB) CommitToDB(context.Context) error { return nil } @@ -1014,7 +1025,7 @@ func (db *merkleDB) VerifyChangeProof( case start.HasValue() && end.HasValue() && bytes.Compare(start.Value(), end.Value()) > 0: return ErrStartAfterEnd case proof.Empty(): - return ErrNoMerkleProof + return ErrEmptyProof case end.HasValue() && len(proof.KeyChanges) == 0 && len(proof.EndProof) == 0: // We requested an end proof but didn't get one. return ErrNoEndProof @@ -1100,7 +1111,7 @@ func (db *merkleDB) VerifyChangeProof( } // Don't need to lock [view] because nobody else has a reference to it. - view, err := newTrieView(db, db, ViewChanges{BatchOps: ops, ConsumeBytes: true}) + view, err := newView(db, db, ViewChanges{BatchOps: ops, ConsumeBytes: true}) if err != nil { return err } @@ -1140,7 +1151,7 @@ func (db *merkleDB) VerifyChangeProof( // Invalidates and removes any child views that aren't [exception]. // Assumes [db.lock] is held. -func (db *merkleDB) invalidateChildrenExcept(exception *trieView) { +func (db *merkleDB) invalidateChildrenExcept(exception *view) { isTrackedView := false for _, childView := range db.childViews { @@ -1150,43 +1161,47 @@ func (db *merkleDB) invalidateChildrenExcept(exception *trieView) { isTrackedView = true } } - db.childViews = make([]*trieView, 0, defaultPreallocationSize) + db.childViews = make([]*view, 0, defaultPreallocationSize) if isTrackedView { db.childViews = append(db.childViews, exception) } } +// If the root is on disk, set [db.root] to it. +// Otherwise leave [db.root] as Nothing. func (db *merkleDB) initializeRoot() error { - // Not sure if the sentinel node exists or if it had a value, - // so check under both prefixes - var err error - db.sentinelNode, err = db.intermediateNodeDB.Get(Key{}) + rootKeyBytes, err := db.baseDB.Get(rootDBKey) + if err != nil { + if !errors.Is(err, database.ErrNotFound) { + return err + } + // Root isn't on disk. + return nil + } - if errors.Is(err, database.ErrNotFound) { - // Didn't find the sentinel in the intermediateNodeDB, check the valueNodeDB - db.sentinelNode, err = db.valueNodeDB.Get(Key{}) + // Root is on disk. + rootKey, err := codec.decodeKey(rootKeyBytes) + if err != nil { + return err } + // First, see if root is an intermediate node. + var root *node + root, err = db.getEditableNode(rootKey, false /* hasValue */) if err != nil { if !errors.Is(err, database.ErrNotFound) { return err } - // Sentinel node doesn't exist in either database prefix. - // Make a new one and store it in the intermediateNodeDB - db.sentinelNode = newNode(Key{}) - if err := db.intermediateNodeDB.Put(Key{}, db.sentinelNode); err != nil { + // The root must be a value node. + root, err = db.getEditableNode(rootKey, true /* hasValue */) + if err != nil { return err } } - db.rootID = db.sentinelNode.calculateID(db.metrics) - if !isSentinelNodeTheRoot(db.sentinelNode) { - // If the sentinel node is not the root, the trie's root is the sentinel node's only child - for _, childEntry := range db.sentinelNode.children { - db.rootID = childEntry.id - } - } + db.rootID = root.calculateID(db.metrics) + db.root = maybe.Some(root) return nil } @@ -1194,25 +1209,21 @@ func (db *merkleDB) initializeRoot() error { // If [start] is Nothing, there's no lower bound on the range. // If [end] is Nothing, there's no upper bound on the range. // Assumes [db.commitLock] is read locked. -// Assumes [db.lock] isn't held. -func (db *merkleDB) getHistoricalViewForRange( +func (db *merkleDB) getTrieAtRootForRange( rootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], -) (*trieView, error) { - currentRootID := db.getMerkleRoot() - +) (Trie, error) { // looking for the trie's current root id, so return the trie unmodified - if currentRootID == rootID { - // create an empty trie - return newTrieView(db, db, ViewChanges{}) + if rootID == db.getMerkleRoot() { + return db, nil } changeHistory, err := db.history.getChangesToGetToRoot(rootID, start, end) if err != nil { return nil, err } - return newHistoricalTrieView(db, changeHistory) + return newViewWithChanges(db, changeHistory) } // Returns all keys in range [start, end] that aren't in [keySet]. @@ -1263,12 +1274,18 @@ func (db *merkleDB) getNode(key Key, hasValue bool) (*node, error) { switch { case db.closed: return nil, database.ErrClosed - case key == Key{}: - return db.sentinelNode, nil + case db.root.HasValue() && key == db.root.Value().key: + return db.root.Value(), nil case hasValue: return db.valueNodeDB.Get(key) + default: + return db.intermediateNodeDB.Get(key) } - return db.intermediateNodeDB.Get(key) +} + +// Assumes [db.lock] or [db.commitLock] is read locked. +func (db *merkleDB) getRoot() maybe.Maybe[*node] { + return db.root } func (db *merkleDB) Clear() error { @@ -1287,19 +1304,23 @@ func (db *merkleDB) Clear() error { } // Clear root - db.sentinelNode = newNode(Key{}) - db.rootID = db.sentinelNode.calculateID(db.metrics) + db.root = maybe.Nothing[*node]() + db.rootID = ids.Empty // Clear history db.history = newTrieHistory(db.history.maxHistoryLen) db.history.record(&changeSummary{ - rootID: db.getMerkleRoot(), + rootID: db.rootID, values: map[Key]*change[maybe.Maybe[[]byte]]{}, nodes: map[Key]*change[*node]{}, }) return nil } +func (db *merkleDB) getTokenSize() int { + return db.tokenSize +} + // Returns [key] prefixed by [prefix]. // The returned []byte is taken from [bufferPool] and // should be returned to it when the caller is done with it. @@ -1328,11 +1349,10 @@ func getBufferFromPool(bufferPool *sync.Pool, size int) []byte { return buffer } -// cacheEntrySize returns a rough approximation of the memory consumed by storing the key and node +// cacheEntrySize returns a rough approximation of the memory consumed by storing the key and node. func cacheEntrySize(key Key, n *node) int { if n == nil { - return len(key.Bytes()) + return cacheEntryOverHead + len(key.Bytes()) } - // nodes cache their bytes representation so the total memory consumed is roughly twice that - return len(key.Bytes()) + 2*len(n.bytes()) + return cacheEntryOverHead + len(key.Bytes()) + codec.encodedDBNodeSize(&n.dbNode) } diff --git a/x/merkledb/db_test.go b/x/merkledb/db_test.go index 1cbce5a7792d..244858aeccc8 100644 --- a/x/merkledb/db_test.go +++ b/x/merkledb/db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -31,8 +31,6 @@ import ( const defaultHistoryLength = 300 -var emptyKey Key - // newDB returns a new merkle database with the underlying type so that tests can access unexported fields func newDB(ctx context.Context, db database.Database, config Config) (*merkleDB, error) { db, err := New(ctx, db, config) @@ -44,13 +42,14 @@ func newDB(ctx context.Context, db database.Database, config Config) (*merkleDB, func newDefaultConfig() Config { return Config{ - EvictionBatchSize: 10, - HistoryLength: defaultHistoryLength, - ValueNodeCacheSize: units.MiB, - IntermediateNodeCacheSize: units.MiB, - Reg: prometheus.NewRegistry(), - Tracer: trace.Noop, - BranchFactor: BranchFactor16, + IntermediateWriteBatchSize: 10, + HistoryLength: defaultHistoryLength, + ValueNodeCacheSize: units.MiB, + IntermediateNodeCacheSize: units.MiB, + IntermediateWriteBufferSize: units.KiB, + Reg: prometheus.NewRegistry(), + Tracer: trace.Noop, + BranchFactor: BranchFactor16, } } @@ -153,7 +152,7 @@ func Test_MerkleDB_DB_Load_Root_From_DB(t *testing.T) { require.NoError(db.Close()) - // reloading the db, should set the root back to the one that was saved to [baseDB] + // reloading the db should set the root back to the one that was saved to [baseDB] db, err = New( context.Background(), baseDB, @@ -299,15 +298,15 @@ func Test_MerkleDB_Invalidate_Siblings_On_Commit(t *testing.T) { sibling2, err := dbTrie.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.False(sibling1.(*trieView).isInvalid()) - require.False(sibling2.(*trieView).isInvalid()) + require.False(sibling1.(*view).isInvalid()) + require.False(sibling2.(*view).isInvalid()) // Committing viewToCommit should invalidate siblings require.NoError(viewToCommit.CommitToDB(context.Background())) - require.True(sibling1.(*trieView).isInvalid()) - require.True(sibling2.(*trieView).isInvalid()) - require.False(viewToCommit.(*trieView).isInvalid()) + require.True(sibling1.(*view).isInvalid()) + require.True(sibling2.(*view).isInvalid()) + require.False(viewToCommit.(*view).isInvalid()) } func Test_MerkleDB_CommitRangeProof_DeletesValuesInRange(t *testing.T) { @@ -503,7 +502,7 @@ func TestDatabaseNewUntrackedView(t *testing.T) { require.NoError(err) // Create a new untracked view. - view, err := newTrieView( + view, err := newView( db, db, ViewChanges{ @@ -563,7 +562,7 @@ func TestDatabaseCommitChanges(t *testing.T) { // Committing an invalid view should fail. invalidView, err := db.NewView(context.Background(), ViewChanges{}) require.NoError(err) - invalidView.(*trieView).invalidate() + invalidView.(*view).invalidate() err = invalidView.CommitToDB(context.Background()) require.ErrorIs(err, ErrInvalid) @@ -584,22 +583,22 @@ func TestDatabaseCommitChanges(t *testing.T) { }, ) require.NoError(err) - require.IsType(&trieView{}, view1Intf) - view1 := view1Intf.(*trieView) + require.IsType(&view{}, view1Intf) + view1 := view1Intf.(*view) view1Root, err := view1.GetMerkleRoot(context.Background()) require.NoError(err) // Make a second view view2Intf, err := db.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view2Intf) - view2 := view2Intf.(*trieView) + require.IsType(&view{}, view2Intf) + view2 := view2Intf.(*view) // Make a view atop a view view3Intf, err := view1.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view3Intf) - view3 := view3Intf.(*trieView) + require.IsType(&view{}, view3Intf) + view3 := view3Intf.(*view) // view3 // | @@ -648,18 +647,18 @@ func TestDatabaseInvalidateChildrenExcept(t *testing.T) { // Create children view1Intf, err := db.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view1Intf) - view1 := view1Intf.(*trieView) + require.IsType(&view{}, view1Intf) + view1 := view1Intf.(*view) view2Intf, err := db.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view2Intf) - view2 := view2Intf.(*trieView) + require.IsType(&view{}, view2Intf) + view2 := view2Intf.(*view) view3Intf, err := db.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view3Intf) - view3 := view3Intf.(*trieView) + require.IsType(&view{}, view3Intf) + view3 := view3Intf.(*view) db.invalidateChildrenExcept(view1) @@ -804,12 +803,12 @@ func TestMerkleDBClear(t *testing.T) { iter := db.NewIterator() defer iter.Release() require.False(iter.Next()) - require.Equal(emptyRootID, db.getMerkleRoot()) - require.Equal(emptyKey, db.sentinelNode.key) + require.Equal(ids.Empty, db.getMerkleRoot()) + require.True(db.root.IsNothing()) // Assert caches are empty. require.Zero(db.valueNodeDB.nodeCache.Len()) - require.Zero(db.intermediateNodeDB.nodeCache.currentSize) + require.Zero(db.intermediateNodeDB.writeBuffer.currentSize) // Assert history has only the clearing change. require.Len(db.history.lastChanges, 1) @@ -948,6 +947,10 @@ func runRandDBTest(require *require.Assertions, r *rand.Rand, rt randTest, token } rangeProof, err := db.GetRangeProofAtRoot(context.Background(), root, start, end, maxProofLen) + if root == ids.Empty { + require.ErrorIs(err, ErrEmptyProof) + continue + } require.NoError(err) require.LessOrEqual(len(rangeProof.KeyValues), maxProofLen) @@ -981,6 +984,10 @@ func runRandDBTest(require *require.Assertions, r *rand.Rand, rt randTest, token require.ErrorIs(err, errSameRoot) continue } + if root == ids.Empty { + require.ErrorIs(err, ErrEmptyProof) + continue + } require.NoError(err) require.LessOrEqual(len(changeProof.KeyChanges), maxProofLen) @@ -1242,3 +1249,40 @@ func insertRandomKeyValues( } } } + +func TestGetRangeProofAtRootEmptyRootID(t *testing.T) { + require := require.New(t) + + db, err := getBasicDB() + require.NoError(err) + + _, err = db.GetRangeProofAtRoot( + context.Background(), + ids.Empty, + maybe.Nothing[[]byte](), + maybe.Nothing[[]byte](), + 10, + ) + require.ErrorIs(err, ErrEmptyProof) +} + +func TestGetChangeProofEmptyRootID(t *testing.T) { + require := require.New(t) + + db, err := getBasicDB() + require.NoError(err) + + require.NoError(db.Put([]byte("key"), []byte("value"))) + + rootID := db.getMerkleRoot() + + _, err = db.GetChangeProof( + context.Background(), + rootID, + ids.Empty, + maybe.Nothing[[]byte](), + maybe.Nothing[[]byte](), + 10, + ) + require.ErrorIs(err, ErrEmptyProof) +} diff --git a/x/merkledb/helpers_test.go b/x/merkledb/helpers_test.go index b7a2908ff377..acb620aba5f1 100644 --- a/x/merkledb/helpers_test.go +++ b/x/merkledb/helpers_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb diff --git a/x/merkledb/history.go b/x/merkledb/history.go index c52385445cd2..22d87cd1cb48 100644 --- a/x/merkledb/history.go +++ b/x/merkledb/history.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -15,7 +15,10 @@ import ( "github.com/ava-labs/avalanchego/utils/set" ) -var ErrInsufficientHistory = errors.New("insufficient history to generate proof") +var ( + ErrInsufficientHistory = errors.New("insufficient history to generate proof") + ErrNoEndRoot = fmt.Errorf("%w: end root not found", ErrInsufficientHistory) +) // stores previous trie states type trieHistory struct { @@ -51,15 +54,20 @@ type changeSummaryAndInsertNumber struct { // Tracks all the node and value changes that resulted in the rootID. type changeSummary struct { + // The ID of the trie after these changes. rootID ids.ID - nodes map[Key]*change[*node] - values map[Key]*change[maybe.Maybe[[]byte]] + // The root before/after this change. + // Set in [calculateNodeIDs]. + rootChange change[maybe.Maybe[*node]] + nodes map[Key]*change[*node] + values map[Key]*change[maybe.Maybe[[]byte]] } func newChangeSummary(estimatedSize int) *changeSummary { return &changeSummary{ - nodes: make(map[Key]*change[*node], estimatedSize), - values: make(map[Key]*change[maybe.Maybe[[]byte]], estimatedSize), + nodes: make(map[Key]*change[*node], estimatedSize), + values: make(map[Key]*change[maybe.Maybe[[]byte]], estimatedSize), + rootChange: change[maybe.Maybe[*node]]{}, } } @@ -77,6 +85,8 @@ func newTrieHistory(maxHistoryLookback int) *trieHistory { // If [end] is Nothing, there's no upper bound on the range. // Returns [ErrInsufficientHistory] if the history is insufficient // to generate the proof. +// Returns [ErrNoEndRoot], which wraps [ErrInsufficientHistory], if +// the [endRoot] isn't in the history. func (th *trieHistory) getValueChanges( startRoot ids.ID, endRoot ids.ID, @@ -93,13 +103,9 @@ func (th *trieHistory) getValueChanges( } // [endRootChanges] is the last change in the history resulting in [endRoot]. - // TODO when we update to minimum go version 1.20.X, make this return another - // wrapped error ErrNoEndRoot. In NetworkServer.HandleChangeProofRequest, if we return - // that error, we know we shouldn't try to generate a range proof since we - // lack the necessary history. endRootChanges, ok := th.lastChanges[endRoot] if !ok { - return nil, fmt.Errorf("%w: end root %s not found", ErrInsufficientHistory, endRoot) + return nil, fmt.Errorf("%w: %s", ErrNoEndRoot, endRoot) } // Confirm there's a change resulting in [startRoot] before @@ -249,6 +255,13 @@ func (th *trieHistory) getChangesToGetToRoot(rootID ids.ID, start maybe.Maybe[[] for i := mostRecentChangeIndex; i > lastRootChangeIndex; i-- { changes, _ := th.history.Index(i) + if i == mostRecentChangeIndex { + combinedChanges.rootChange.before = changes.rootChange.after + } + if i == lastRootChangeIndex+1 { + combinedChanges.rootChange.after = changes.rootChange.before + } + for key, changedNode := range changes.nodes { combinedChanges.nodes[key] = &change[*node]{ after: changedNode.before, diff --git a/x/merkledb/history_test.go b/x/merkledb/history_test.go index d2945c9c5018..09c84321f50c 100644 --- a/x/merkledb/history_test.go +++ b/x/merkledb/history_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -36,8 +36,7 @@ func Test_History_Simple(t *testing.T) { origProof, err := db.GetRangeProof(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), 10) require.NoError(err) require.NotNil(origProof) - - origRootID := db.getMerkleRoot() + origRootID := db.rootID require.NoError(origProof.Verify(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), origRootID, db.tokenSize)) batch = db.NewBatch() @@ -165,13 +164,13 @@ func Test_History_Bad_GetValueChanges_Input(t *testing.T) { require.NoError(batch.Put([]byte("key"), []byte("value"))) require.NoError(batch.Write()) - toBeDeletedRoot := db.getMerkleRoot() + root1 := db.getMerkleRoot() batch = db.NewBatch() require.NoError(batch.Put([]byte("key"), []byte("value0"))) require.NoError(batch.Write()) - startRoot := db.getMerkleRoot() + root2 := db.getMerkleRoot() batch = db.NewBatch() require.NoError(batch.Put([]byte("key1"), []byte("value0"))) @@ -185,31 +184,30 @@ func Test_History_Bad_GetValueChanges_Input(t *testing.T) { require.NoError(batch.Put([]byte("key2"), []byte("value3"))) require.NoError(batch.Write()) - endRoot := db.getMerkleRoot() + root3 := db.getMerkleRoot() // ensure these start as valid calls - _, err = db.history.getValueChanges(toBeDeletedRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1) + _, err = db.history.getValueChanges(root1, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1) require.NoError(err) - _, err = db.history.getValueChanges(startRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1) + _, err = db.history.getValueChanges(root2, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1) require.NoError(err) - _, err = db.history.getValueChanges(startRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), -1) + _, err = db.history.getValueChanges(root2, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), -1) require.ErrorIs(err, ErrInvalidMaxLength) - _, err = db.history.getValueChanges(endRoot, startRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1) + _, err = db.history.getValueChanges(root3, root2, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1) require.ErrorIs(err, ErrInsufficientHistory) - // trigger the first root to be deleted by exiting the lookback window + // Cause root1 to be removed from the history batch = db.NewBatch() require.NoError(batch.Put([]byte("key2"), []byte("value4"))) require.NoError(batch.Write()) - // now this root should no longer be present - _, err = db.history.getValueChanges(toBeDeletedRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1) + _, err = db.history.getValueChanges(root1, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 1) require.ErrorIs(err, ErrInsufficientHistory) // same start/end roots should yield an empty changelist - changes, err := db.history.getValueChanges(endRoot, endRoot, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 10) + changes, err := db.history.getValueChanges(root3, root3, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 10) require.NoError(err) require.Empty(changes.values) } @@ -339,8 +337,7 @@ func Test_History_RepeatedRoot(t *testing.T) { origProof, err := db.GetRangeProof(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), 10) require.NoError(err) require.NotNil(origProof) - - origRootID := db.getMerkleRoot() + origRootID := db.rootID require.NoError(origProof.Verify(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), origRootID, db.tokenSize)) batch = db.NewBatch() @@ -382,8 +379,7 @@ func Test_History_ExcessDeletes(t *testing.T) { origProof, err := db.GetRangeProof(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), 10) require.NoError(err) require.NotNil(origProof) - - origRootID := db.getMerkleRoot() + origRootID := db.rootID require.NoError(origProof.Verify(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), origRootID, db.tokenSize)) batch = db.NewBatch() @@ -415,8 +411,7 @@ func Test_History_DontIncludeAllNodes(t *testing.T) { origProof, err := db.GetRangeProof(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), 10) require.NoError(err) require.NotNil(origProof) - - origRootID := db.getMerkleRoot() + origRootID := db.rootID require.NoError(origProof.Verify(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), origRootID, db.tokenSize)) batch = db.NewBatch() @@ -444,7 +439,7 @@ func Test_History_Branching2Nodes(t *testing.T) { origProof, err := db.GetRangeProof(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), 10) require.NoError(err) require.NotNil(origProof) - origRootID := db.getMerkleRoot() + origRootID := db.rootID require.NoError(origProof.Verify(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), origRootID, db.tokenSize)) batch = db.NewBatch() @@ -472,8 +467,7 @@ func Test_History_Branching3Nodes(t *testing.T) { origProof, err := db.GetRangeProof(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), 10) require.NoError(err) require.NotNil(origProof) - - origRootID := db.getMerkleRoot() + origRootID := db.rootID require.NoError(origProof.Verify(context.Background(), maybe.Some([]byte("k")), maybe.Some([]byte("key3")), origRootID, db.tokenSize)) batch = db.NewBatch() @@ -658,6 +652,9 @@ func TestHistoryGetChangesToRoot(t *testing.T) { for i := 0; i < maxHistoryLen; i++ { // Fill the history changes = append(changes, &changeSummary{ rootID: ids.GenerateTestID(), + rootChange: change[maybe.Maybe[*node]]{ + before: maybe.Some(&node{}), + }, nodes: map[Key]*change[*node]{ ToKey([]byte{byte(i)}): { before: &node{}, @@ -693,7 +690,8 @@ func TestHistoryGetChangesToRoot(t *testing.T) { name: "most recent change", rootID: changes[maxHistoryLen-1].rootID, validateFunc: func(require *require.Assertions, got *changeSummary) { - require.Equal(newChangeSummary(defaultPreallocationSize), got) + expected := newChangeSummary(defaultPreallocationSize) + require.Equal(expected, got) }, }, { diff --git a/x/merkledb/intermediate_node_db.go b/x/merkledb/intermediate_node_db.go index 91cef6242410..b0318e99064d 100644 --- a/x/merkledb/intermediate_node_db.go +++ b/x/merkledb/intermediate_node_db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -6,6 +6,8 @@ package merkledb import ( "sync" + "github.com/ava-labs/avalanchego/cache" + "github.com/ava-labs/avalanchego/database" ) @@ -22,12 +24,16 @@ type intermediateNodeDB struct { // Keys written to [baseDB] are prefixed with [intermediateNodePrefix]. baseDB database.Database - // If a value is nil, the corresponding key isn't in the trie. + // The write buffer contains nodes that have been changed but have not been written to disk. // Note that a call to Put may cause a node to be evicted // from the cache, which will call [OnEviction]. // A non-nil error returned from Put is considered fatal. // Keys in [nodeCache] aren't prefixed with [intermediateNodePrefix]. - nodeCache onEvictCache[Key, *node] + writeBuffer onEvictCache[Key, *node] + + // If a value is nil, the corresponding key isn't in the trie. + nodeCache cache.Cacher[Key, *node] + // the number of bytes to evict during an eviction batch evictionBatchSize int metrics merkleMetrics @@ -38,7 +44,8 @@ func newIntermediateNodeDB( db database.Database, bufferPool *sync.Pool, metrics merkleMetrics, - size int, + cacheSize int, + writeBufferSize int, evictionBatchSize int, tokenSize int, ) *intermediateNodeDB { @@ -48,19 +55,20 @@ func newIntermediateNodeDB( bufferPool: bufferPool, evictionBatchSize: evictionBatchSize, tokenSize: tokenSize, + nodeCache: cache.NewSizedLRU(cacheSize, cacheEntrySize), } - result.nodeCache = newOnEvictCache( - size, + result.writeBuffer = newOnEvictCache( + writeBufferSize, cacheEntrySize, result.onEviction, ) + return result } // A non-nil error is considered fatal and closes [db.baseDB]. func (db *intermediateNodeDB) onEviction(key Key, n *node) error { writeBatch := db.baseDB.NewBatch() - totalSize := cacheEntrySize(key, n) if err := db.addToBatch(writeBatch, key, n); err != nil { _ = db.baseDB.Close() @@ -73,7 +81,7 @@ func (db *intermediateNodeDB) onEviction(key Key, n *node) error { // node, because each time this method is called we do a disk write. // Evicts a total number of bytes, rather than a number of nodes for totalSize < db.evictionBatchSize { - key, n, exists := db.nodeCache.removeOldest() + key, n, exists := db.writeBuffer.removeOldest() if !exists { // The cache is empty. break @@ -111,6 +119,15 @@ func (db *intermediateNodeDB) Get(key Key) (*node, error) { } db.metrics.IntermediateNodeCacheMiss() + if cachedValue, isCached := db.writeBuffer.Get(key); isCached { + db.metrics.IntermediateNodeCacheHit() + if cachedValue == nil { + return nil, database.ErrNotFound + } + return cachedValue, nil + } + db.metrics.IntermediateNodeCacheMiss() + dbKey := db.constructDBKey(key) db.metrics.DatabaseNodeRead() nodeBytes, err := db.baseDB.Get(dbKey) @@ -136,24 +153,29 @@ func (db *intermediateNodeDB) constructDBKey(key Key) []byte { } func (db *intermediateNodeDB) Put(key Key, n *node) error { - return db.nodeCache.Put(key, n) + db.nodeCache.Put(key, n) + return db.writeBuffer.Put(key, n) } func (db *intermediateNodeDB) Flush() error { - return db.nodeCache.Flush() + db.nodeCache.Flush() + return db.writeBuffer.Flush() } func (db *intermediateNodeDB) Delete(key Key) error { - return db.nodeCache.Put(key, nil) + db.nodeCache.Put(key, nil) + return db.writeBuffer.Put(key, nil) } func (db *intermediateNodeDB) Clear() error { - // Reset the cache. Note we don't flush because that would cause us to + db.nodeCache.Flush() + + // Reset the buffer. Note we don't flush because that would cause us to // persist intermediate nodes we're about to delete. - db.nodeCache = newOnEvictCache( - db.nodeCache.maxSize, - db.nodeCache.size, - db.nodeCache.onEviction, + db.writeBuffer = newOnEvictCache( + db.writeBuffer.maxSize, + db.writeBuffer.size, + db.writeBuffer.onEviction, ) return database.AtomicClearPrefix(db.baseDB, db.baseDB, intermediateNodePrefix) } diff --git a/x/merkledb/intermediate_node_db_test.go b/x/merkledb/intermediate_node_db_test.go index 91709708f148..26ad722ffa45 100644 --- a/x/merkledb/intermediate_node_db_test.go +++ b/x/merkledb/intermediate_node_db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -28,8 +28,10 @@ func Test_IntermediateNodeDB(t *testing.T) { nodeSize := cacheEntrySize(n.key, n) // use exact multiple of node size so require.Equal(1, db.nodeCache.fifo.Len()) is correct later - cacheSize := nodeSize * 20 - evictionBatchSize := cacheSize + cacheSize := nodeSize * 100 + bufferSize := nodeSize * 20 + + evictionBatchSize := bufferSize baseDB := memdb.New() db := newIntermediateNodeDB( baseDB, @@ -38,6 +40,7 @@ func Test_IntermediateNodeDB(t *testing.T) { }, &mockMetrics{}, cacheSize, + bufferSize, evictionBatchSize, 4, ) @@ -78,7 +81,7 @@ func Test_IntermediateNodeDB(t *testing.T) { node := newNode(Key{}) node.setValue(maybe.Some([]byte{byte(added)})) newExpectedSize := expectedSize + cacheEntrySize(key, node) - if newExpectedSize > cacheSize { + if newExpectedSize > bufferSize { // Don't trigger eviction. break } @@ -89,7 +92,7 @@ func Test_IntermediateNodeDB(t *testing.T) { } // Assert cache has expected number of elements - require.Equal(added, db.nodeCache.fifo.Len()) + require.Equal(added, db.writeBuffer.fifo.Len()) // Put one more element in the cache, which should trigger an eviction // of all but 2 elements. 2 elements remain rather than 1 element because of @@ -100,14 +103,14 @@ func Test_IntermediateNodeDB(t *testing.T) { require.NoError(db.Put(key, node)) // Assert cache has expected number of elements - require.Equal(1, db.nodeCache.fifo.Len()) - gotKey, _, ok := db.nodeCache.fifo.Oldest() + require.Equal(1, db.writeBuffer.fifo.Len()) + gotKey, _, ok := db.writeBuffer.fifo.Oldest() require.True(ok) require.Equal(ToKey([]byte{byte(added)}), gotKey) // Get a node from the base database // Use an early key that has been evicted from the cache - _, inCache := db.nodeCache.Get(node1Key) + _, inCache := db.writeBuffer.Get(node1Key) require.False(inCache) nodeRead, err := db.Get(node1Key) require.NoError(err) @@ -117,7 +120,7 @@ func Test_IntermediateNodeDB(t *testing.T) { require.NoError(db.Flush()) // Assert the cache is empty - require.Zero(db.nodeCache.fifo.Len()) + require.Zero(db.writeBuffer.fifo.Len()) // Assert the evicted cache elements were written to disk with prefix. it := baseDB.NewIteratorWithPrefix(intermediateNodePrefix) @@ -132,8 +135,9 @@ func Test_IntermediateNodeDB(t *testing.T) { } func FuzzIntermediateNodeDBConstructDBKey(f *testing.F) { + bufferSize := 200 cacheSize := 200 - evictionBatchSize := cacheSize + evictionBatchSize := bufferSize baseDB := memdb.New() f.Fuzz(func( @@ -150,6 +154,7 @@ func FuzzIntermediateNodeDBConstructDBKey(f *testing.F) { }, &mockMetrics{}, cacheSize, + bufferSize, evictionBatchSize, tokenSize, ) @@ -182,7 +187,8 @@ func FuzzIntermediateNodeDBConstructDBKey(f *testing.F) { func Test_IntermediateNodeDB_ConstructDBKey_DirtyBuffer(t *testing.T) { require := require.New(t) cacheSize := 200 - evictionBatchSize := cacheSize + bufferSize := 200 + evictionBatchSize := bufferSize baseDB := memdb.New() db := newIntermediateNodeDB( baseDB, @@ -191,6 +197,7 @@ func Test_IntermediateNodeDB_ConstructDBKey_DirtyBuffer(t *testing.T) { }, &mockMetrics{}, cacheSize, + bufferSize, evictionBatchSize, 4, ) @@ -217,7 +224,8 @@ func Test_IntermediateNodeDB_ConstructDBKey_DirtyBuffer(t *testing.T) { func TestIntermediateNodeDBClear(t *testing.T) { require := require.New(t) cacheSize := 200 - evictionBatchSize := cacheSize + bufferSize := 200 + evictionBatchSize := bufferSize baseDB := memdb.New() db := newIntermediateNodeDB( baseDB, @@ -226,6 +234,7 @@ func TestIntermediateNodeDBClear(t *testing.T) { }, &mockMetrics{}, cacheSize, + bufferSize, evictionBatchSize, 4, ) @@ -240,5 +249,46 @@ func TestIntermediateNodeDBClear(t *testing.T) { defer iter.Release() require.False(iter.Next()) - require.Zero(db.nodeCache.currentSize) + require.Zero(db.writeBuffer.currentSize) +} + +// Test that deleting the empty key and flushing works correctly. +// Previously, there was a bug that occurred when deleting the empty key +// if the cache was empty. The size of the cache entry was reported as 0, +// which caused the cache's currentSize to be 0, so on resize() we didn't +// call onEviction. This caused the empty key to not be deleted from the baseDB. +func TestIntermediateNodeDBDeleteEmptyKey(t *testing.T) { + require := require.New(t) + cacheSize := 200 + bufferSize := 200 + evictionBatchSize := bufferSize + baseDB := memdb.New() + db := newIntermediateNodeDB( + baseDB, + &sync.Pool{ + New: func() interface{} { return make([]byte, 0) }, + }, + &mockMetrics{}, + cacheSize, + bufferSize, + evictionBatchSize, + 4, + ) + + emptyKey := ToKey([]byte{}) + require.NoError(db.Put(emptyKey, newNode(emptyKey))) + require.NoError(db.Flush()) + + emptyDBKey := db.constructDBKey(emptyKey) + has, err := baseDB.Has(emptyDBKey) + require.NoError(err) + require.True(has) + + require.NoError(db.Delete(ToKey([]byte{}))) + require.NoError(db.Flush()) + + emptyDBKey = db.constructDBKey(emptyKey) + has, err = baseDB.Has(emptyDBKey) + require.NoError(err) + require.False(has) } diff --git a/x/merkledb/key.go b/x/merkledb/key.go index d65d9b74a0a6..dd9938f6aaf0 100644 --- a/x/merkledb/key.go +++ b/x/merkledb/key.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -11,6 +11,8 @@ import ( "golang.org/x/exp/maps" "golang.org/x/exp/slices" + + "github.com/ava-labs/avalanchego/utils" ) var ( @@ -164,12 +166,19 @@ func (k Key) Length() int { // Greater returns true if current Key is greater than other Key func (k Key) Greater(other Key) bool { - return k.value > other.value || (k.value == other.value && k.length > other.length) + return k.Compare(other) == 1 } // Less will return true if current Key is less than other Key func (k Key) Less(other Key) bool { - return k.value < other.value || (k.value == other.value && k.length < other.length) + return k.Compare(other) == -1 +} + +func (k Key) Compare(other Key) int { + if valueCmp := utils.Compare(k.value, other.value); valueCmp != 0 { + return valueCmp + } + return utils.Compare(k.length, other.length) } // Extend returns a new Key that is the in-order aggregation of Key [k] with [keys] diff --git a/x/merkledb/key_test.go b/x/merkledb/key_test.go index f0819483b1a8..aab666e13afc 100644 --- a/x/merkledb/key_test.go +++ b/x/merkledb/key_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb diff --git a/x/merkledb/metrics.go b/x/merkledb/metrics.go index d8a80a02db5a..058b4869904a 100644 --- a/x/merkledb/metrics.go +++ b/x/merkledb/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb diff --git a/x/merkledb/metrics_test.go b/x/merkledb/metrics_test.go index 3bf5a9480a54..20c4accbd13c 100644 --- a/x/merkledb/metrics_test.go +++ b/x/merkledb/metrics_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -34,20 +34,20 @@ func Test_Metrics_Basic_Usage(t *testing.T) { require.Equal(t, int64(1), db.metrics.(*mockMetrics).keyReadCount) require.Equal(t, int64(1), db.metrics.(*mockMetrics).keyWriteCount) - require.Equal(t, int64(2), db.metrics.(*mockMetrics).hashCount) + require.Equal(t, int64(1), db.metrics.(*mockMetrics).hashCount) require.NoError(t, db.Delete([]byte("key"))) require.Equal(t, int64(1), db.metrics.(*mockMetrics).keyReadCount) require.Equal(t, int64(2), db.metrics.(*mockMetrics).keyWriteCount) - require.Equal(t, int64(3), db.metrics.(*mockMetrics).hashCount) + require.Equal(t, int64(1), db.metrics.(*mockMetrics).hashCount) _, err = db.Get([]byte("key2")) require.ErrorIs(t, err, database.ErrNotFound) require.Equal(t, int64(2), db.metrics.(*mockMetrics).keyReadCount) require.Equal(t, int64(2), db.metrics.(*mockMetrics).keyWriteCount) - require.Equal(t, int64(3), db.metrics.(*mockMetrics).hashCount) + require.Equal(t, int64(1), db.metrics.(*mockMetrics).hashCount) } func Test_Metrics_Initialize(t *testing.T) { diff --git a/x/merkledb/mock_db.go b/x/merkledb/mock_db.go index a4d1d6b6d6f3..d43e276103f7 100644 --- a/x/merkledb/mock_db.go +++ b/x/merkledb/mock_db.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ava-labs/avalanchego/x/merkledb (interfaces: MerkleDB) +// Source: x/merkledb/db.go +// +// Generated by this command: +// +// mockgen -source=x/merkledb/db.go -destination=x/merkledb/mock_db.go -package=merkledb -exclude_interfaces=ChangeProofer,RangeProofer,Clearer,Prefetcher +// // Package merkledb is a generated GoMock package. package merkledb @@ -69,207 +71,207 @@ func (mr *MockMerkleDBMockRecorder) Close() *gomock.Call { } // CommitChangeProof mocks base method. -func (m *MockMerkleDB) CommitChangeProof(arg0 context.Context, arg1 *ChangeProof) error { +func (m *MockMerkleDB) CommitChangeProof(ctx context.Context, proof *ChangeProof) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CommitChangeProof", arg0, arg1) + ret := m.ctrl.Call(m, "CommitChangeProof", ctx, proof) ret0, _ := ret[0].(error) return ret0 } // CommitChangeProof indicates an expected call of CommitChangeProof. -func (mr *MockMerkleDBMockRecorder) CommitChangeProof(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) CommitChangeProof(ctx, proof any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitChangeProof), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitChangeProof), ctx, proof) } // CommitRangeProof mocks base method. -func (m *MockMerkleDB) CommitRangeProof(arg0 context.Context, arg1, arg2 maybe.Maybe[[]uint8], arg3 *RangeProof) error { +func (m *MockMerkleDB) CommitRangeProof(ctx context.Context, start, end maybe.Maybe[[]byte], proof *RangeProof) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CommitRangeProof", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "CommitRangeProof", ctx, start, end, proof) ret0, _ := ret[0].(error) return ret0 } // CommitRangeProof indicates an expected call of CommitRangeProof. -func (mr *MockMerkleDBMockRecorder) CommitRangeProof(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) CommitRangeProof(ctx, start, end, proof any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitRangeProof), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).CommitRangeProof), ctx, start, end, proof) } // Compact mocks base method. -func (m *MockMerkleDB) Compact(arg0, arg1 []byte) error { +func (m *MockMerkleDB) Compact(start, limit []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Compact", arg0, arg1) + ret := m.ctrl.Call(m, "Compact", start, limit) ret0, _ := ret[0].(error) return ret0 } // Compact indicates an expected call of Compact. -func (mr *MockMerkleDBMockRecorder) Compact(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) Compact(start, limit any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Compact", reflect.TypeOf((*MockMerkleDB)(nil).Compact), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Compact", reflect.TypeOf((*MockMerkleDB)(nil).Compact), start, limit) } // Delete mocks base method. -func (m *MockMerkleDB) Delete(arg0 []byte) error { +func (m *MockMerkleDB) Delete(key []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Delete", arg0) + ret := m.ctrl.Call(m, "Delete", key) ret0, _ := ret[0].(error) return ret0 } // Delete indicates an expected call of Delete. -func (mr *MockMerkleDBMockRecorder) Delete(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) Delete(key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockMerkleDB)(nil).Delete), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockMerkleDB)(nil).Delete), key) } // Get mocks base method. -func (m *MockMerkleDB) Get(arg0 []byte) ([]byte, error) { +func (m *MockMerkleDB) Get(key []byte) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", arg0) + ret := m.ctrl.Call(m, "Get", key) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // Get indicates an expected call of Get. -func (mr *MockMerkleDBMockRecorder) Get(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) Get(key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMerkleDB)(nil).Get), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMerkleDB)(nil).Get), key) } // GetChangeProof mocks base method. -func (m *MockMerkleDB) GetChangeProof(arg0 context.Context, arg1, arg2 ids.ID, arg3, arg4 maybe.Maybe[[]uint8], arg5 int) (*ChangeProof, error) { +func (m *MockMerkleDB) GetChangeProof(ctx context.Context, startRootID, endRootID ids.ID, start, end maybe.Maybe[[]byte], maxLength int) (*ChangeProof, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetChangeProof", arg0, arg1, arg2, arg3, arg4, arg5) + ret := m.ctrl.Call(m, "GetChangeProof", ctx, startRootID, endRootID, start, end, maxLength) ret0, _ := ret[0].(*ChangeProof) ret1, _ := ret[1].(error) return ret0, ret1 } // GetChangeProof indicates an expected call of GetChangeProof. -func (mr *MockMerkleDBMockRecorder) GetChangeProof(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) GetChangeProof(ctx, startRootID, endRootID, start, end, maxLength any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetChangeProof), arg0, arg1, arg2, arg3, arg4, arg5) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetChangeProof), ctx, startRootID, endRootID, start, end, maxLength) } // GetMerkleRoot mocks base method. -func (m *MockMerkleDB) GetMerkleRoot(arg0 context.Context) (ids.ID, error) { +func (m *MockMerkleDB) GetMerkleRoot(ctx context.Context) (ids.ID, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMerkleRoot", arg0) + ret := m.ctrl.Call(m, "GetMerkleRoot", ctx) ret0, _ := ret[0].(ids.ID) ret1, _ := ret[1].(error) return ret0, ret1 } // GetMerkleRoot indicates an expected call of GetMerkleRoot. -func (mr *MockMerkleDBMockRecorder) GetMerkleRoot(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) GetMerkleRoot(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMerkleRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetMerkleRoot), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMerkleRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetMerkleRoot), ctx) } // GetProof mocks base method. -func (m *MockMerkleDB) GetProof(arg0 context.Context, arg1 []byte) (*Proof, error) { +func (m *MockMerkleDB) GetProof(ctx context.Context, keyBytes []byte) (*Proof, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetProof", arg0, arg1) + ret := m.ctrl.Call(m, "GetProof", ctx, keyBytes) ret0, _ := ret[0].(*Proof) ret1, _ := ret[1].(error) return ret0, ret1 } // GetProof indicates an expected call of GetProof. -func (mr *MockMerkleDBMockRecorder) GetProof(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) GetProof(ctx, keyBytes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProof", reflect.TypeOf((*MockMerkleDB)(nil).GetProof), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProof", reflect.TypeOf((*MockMerkleDB)(nil).GetProof), ctx, keyBytes) } // GetRangeProof mocks base method. -func (m *MockMerkleDB) GetRangeProof(arg0 context.Context, arg1, arg2 maybe.Maybe[[]uint8], arg3 int) (*RangeProof, error) { +func (m *MockMerkleDB) GetRangeProof(ctx context.Context, start, end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRangeProof", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "GetRangeProof", ctx, start, end, maxLength) ret0, _ := ret[0].(*RangeProof) ret1, _ := ret[1].(error) return ret0, ret1 } // GetRangeProof indicates an expected call of GetRangeProof. -func (mr *MockMerkleDBMockRecorder) GetRangeProof(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) GetRangeProof(ctx, start, end, maxLength any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProof), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProof", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProof), ctx, start, end, maxLength) } // GetRangeProofAtRoot mocks base method. -func (m *MockMerkleDB) GetRangeProofAtRoot(arg0 context.Context, arg1 ids.ID, arg2, arg3 maybe.Maybe[[]uint8], arg4 int) (*RangeProof, error) { +func (m *MockMerkleDB) GetRangeProofAtRoot(ctx context.Context, rootID ids.ID, start, end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetRangeProofAtRoot", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "GetRangeProofAtRoot", ctx, rootID, start, end, maxLength) ret0, _ := ret[0].(*RangeProof) ret1, _ := ret[1].(error) return ret0, ret1 } // GetRangeProofAtRoot indicates an expected call of GetRangeProofAtRoot. -func (mr *MockMerkleDBMockRecorder) GetRangeProofAtRoot(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) GetRangeProofAtRoot(ctx, rootID, start, end, maxLength any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProofAtRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProofAtRoot), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProofAtRoot", reflect.TypeOf((*MockMerkleDB)(nil).GetRangeProofAtRoot), ctx, rootID, start, end, maxLength) } // GetValue mocks base method. -func (m *MockMerkleDB) GetValue(arg0 context.Context, arg1 []byte) ([]byte, error) { +func (m *MockMerkleDB) GetValue(ctx context.Context, key []byte) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetValue", arg0, arg1) + ret := m.ctrl.Call(m, "GetValue", ctx, key) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // GetValue indicates an expected call of GetValue. -func (mr *MockMerkleDBMockRecorder) GetValue(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) GetValue(ctx, key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValue", reflect.TypeOf((*MockMerkleDB)(nil).GetValue), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValue", reflect.TypeOf((*MockMerkleDB)(nil).GetValue), ctx, key) } // GetValues mocks base method. -func (m *MockMerkleDB) GetValues(arg0 context.Context, arg1 [][]byte) ([][]byte, []error) { +func (m *MockMerkleDB) GetValues(ctx context.Context, keys [][]byte) ([][]byte, []error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetValues", arg0, arg1) + ret := m.ctrl.Call(m, "GetValues", ctx, keys) ret0, _ := ret[0].([][]byte) ret1, _ := ret[1].([]error) return ret0, ret1 } // GetValues indicates an expected call of GetValues. -func (mr *MockMerkleDBMockRecorder) GetValues(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) GetValues(ctx, keys any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValues", reflect.TypeOf((*MockMerkleDB)(nil).GetValues), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValues", reflect.TypeOf((*MockMerkleDB)(nil).GetValues), ctx, keys) } // Has mocks base method. -func (m *MockMerkleDB) Has(arg0 []byte) (bool, error) { +func (m *MockMerkleDB) Has(key []byte) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Has", arg0) + ret := m.ctrl.Call(m, "Has", key) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } // Has indicates an expected call of Has. -func (mr *MockMerkleDBMockRecorder) Has(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) Has(key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockMerkleDB)(nil).Has), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockMerkleDB)(nil).Has), key) } // HealthCheck mocks base method. -func (m *MockMerkleDB) HealthCheck(arg0 context.Context) (interface{}, error) { +func (m *MockMerkleDB) HealthCheck(arg0 context.Context) (any, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HealthCheck", arg0) - ret0, _ := ret[0].(interface{}) + ret0, _ := ret[0].(any) ret1, _ := ret[1].(error) return ret0, ret1 } // HealthCheck indicates an expected call of HealthCheck. -func (mr *MockMerkleDBMockRecorder) HealthCheck(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) HealthCheck(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockMerkleDB)(nil).HealthCheck), arg0) } @@ -303,144 +305,187 @@ func (mr *MockMerkleDBMockRecorder) NewIterator() *gomock.Call { } // NewIteratorWithPrefix mocks base method. -func (m *MockMerkleDB) NewIteratorWithPrefix(arg0 []byte) database.Iterator { +func (m *MockMerkleDB) NewIteratorWithPrefix(prefix []byte) database.Iterator { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewIteratorWithPrefix", arg0) + ret := m.ctrl.Call(m, "NewIteratorWithPrefix", prefix) ret0, _ := ret[0].(database.Iterator) return ret0 } // NewIteratorWithPrefix indicates an expected call of NewIteratorWithPrefix. -func (mr *MockMerkleDBMockRecorder) NewIteratorWithPrefix(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) NewIteratorWithPrefix(prefix any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithPrefix), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithPrefix), prefix) } // NewIteratorWithStart mocks base method. -func (m *MockMerkleDB) NewIteratorWithStart(arg0 []byte) database.Iterator { +func (m *MockMerkleDB) NewIteratorWithStart(start []byte) database.Iterator { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewIteratorWithStart", arg0) + ret := m.ctrl.Call(m, "NewIteratorWithStart", start) ret0, _ := ret[0].(database.Iterator) return ret0 } // NewIteratorWithStart indicates an expected call of NewIteratorWithStart. -func (mr *MockMerkleDBMockRecorder) NewIteratorWithStart(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) NewIteratorWithStart(start any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStart", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStart), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStart", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStart), start) } // NewIteratorWithStartAndPrefix mocks base method. -func (m *MockMerkleDB) NewIteratorWithStartAndPrefix(arg0, arg1 []byte) database.Iterator { +func (m *MockMerkleDB) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewIteratorWithStartAndPrefix", arg0, arg1) + ret := m.ctrl.Call(m, "NewIteratorWithStartAndPrefix", start, prefix) ret0, _ := ret[0].(database.Iterator) return ret0 } // NewIteratorWithStartAndPrefix indicates an expected call of NewIteratorWithStartAndPrefix. -func (mr *MockMerkleDBMockRecorder) NewIteratorWithStartAndPrefix(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) NewIteratorWithStartAndPrefix(start, prefix any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStartAndPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStartAndPrefix), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewIteratorWithStartAndPrefix", reflect.TypeOf((*MockMerkleDB)(nil).NewIteratorWithStartAndPrefix), start, prefix) } // NewView mocks base method. -func (m *MockMerkleDB) NewView(arg0 context.Context, arg1 ViewChanges) (TrieView, error) { +func (m *MockMerkleDB) NewView(ctx context.Context, changes ViewChanges) (View, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewView", arg0, arg1) - ret0, _ := ret[0].(TrieView) + ret := m.ctrl.Call(m, "NewView", ctx, changes) + ret0, _ := ret[0].(View) ret1, _ := ret[1].(error) return ret0, ret1 } // NewView indicates an expected call of NewView. -func (mr *MockMerkleDBMockRecorder) NewView(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) NewView(ctx, changes any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewView", reflect.TypeOf((*MockMerkleDB)(nil).NewView), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewView", reflect.TypeOf((*MockMerkleDB)(nil).NewView), ctx, changes) } // PrefetchPath mocks base method. -func (m *MockMerkleDB) PrefetchPath(arg0 []byte) error { +func (m *MockMerkleDB) PrefetchPath(key []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PrefetchPath", arg0) + ret := m.ctrl.Call(m, "PrefetchPath", key) ret0, _ := ret[0].(error) return ret0 } // PrefetchPath indicates an expected call of PrefetchPath. -func (mr *MockMerkleDBMockRecorder) PrefetchPath(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) PrefetchPath(key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrefetchPath", reflect.TypeOf((*MockMerkleDB)(nil).PrefetchPath), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrefetchPath", reflect.TypeOf((*MockMerkleDB)(nil).PrefetchPath), key) } // PrefetchPaths mocks base method. -func (m *MockMerkleDB) PrefetchPaths(arg0 [][]byte) error { +func (m *MockMerkleDB) PrefetchPaths(keys [][]byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PrefetchPaths", arg0) + ret := m.ctrl.Call(m, "PrefetchPaths", keys) ret0, _ := ret[0].(error) return ret0 } // PrefetchPaths indicates an expected call of PrefetchPaths. -func (mr *MockMerkleDBMockRecorder) PrefetchPaths(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) PrefetchPaths(keys any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrefetchPaths", reflect.TypeOf((*MockMerkleDB)(nil).PrefetchPaths), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrefetchPaths", reflect.TypeOf((*MockMerkleDB)(nil).PrefetchPaths), keys) } // Put mocks base method. -func (m *MockMerkleDB) Put(arg0, arg1 []byte) error { +func (m *MockMerkleDB) Put(key, value []byte) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Put", arg0, arg1) + ret := m.ctrl.Call(m, "Put", key, value) ret0, _ := ret[0].(error) return ret0 } // Put indicates an expected call of Put. -func (mr *MockMerkleDBMockRecorder) Put(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) Put(key, value any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockMerkleDB)(nil).Put), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockMerkleDB)(nil).Put), key, value) } // VerifyChangeProof mocks base method. -func (m *MockMerkleDB) VerifyChangeProof(arg0 context.Context, arg1 *ChangeProof, arg2, arg3 maybe.Maybe[[]uint8], arg4 ids.ID) error { +func (m *MockMerkleDB) VerifyChangeProof(ctx context.Context, proof *ChangeProof, start, end maybe.Maybe[[]byte], expectedEndRootID ids.ID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "VerifyChangeProof", arg0, arg1, arg2, arg3, arg4) + ret := m.ctrl.Call(m, "VerifyChangeProof", ctx, proof, start, end, expectedEndRootID) ret0, _ := ret[0].(error) return ret0 } // VerifyChangeProof indicates an expected call of VerifyChangeProof. -func (mr *MockMerkleDBMockRecorder) VerifyChangeProof(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) VerifyChangeProof(ctx, proof, start, end, expectedEndRootID any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).VerifyChangeProof), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyChangeProof", reflect.TypeOf((*MockMerkleDB)(nil).VerifyChangeProof), ctx, proof, start, end, expectedEndRootID) } // getEditableNode mocks base method. -func (m *MockMerkleDB) getEditableNode(arg0 Key, arg1 bool) (*node, error) { +func (m *MockMerkleDB) getEditableNode(key Key, hasValue bool) (*node, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getEditableNode", arg0, arg1) + ret := m.ctrl.Call(m, "getEditableNode", key, hasValue) ret0, _ := ret[0].(*node) ret1, _ := ret[1].(error) return ret0, ret1 } // getEditableNode indicates an expected call of getEditableNode. -func (mr *MockMerkleDBMockRecorder) getEditableNode(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) getEditableNode(key, hasValue any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getEditableNode", reflect.TypeOf((*MockMerkleDB)(nil).getEditableNode), key, hasValue) +} + +// getNode mocks base method. +func (m *MockMerkleDB) getNode(key Key, hasValue bool) (*node, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getNode", key, hasValue) + ret0, _ := ret[0].(*node) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// getNode indicates an expected call of getNode. +func (mr *MockMerkleDBMockRecorder) getNode(key, hasValue any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getNode", reflect.TypeOf((*MockMerkleDB)(nil).getNode), key, hasValue) +} + +// getRoot mocks base method. +func (m *MockMerkleDB) getRoot() maybe.Maybe[*node] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getRoot") + ret0, _ := ret[0].(maybe.Maybe[*node]) + return ret0 +} + +// getRoot indicates an expected call of getRoot. +func (mr *MockMerkleDBMockRecorder) getRoot() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getRoot", reflect.TypeOf((*MockMerkleDB)(nil).getRoot)) +} + +// getTokenSize mocks base method. +func (m *MockMerkleDB) getTokenSize() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getTokenSize") + ret0, _ := ret[0].(int) + return ret0 +} + +// getTokenSize indicates an expected call of getTokenSize. +func (mr *MockMerkleDBMockRecorder) getTokenSize() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getEditableNode", reflect.TypeOf((*MockMerkleDB)(nil).getEditableNode), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getTokenSize", reflect.TypeOf((*MockMerkleDB)(nil).getTokenSize)) } // getValue mocks base method. -func (m *MockMerkleDB) getValue(arg0 Key) ([]byte, error) { +func (m *MockMerkleDB) getValue(key Key) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "getValue", arg0) + ret := m.ctrl.Call(m, "getValue", key) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // getValue indicates an expected call of getValue. -func (mr *MockMerkleDBMockRecorder) getValue(arg0 interface{}) *gomock.Call { +func (mr *MockMerkleDBMockRecorder) getValue(key any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getValue", reflect.TypeOf((*MockMerkleDB)(nil).getValue), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getValue", reflect.TypeOf((*MockMerkleDB)(nil).getValue), key) } diff --git a/x/merkledb/node.go b/x/merkledb/node.go index 9a63ef82c4a7..701e120e4b52 100644 --- a/x/merkledb/node.go +++ b/x/merkledb/node.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -29,7 +29,6 @@ type child struct { type node struct { dbNode key Key - nodeBytes []byte valueDigest maybe.Maybe[[]byte] } @@ -50,9 +49,8 @@ func parseNode(key Key, nodeBytes []byte) (*node, error) { return nil, err } result := &node{ - dbNode: n, - key: key, - nodeBytes: nodeBytes, + dbNode: n, + key: key, } result.setValueDigest() @@ -66,17 +64,7 @@ func (n *node) hasValue() bool { // Returns the byte representation of this node. func (n *node) bytes() []byte { - if n.nodeBytes == nil { - n.nodeBytes = codec.encodeDBNode(&n.dbNode) - } - - return n.nodeBytes -} - -// clear the cached values that will need to be recalculated whenever the node changes -// for example, node ID and byte representation -func (n *node) onNodeChanged() { - n.nodeBytes = nil + return codec.encodeDBNode(&n.dbNode) } // Returns and caches the ID of this node. @@ -88,7 +76,6 @@ func (n *node) calculateID(metrics merkleMetrics) ids.ID { // Set [n]'s value to [val]. func (n *node) setValue(val maybe.Maybe[[]byte]) { - n.onNodeChanged() n.value = val n.setValueDigest() } @@ -105,10 +92,15 @@ func (n *node) setValueDigest() { // Assumes [child]'s key is valid as a child of [n]. // That is, [n.key] is a prefix of [child.key]. func (n *node) addChild(childNode *node, tokenSize int) { + n.addChildWithID(childNode, tokenSize, ids.Empty) +} + +func (n *node) addChildWithID(childNode *node, tokenSize int, childID ids.ID) { n.setChildEntry( childNode.key.Token(n.key.length, tokenSize), &child{ compressedKey: childNode.key.Skip(n.key.length + tokenSize), + id: childID, hasValue: childNode.hasValue(), }, ) @@ -116,13 +108,11 @@ func (n *node) addChild(childNode *node, tokenSize int) { // Adds a child to [n] without a reference to the child node. func (n *node) setChildEntry(index byte, childEntry *child) { - n.onNodeChanged() n.children[index] = childEntry } // Removes [child] from [n]'s children. func (n *node) removeChild(child *node, tokenSize int) { - n.onNodeChanged() delete(n.children, child.key.Token(n.key.length, tokenSize)) } @@ -138,7 +128,6 @@ func (n *node) clone() *node { children: make(map[byte]*child, len(n.children)), }, valueDigest: n.valueDigest, - nodeBytes: n.nodeBytes, } for key, existing := range n.children { result.children[key] = &child{ diff --git a/x/merkledb/node_test.go b/x/merkledb/node_test.go index e0cb4dd04b06..3c09679570f3 100644 --- a/x/merkledb/node_test.go +++ b/x/merkledb/node_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb diff --git a/x/merkledb/proof.go b/x/merkledb/proof.go index 39ceff3d3157..8ddd97ffa5f9 100644 --- a/x/merkledb/proof.go +++ b/x/merkledb/proof.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -20,10 +20,7 @@ import ( pb "github.com/ava-labs/avalanchego/proto/pb/sync" ) -const ( - verificationEvictionBatchSize = 0 - verificationCacheSize = math.MaxInt -) +const verificationCacheSize = math.MaxUint16 var ( ErrInvalidProof = errors.New("proof obtained an invalid root ID") @@ -31,11 +28,13 @@ var ( ErrNonIncreasingValues = errors.New("keys sent are not in increasing order") ErrStateFromOutsideOfRange = errors.New("state key falls outside of the start->end range") ErrNonIncreasingProofNodes = errors.New("each proof node key must be a strict prefix of the next") + ErrExtraProofNodes = errors.New("extra proof nodes in path") + ErrDataInMissingRootProof = errors.New("there should be no state or deleted keys in a change proof that had a missing root") + ErrEmptyProof = errors.New("proof is empty") ErrNoMerkleProof = errors.New("empty key response must include merkle proof") ErrShouldJustBeRoot = errors.New("end proof should only contain root") ErrNoStartProof = errors.New("no start proof") ErrNoEndProof = errors.New("no end proof") - ErrNoProof = errors.New("proof has no nodes") ErrProofNodeNotForKey = errors.New("the provided node has a key that is not a prefix of the specified key") ErrProofValueDoesntMatch = errors.New("the provided value does not match the proof node for the provided key's value") ErrProofNodeHasUnincludedValue = errors.New("the provided proof has a value for a key within the range that is not present in the provided key/values") @@ -121,7 +120,7 @@ func (node *ProofNode) UnmarshalProto(pbNode *pb.ProofNode) error { type Proof struct { // Nodes in the proof path from root --> target key // (or node that would be where key is if it doesn't exist). - // Must always be non-empty (i.e. have the root node). + // Always contains at least the root. Path []ProofNode // This is a proof that [key] exists/doesn't exist. Key Key @@ -137,8 +136,9 @@ type Proof struct { func (proof *Proof) Verify(ctx context.Context, expectedRootID ids.ID, tokenSize int) error { // Make sure the proof is well-formed. if len(proof.Path) == 0 { - return ErrNoProof + return ErrEmptyProof } + if err := verifyProofPath(proof.Path, maybe.Some(proof.Key)); err != nil { return err } @@ -166,7 +166,7 @@ func (proof *Proof) Verify(ctx context.Context, expectedRootID ids.ID, tokenSize } // Don't bother locking [view] -- nobody else has a reference to it. - view, err := getStandaloneTrieView(ctx, nil, tokenSize) + view, err := getStandaloneView(ctx, nil, tokenSize) if err != nil { return err } @@ -249,16 +249,12 @@ type RangeProof struct { // they are also in [EndProof]. StartProof []ProofNode - // If no upper range bound was given, [KeyValues] is empty, - // and [StartProof] is non-empty, this is empty. - // - // If no upper range bound was given, [KeyValues] is empty, - // and [StartProof] is empty, this is the root. + // If no upper range bound was given and [KeyValues] is empty, this is empty. // - // If an upper range bound was given and [KeyValues] is empty, - // this is a proof for the upper range bound. + // If no upper range bound was given and [KeyValues] is non-empty, this is + // a proof for the largest key in [KeyValues]. // - // Otherwise, this is a proof for the largest key in [KeyValues]. + // Otherwise this is a proof for the upper range bound. EndProof []ProofNode // This proof proves that the key-value pairs in [KeyValues] are in the trie. @@ -287,11 +283,9 @@ func (proof *RangeProof) Verify( case start.HasValue() && end.HasValue() && bytes.Compare(start.Value(), end.Value()) > 0: return ErrStartAfterEnd case len(proof.KeyValues) == 0 && len(proof.StartProof) == 0 && len(proof.EndProof) == 0: - return ErrNoMerkleProof - case end.IsNothing() && len(proof.KeyValues) == 0 && len(proof.StartProof) > 0 && len(proof.EndProof) != 0: + return ErrEmptyProof + case end.IsNothing() && len(proof.KeyValues) == 0 && len(proof.EndProof) != 0: return ErrUnexpectedEndProof - case end.IsNothing() && len(proof.KeyValues) == 0 && len(proof.StartProof) == 0 && len(proof.EndProof) != 1: - return ErrShouldJustBeRoot case len(proof.EndProof) == 0 && (end.HasValue() || len(proof.KeyValues) > 0): return ErrNoEndProof } @@ -363,7 +357,7 @@ func (proof *RangeProof) Verify( } // Don't need to lock [view] because nobody else has a reference to it. - view, err := getStandaloneTrieView(ctx, ops, tokenSize) + view, err := getStandaloneView(ctx, ops, tokenSize) if err != nil { return err } @@ -797,9 +791,9 @@ func valueOrHashMatches(value maybe.Maybe[[]byte], valueOrHash maybe.Maybe[[]byt // < [insertChildrenLessThan] or > [insertChildrenGreaterThan]. // If [insertChildrenLessThan] is Nothing, no children are < [insertChildrenLessThan]. // If [insertChildrenGreaterThan] is Nothing, no children are > [insertChildrenGreaterThan]. -// Assumes [t.lock] is held. +// Assumes [v.lock] is held. func addPathInfo( - t *trieView, + v *view, proofPath []ProofNode, insertChildrenLessThan maybe.Maybe[Key], insertChildrenGreaterThan maybe.Maybe[Key], @@ -819,7 +813,7 @@ func addPathInfo( // load the node associated with the key or create a new one // pass nothing because we are going to overwrite the value digest below - n, err := t.insert(key, maybe.Nothing[[]byte]()) + n, err := v.insert(key, maybe.Nothing[[]byte]()) if err != nil { return err } @@ -840,7 +834,7 @@ func addPathInfo( if existingChild, ok := n.children[index]; ok { compressedKey = existingChild.compressedKey } - childKey := key.Extend(ToToken(index, t.tokenSize), compressedKey) + childKey := key.Extend(ToToken(index, v.tokenSize), compressedKey) if (shouldInsertLeftChildren && childKey.Less(insertChildrenLessThan.Value())) || (shouldInsertRightChildren && childKey.Greater(insertChildrenGreaterThan.Value())) { // We didn't set the other values on the child entry, but it doesn't matter. @@ -858,17 +852,18 @@ func addPathInfo( return nil } -// getStandaloneTrieView returns a new view that has nothing in it besides the changes due to [ops] -func getStandaloneTrieView(ctx context.Context, ops []database.BatchOp, size int) (*trieView, error) { +// getStandaloneView returns a new view that has nothing in it besides the changes due to [ops] +func getStandaloneView(ctx context.Context, ops []database.BatchOp, size int) (*view, error) { db, err := newDatabase( ctx, memdb.New(), Config{ - EvictionBatchSize: verificationEvictionBatchSize, - Tracer: trace.Noop, - ValueNodeCacheSize: verificationCacheSize, - IntermediateNodeCacheSize: verificationCacheSize, - BranchFactor: tokenSizeToBranchFactor[size], + BranchFactor: tokenSizeToBranchFactor[size], + Tracer: trace.Noop, + ValueNodeCacheSize: verificationCacheSize, + IntermediateNodeCacheSize: verificationCacheSize, + IntermediateWriteBufferSize: verificationCacheSize, + IntermediateWriteBatchSize: verificationCacheSize, }, &mockMetrics{}, ) @@ -876,5 +871,5 @@ func getStandaloneTrieView(ctx context.Context, ops []database.BatchOp, size int return nil, err } - return newTrieView(db, db, ViewChanges{BatchOps: ops, ConsumeBytes: true}) + return newView(db, db, ViewChanges{BatchOps: ops, ConsumeBytes: true}) } diff --git a/x/merkledb/proof_test.go b/x/merkledb/proof_test.go index b22b80ffd09d..fa047e87d4a4 100644 --- a/x/merkledb/proof_test.go +++ b/x/merkledb/proof_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -24,7 +24,7 @@ import ( func Test_Proof_Empty(t *testing.T) { proof := &Proof{} err := proof.Verify(context.Background(), ids.Empty, 4) - require.ErrorIs(t, err, ErrNoProof) + require.ErrorIs(t, err, ErrEmptyProof) } func Test_Proof_Simple(t *testing.T) { @@ -59,6 +59,13 @@ func Test_Proof_Verify_Bad_Data(t *testing.T) { malform: func(proof *Proof) {}, expectedErr: nil, }, + { + name: "empty", + malform: func(proof *Proof) { + proof.Path = nil + }, + expectedErr: ErrEmptyProof, + }, { name: "odd length key path with value", malform: func(proof *Proof) { @@ -150,7 +157,7 @@ func Test_RangeProof_Extra_Value(t *testing.T) { context.Background(), maybe.Some([]byte{1}), maybe.Some([]byte{5, 5}), - db.getMerkleRoot(), + db.rootID, db.tokenSize, )) @@ -160,7 +167,7 @@ func Test_RangeProof_Extra_Value(t *testing.T) { context.Background(), maybe.Some([]byte{1}), maybe.Some([]byte{5, 5}), - db.getMerkleRoot(), + db.rootID, db.tokenSize, ) require.ErrorIs(err, ErrInvalidProof) @@ -179,6 +186,15 @@ func Test_RangeProof_Verify_Bad_Data(t *testing.T) { malform: func(proof *RangeProof) {}, expectedErr: nil, }, + { + name: "empty", + malform: func(proof *RangeProof) { + proof.KeyValues = nil + proof.StartProof = nil + proof.EndProof = nil + }, + expectedErr: ErrEmptyProof, + }, { name: "StartProof: last proof node has missing value", malform: func(proof *RangeProof) { @@ -276,6 +292,8 @@ func Test_Proof(t *testing.T) { require.Equal(ToKey([]byte("key")), proof.Path[0].Key) require.Equal(maybe.Some([]byte("value")), proof.Path[0].ValueOrHash) + require.Equal(ToKey([]byte("key0")).Take(28), proof.Path[1].Key) + require.True(proof.Path[1].ValueOrHash.IsNothing()) // intermediate node require.Equal(ToKey([]byte("key1")), proof.Path[2].Key) require.Equal(maybe.Some([]byte("value1")), proof.Path[2].ValueOrHash) @@ -283,10 +301,9 @@ func Test_Proof(t *testing.T) { require.NoError(err) require.NoError(proof.Verify(context.Background(), expectedRootID, dbTrie.tokenSize)) - proof.Path[0].ValueOrHash = maybe.Some([]byte("value2")) - + proof.Path[0].Key = ToKey([]byte("key1")) err = proof.Verify(context.Background(), expectedRootID, dbTrie.tokenSize) - require.ErrorIs(err, ErrInvalidProof) + require.ErrorIs(err, ErrProofNodeNotForKey) } func Test_RangeProof_Syntactic_Verify(t *testing.T) { @@ -307,11 +324,11 @@ func Test_RangeProof_Syntactic_Verify(t *testing.T) { expectedErr: ErrStartAfterEnd, }, { - name: "empty", // Also tests start can be > end if end is nil + name: "empty", start: maybe.Some([]byte{1}), end: maybe.Nothing[[]byte](), proof: &RangeProof{}, - expectedErr: ErrNoMerkleProof, + expectedErr: ErrEmptyProof, }, { name: "unexpected end proof", @@ -323,15 +340,6 @@ func Test_RangeProof_Syntactic_Verify(t *testing.T) { }, expectedErr: ErrUnexpectedEndProof, }, - { - name: "should just be root", - start: maybe.Nothing[[]byte](), - end: maybe.Nothing[[]byte](), - proof: &RangeProof{ - EndProof: []ProofNode{{}, {}}, - }, - expectedErr: ErrShouldJustBeRoot, - }, { name: "no end proof; has end bound", start: maybe.Some([]byte{1}), @@ -501,7 +509,9 @@ func Test_RangeProof(t *testing.T) { require.Equal([]byte{2}, proof.KeyValues[1].Value) require.Equal([]byte{3}, proof.KeyValues[2].Value) + require.Len(proof.EndProof, 2) require.Equal([]byte{0}, proof.EndProof[0].Key.Bytes()) + require.Len(proof.EndProof[0].Children, 5) // 0,1,2,3,4 require.Equal([]byte{3}, proof.EndProof[1].Key.Bytes()) // only a single node here since others are duplicates in endproof @@ -511,7 +521,7 @@ func Test_RangeProof(t *testing.T) { context.Background(), maybe.Some([]byte{1}), maybe.Some([]byte{3, 5}), - db.getMerkleRoot(), + db.rootID, db.tokenSize, )) } @@ -522,6 +532,8 @@ func Test_RangeProof_BadBounds(t *testing.T) { db, err := getBasicDB() require.NoError(err) + require.NoError(db.Put(nil, nil)) + // non-nil start/end proof, err := db.GetRangeProof(context.Background(), maybe.Some([]byte{4}), maybe.Some([]byte{3}), 50) require.ErrorIs(err, ErrStartAfterEnd) @@ -556,14 +568,14 @@ func Test_RangeProof_NilStart(t *testing.T) { require.Equal([]byte("value1"), proof.KeyValues[0].Value) require.Equal([]byte("value2"), proof.KeyValues[1].Value) - require.Equal(ToKey([]byte("key2")), proof.EndProof[1].Key) + require.Equal(ToKey([]byte("key2")), proof.EndProof[1].Key, db.tokenSize) require.Equal(ToKey([]byte("key2")).Take(28), proof.EndProof[0].Key) require.NoError(proof.Verify( context.Background(), maybe.Nothing[[]byte](), maybe.Some([]byte("key35")), - db.getMerkleRoot(), + db.rootID, db.tokenSize, )) } @@ -573,10 +585,16 @@ func Test_RangeProof_NilEnd(t *testing.T) { db, err := getBasicDB() require.NoError(err) + writeBasicBatch(t, db) require.NoError(err) - proof, err := db.GetRangeProof(context.Background(), maybe.Some([]byte{1}), maybe.Nothing[[]byte](), 2) + proof, err := db.GetRangeProof( // Should have keys [1], [2] + context.Background(), + maybe.Some([]byte{1}), + maybe.Nothing[[]byte](), + 2, + ) require.NoError(err) require.NotNil(proof) @@ -590,14 +608,14 @@ func Test_RangeProof_NilEnd(t *testing.T) { require.Equal([]byte{1}, proof.StartProof[0].Key.Bytes()) - require.Equal([]byte{0}, proof.EndProof[0].Key.Bytes()) + require.Equal(db.root.Value().key, proof.EndProof[0].Key) require.Equal([]byte{2}, proof.EndProof[1].Key.Bytes()) require.NoError(proof.Verify( context.Background(), maybe.Some([]byte{1}), maybe.Nothing[[]byte](), - db.getMerkleRoot(), + db.rootID, db.tokenSize, )) } @@ -633,28 +651,68 @@ func Test_RangeProof_EmptyValues(t *testing.T) { require.Equal(ToKey([]byte("key1")), proof.StartProof[0].Key) require.Len(proof.EndProof, 2) - require.Equal(ToKey([]byte("key2")), proof.EndProof[1].Key) - require.Equal(ToKey([]byte("key2")).Take(28), proof.EndProof[0].Key) + require.Equal(ToKey([]byte("key1")).Take(28), proof.EndProof[0].Key, db.tokenSize) // root + require.Equal(ToKey([]byte("key2")), proof.EndProof[1].Key, db.tokenSize) require.NoError(proof.Verify( context.Background(), maybe.Some([]byte("key1")), maybe.Some([]byte("key2")), - db.getMerkleRoot(), + db.rootID, db.tokenSize, )) } func Test_ChangeProof_Missing_History_For_EndRoot(t *testing.T) { require := require.New(t) + seed := time.Now().UnixNano() + t.Logf("Seed: %d", seed) + rand := rand.New(rand.NewSource(seed)) // #nosec G404 db, err := getBasicDB() require.NoError(err) - startRoot, err := db.GetMerkleRoot(context.Background()) - require.NoError(err) - _, err = db.GetChangeProof(context.Background(), startRoot, ids.Empty, maybe.Nothing[[]byte](), maybe.Nothing[[]byte](), 50) + roots := []ids.ID{} + for i := 0; i < defaultHistoryLength+1; i++ { + key := make([]byte, 16) + _, _ = rand.Read(key) + require.NoError(db.Put(key, nil)) + root, err := db.GetMerkleRoot(context.Background()) + require.NoError(err) + roots = append(roots, root) + } + + _, err = db.GetChangeProof( + context.Background(), + roots[len(roots)-1], + ids.GenerateTestID(), + maybe.Nothing[[]byte](), + maybe.Nothing[[]byte](), + 50, + ) + require.ErrorIs(err, ErrNoEndRoot) + require.ErrorIs(err, ErrInsufficientHistory) + + _, err = db.GetChangeProof( + context.Background(), + roots[0], + roots[len(roots)-1], + maybe.Nothing[[]byte](), + maybe.Nothing[[]byte](), + 50, + ) + require.NotErrorIs(err, ErrNoEndRoot) require.ErrorIs(err, ErrInsufficientHistory) + + _, err = db.GetChangeProof( + context.Background(), + roots[1], + roots[len(roots)-1], + maybe.Nothing[[]byte](), + maybe.Nothing[[]byte](), + 50, + ) + require.NoError(err) } func Test_ChangeProof_BadBounds(t *testing.T) { @@ -815,13 +873,26 @@ func Test_ChangeProof_Verify_Bad_Data(t *testing.T) { dbClone, err := getBasicDB() require.NoError(err) - proof, err := db.GetChangeProof(context.Background(), startRoot, endRoot, maybe.Some([]byte{2}), maybe.Some([]byte{3, 0}), 50) + proof, err := db.GetChangeProof( + context.Background(), + startRoot, + endRoot, + maybe.Some([]byte{2}), + maybe.Some([]byte{3, 0}), + 50, + ) require.NoError(err) require.NotNil(proof) tt.malform(proof) - err = dbClone.VerifyChangeProof(context.Background(), proof, maybe.Some([]byte{2}), maybe.Some([]byte{3, 0}), db.getMerkleRoot()) + err = dbClone.VerifyChangeProof( + context.Background(), + proof, + maybe.Some([]byte{2}), + maybe.Some([]byte{3, 0}), + db.getMerkleRoot(), + ) require.ErrorIs(err, tt.expectedErr) }) } @@ -849,7 +920,7 @@ func Test_ChangeProof_Syntactic_Verify(t *testing.T) { proof: &ChangeProof{}, start: maybe.Nothing[[]byte](), end: maybe.Nothing[[]byte](), - expectedErr: ErrNoMerkleProof, + expectedErr: ErrEmptyProof, }, { name: "no end proof", @@ -1626,6 +1697,9 @@ func FuzzRangeProofInvariants(f *testing.F) { if maxProofLen == 0 { t.SkipNow() } + if numKeyValues == 0 { + t.SkipNow() + } // Make sure proof bounds are valid if len(endBytes) != 0 && bytes.Compare(startBytes, endBytes) > 0 { @@ -1656,15 +1730,19 @@ func FuzzRangeProofInvariants(f *testing.F) { end = maybe.Some(endBytes) } + rootID, err := db.GetMerkleRoot(context.Background()) + require.NoError(err) + rangeProof, err := db.GetRangeProof( context.Background(), start, end, int(maxProofLen), ) - require.NoError(err) - - rootID, err := db.GetMerkleRoot(context.Background()) + if rootID == ids.Empty { + require.ErrorIs(err, ErrEmptyProof) + return + } require.NoError(err) require.NoError(rangeProof.Verify( @@ -1760,10 +1838,15 @@ func FuzzProofVerification(f *testing.F) { deletePortion, ) + if db.getMerkleRoot() == ids.Empty { + return + } + proof, err := db.GetProof( context.Background(), key, ) + require.NoError(err) rootID, err := db.GetMerkleRoot(context.Background()) diff --git a/x/merkledb/tracer.go b/x/merkledb/tracer.go index 707028f2c9cd..d4e7a6fce4d7 100644 --- a/x/merkledb/tracer.go +++ b/x/merkledb/tracer.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb diff --git a/x/merkledb/trie.go b/x/merkledb/trie.go index d4b01d2de29a..bc2b4db81541 100644 --- a/x/merkledb/trie.go +++ b/x/merkledb/trie.go @@ -1,30 +1,66 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb import ( + "bytes" "context" + "fmt" + + "golang.org/x/exp/slices" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/maybe" ) +type ViewChanges struct { + BatchOps []database.BatchOp + MapOps map[string]maybe.Maybe[[]byte] + // ConsumeBytes when set to true will skip copying of bytes and assume + // ownership of the provided bytes. + ConsumeBytes bool +} + type MerkleRootGetter interface { - // GetMerkleRoot returns the merkle root of the Trie + // GetMerkleRoot returns the merkle root of the trie. + // Returns ids.Empty if the trie is empty. GetMerkleRoot(ctx context.Context) (ids.ID, error) } type ProofGetter interface { // GetProof generates a proof of the value associated with a particular key, // or a proof of its absence from the trie + // Returns ErrEmptyProof if the trie is empty. GetProof(ctx context.Context, keyBytes []byte) (*Proof, error) } -type ReadOnlyTrie interface { +type trieInternals interface { + // get the value associated with the key in path form + // database.ErrNotFound if the key is not present + getValue(key Key) ([]byte, error) + + // get an editable copy of the node with the given key path + // hasValue indicates which db to look in (value or intermediate) + getEditableNode(key Key, hasValue bool) (*node, error) + + // get the node associated with the key without locking + getNode(key Key, hasValue bool) (*node, error) + + // If this trie is non-empty, returns the root node. + // Must be copied before modification. + // Otherwise returns Nothing. + getRoot() maybe.Maybe[*node] + + getTokenSize() int +} + +type Trie interface { + trieInternals MerkleRootGetter ProofGetter + database.Iteratee // GetValue gets the value associated with the specified key // database.ErrNotFound if the key is not present @@ -34,46 +70,202 @@ type ReadOnlyTrie interface { // database.ErrNotFound if the key is not present GetValues(ctx context.Context, keys [][]byte) ([][]byte, []error) - // get the value associated with the key in path form - // database.ErrNotFound if the key is not present - getValue(key Key) ([]byte, error) - - // get an editable copy of the node with the given key path - // hasValue indicates which db to look in (value or intermediate) - getEditableNode(key Key, hasValue bool) (*node, error) - // GetRangeProof returns a proof of up to [maxLength] key-value pairs with // keys in range [start, end]. // If [start] is Nothing, there's no lower bound on the range. // If [end] is Nothing, there's no upper bound on the range. + // Returns ErrEmptyProof if the trie is empty. GetRangeProof(ctx context.Context, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte], maxLength int) (*RangeProof, error) - database.Iteratee -} - -type ViewChanges struct { - BatchOps []database.BatchOp - MapOps map[string]maybe.Maybe[[]byte] - // ConsumeBytes when set to true will skip copying of bytes and assume - // ownership of the provided bytes. - ConsumeBytes bool -} - -type Trie interface { - ReadOnlyTrie - // NewView returns a new view on top of this Trie where the passed changes // have been applied. NewView( ctx context.Context, changes ViewChanges, - ) (TrieView, error) + ) (View, error) } -type TrieView interface { +type View interface { Trie // CommitToDB writes the changes in this view to the database. // Takes the DB commit lock. CommitToDB(ctx context.Context) error } + +// Returns the nodes along the path to [key]. +// The first node is the root, and the last node is either the node with the +// given [key], if it's in the trie, or the node with the largest prefix of +// the [key] if it isn't in the trie. +// Always returns at least the root node. +// Assumes [t] doesn't change while this function is running. +func visitPathToKey(t Trie, key Key, visitNode func(*node) error) error { + maybeRoot := t.getRoot() + if maybeRoot.IsNothing() { + return nil + } + root := maybeRoot.Value() + if !key.HasPrefix(root.key) { + return nil + } + var ( + // all node paths start at the root + currentNode = root + tokenSize = t.getTokenSize() + err error + ) + if err := visitNode(currentNode); err != nil { + return err + } + // while the entire path hasn't been matched + for currentNode.key.length < key.length { + // confirm that a child exists and grab its ID before attempting to load it + nextChildEntry, hasChild := currentNode.children[key.Token(currentNode.key.length, tokenSize)] + + if !hasChild || !key.iteratedHasPrefix(nextChildEntry.compressedKey, currentNode.key.length+tokenSize, tokenSize) { + // there was no child along the path or the child that was there doesn't match the remaining path + return nil + } + // grab the next node along the path + currentNode, err = t.getNode(key.Take(currentNode.key.length+tokenSize+nextChildEntry.compressedKey.length), nextChildEntry.hasValue) + if err != nil { + return err + } + if err := visitNode(currentNode); err != nil { + return err + } + } + return nil +} + +// Returns a proof that [bytesPath] is in or not in trie [t]. +// Assumes [t] doesn't change while this function is running. +func getProof(t Trie, key []byte) (*Proof, error) { + root := t.getRoot() + if root.IsNothing() { + return nil, ErrEmptyProof + } + + proof := &Proof{ + Key: ToKey(key), + } + + var closestNode *node + if err := visitPathToKey(t, proof.Key, func(n *node) error { + closestNode = n + // From root --> node from left --> right. + proof.Path = append(proof.Path, n.asProofNode()) + return nil + }); err != nil { + return nil, err + } + + if len(proof.Path) == 0 { + // No key in [t] is a prefix of [key]. + // The root alone proves that [key] isn't in [t]. + proof.Path = append(proof.Path, root.Value().asProofNode()) + return proof, nil + } + + if closestNode.key == proof.Key { + // There is a node with the given [key]. + proof.Value = maybe.Bind(closestNode.value, slices.Clone[[]byte]) + return proof, nil + } + + // There is no node with the given [key]. + // If there is a child at the index where the node would be + // if it existed, include that child in the proof. + nextIndex := proof.Key.Token(closestNode.key.length, t.getTokenSize()) + child, ok := closestNode.children[nextIndex] + if !ok { + return proof, nil + } + + childNode, err := t.getNode( + closestNode.key.Extend(ToToken(nextIndex, t.getTokenSize()), child.compressedKey), + child.hasValue, + ) + if err != nil { + return nil, err + } + proof.Path = append(proof.Path, childNode.asProofNode()) + return proof, nil +} + +// GetRangeProof returns a range proof for (at least part of) the key range [start, end]. +// The returned proof's [KeyValues] has at most [maxLength] values. +// [maxLength] must be > 0. +// Assumes [t] doesn't change while this function is running. +func getRangeProof( + t Trie, + start maybe.Maybe[[]byte], + end maybe.Maybe[[]byte], + maxLength int, +) (*RangeProof, error) { + switch { + case start.HasValue() && end.HasValue() && bytes.Compare(start.Value(), end.Value()) == 1: + return nil, ErrStartAfterEnd + case maxLength <= 0: + return nil, fmt.Errorf("%w but was %d", ErrInvalidMaxLength, maxLength) + case t.getRoot().IsNothing(): + return nil, ErrEmptyProof + } + + result := RangeProof{ + KeyValues: make([]KeyValue, 0, initKeyValuesSize), + } + it := t.NewIteratorWithStart(start.Value()) + for it.Next() && len(result.KeyValues) < maxLength && (end.IsNothing() || bytes.Compare(it.Key(), end.Value()) <= 0) { + // clone the value to prevent editing of the values stored within the trie + result.KeyValues = append(result.KeyValues, KeyValue{ + Key: it.Key(), + Value: slices.Clone(it.Value()), + }) + } + it.Release() + if err := it.Error(); err != nil { + return nil, err + } + + // This proof may not contain all key-value pairs in [start, end] due to size limitations. + // The end proof we provide should be for the last key-value pair in the proof, not for + // the last key-value pair requested, which may not be in this proof. + var ( + endProof *Proof + err error + ) + if len(result.KeyValues) > 0 { + greatestKey := result.KeyValues[len(result.KeyValues)-1].Key + endProof, err = getProof(t, greatestKey) + if err != nil { + return nil, err + } + } else if end.HasValue() { + endProof, err = getProof(t, end.Value()) + if err != nil { + return nil, err + } + } + if endProof != nil { + result.EndProof = endProof.Path + } + + if start.HasValue() { + startProof, err := getProof(t, start.Value()) + if err != nil { + return nil, err + } + result.StartProof = startProof.Path + + // strip out any common nodes to reduce proof size + i := 0 + for ; i < len(result.StartProof) && + i < len(result.EndProof) && + result.StartProof[i].Key == result.EndProof[i].Key; i++ { + } + result.StartProof = result.StartProof[i:] + } + + return &result, nil +} diff --git a/x/merkledb/trie_test.go b/x/merkledb/trie_test.go index a431dd6b254d..f6dc0351f549 100644 --- a/x/merkledb/trie_test.go +++ b/x/merkledb/trie_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -18,25 +18,17 @@ import ( "github.com/ava-labs/avalanchego/utils/hashing" ) -func getNodeValue(t ReadOnlyTrie, key string) ([]byte, error) { - var view *trieView - if asTrieView, ok := t.(*trieView); ok { - if err := asTrieView.calculateNodeIDs(context.Background()); err != nil { - return nil, err - } - view = asTrieView - } - if asDatabases, ok := t.(*merkleDB); ok { - dbView, err := asDatabases.NewView(context.Background(), ViewChanges{}) - if err != nil { +func getNodeValue(t Trie, key string) ([]byte, error) { + path := ToKey([]byte(key)) + if asView, ok := t.(*view); ok { + if err := asView.calculateNodeIDs(context.Background()); err != nil { return nil, err } - view = dbView.(*trieView) } - path := ToKey([]byte(key)) var result *node - err := view.visitPathToKey(path, func(n *node) error { + + err := visitPathToKey(t, path, func(n *node) error { result = n return nil }) @@ -56,7 +48,7 @@ func Test_GetValue_Safety(t *testing.T) { db, err := getBasicDB() require.NoError(err) - trieView, err := db.NewView( + view, err := db.NewView( context.Background(), ViewChanges{ BatchOps: []database.BatchOp{ @@ -66,13 +58,13 @@ func Test_GetValue_Safety(t *testing.T) { ) require.NoError(err) - trieVal, err := trieView.GetValue(context.Background(), []byte{0}) + trieVal, err := view.GetValue(context.Background(), []byte{0}) require.NoError(err) require.Equal([]byte{0}, trieVal) trieVal[0] = 1 // should still be []byte{0} after edit - trieVal, err = trieView.GetValue(context.Background(), []byte{0}) + trieVal, err = view.GetValue(context.Background(), []byte{0}) require.NoError(err) require.Equal([]byte{0}, trieVal) } @@ -83,7 +75,7 @@ func Test_GetValues_Safety(t *testing.T) { db, err := getBasicDB() require.NoError(err) - trieView, err := db.NewView( + view, err := db.NewView( context.Background(), ViewChanges{ BatchOps: []database.BatchOp{ @@ -93,7 +85,7 @@ func Test_GetValues_Safety(t *testing.T) { ) require.NoError(err) - trieVals, errs := trieView.GetValues(context.Background(), [][]byte{{0}}) + trieVals, errs := view.GetValues(context.Background(), [][]byte{{0}}) require.Len(errs, 1) require.NoError(errs[0]) require.Equal([]byte{0}, trieVals[0]) @@ -101,13 +93,13 @@ func Test_GetValues_Safety(t *testing.T) { require.Equal([]byte{1}, trieVals[0]) // should still be []byte{0} after edit - trieVals, errs = trieView.GetValues(context.Background(), [][]byte{{0}}) + trieVals, errs = view.GetValues(context.Background(), [][]byte{{0}}) require.Len(errs, 1) require.NoError(errs[0]) require.Equal([]byte{0}, trieVals[0]) } -func TestTrieViewVisitPathToKey(t *testing.T) { +func TestVisitPathToKey(t *testing.T) { require := require.New(t) db, err := getBasicDB() @@ -115,18 +107,16 @@ func TestTrieViewVisitPathToKey(t *testing.T) { trieIntf, err := db.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, trieIntf) - trie := trieIntf.(*trieView) + require.IsType(&view{}, trieIntf) + trie := trieIntf.(*view) var nodePath []*node - require.NoError(trie.visitPathToKey(ToKey(nil), func(n *node) error { + require.NoError(visitPathToKey(trie, ToKey(nil), func(n *node) error { nodePath = append(nodePath, n) return nil })) - // Just the root - require.Len(nodePath, 1) - require.Equal(trie.sentinelNode, nodePath[0]) + require.Empty(nodePath) // Insert a key key1 := []byte{0} @@ -139,21 +129,19 @@ func TestTrieViewVisitPathToKey(t *testing.T) { }, ) require.NoError(err) - require.IsType(&trieView{}, trieIntf) - trie = trieIntf.(*trieView) + require.IsType(&view{}, trieIntf) + trie = trieIntf.(*view) require.NoError(trie.calculateNodeIDs(context.Background())) - nodePath = make([]*node, 0, 2) - require.NoError(trie.visitPathToKey(ToKey(key1), func(n *node) error { + nodePath = make([]*node, 0, 1) + require.NoError(visitPathToKey(trie, ToKey(key1), func(n *node) error { nodePath = append(nodePath, n) return nil })) - // Root and 1 value - require.Len(nodePath, 2) - - require.Equal(trie.sentinelNode, nodePath[0]) - require.Equal(ToKey(key1), nodePath[1].key) + // 1 value + require.Len(nodePath, 1) + require.Equal(ToKey(key1), nodePath[0].key) // Insert another key which is a child of the first key2 := []byte{0, 1} @@ -166,21 +154,24 @@ func TestTrieViewVisitPathToKey(t *testing.T) { }, ) require.NoError(err) - require.IsType(&trieView{}, trieIntf) - trie = trieIntf.(*trieView) + require.IsType(&view{}, trieIntf) + trie = trieIntf.(*view) require.NoError(trie.calculateNodeIDs(context.Background())) - nodePath = make([]*node, 0, 3) - require.NoError(trie.visitPathToKey(ToKey(key2), func(n *node) error { + nodePath = make([]*node, 0, 2) + require.NoError(visitPathToKey(trie, ToKey(key2), func(n *node) error { nodePath = append(nodePath, n) return nil })) - require.Len(nodePath, 3) - - require.Equal(trie.sentinelNode, nodePath[0]) - require.Equal(ToKey(key1), nodePath[1].key) - require.Equal(ToKey(key2), nodePath[2].key) - + require.Len(nodePath, 2) + require.Equal(trie.root.Value(), nodePath[0]) + require.Equal(ToKey(key1), nodePath[0].key) + require.Equal(ToKey(key2), nodePath[1].key) + + // Trie is: + // [0] + // | + // [0,1] // Insert a key which shares no prefix with the others key3 := []byte{255} trieIntf, err = trie.NewView( @@ -192,55 +183,60 @@ func TestTrieViewVisitPathToKey(t *testing.T) { }, ) require.NoError(err) - require.IsType(&trieView{}, trieIntf) - trie = trieIntf.(*trieView) + require.IsType(&view{}, trieIntf) + trie = trieIntf.(*view) require.NoError(trie.calculateNodeIDs(context.Background())) + // Trie is: + // [] + // / \ + // [0] [255] + // | + // [0,1] nodePath = make([]*node, 0, 2) - require.NoError(trie.visitPathToKey(ToKey(key3), func(n *node) error { + require.NoError(visitPathToKey(trie, ToKey(key3), func(n *node) error { nodePath = append(nodePath, n) return nil })) require.Len(nodePath, 2) - - require.Equal(trie.sentinelNode, nodePath[0]) + require.Equal(trie.root.Value(), nodePath[0]) + require.Zero(trie.root.Value().key.length) require.Equal(ToKey(key3), nodePath[1].key) // Other key path not affected nodePath = make([]*node, 0, 3) - require.NoError(trie.visitPathToKey(ToKey(key2), func(n *node) error { + require.NoError(visitPathToKey(trie, ToKey(key2), func(n *node) error { nodePath = append(nodePath, n) return nil })) require.Len(nodePath, 3) - - require.Equal(trie.sentinelNode, nodePath[0]) + require.Equal(trie.root.Value(), nodePath[0]) require.Equal(ToKey(key1), nodePath[1].key) require.Equal(ToKey(key2), nodePath[2].key) // Gets closest node when key doesn't exist key4 := []byte{0, 1, 2} nodePath = make([]*node, 0, 3) - require.NoError(trie.visitPathToKey(ToKey(key4), func(n *node) error { + require.NoError(visitPathToKey(trie, ToKey(key4), func(n *node) error { nodePath = append(nodePath, n) return nil })) require.Len(nodePath, 3) - require.Equal(trie.sentinelNode, nodePath[0]) + require.Equal(trie.root.Value(), nodePath[0]) require.Equal(ToKey(key1), nodePath[1].key) require.Equal(ToKey(key2), nodePath[2].key) // Gets just root when key doesn't exist and no key shares a prefix key5 := []byte{128} nodePath = make([]*node, 0, 1) - require.NoError(trie.visitPathToKey(ToKey(key5), func(n *node) error { + require.NoError(visitPathToKey(trie, ToKey(key5), func(n *node) error { nodePath = append(nodePath, n) return nil })) require.Len(nodePath, 1) - require.Equal(trie.sentinelNode, nodePath[0]) + require.Equal(trie.root.Value(), nodePath[0]) } func Test_Trie_ViewOnCommitedView(t *testing.T) { @@ -290,7 +286,7 @@ func Test_Trie_WriteToDB(t *testing.T) { trieIntf1, err := dbTrie.NewView(context.Background(), ViewChanges{}) require.NoError(err) - trie1 := trieIntf1.(*trieView) + trie1 := trieIntf1.(*view) // value hasn't been inserted so shouldn't exist value, err := trie1.GetValue(context.Background(), []byte("key")) @@ -306,7 +302,7 @@ func Test_Trie_WriteToDB(t *testing.T) { }, ) require.NoError(err) - trie2 := trieIntf2.(*trieView) + trie2 := trieIntf2.(*view) value, err = getNodeValue(trie2, "key") require.NoError(err) @@ -441,7 +437,7 @@ func Test_Trie_ExpandOnKeyPath(t *testing.T) { }, ) require.NoError(err) - trie := trieIntf.(*trieView) + trie := trieIntf.(*view) value, err := getNodeValue(trie, "key") require.NoError(err) @@ -456,7 +452,7 @@ func Test_Trie_ExpandOnKeyPath(t *testing.T) { }, ) require.NoError(err) - trie = trieIntf.(*trieView) + trie = trieIntf.(*view) value, err = getNodeValue(trie, "key") require.NoError(err) @@ -475,7 +471,7 @@ func Test_Trie_ExpandOnKeyPath(t *testing.T) { }, ) require.NoError(err) - trie = trieIntf.(*trieView) + trie = trieIntf.(*view) value, err = getNodeValue(trie, "key") require.NoError(err) @@ -490,7 +486,7 @@ func Test_Trie_ExpandOnKeyPath(t *testing.T) { require.Equal([]byte("value12"), value) } -func Test_Trie_compressedKeys(t *testing.T) { +func Test_Trie_CompressedKeys(t *testing.T) { require := require.New(t) dbTrie, err := getBasicDB() @@ -505,7 +501,7 @@ func Test_Trie_compressedKeys(t *testing.T) { }, ) require.NoError(err) - trie := trieIntf.(*trieView) + trie := trieIntf.(*view) value, err := getNodeValue(trie, "key12") require.NoError(err) @@ -520,7 +516,7 @@ func Test_Trie_compressedKeys(t *testing.T) { }, ) require.NoError(err) - trie = trieIntf.(*trieView) + trie = trieIntf.(*view) value, err = getNodeValue(trie, "key12") require.NoError(err) @@ -539,7 +535,7 @@ func Test_Trie_compressedKeys(t *testing.T) { }, ) require.NoError(err) - trie = trieIntf.(*trieView) + trie = trieIntf.(*view) value, err = getNodeValue(trie, "key12") require.NoError(err) @@ -589,9 +585,9 @@ func Test_Trie_HashCountOnBranch(t *testing.T) { require.NoError(err) require.NotNil(dbTrie) - key1, key2, keyPrefix := []byte("key12"), []byte("key1F"), []byte("key1") + key1, key2, keyPrefix := []byte("12"), []byte("1F"), []byte("1") - trieIntf, err := dbTrie.NewView( + view1, err := dbTrie.NewView( context.Background(), ViewChanges{ BatchOps: []database.BatchOp{ @@ -599,11 +595,13 @@ func Test_Trie_HashCountOnBranch(t *testing.T) { }, }) require.NoError(err) - trie := trieIntf.(*trieView) + + // trie is: + // [1] // create new node with common prefix whose children // are key1, key2 - view2, err := trie.NewView( + view2, err := view1.NewView( context.Background(), ViewChanges{ BatchOps: []database.BatchOp{ @@ -612,20 +610,27 @@ func Test_Trie_HashCountOnBranch(t *testing.T) { }) require.NoError(err) + // trie is: + // [1] + // / \ + // [12] [1F] + // clear the hash count to ignore setup dbTrie.metrics.(*mockMetrics).hashCount = 0 - // force the new root to calculate + // calculate the root _, err = view2.GetMerkleRoot(context.Background()) require.NoError(err) - // Make sure the branch node with the common prefix was created. + // Make sure the root is an intermediate node with the expected common prefix. // Note it's only created on call to GetMerkleRoot, not in NewView. - _, err = view2.getEditableNode(ToKey(keyPrefix), false) + prefixNode, err := view2.getEditableNode(ToKey(keyPrefix), false) require.NoError(err) + root := view2.getRoot().Value() + require.Equal(root, prefixNode) + require.Len(root.children, 2) - // only hashes the new branch node, the new child node, and root - // shouldn't hash the existing node + // Had to hash each of the new nodes ("12" and "1F") and the new root require.Equal(int64(3), dbTrie.metrics.(*mockMetrics).hashCount) } @@ -667,7 +672,16 @@ func Test_Trie_HashCountOnDelete(t *testing.T) { require.NoError(err) require.NoError(view.CommitToDB(context.Background())) - // the root is the only updated node so only one new hash + // trie is: + // [key0] (first 28 bits) + // / \ + // [key1] [key2] + root := view.getRoot().Value() + expectedRootKey := ToKey([]byte("key0")).Take(28) + require.Equal(expectedRootKey, root.key) + require.Len(root.children, 2) + + // Had to hash the new root but not [key1] or [key2] nodes require.Equal(oldCount+1, dbTrie.metrics.(*mockMetrics).hashCount) } @@ -761,10 +775,12 @@ func Test_Trie_ChainDeletion(t *testing.T) { ) require.NoError(err) - require.NoError(newTrie.(*trieView).calculateNodeIDs(context.Background())) - root, err := newTrie.getEditableNode(Key{}, false) + require.NoError(newTrie.(*view).calculateNodeIDs(context.Background())) + maybeRoot := newTrie.getRoot() require.NoError(err) - require.Len(root.children, 1) + require.True(maybeRoot.HasValue()) + require.Equal([]byte("value0"), maybeRoot.Value().value.Value()) + require.Len(maybeRoot.Value().children, 1) newTrie, err = newTrie.NewView( context.Background(), @@ -778,11 +794,11 @@ func Test_Trie_ChainDeletion(t *testing.T) { }, ) require.NoError(err) - require.NoError(newTrie.(*trieView).calculateNodeIDs(context.Background())) - root, err = newTrie.getEditableNode(Key{}, false) - require.NoError(err) - // since all values have been deleted, the nodes should have been cleaned up - require.Empty(root.children) + require.NoError(newTrie.(*view).calculateNodeIDs(context.Background())) + + // trie should be empty + root := newTrie.getRoot() + require.False(root.HasValue()) } func Test_Trie_Invalidate_Siblings_On_Commit(t *testing.T) { @@ -811,15 +827,15 @@ func Test_Trie_Invalidate_Siblings_On_Commit(t *testing.T) { sibling2, err := view1.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.False(sibling1.(*trieView).isInvalid()) - require.False(sibling2.(*trieView).isInvalid()) + require.False(sibling1.(*view).isInvalid()) + require.False(sibling2.(*view).isInvalid()) require.NoError(view1.CommitToDB(context.Background())) require.NoError(view2.CommitToDB(context.Background())) - require.True(sibling1.(*trieView).isInvalid()) - require.True(sibling2.(*trieView).isInvalid()) - require.False(view2.(*trieView).isInvalid()) + require.True(sibling1.(*view).isInvalid()) + require.True(sibling2.(*view).isInvalid()) + require.False(view2.(*view).isInvalid()) } func Test_Trie_NodeCollapse(t *testing.T) { @@ -829,54 +845,63 @@ func Test_Trie_NodeCollapse(t *testing.T) { require.NoError(err) require.NotNil(dbTrie) + kvs := []database.BatchOp{ + {Key: []byte("k"), Value: []byte("value0")}, + {Key: []byte("ke"), Value: []byte("value1")}, + {Key: []byte("key"), Value: []byte("value2")}, + {Key: []byte("key1"), Value: []byte("value3")}, + {Key: []byte("key2"), Value: []byte("value4")}, + } + trie, err := dbTrie.NewView( context.Background(), ViewChanges{ - BatchOps: []database.BatchOp{ - {Key: []byte("k"), Value: []byte("value0")}, - {Key: []byte("ke"), Value: []byte("value1")}, - {Key: []byte("key"), Value: []byte("value2")}, - {Key: []byte("key1"), Value: []byte("value3")}, - {Key: []byte("key2"), Value: []byte("value4")}, - }, + BatchOps: kvs, }, ) require.NoError(err) - require.NoError(trie.(*trieView).calculateNodeIDs(context.Background())) - root, err := trie.getEditableNode(Key{}, false) - require.NoError(err) - require.Len(root.children, 1) + require.NoError(trie.(*view).calculateNodeIDs(context.Background())) - root, err = trie.getEditableNode(Key{}, false) - require.NoError(err) - require.Len(root.children, 1) + for _, kv := range kvs { + node, err := trie.getEditableNode(ToKey(kv.Key), true) + require.NoError(err) - firstNode, err := trie.getEditableNode(getSingleChildKey(root, dbTrie.tokenSize), true) - require.NoError(err) - require.Len(firstNode.children, 1) + require.Equal(kv.Value, node.value.Value()) + } + + // delete some values + deletedKVs, remainingKVs := kvs[:3], kvs[3:] + deleteOps := make([]database.BatchOp, len(deletedKVs)) + for i, kv := range deletedKVs { + deleteOps[i] = database.BatchOp{ + Key: kv.Key, + Delete: true, + } + } - // delete the middle values trie, err = trie.NewView( context.Background(), ViewChanges{ - BatchOps: []database.BatchOp{ - {Key: []byte("k"), Delete: true}, - {Key: []byte("ke"), Delete: true}, - {Key: []byte("key"), Delete: true}, - }, + BatchOps: deleteOps, }, ) require.NoError(err) - require.NoError(trie.(*trieView).calculateNodeIDs(context.Background())) - root, err = trie.getEditableNode(Key{}, false) - require.NoError(err) - require.Len(root.children, 1) + require.NoError(trie.(*view).calculateNodeIDs(context.Background())) - firstNode, err = trie.getEditableNode(getSingleChildKey(root, dbTrie.tokenSize), true) - require.NoError(err) - require.Len(firstNode.children, 2) + for _, kv := range deletedKVs { + _, err := trie.getEditableNode(ToKey(kv.Key), true) + require.ErrorIs(err, database.ErrNotFound) + } + + // make sure the other values are still there + for _, kv := range remainingKVs { + node, err := trie.getEditableNode(ToKey(kv.Key), true) + require.NoError(err) + + require.Equal(kv.Value, node.value.Value()) + } } func Test_Trie_MultipleStates(t *testing.T) { @@ -996,8 +1021,8 @@ func TestNewViewOnCommittedView(t *testing.T) { // Create a view view1Intf, err := db.NewView(context.Background(), ViewChanges{BatchOps: []database.BatchOp{{Key: []byte{1}, Value: []byte{1}}}}) require.NoError(err) - require.IsType(&trieView{}, view1Intf) - view1 := view1Intf.(*trieView) + require.IsType(&view{}, view1Intf) + view1 := view1Intf.(*view) // view1 // | @@ -1021,8 +1046,8 @@ func TestNewViewOnCommittedView(t *testing.T) { // Create a new view on the committed view view2Intf, err := view1.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view2Intf) - view2 := view2Intf.(*trieView) + require.IsType(&view{}, view2Intf) + view2 := view2Intf.(*view) // view2 // | @@ -1043,8 +1068,8 @@ func TestNewViewOnCommittedView(t *testing.T) { // Make another view view3Intf, err := view2.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view3Intf) - view3 := view3Intf.(*trieView) + require.IsType(&view{}, view3Intf) + view3 := view3Intf.(*view) // view3 // | @@ -1089,7 +1114,7 @@ func TestNewViewOnCommittedView(t *testing.T) { require.Equal(db, view3.parentTrie) } -func Test_TrieView_NewView(t *testing.T) { +func Test_View_NewView(t *testing.T) { require := require.New(t) db, err := getBasicDB() @@ -1098,14 +1123,14 @@ func Test_TrieView_NewView(t *testing.T) { // Create a view view1Intf, err := db.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view1Intf) - view1 := view1Intf.(*trieView) + require.IsType(&view{}, view1Intf) + view1 := view1Intf.(*view) // Create a view atop view1 view2Intf, err := view1.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view2Intf) - view2 := view2Intf.(*trieView) + require.IsType(&view{}, view2Intf) + view2 := view2Intf.(*view) // view2 // | @@ -1124,8 +1149,8 @@ func Test_TrieView_NewView(t *testing.T) { // Make another view atop view1 view3Intf, err := view1.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view3Intf) - view3 := view3Intf.(*trieView) + require.IsType(&view{}, view3Intf) + view3 := view3Intf.(*view) // view3 // | @@ -1141,12 +1166,12 @@ func Test_TrieView_NewView(t *testing.T) { require.NotContains(view1.childViews, view3) // Assert that NewPreallocatedView on an invalid view fails - invalidView := &trieView{invalidated: true} + invalidView := &view{invalidated: true} _, err = invalidView.NewView(context.Background(), ViewChanges{}) require.ErrorIs(err, ErrInvalid) } -func TestTrieViewInvalidate(t *testing.T) { +func TestViewInvalidate(t *testing.T) { require := require.New(t) db, err := getBasicDB() @@ -1155,19 +1180,19 @@ func TestTrieViewInvalidate(t *testing.T) { // Create a view view1Intf, err := db.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view1Intf) - view1 := view1Intf.(*trieView) + require.IsType(&view{}, view1Intf) + view1 := view1Intf.(*view) // Create 2 views atop view1 view2Intf, err := view1.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view2Intf) - view2 := view2Intf.(*trieView) + require.IsType(&view{}, view2Intf) + view2 := view2Intf.(*view) view3Intf, err := view1.NewView(context.Background(), ViewChanges{}) require.NoError(err) - require.IsType(&trieView{}, view3Intf) - view3 := view3Intf.(*trieView) + require.IsType(&view{}, view3Intf) + view3 := view3Intf.(*view) // view2 view3 // | / @@ -1229,7 +1254,7 @@ func TestTrieCommitToDB(t *testing.T) { type test struct { name string - trieFunc func() TrieView + trieFunc func() View expectedErr error } @@ -1240,20 +1265,20 @@ func TestTrieCommitToDB(t *testing.T) { tests := []test{ { name: "invalid", - trieFunc: func() TrieView { - view, err := db.NewView(context.Background(), ViewChanges{}) + trieFunc: func() View { + nView, err := db.NewView(context.Background(), ViewChanges{}) r.NoError(err) // Invalidate the view - view.(*trieView).invalidate() + nView.(*view).invalidate() - return view + return nView }, expectedErr: ErrInvalid, }, { name: "committed", - trieFunc: func() TrieView { + trieFunc: func() View { view, err := db.NewView(context.Background(), ViewChanges{}) r.NoError(err) @@ -1266,14 +1291,14 @@ func TestTrieCommitToDB(t *testing.T) { }, { name: "parent not database", - trieFunc: func() TrieView { - view, err := db.NewView(context.Background(), ViewChanges{}) + trieFunc: func() View { + nView, err := db.NewView(context.Background(), ViewChanges{}) r.NoError(err) // Change the parent - view.(*trieView).parentTrie = &trieView{} + nView.(*view).parentTrie = &view{} - return view + return nView }, expectedErr: ErrParentNotDatabase, }, diff --git a/x/merkledb/trieview.go b/x/merkledb/trieview.go deleted file mode 100644 index 730d4b0187d0..000000000000 --- a/x/merkledb/trieview.go +++ /dev/null @@ -1,1002 +0,0 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package merkledb - -import ( - "bytes" - "context" - "errors" - "fmt" - "sync" - - "go.opentelemetry.io/otel/attribute" - - oteltrace "go.opentelemetry.io/otel/trace" - - "golang.org/x/exp/slices" - - "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/avalanchego/utils/maybe" -) - -const ( - initKeyValuesSize = 256 - defaultPreallocationSize = 100 -) - -var ( - _ TrieView = (*trieView)(nil) - - ErrCommitted = errors.New("view has been committed") - ErrInvalid = errors.New("the trie this view was based on has changed, rendering this view invalid") - ErrPartialByteLengthWithValue = errors.New( - "the underlying db only supports whole number of byte keys, so cannot record changes with partial byte lengths", - ) - ErrVisitPathToKey = errors.New("failed to visit expected node during insertion") - ErrStartAfterEnd = errors.New("start key > end key") - ErrNoValidRoot = errors.New("a valid root was not provided to the trieView constructor") - ErrParentNotDatabase = errors.New("parent trie is not database") - ErrNodesAlreadyCalculated = errors.New("cannot modify the trie after the node changes have been calculated") -) - -type trieView struct { - // If true, this view has been committed. - // [commitLock] must be held while accessing this field. - committed bool - commitLock sync.RWMutex - - // tracking bool to enforce that no changes are made to the trie after the nodes have been calculated - nodesAlreadyCalculated utils.Atomic[bool] - - // calculateNodesOnce is a once to ensure that node calculation only occurs a single time - calculateNodesOnce sync.Once - - // Controls the trie's validity related fields. - // Must be held while reading/writing [childViews], [invalidated], and [parentTrie]. - // Only use to lock current trieView or descendants of the current trieView - // DO NOT grab the [validityTrackingLock] of any ancestor trie while this is held. - validityTrackingLock sync.RWMutex - - // If true, this view has been invalidated and can't be used. - // - // Invariant: This view is marked as invalid before any of its ancestors change. - // Since we ensure that all subviews are marked invalid before making an invalidating change - // then if we are still valid at the end of the function, then no corrupting changes could have - // occurred during execution. - // Namely, if we have a method with: - // - // *Code Accessing Ancestor State* - // - // if t.isInvalid() { - // return ErrInvalid - // } - // return [result] - // - // If the invalidated check passes, then we're guaranteed that no ancestor changes occurred - // during the code that accessed ancestor state and the result of that work is still valid - // - // [validityTrackingLock] must be held when reading/writing this field. - invalidated bool - - // the uncommitted parent trie of this view - // [validityTrackingLock] must be held when reading/writing this field. - parentTrie TrieView - - // The valid children of this trie. - // [validityTrackingLock] must be held when reading/writing this field. - childViews []*trieView - - // Changes made to this view. - // May include nodes that haven't been updated - // but will when their ID is recalculated. - changes *changeSummary - - db *merkleDB - - // The nil key node - // It is either the root of the trie or the root of the trie is its single child node - sentinelNode *node - - tokenSize int -} - -// NewView returns a new view on top of this Trie where the passed changes -// have been applied. -// Adds the new view to [t.childViews]. -// Assumes [t.commitLock] isn't held. -func (t *trieView) NewView( - ctx context.Context, - changes ViewChanges, -) (TrieView, error) { - if t.isInvalid() { - return nil, ErrInvalid - } - t.commitLock.RLock() - defer t.commitLock.RUnlock() - - if t.committed { - return t.getParentTrie().NewView(ctx, changes) - } - - if err := t.calculateNodeIDs(ctx); err != nil { - return nil, err - } - - newView, err := newTrieView(t.db, t, changes) - if err != nil { - return nil, err - } - - t.validityTrackingLock.Lock() - defer t.validityTrackingLock.Unlock() - - if t.invalidated { - return nil, ErrInvalid - } - t.childViews = append(t.childViews, newView) - - return newView, nil -} - -// Creates a new view with the given [parentTrie]. -// Assumes [parentTrie] isn't locked. -func newTrieView( - db *merkleDB, - parentTrie TrieView, - changes ViewChanges, -) (*trieView, error) { - sentinelNode, err := parentTrie.getEditableNode(Key{}, false /* hasValue */) - if err != nil { - if errors.Is(err, database.ErrNotFound) { - return nil, ErrNoValidRoot - } - return nil, err - } - - newView := &trieView{ - sentinelNode: sentinelNode, - db: db, - parentTrie: parentTrie, - changes: newChangeSummary(len(changes.BatchOps) + len(changes.MapOps)), - tokenSize: db.tokenSize, - } - - for _, op := range changes.BatchOps { - key := op.Key - if !changes.ConsumeBytes { - key = slices.Clone(op.Key) - } - - newVal := maybe.Nothing[[]byte]() - if !op.Delete { - newVal = maybe.Some(op.Value) - if !changes.ConsumeBytes { - newVal = maybe.Some(slices.Clone(op.Value)) - } - } - if err := newView.recordValueChange(toKey(key), newVal); err != nil { - return nil, err - } - } - for key, val := range changes.MapOps { - if !changes.ConsumeBytes { - val = maybe.Bind(val, slices.Clone[[]byte]) - } - if err := newView.recordValueChange(toKey(stringToByteSlice(key)), val); err != nil { - return nil, err - } - } - return newView, nil -} - -// Creates a view of the db at a historical root using the provided changes -func newHistoricalTrieView( - db *merkleDB, - changes *changeSummary, -) (*trieView, error) { - if changes == nil { - return nil, ErrNoValidRoot - } - - passedSentinelChange, ok := changes.nodes[Key{}] - if !ok { - return nil, ErrNoValidRoot - } - - newView := &trieView{ - sentinelNode: passedSentinelChange.after, - db: db, - parentTrie: db, - changes: changes, - tokenSize: db.tokenSize, - } - // since this is a set of historical changes, all nodes have already been calculated - // since no new changes have occurred, no new calculations need to be done - newView.calculateNodesOnce.Do(func() {}) - newView.nodesAlreadyCalculated.Set(true) - return newView, nil -} - -// Recalculates the node IDs for all changed nodes in the trie. -// Cancelling [ctx] doesn't cancel calculation. It's used only for tracing. -func (t *trieView) calculateNodeIDs(ctx context.Context) error { - var err error - t.calculateNodesOnce.Do(func() { - if t.isInvalid() { - err = ErrInvalid - return - } - defer t.nodesAlreadyCalculated.Set(true) - - // We wait to create the span until after checking that we need to actually - // calculateNodeIDs to make traces more useful (otherwise there may be a span - // per key modified even though IDs are not re-calculated). - _, span := t.db.infoTracer.Start(ctx, "MerkleDB.trieview.calculateNodeIDs") - defer span.End() - - // add all the changed key/values to the nodes of the trie - for key, change := range t.changes.values { - if change.after.IsNothing() { - // Note we're setting [err] defined outside this function. - if err = t.remove(key); err != nil { - return - } - // Note we're setting [err] defined outside this function. - } else if _, err = t.insert(key, change.after); err != nil { - return - } - } - - _ = t.db.calculateNodeIDsSema.Acquire(context.Background(), 1) - t.changes.rootID = t.calculateNodeIDsHelper(t.sentinelNode) - t.db.calculateNodeIDsSema.Release(1) - - // If the sentinel node is not the root, the trie's root is the sentinel node's only child - if !isSentinelNodeTheRoot(t.sentinelNode) { - for _, childEntry := range t.sentinelNode.children { - t.changes.rootID = childEntry.id - } - } - - // ensure no ancestor changes occurred during execution - if t.isInvalid() { - err = ErrInvalid - return - } - }) - return err -} - -// Calculates the ID of all descendants of [n] which need to be recalculated, -// and then calculates the ID of [n] itself. -func (t *trieView) calculateNodeIDsHelper(n *node) ids.ID { - // We use [wg] to wait until all descendants of [n] have been updated. - var wg sync.WaitGroup - - for childIndex := range n.children { - childEntry := n.children[childIndex] - childKey := n.key.Extend(ToToken(childIndex, t.tokenSize), childEntry.compressedKey) - childNodeChange, ok := t.changes.nodes[childKey] - if !ok { - // This child wasn't changed. - continue - } - n.onNodeChanged() - childEntry.hasValue = childNodeChange.after.hasValue() - - // Try updating the child and its descendants in a goroutine. - if ok := t.db.calculateNodeIDsSema.TryAcquire(1); ok { - wg.Add(1) - go func() { - childEntry.id = t.calculateNodeIDsHelper(childNodeChange.after) - t.db.calculateNodeIDsSema.Release(1) - wg.Done() - }() - } else { - // We're at the goroutine limit; do the work in this goroutine. - childEntry.id = t.calculateNodeIDsHelper(childNodeChange.after) - } - } - - // Wait until all descendants of [n] have been updated. - wg.Wait() - - // The IDs [n]'s descendants are up to date so we can calculate [n]'s ID. - return n.calculateID(t.db.metrics) -} - -// GetProof returns a proof that [bytesPath] is in or not in trie [t]. -func (t *trieView) GetProof(ctx context.Context, key []byte) (*Proof, error) { - _, span := t.db.infoTracer.Start(ctx, "MerkleDB.trieview.GetProof") - defer span.End() - - if err := t.calculateNodeIDs(ctx); err != nil { - return nil, err - } - - return t.getProof(ctx, key) -} - -// Returns a proof that [bytesPath] is in or not in trie [t]. -func (t *trieView) getProof(ctx context.Context, key []byte) (*Proof, error) { - _, span := t.db.infoTracer.Start(ctx, "MerkleDB.trieview.getProof") - defer span.End() - - proof := &Proof{ - Key: ToKey(key), - } - - var closestNode *node - if err := t.visitPathToKey(proof.Key, func(n *node) error { - closestNode = n - proof.Path = append(proof.Path, n.asProofNode()) - return nil - }); err != nil { - return nil, err - } - root, err := t.getRoot() - if err != nil { - return nil, err - } - - // The sentinel node is always the first node in the path. - // If the sentinel node is not the root, remove it from the proofPath. - if root != t.sentinelNode { - proof.Path = proof.Path[1:] - - // if there are no nodes in the proof path, add the root to serve as an exclusion proof - if len(proof.Path) == 0 { - proof.Path = []ProofNode{root.asProofNode()} - return proof, nil - } - } - - if closestNode.key == proof.Key { - // There is a node with the given [key]. - proof.Value = maybe.Bind(closestNode.value, slices.Clone[[]byte]) - return proof, nil - } - - // There is no node with the given [key]. - // If there is a child at the index where the node would be - // if it existed, include that child in the proof. - nextIndex := proof.Key.Token(closestNode.key.length, t.tokenSize) - child, ok := closestNode.children[nextIndex] - if !ok { - return proof, nil - } - - childNode, err := t.getNode( - closestNode.key.Extend(ToToken(nextIndex, t.tokenSize), child.compressedKey), - child.hasValue, - ) - if err != nil { - return nil, err - } - proof.Path = append(proof.Path, childNode.asProofNode()) - if t.isInvalid() { - return nil, ErrInvalid - } - return proof, nil -} - -// GetRangeProof returns a range proof for (at least part of) the key range [start, end]. -// The returned proof's [KeyValues] has at most [maxLength] values. -// [maxLength] must be > 0. -func (t *trieView) GetRangeProof( - ctx context.Context, - start maybe.Maybe[[]byte], - end maybe.Maybe[[]byte], - maxLength int, -) (*RangeProof, error) { - ctx, span := t.db.infoTracer.Start(ctx, "MerkleDB.trieview.GetRangeProof") - defer span.End() - - if start.HasValue() && end.HasValue() && bytes.Compare(start.Value(), end.Value()) == 1 { - return nil, ErrStartAfterEnd - } - - if maxLength <= 0 { - return nil, fmt.Errorf("%w but was %d", ErrInvalidMaxLength, maxLength) - } - - if err := t.calculateNodeIDs(ctx); err != nil { - return nil, err - } - - var result RangeProof - - result.KeyValues = make([]KeyValue, 0, initKeyValuesSize) - it := t.NewIteratorWithStart(start.Value()) - for it.Next() && len(result.KeyValues) < maxLength && (end.IsNothing() || bytes.Compare(it.Key(), end.Value()) <= 0) { - // clone the value to prevent editing of the values stored within the trie - result.KeyValues = append(result.KeyValues, KeyValue{ - Key: it.Key(), - Value: slices.Clone(it.Value()), - }) - } - it.Release() - if err := it.Error(); err != nil { - return nil, err - } - - // This proof may not contain all key-value pairs in [start, end] due to size limitations. - // The end proof we provide should be for the last key-value pair in the proof, not for - // the last key-value pair requested, which may not be in this proof. - var ( - endProof *Proof - err error - ) - if len(result.KeyValues) > 0 { - greatestKey := result.KeyValues[len(result.KeyValues)-1].Key - endProof, err = t.getProof(ctx, greatestKey) - if err != nil { - return nil, err - } - } else if end.HasValue() { - endProof, err = t.getProof(ctx, end.Value()) - if err != nil { - return nil, err - } - } - if endProof != nil { - result.EndProof = endProof.Path - } - - if start.HasValue() { - startProof, err := t.getProof(ctx, start.Value()) - if err != nil { - return nil, err - } - result.StartProof = startProof.Path - - // strip out any common nodes to reduce proof size - i := 0 - for ; i < len(result.StartProof) && - i < len(result.EndProof) && - result.StartProof[i].Key == result.EndProof[i].Key; i++ { - } - result.StartProof = result.StartProof[i:] - } - - if len(result.StartProof) == 0 && len(result.EndProof) == 0 && len(result.KeyValues) == 0 { - // If the range is empty, return the root proof. - root, err := t.getRoot() - if err != nil { - return nil, err - } - rootProof, err := t.getProof(ctx, root.key.Bytes()) - if err != nil { - return nil, err - } - result.EndProof = rootProof.Path - } - - if t.isInvalid() { - return nil, ErrInvalid - } - return &result, nil -} - -// CommitToDB commits changes from this trie to the underlying DB. -func (t *trieView) CommitToDB(ctx context.Context) error { - ctx, span := t.db.infoTracer.Start(ctx, "MerkleDB.trieview.CommitToDB") - defer span.End() - - t.db.commitLock.Lock() - defer t.db.commitLock.Unlock() - - return t.commitToDB(ctx) -} - -// Commits the changes from [trieToCommit] to this view, -// this view to its parent, and so on until committing to the db. -// Assumes [t.db.commitLock] is held. -func (t *trieView) commitToDB(ctx context.Context) error { - t.commitLock.Lock() - defer t.commitLock.Unlock() - - ctx, span := t.db.infoTracer.Start(ctx, "MerkleDB.trieview.commitToDB", oteltrace.WithAttributes( - attribute.Int("changeCount", len(t.changes.values)), - )) - defer span.End() - - // Call this here instead of in [t.db.commitChanges] - // because doing so there would be a deadlock. - if err := t.calculateNodeIDs(ctx); err != nil { - return err - } - - if err := t.db.commitChanges(ctx, t); err != nil { - return err - } - - t.committed = true - - return nil -} - -// Assumes [t.validityTrackingLock] isn't held. -func (t *trieView) isInvalid() bool { - t.validityTrackingLock.RLock() - defer t.validityTrackingLock.RUnlock() - - return t.invalidated -} - -// Invalidates this view and all descendants. -// Assumes [t.validityTrackingLock] isn't held. -func (t *trieView) invalidate() { - t.validityTrackingLock.Lock() - defer t.validityTrackingLock.Unlock() - - t.invalidated = true - - for _, childView := range t.childViews { - childView.invalidate() - } - - // after invalidating the children, they no longer need to be tracked - t.childViews = make([]*trieView, 0, defaultPreallocationSize) -} - -func (t *trieView) updateParent(newParent TrieView) { - t.validityTrackingLock.Lock() - defer t.validityTrackingLock.Unlock() - - t.parentTrie = newParent -} - -// GetMerkleRoot returns the ID of the root of this trie. -func (t *trieView) GetMerkleRoot(ctx context.Context) (ids.ID, error) { - if err := t.calculateNodeIDs(ctx); err != nil { - return ids.Empty, err - } - return t.changes.rootID, nil -} - -func (t *trieView) GetValues(ctx context.Context, keys [][]byte) ([][]byte, []error) { - _, span := t.db.debugTracer.Start(ctx, "MerkleDB.trieview.GetValues", oteltrace.WithAttributes( - attribute.Int("keyCount", len(keys)), - )) - defer span.End() - - results := make([][]byte, len(keys)) - valueErrors := make([]error, len(keys)) - - for i, key := range keys { - results[i], valueErrors[i] = t.getValueCopy(ToKey(key)) - } - return results, valueErrors -} - -// GetValue returns the value for the given [key]. -// Returns database.ErrNotFound if it doesn't exist. -func (t *trieView) GetValue(ctx context.Context, key []byte) ([]byte, error) { - _, span := t.db.debugTracer.Start(ctx, "MerkleDB.trieview.GetValue") - defer span.End() - - return t.getValueCopy(ToKey(key)) -} - -// getValueCopy returns a copy of the value for the given [key]. -// Returns database.ErrNotFound if it doesn't exist. -func (t *trieView) getValueCopy(key Key) ([]byte, error) { - val, err := t.getValue(key) - if err != nil { - return nil, err - } - return slices.Clone(val), nil -} - -func (t *trieView) getValue(key Key) ([]byte, error) { - if t.isInvalid() { - return nil, ErrInvalid - } - - if change, ok := t.changes.values[key]; ok { - t.db.metrics.ViewValueCacheHit() - if change.after.IsNothing() { - return nil, database.ErrNotFound - } - return change.after.Value(), nil - } - t.db.metrics.ViewValueCacheMiss() - - // if we don't have local copy of the key, then grab a copy from the parent trie - value, err := t.getParentTrie().getValue(key) - if err != nil { - return nil, err - } - - // ensure no ancestor changes occurred during execution - if t.isInvalid() { - return nil, ErrInvalid - } - - return value, nil -} - -// Must not be called after [calculateNodeIDs] has returned. -func (t *trieView) remove(key Key) error { - if t.nodesAlreadyCalculated.Get() { - return ErrNodesAlreadyCalculated - } - - // confirm a node exists with a value - keyNode, err := t.getNode(key, true) - if err != nil { - if errors.Is(err, database.ErrNotFound) { - // key didn't exist - return nil - } - return err - } - - // node doesn't contain a value - if !keyNode.hasValue() { - return nil - } - - // if the node exists and contains a value - // mark all ancestor for change - // grab parent and grandparent nodes for path compression - var grandParent, parent, nodeToDelete *node - if err := t.visitPathToKey(key, func(n *node) error { - grandParent = parent - parent = nodeToDelete - nodeToDelete = n - return t.recordNodeChange(n) - }); err != nil { - return err - } - - nodeToDelete.setValue(maybe.Nothing[[]byte]()) - if len(nodeToDelete.children) != 0 { - // merge this node and its child into a single node if possible - return t.compressNodePath(parent, nodeToDelete) - } - - // if the removed node has no children, the node can be removed from the trie - if err := t.recordNodeDeleted(nodeToDelete); err != nil { - return err - } - if parent != nil { - parent.removeChild(nodeToDelete, t.tokenSize) - - // merge the parent node and its child into a single node if possible - return t.compressNodePath(grandParent, parent) - } - return nil -} - -// Merges together nodes in the inclusive descendants of [node] that -// have no value and a single child into one node with a compressed -// path until a node that doesn't meet those criteria is reached. -// [parent] is [node]'s parent. -// Assumes at least one of the following is true: -// * [node] has a value. -// * [node] has children. -// Must not be called after [calculateNodeIDs] has returned. -func (t *trieView) compressNodePath(parent, node *node) error { - if t.nodesAlreadyCalculated.Get() { - return ErrNodesAlreadyCalculated - } - - // don't collapse into this node if it's the root, doesn't have 1 child, or has a value - if parent == nil || len(node.children) != 1 || node.hasValue() { - return nil - } - - if err := t.recordNodeDeleted(node); err != nil { - return err - } - - var ( - childEntry *child - childKey Key - ) - // There is only one child, but we don't know the index. - // "Cycle" over the key/values to find the only child. - // Note this iteration once because len(node.children) == 1. - for index, entry := range node.children { - childKey = node.key.Extend(ToToken(index, t.tokenSize), entry.compressedKey) - childEntry = entry - } - - // [node] is the first node with multiple children. - // combine it with the [node] passed in. - parent.setChildEntry(childKey.Token(parent.key.length, t.tokenSize), - &child{ - compressedKey: childKey.Skip(parent.key.length + t.tokenSize), - id: childEntry.id, - hasValue: childEntry.hasValue, - }) - return t.recordNodeChange(parent) -} - -// Returns the nodes along the path to [key]. -// The first node is the root, and the last node is either the node with the -// given [key], if it's in the trie, or the node with the largest prefix of -// the [key] if it isn't in the trie. -// Always returns at least the root node. -func (t *trieView) visitPathToKey(key Key, visitNode func(*node) error) error { - var ( - // all node paths start at the sentinelNode since its nil key is a prefix of all keys - currentNode = t.sentinelNode - err error - ) - if err := visitNode(currentNode); err != nil { - return err - } - // while the entire path hasn't been matched - for currentNode.key.length < key.length { - // confirm that a child exists and grab its ID before attempting to load it - nextChildEntry, hasChild := currentNode.children[key.Token(currentNode.key.length, t.tokenSize)] - - if !hasChild || !key.iteratedHasPrefix(nextChildEntry.compressedKey, currentNode.key.length+t.tokenSize, t.tokenSize) { - // there was no child along the path or the child that was there doesn't match the remaining path - return nil - } - // grab the next node along the path - currentNode, err = t.getNode(key.Take(currentNode.key.length+t.tokenSize+nextChildEntry.compressedKey.length), nextChildEntry.hasValue) - if err != nil { - return err - } - if err := visitNode(currentNode); err != nil { - return err - } - } - return nil -} - -// Get a copy of the node matching the passed key from the trie. -// Used by views to get nodes from their ancestors. -func (t *trieView) getEditableNode(key Key, hadValue bool) (*node, error) { - if t.isInvalid() { - return nil, ErrInvalid - } - - // grab the node in question - n, err := t.getNode(key, hadValue) - if err != nil { - return nil, err - } - - // ensure no ancestor changes occurred during execution - if t.isInvalid() { - return nil, ErrInvalid - } - - // return a clone of the node, so it can be edited without affecting this trie - return n.clone(), nil -} - -// insert a key/value pair into the correct node of the trie. -// Must not be called after [calculateNodeIDs] has returned. -func (t *trieView) insert( - key Key, - value maybe.Maybe[[]byte], -) (*node, error) { - if t.nodesAlreadyCalculated.Get() { - return nil, ErrNodesAlreadyCalculated - } - - var closestNode *node - if err := t.visitPathToKey(key, func(n *node) error { - closestNode = n - return t.recordNodeChange(n) - }); err != nil { - return nil, err - } - - // a node with that exact key already exists so update its value - if closestNode.key == key { - closestNode.setValue(value) - // closestNode was already marked as changed in the ancestry loop above - return closestNode, nil - } - - // A node with the exact key doesn't exist so determine the portion of the - // key that hasn't been matched yet - // Note that [key] has prefix [closestNode.key], so [key] must be longer - // and the following index won't OOB. - existingChildEntry, hasChild := closestNode.children[key.Token(closestNode.key.length, t.tokenSize)] - if !hasChild { - // there are no existing nodes along the key [key], so create a new node to insert [value] - newNode := newNode(key) - newNode.setValue(value) - closestNode.addChild(newNode, t.tokenSize) - return newNode, t.recordNewNode(newNode) - } - - // if we have reached this point, then the [key] we are trying to insert and - // the existing path node have some common prefix. - // a new branching node will be created that will represent this common prefix and - // have the existing path node and the value being inserted as children. - - // generate the new branch node - // find how many tokens are common between the existing child's compressed key and - // the current key(offset by the closest node's key), - // then move all the common tokens into the branch node - commonPrefixLength := getLengthOfCommonPrefix( - existingChildEntry.compressedKey, - key, - closestNode.key.length+t.tokenSize, - t.tokenSize, - ) - - if existingChildEntry.compressedKey.length <= commonPrefixLength { - // Since the compressed key is shorter than the common prefix, - // we should have visited [existingChildEntry] in [visitPathToKey]. - return nil, ErrVisitPathToKey - } - - branchNode := newNode(key.Take(closestNode.key.length + t.tokenSize + commonPrefixLength)) - closestNode.addChild(branchNode, t.tokenSize) - nodeWithValue := branchNode - - if key.length == branchNode.key.length { - // the branch node has exactly the key to be inserted as its key, so set the value on the branch node - branchNode.setValue(value) - } else { - // the key to be inserted is a child of the branch node - // create a new node and add the value to it - newNode := newNode(key) - newNode.setValue(value) - branchNode.addChild(newNode, t.tokenSize) - if err := t.recordNewNode(newNode); err != nil { - return nil, err - } - nodeWithValue = newNode - } - - // add the existing child onto the branch node - branchNode.setChildEntry( - existingChildEntry.compressedKey.Token(commonPrefixLength, t.tokenSize), - &child{ - compressedKey: existingChildEntry.compressedKey.Skip(commonPrefixLength + t.tokenSize), - id: existingChildEntry.id, - hasValue: existingChildEntry.hasValue, - }) - - return nodeWithValue, t.recordNewNode(branchNode) -} - -func getLengthOfCommonPrefix(first, second Key, secondOffset int, tokenSize int) int { - commonIndex := 0 - for first.length > commonIndex && second.length > commonIndex+secondOffset && - first.Token(commonIndex, tokenSize) == second.Token(commonIndex+secondOffset, tokenSize) { - commonIndex += tokenSize - } - return commonIndex -} - -// Records that a node has been created. -// Must not be called after [calculateNodeIDs] has returned. -func (t *trieView) recordNewNode(after *node) error { - return t.recordKeyChange(after.key, after, after.hasValue(), true /* newNode */) -} - -// Records that an existing node has been changed. -// Must not be called after [calculateNodeIDs] has returned. -func (t *trieView) recordNodeChange(after *node) error { - return t.recordKeyChange(after.key, after, after.hasValue(), false /* newNode */) -} - -// Records that the node associated with the given key has been deleted. -// Must not be called after [calculateNodeIDs] has returned. -func (t *trieView) recordNodeDeleted(after *node) error { - // don't delete the root. - if after.key.length == 0 { - return t.recordKeyChange(after.key, after, after.hasValue(), false /* newNode */) - } - return t.recordKeyChange(after.key, nil, after.hasValue(), false /* newNode */) -} - -func (t *trieView) getRoot() (*node, error) { - if !isSentinelNodeTheRoot(t.sentinelNode) { - // sentinelNode has one child, which is the root - for index, childEntry := range t.sentinelNode.children { - return t.getNode( - t.sentinelNode.key.Extend(ToToken(index, t.tokenSize), childEntry.compressedKey), - childEntry.hasValue) - } - } - - return t.sentinelNode, nil -} - -// Records that the node associated with the given key has been changed. -// If it is an existing node, record what its value was before it was changed. -// Must not be called after [calculateNodeIDs] has returned. -func (t *trieView) recordKeyChange(key Key, after *node, hadValue bool, newNode bool) error { - if t.nodesAlreadyCalculated.Get() { - return ErrNodesAlreadyCalculated - } - - if existing, ok := t.changes.nodes[key]; ok { - existing.after = after - return nil - } - - if newNode { - t.changes.nodes[key] = &change[*node]{ - after: after, - } - return nil - } - - before, err := t.getParentTrie().getEditableNode(key, hadValue) - if err != nil && !errors.Is(err, database.ErrNotFound) { - return err - } - t.changes.nodes[key] = &change[*node]{ - before: before, - after: after, - } - return nil -} - -// Records that a key's value has been added or updated. -// Doesn't actually change the trie data structure. -// That's deferred until we call [calculateNodeIDs]. -// Must not be called after [calculateNodeIDs] has returned. -func (t *trieView) recordValueChange(key Key, value maybe.Maybe[[]byte]) error { - if t.nodesAlreadyCalculated.Get() { - return ErrNodesAlreadyCalculated - } - - // update the existing change if it exists - if existing, ok := t.changes.values[key]; ok { - existing.after = value - return nil - } - - // grab the before value - var beforeMaybe maybe.Maybe[[]byte] - before, err := t.getParentTrie().getValue(key) - switch err { - case nil: - beforeMaybe = maybe.Some(before) - case database.ErrNotFound: - beforeMaybe = maybe.Nothing[[]byte]() - default: - return err - } - - t.changes.values[key] = &change[maybe.Maybe[[]byte]]{ - before: beforeMaybe, - after: value, - } - return nil -} - -// Retrieves a node with the given [key]. -// If the node is fetched from [t.parentTrie] and [id] isn't empty, -// sets the node's ID to [id]. -// If the node is loaded from the baseDB, [hasValue] determines which database the node is stored in. -// Returns database.ErrNotFound if the node doesn't exist. -func (t *trieView) getNode(key Key, hasValue bool) (*node, error) { - // check for the key within the changed nodes - if nodeChange, isChanged := t.changes.nodes[key]; isChanged { - t.db.metrics.ViewNodeCacheHit() - if nodeChange.after == nil { - return nil, database.ErrNotFound - } - return nodeChange.after, nil - } - - // get the node from the parent trie and store a local copy - return t.getParentTrie().getEditableNode(key, hasValue) -} - -// Get the parent trie of the view -func (t *trieView) getParentTrie() TrieView { - t.validityTrackingLock.RLock() - defer t.validityTrackingLock.RUnlock() - return t.parentTrie -} diff --git a/x/merkledb/value_node_db.go b/x/merkledb/value_node_db.go index 406c9a986dba..16cabe3d718f 100644 --- a/x/merkledb/value_node_db.go +++ b/x/merkledb/value_node_db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb diff --git a/x/merkledb/value_node_db_test.go b/x/merkledb/value_node_db_test.go index c87f0ab5ebf8..224a4fe94ac1 100644 --- a/x/merkledb/value_node_db_test.go +++ b/x/merkledb/value_node_db_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -20,14 +20,14 @@ func TestValueNodeDB(t *testing.T) { baseDB := memdb.New() - size := 10 + cacheSize := 10_000 db := newValueNodeDB( baseDB, &sync.Pool{ New: func() interface{} { return make([]byte, 0) }, }, &mockMetrics{}, - size, + cacheSize, ) // Getting a key that doesn't exist should return an error. diff --git a/x/merkledb/view.go b/x/merkledb/view.go new file mode 100644 index 000000000000..8f9e688efc26 --- /dev/null +++ b/x/merkledb/view.go @@ -0,0 +1,865 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package merkledb + +import ( + "context" + "errors" + "sync" + + "go.opentelemetry.io/otel/attribute" + + oteltrace "go.opentelemetry.io/otel/trace" + + "golang.org/x/exp/slices" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/utils/maybe" +) + +const ( + initKeyValuesSize = 256 + defaultPreallocationSize = 100 +) + +var ( + _ View = (*view)(nil) + + ErrCommitted = errors.New("view has been committed") + ErrInvalid = errors.New("the trie this view was based on has changed, rendering this view invalid") + ErrPartialByteLengthWithValue = errors.New( + "the underlying db only supports whole number of byte keys, so cannot record changes with partial byte lengths", + ) + ErrVisitPathToKey = errors.New("failed to visit expected node during insertion") + ErrStartAfterEnd = errors.New("start key > end key") + ErrNoChanges = errors.New("no changes provided") + ErrParentNotDatabase = errors.New("parent trie is not database") + ErrNodesAlreadyCalculated = errors.New("cannot modify the trie after the node changes have been calculated") +) + +type view struct { + // If true, this view has been committed. + // [commitLock] must be held while accessing this field. + committed bool + commitLock sync.RWMutex + + // tracking bool to enforce that no changes are made to the trie after the nodes have been calculated + nodesAlreadyCalculated utils.Atomic[bool] + + // calculateNodesOnce is a once to ensure that node calculation only occurs a single time + calculateNodesOnce sync.Once + + // Controls the view's validity related fields. + // Must be held while reading/writing [childViews], [invalidated], and [parentTrie]. + // Only use to lock current view or descendants of the current view + // DO NOT grab the [validityTrackingLock] of any ancestor trie while this is held. + validityTrackingLock sync.RWMutex + + // If true, this view has been invalidated and can't be used. + // + // Invariant: This view is marked as invalid before any of its ancestors change. + // Since we ensure that all subviews are marked invalid before making an invalidating change + // then if we are still valid at the end of the function, then no corrupting changes could have + // occurred during execution. + // Namely, if we have a method with: + // + // *Code Accessing Ancestor State* + // + // if v.isInvalid() { + // return ErrInvalid + // } + // return [result] + // + // If the invalidated check passes, then we're guaranteed that no ancestor changes occurred + // during the code that accessed ancestor state and the result of that work is still valid + // + // [validityTrackingLock] must be held when reading/writing this field. + invalidated bool + + // the uncommitted parent trie of this view + // [validityTrackingLock] must be held when reading/writing this field. + parentTrie View + + // The valid children of this view. + // [validityTrackingLock] must be held when reading/writing this field. + childViews []*view + + // Changes made to this view. + // May include nodes that haven't been updated + // but will when their ID is recalculated. + changes *changeSummary + + db *merkleDB + + // The root of the trie represented by this view. + root maybe.Maybe[*node] + + tokenSize int +} + +// NewView returns a new view on top of this view where the passed changes +// have been applied. +// Adds the new view to [v.childViews]. +// Assumes [v.commitLock] isn't held. +func (v *view) NewView( + ctx context.Context, + changes ViewChanges, +) (View, error) { + if v.isInvalid() { + return nil, ErrInvalid + } + v.commitLock.RLock() + defer v.commitLock.RUnlock() + + if v.committed { + return v.getParentTrie().NewView(ctx, changes) + } + + if err := v.calculateNodeIDs(ctx); err != nil { + return nil, err + } + + newView, err := newView(v.db, v, changes) + if err != nil { + return nil, err + } + + v.validityTrackingLock.Lock() + defer v.validityTrackingLock.Unlock() + + if v.invalidated { + return nil, ErrInvalid + } + v.childViews = append(v.childViews, newView) + + return newView, nil +} + +// Creates a new view with the given [parentTrie]. +func newView( + db *merkleDB, + parentTrie View, + changes ViewChanges, +) (*view, error) { + newView := &view{ + root: maybe.Bind(parentTrie.getRoot(), (*node).clone), + db: db, + parentTrie: parentTrie, + changes: newChangeSummary(len(changes.BatchOps) + len(changes.MapOps)), + tokenSize: db.tokenSize, + } + + for _, op := range changes.BatchOps { + key := op.Key + if !changes.ConsumeBytes { + key = slices.Clone(op.Key) + } + + newVal := maybe.Nothing[[]byte]() + if !op.Delete { + newVal = maybe.Some(op.Value) + if !changes.ConsumeBytes { + newVal = maybe.Some(slices.Clone(op.Value)) + } + } + if err := newView.recordValueChange(toKey(key), newVal); err != nil { + return nil, err + } + } + for key, val := range changes.MapOps { + if !changes.ConsumeBytes { + val = maybe.Bind(val, slices.Clone[[]byte]) + } + if err := newView.recordValueChange(toKey(stringToByteSlice(key)), val); err != nil { + return nil, err + } + } + return newView, nil +} + +// Creates a view of the db at a historical root using the provided [changes]. +// Returns ErrNoChanges if [changes] is empty. +func newViewWithChanges( + db *merkleDB, + changes *changeSummary, +) (*view, error) { + if changes == nil { + return nil, ErrNoChanges + } + + newView := &view{ + root: changes.rootChange.after, + db: db, + parentTrie: db, + changes: changes, + tokenSize: db.tokenSize, + } + // since this is a set of historical changes, all nodes have already been calculated + // since no new changes have occurred, no new calculations need to be done + newView.calculateNodesOnce.Do(func() {}) + newView.nodesAlreadyCalculated.Set(true) + return newView, nil +} + +func (v *view) getTokenSize() int { + return v.tokenSize +} + +func (v *view) getRoot() maybe.Maybe[*node] { + return v.root +} + +// Recalculates the node IDs for all changed nodes in the trie. +// Cancelling [ctx] doesn't cancel calculation. It's used only for tracing. +func (v *view) calculateNodeIDs(ctx context.Context) error { + var err error + v.calculateNodesOnce.Do(func() { + if v.isInvalid() { + err = ErrInvalid + return + } + defer v.nodesAlreadyCalculated.Set(true) + + oldRoot := maybe.Bind(v.root, (*node).clone) + + // We wait to create the span until after checking that we need to actually + // calculateNodeIDs to make traces more useful (otherwise there may be a span + // per key modified even though IDs are not re-calculated). + _, span := v.db.infoTracer.Start(ctx, "MerkleDB.view.calculateNodeIDs") + defer span.End() + + // add all the changed key/values to the nodes of the trie + for key, change := range v.changes.values { + if change.after.IsNothing() { + // Note we're setting [err] defined outside this function. + if err = v.remove(key); err != nil { + return + } + // Note we're setting [err] defined outside this function. + } else if _, err = v.insert(key, change.after); err != nil { + return + } + } + + if !v.root.IsNothing() { + _ = v.db.calculateNodeIDsSema.Acquire(context.Background(), 1) + v.changes.rootID = v.calculateNodeIDsHelper(v.root.Value()) + v.db.calculateNodeIDsSema.Release(1) + } else { + v.changes.rootID = ids.Empty + } + + v.changes.rootChange = change[maybe.Maybe[*node]]{ + before: oldRoot, + after: v.root, + } + + // ensure no ancestor changes occurred during execution + if v.isInvalid() { + err = ErrInvalid + return + } + }) + return err +} + +// Calculates the ID of all descendants of [n] which need to be recalculated, +// and then calculates the ID of [n] itself. +func (v *view) calculateNodeIDsHelper(n *node) ids.ID { + // We use [wg] to wait until all descendants of [n] have been updated. + var wg sync.WaitGroup + + for childIndex := range n.children { + childEntry := n.children[childIndex] + childKey := n.key.Extend(ToToken(childIndex, v.tokenSize), childEntry.compressedKey) + childNodeChange, ok := v.changes.nodes[childKey] + if !ok { + // This child wasn't changed. + continue + } + childEntry.hasValue = childNodeChange.after.hasValue() + + // Try updating the child and its descendants in a goroutine. + if ok := v.db.calculateNodeIDsSema.TryAcquire(1); ok { + wg.Add(1) + go func() { + childEntry.id = v.calculateNodeIDsHelper(childNodeChange.after) + v.db.calculateNodeIDsSema.Release(1) + wg.Done() + }() + } else { + // We're at the goroutine limit; do the work in this goroutine. + childEntry.id = v.calculateNodeIDsHelper(childNodeChange.after) + } + } + + // Wait until all descendants of [n] have been updated. + wg.Wait() + + // The IDs [n]'s descendants are up to date so we can calculate [n]'s ID. + return n.calculateID(v.db.metrics) +} + +// GetProof returns a proof that [bytesPath] is in or not in trie [t]. +func (v *view) GetProof(ctx context.Context, key []byte) (*Proof, error) { + _, span := v.db.infoTracer.Start(ctx, "MerkleDB.view.GetProof") + defer span.End() + + if err := v.calculateNodeIDs(ctx); err != nil { + return nil, err + } + + result, err := getProof(v, key) + if err != nil { + return nil, err + } + if v.isInvalid() { + return nil, ErrInvalid + } + return result, nil +} + +// GetRangeProof returns a range proof for (at least part of) the key range [start, end]. +// The returned proof's [KeyValues] has at most [maxLength] values. +// [maxLength] must be > 0. +func (v *view) GetRangeProof( + ctx context.Context, + start maybe.Maybe[[]byte], + end maybe.Maybe[[]byte], + maxLength int, +) (*RangeProof, error) { + _, span := v.db.infoTracer.Start(ctx, "MerkleDB.view.GetRangeProof") + defer span.End() + + if err := v.calculateNodeIDs(ctx); err != nil { + return nil, err + } + result, err := getRangeProof(v, start, end, maxLength) + if err != nil { + return nil, err + } + if v.isInvalid() { + return nil, ErrInvalid + } + return result, nil +} + +// CommitToDB commits changes from this view to the underlying DB. +func (v *view) CommitToDB(ctx context.Context) error { + ctx, span := v.db.infoTracer.Start(ctx, "MerkleDB.view.CommitToDB") + defer span.End() + + v.db.commitLock.Lock() + defer v.db.commitLock.Unlock() + + return v.commitToDB(ctx) +} + +// Commits the changes from [trieToCommit] to this view, +// this view to its parent, and so on until committing to the db. +// Assumes [v.db.commitLock] is held. +func (v *view) commitToDB(ctx context.Context) error { + v.commitLock.Lock() + defer v.commitLock.Unlock() + + ctx, span := v.db.infoTracer.Start(ctx, "MerkleDB.view.commitToDB", oteltrace.WithAttributes( + attribute.Int("changeCount", len(v.changes.values)), + )) + defer span.End() + + // Call this here instead of in [v.db.commitChanges] + // because doing so there would be a deadlock. + if err := v.calculateNodeIDs(ctx); err != nil { + return err + } + + if err := v.db.commitChanges(ctx, v); err != nil { + return err + } + + v.committed = true + + return nil +} + +// Assumes [v.validityTrackingLock] isn't held. +func (v *view) isInvalid() bool { + v.validityTrackingLock.RLock() + defer v.validityTrackingLock.RUnlock() + + return v.invalidated +} + +// Invalidates this view and all descendants. +// Assumes [v.validityTrackingLock] isn't held. +func (v *view) invalidate() { + v.validityTrackingLock.Lock() + defer v.validityTrackingLock.Unlock() + + v.invalidated = true + + for _, childView := range v.childViews { + childView.invalidate() + } + + // after invalidating the children, they no longer need to be tracked + v.childViews = make([]*view, 0, defaultPreallocationSize) +} + +func (v *view) updateParent(newParent View) { + v.validityTrackingLock.Lock() + defer v.validityTrackingLock.Unlock() + + v.parentTrie = newParent +} + +// GetMerkleRoot returns the ID of the root of this view. +func (v *view) GetMerkleRoot(ctx context.Context) (ids.ID, error) { + if err := v.calculateNodeIDs(ctx); err != nil { + return ids.Empty, err + } + return v.changes.rootID, nil +} + +func (v *view) GetValues(ctx context.Context, keys [][]byte) ([][]byte, []error) { + _, span := v.db.debugTracer.Start(ctx, "MerkleDB.view.GetValues", oteltrace.WithAttributes( + attribute.Int("keyCount", len(keys)), + )) + defer span.End() + + results := make([][]byte, len(keys)) + valueErrors := make([]error, len(keys)) + + for i, key := range keys { + results[i], valueErrors[i] = v.getValueCopy(ToKey(key)) + } + return results, valueErrors +} + +// GetValue returns the value for the given [key]. +// Returns database.ErrNotFound if it doesn't exist. +func (v *view) GetValue(ctx context.Context, key []byte) ([]byte, error) { + _, span := v.db.debugTracer.Start(ctx, "MerkleDB.view.GetValue") + defer span.End() + + return v.getValueCopy(ToKey(key)) +} + +// getValueCopy returns a copy of the value for the given [key]. +// Returns database.ErrNotFound if it doesn't exist. +func (v *view) getValueCopy(key Key) ([]byte, error) { + val, err := v.getValue(key) + if err != nil { + return nil, err + } + return slices.Clone(val), nil +} + +func (v *view) getValue(key Key) ([]byte, error) { + if v.isInvalid() { + return nil, ErrInvalid + } + + if change, ok := v.changes.values[key]; ok { + v.db.metrics.ViewValueCacheHit() + if change.after.IsNothing() { + return nil, database.ErrNotFound + } + return change.after.Value(), nil + } + v.db.metrics.ViewValueCacheMiss() + + // if we don't have local copy of the key, then grab a copy from the parent trie + value, err := v.getParentTrie().getValue(key) + if err != nil { + return nil, err + } + + // ensure no ancestor changes occurred during execution + if v.isInvalid() { + return nil, ErrInvalid + } + + return value, nil +} + +// Must not be called after [calculateNodeIDs] has returned. +func (v *view) remove(key Key) error { + if v.nodesAlreadyCalculated.Get() { + return ErrNodesAlreadyCalculated + } + + // confirm a node exists with a value + keyNode, err := v.getNode(key, true) + if err != nil { + if errors.Is(err, database.ErrNotFound) { + // [key] isn't in the trie. + return nil + } + return err + } + + if !keyNode.hasValue() { + // [key] doesn't have a value. + return nil + } + + // if the node exists and contains a value + // mark all ancestor for change + // grab parent and grandparent nodes for path compression + var grandParent, parent, nodeToDelete *node + if err := visitPathToKey(v, key, func(n *node) error { + grandParent = parent + parent = nodeToDelete + nodeToDelete = n + return v.recordNodeChange(n) + }); err != nil { + return err + } + + nodeToDelete.setValue(maybe.Nothing[[]byte]()) + + // if the removed node has no children, the node can be removed from the trie + if len(nodeToDelete.children) == 0 { + if err := v.recordNodeDeleted(nodeToDelete); err != nil { + return err + } + + if nodeToDelete.key == v.root.Value().key { + // We deleted the root. The trie is empty now. + v.root = maybe.Nothing[*node]() + return nil + } + + // Note [parent] != nil since [nodeToDelete.key] != [v.root.key]. + // i.e. There's the root and at least one more node. + parent.removeChild(nodeToDelete, v.tokenSize) + + // merge the parent node and its child into a single node if possible + return v.compressNodePath(grandParent, parent) + } + + // merge this node and its descendants into a single node if possible + return v.compressNodePath(parent, nodeToDelete) +} + +// Merges together nodes in the inclusive descendants of [n] that +// have no value and a single child into one node with a compressed +// path until a node that doesn't meet those criteria is reached. +// [parent] is [n]'s parent. If [parent] is nil, [n] is the root +// node and [v.root] is updated to [n]. +// Assumes at least one of the following is true: +// * [n] has a value. +// * [n] has children. +// Must not be called after [calculateNodeIDs] has returned. +func (v *view) compressNodePath(parent, n *node) error { + if v.nodesAlreadyCalculated.Get() { + return ErrNodesAlreadyCalculated + } + + if len(n.children) != 1 || n.hasValue() { + return nil + } + + if err := v.recordNodeDeleted(n); err != nil { + return err + } + + var ( + childEntry *child + childKey Key + ) + // There is only one child, but we don't know the index. + // "Cycle" over the key/values to find the only child. + // Note this iteration once because len(node.children) == 1. + for index, entry := range n.children { + childKey = n.key.Extend(ToToken(index, v.tokenSize), entry.compressedKey) + childEntry = entry + } + + if parent == nil { + root, err := v.getNode(childKey, childEntry.hasValue) + if err != nil { + return err + } + v.root = maybe.Some(root) + return nil + } + + parent.setChildEntry(childKey.Token(parent.key.length, v.tokenSize), + &child{ + compressedKey: childKey.Skip(parent.key.length + v.tokenSize), + id: childEntry.id, + hasValue: childEntry.hasValue, + }) + return v.recordNodeChange(parent) +} + +// Get a copy of the node matching the passed key from the view. +// Used by views to get nodes from their ancestors. +func (v *view) getEditableNode(key Key, hadValue bool) (*node, error) { + if v.isInvalid() { + return nil, ErrInvalid + } + + // grab the node in question + n, err := v.getNode(key, hadValue) + if err != nil { + return nil, err + } + + // ensure no ancestor changes occurred during execution + if v.isInvalid() { + return nil, ErrInvalid + } + + // return a clone of the node, so it can be edited without affecting this view + return n.clone(), nil +} + +// insert a key/value pair into the correct node of the trie. +// Must not be called after [calculateNodeIDs] has returned. +func (v *view) insert( + key Key, + value maybe.Maybe[[]byte], +) (*node, error) { + if v.nodesAlreadyCalculated.Get() { + return nil, ErrNodesAlreadyCalculated + } + + if v.root.IsNothing() { + // the trie is empty, so create a new root node. + root := newNode(key) + root.setValue(value) + v.root = maybe.Some(root) + return root, v.recordNewNode(root) + } + + // Find the node that most closely matches [key]. + var closestNode *node + if err := visitPathToKey(v, key, func(n *node) error { + closestNode = n + // Need to recalculate ID for all nodes on path to [key]. + return v.recordNodeChange(n) + }); err != nil { + return nil, err + } + + if closestNode == nil { + // [v.root.key] isn't a prefix of [key]. + var ( + oldRoot = v.root.Value() + commonPrefixLength = getLengthOfCommonPrefix(oldRoot.key, key, 0 /*offset*/, v.tokenSize) + commonPrefix = oldRoot.key.Take(commonPrefixLength) + newRoot = newNode(commonPrefix) + oldRootID = oldRoot.calculateID(v.db.metrics) + ) + + // Call addChildWithID instead of addChild so the old root is added + // to the new root with the correct ID. + // TODO: + // [oldRootID] shouldn't need to be calculated here. + // Either oldRootID should already be calculated or will be calculated at the end with the other nodes + // Initialize the v.changes.rootID during newView and then use that here instead of oldRootID + newRoot.addChildWithID(oldRoot, v.tokenSize, oldRootID) + if err := v.recordNewNode(newRoot); err != nil { + return nil, err + } + v.root = maybe.Some(newRoot) + + closestNode = newRoot + } + + // a node with that exact key already exists so update its value + if closestNode.key == key { + closestNode.setValue(value) + // closestNode was already marked as changed in the ancestry loop above + return closestNode, nil + } + + // A node with the exact key doesn't exist so determine the portion of the + // key that hasn't been matched yet + // Note that [key] has prefix [closestNode.key], so [key] must be longer + // and the following index won't OOB. + existingChildEntry, hasChild := closestNode.children[key.Token(closestNode.key.length, v.tokenSize)] + if !hasChild { + // there are no existing nodes along the key [key], so create a new node to insert [value] + newNode := newNode(key) + newNode.setValue(value) + closestNode.addChild(newNode, v.tokenSize) + return newNode, v.recordNewNode(newNode) + } + + // if we have reached this point, then the [key] we are trying to insert and + // the existing path node have some common prefix. + // a new branching node will be created that will represent this common prefix and + // have the existing path node and the value being inserted as children. + + // generate the new branch node + // find how many tokens are common between the existing child's compressed key and + // the current key(offset by the closest node's key), + // then move all the common tokens into the branch node + commonPrefixLength := getLengthOfCommonPrefix( + existingChildEntry.compressedKey, + key, + closestNode.key.length+v.tokenSize, + v.tokenSize, + ) + + if existingChildEntry.compressedKey.length <= commonPrefixLength { + // Since the compressed key is shorter than the common prefix, + // we should have visited [existingChildEntry] in [visitPathToKey]. + return nil, ErrVisitPathToKey + } + + branchNode := newNode(key.Take(closestNode.key.length + v.tokenSize + commonPrefixLength)) + closestNode.addChild(branchNode, v.tokenSize) + nodeWithValue := branchNode + + if key.length == branchNode.key.length { + // the branch node has exactly the key to be inserted as its key, so set the value on the branch node + branchNode.setValue(value) + } else { + // the key to be inserted is a child of the branch node + // create a new node and add the value to it + newNode := newNode(key) + newNode.setValue(value) + branchNode.addChild(newNode, v.tokenSize) + if err := v.recordNewNode(newNode); err != nil { + return nil, err + } + nodeWithValue = newNode + } + + // add the existing child onto the branch node + branchNode.setChildEntry( + existingChildEntry.compressedKey.Token(commonPrefixLength, v.tokenSize), + &child{ + compressedKey: existingChildEntry.compressedKey.Skip(commonPrefixLength + v.tokenSize), + id: existingChildEntry.id, + hasValue: existingChildEntry.hasValue, + }) + + return nodeWithValue, v.recordNewNode(branchNode) +} + +func getLengthOfCommonPrefix(first, second Key, secondOffset int, tokenSize int) int { + commonIndex := 0 + for first.length > commonIndex && second.length > commonIndex+secondOffset && + first.Token(commonIndex, tokenSize) == second.Token(commonIndex+secondOffset, tokenSize) { + commonIndex += tokenSize + } + return commonIndex +} + +// Records that a node has been created. +// Must not be called after [calculateNodeIDs] has returned. +func (v *view) recordNewNode(after *node) error { + return v.recordKeyChange(after.key, after, after.hasValue(), true /* newNode */) +} + +// Records that an existing node has been changed. +// Must not be called after [calculateNodeIDs] has returned. +func (v *view) recordNodeChange(after *node) error { + return v.recordKeyChange(after.key, after, after.hasValue(), false /* newNode */) +} + +// Records that the node associated with the given key has been deleted. +// Must not be called after [calculateNodeIDs] has returned. +func (v *view) recordNodeDeleted(after *node) error { + return v.recordKeyChange(after.key, nil, after.hasValue(), false /* newNode */) +} + +// Records that the node associated with the given key has been changed. +// If it is an existing node, record what its value was before it was changed. +// Must not be called after [calculateNodeIDs] has returned. +func (v *view) recordKeyChange(key Key, after *node, hadValue bool, newNode bool) error { + if v.nodesAlreadyCalculated.Get() { + return ErrNodesAlreadyCalculated + } + + if existing, ok := v.changes.nodes[key]; ok { + existing.after = after + return nil + } + + if newNode { + v.changes.nodes[key] = &change[*node]{ + after: after, + } + return nil + } + + before, err := v.getParentTrie().getEditableNode(key, hadValue) + if err != nil && !errors.Is(err, database.ErrNotFound) { + return err + } + v.changes.nodes[key] = &change[*node]{ + before: before, + after: after, + } + return nil +} + +// Records that a key's value has been added or updated. +// Doesn't actually change the trie data structure. +// That's deferred until we call [calculateNodeIDs]. +// Must not be called after [calculateNodeIDs] has returned. +func (v *view) recordValueChange(key Key, value maybe.Maybe[[]byte]) error { + if v.nodesAlreadyCalculated.Get() { + return ErrNodesAlreadyCalculated + } + + // update the existing change if it exists + if existing, ok := v.changes.values[key]; ok { + existing.after = value + return nil + } + + // grab the before value + var beforeMaybe maybe.Maybe[[]byte] + before, err := v.getParentTrie().getValue(key) + switch err { + case nil: + beforeMaybe = maybe.Some(before) + case database.ErrNotFound: + beforeMaybe = maybe.Nothing[[]byte]() + default: + return err + } + + v.changes.values[key] = &change[maybe.Maybe[[]byte]]{ + before: beforeMaybe, + after: value, + } + return nil +} + +// Retrieves a node with the given [key]. +// If the node is fetched from [v.parentTrie] and [id] isn't empty, +// sets the node's ID to [id]. +// If the node is loaded from the baseDB, [hasValue] determines which database the node is stored in. +// Returns database.ErrNotFound if the node doesn't exist. +func (v *view) getNode(key Key, hasValue bool) (*node, error) { + // check for the key within the changed nodes + if nodeChange, isChanged := v.changes.nodes[key]; isChanged { + v.db.metrics.ViewNodeCacheHit() + if nodeChange.after == nil { + return nil, database.ErrNotFound + } + return nodeChange.after, nil + } + + // get the node from the parent trie and store a local copy + return v.getParentTrie().getEditableNode(key, hasValue) +} + +// Get the parent trie of the view +func (v *view) getParentTrie() View { + v.validityTrackingLock.RLock() + defer v.validityTrackingLock.RUnlock() + return v.parentTrie +} diff --git a/x/merkledb/view_iterator.go b/x/merkledb/view_iterator.go index fac213bf350b..60d1b8909e76 100644 --- a/x/merkledb/view_iterator.go +++ b/x/merkledb/view_iterator.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -11,26 +11,26 @@ import ( "golang.org/x/exp/slices" ) -func (t *trieView) NewIterator() database.Iterator { - return t.NewIteratorWithStartAndPrefix(nil, nil) +func (v *view) NewIterator() database.Iterator { + return v.NewIteratorWithStartAndPrefix(nil, nil) } -func (t *trieView) NewIteratorWithStart(start []byte) database.Iterator { - return t.NewIteratorWithStartAndPrefix(start, nil) +func (v *view) NewIteratorWithStart(start []byte) database.Iterator { + return v.NewIteratorWithStartAndPrefix(start, nil) } -func (t *trieView) NewIteratorWithPrefix(prefix []byte) database.Iterator { - return t.NewIteratorWithStartAndPrefix(nil, prefix) +func (v *view) NewIteratorWithPrefix(prefix []byte) database.Iterator { + return v.NewIteratorWithStartAndPrefix(nil, prefix) } -func (t *trieView) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator { +func (v *view) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator { var ( - changes = make([]KeyChange, 0, len(t.changes.values)) + changes = make([]KeyChange, 0, len(v.changes.values)) startKey = ToKey(start) prefixKey = ToKey(prefix) ) - for key, change := range t.changes.values { + for key, change := range v.changes.values { if len(start) > 0 && startKey.Greater(key) || !key.HasPrefix(prefixKey) { continue } @@ -41,13 +41,13 @@ func (t *trieView) NewIteratorWithStartAndPrefix(start, prefix []byte) database. } // sort [changes] so they can be merged with the parent trie's state - slices.SortFunc(changes, func(a, b KeyChange) bool { - return bytes.Compare(a.Key, b.Key) == -1 + slices.SortFunc(changes, func(a, b KeyChange) int { + return bytes.Compare(a.Key, b.Key) }) return &viewIterator{ - view: t, - parentIter: t.parentTrie.NewIteratorWithStartAndPrefix(start, prefix), + view: v, + parentIter: v.parentTrie.NewIteratorWithStartAndPrefix(start, prefix), sortedChanges: changes, } } @@ -55,7 +55,7 @@ func (t *trieView) NewIteratorWithStartAndPrefix(start, prefix []byte) database. // viewIterator walks over both the in memory database and the underlying database // at the same time. type viewIterator struct { - view *trieView + view *view parentIter database.Iterator key, value []byte @@ -71,13 +71,7 @@ type viewIterator struct { // based on if the in memory changes or the underlying db should be read next func (it *viewIterator) Next() bool { switch { - case it.view.db.closed: - // Short-circuit and set an error if the underlying database has been closed. - it.key = nil - it.value = nil - it.err = database.ErrClosed - return false - case it.view.invalidated: + case it.view.isInvalid(): it.key = nil it.value = nil it.err = ErrInvalid diff --git a/x/merkledb/view_iterator_test.go b/x/merkledb/view_iterator_test.go index 6ec5c8f49b37..ba71c414c902 100644 --- a/x/merkledb/view_iterator_test.go +++ b/x/merkledb/view_iterator_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package merkledb @@ -19,7 +19,7 @@ import ( "github.com/ava-labs/avalanchego/utils/maybe" ) -func Test_TrieView_Iterator(t *testing.T) { +func Test_View_Iterator(t *testing.T) { require := require.New(t) key1 := []byte("hello1") @@ -34,7 +34,9 @@ func Test_TrieView_Iterator(t *testing.T) { require.NoError(db.Put(key1, value1)) require.NoError(db.Put(key2, value2)) - iterator := db.NewIterator() + view, err := db.NewView(context.Background(), ViewChanges{}) + require.NoError(err) + iterator := view.NewIterator() require.NotNil(iterator) defer iterator.Release() @@ -53,9 +55,36 @@ func Test_TrieView_Iterator(t *testing.T) { require.NoError(iterator.Error()) } -// Test_TrieView_IteratorStart tests to make sure the iterator can be configured to +func Test_View_Iterator_DBClosed(t *testing.T) { + require := require.New(t) + + key1 := []byte("hello1") + value1 := []byte("world1") + + db, err := getBasicDB() + require.NoError(err) + + require.NoError(db.Put(key1, value1)) + + view, err := db.NewView(context.Background(), ViewChanges{}) + require.NoError(err) + iterator := view.NewIterator() + require.NotNil(iterator) + + defer iterator.Release() + + require.NoError(db.Close()) + + require.False(iterator.Next()) + require.Nil(iterator.Key()) + require.Nil(iterator.Value()) + err = iterator.Error() + require.ErrorIs(err, ErrInvalid) +} + +// Test_View_IteratorStart tests to make sure the iterator can be configured to // start midway through the database. -func Test_TrieView_IteratorStart(t *testing.T) { +func Test_View_IteratorStart(t *testing.T) { require := require.New(t) db, err := getBasicDB() require.NoError(err) @@ -69,7 +98,9 @@ func Test_TrieView_IteratorStart(t *testing.T) { require.NoError(db.Put(key1, value1)) require.NoError(db.Put(key2, value2)) - iterator := db.NewIteratorWithStart(key2) + view, err := db.NewView(context.Background(), ViewChanges{}) + require.NoError(err) + iterator := view.NewIteratorWithStart(key2) require.NotNil(iterator) defer iterator.Release() @@ -84,9 +115,9 @@ func Test_TrieView_IteratorStart(t *testing.T) { require.NoError(iterator.Error()) } -// Test_TrieView_IteratorPrefix tests to make sure the iterator can be configured to skip +// Test_View_IteratorPrefix tests to make sure the iterator can be configured to skip // keys missing the provided prefix. -func Test_TrieView_IteratorPrefix(t *testing.T) { +func Test_View_IteratorPrefix(t *testing.T) { require := require.New(t) db, err := getBasicDB() require.NoError(err) @@ -104,7 +135,9 @@ func Test_TrieView_IteratorPrefix(t *testing.T) { require.NoError(db.Put(key2, value2)) require.NoError(db.Put(key3, value3)) - iterator := db.NewIteratorWithPrefix([]byte("h")) + view, err := db.NewView(context.Background(), ViewChanges{}) + require.NoError(err) + iterator := view.NewIteratorWithPrefix([]byte("h")) require.NotNil(iterator) defer iterator.Release() @@ -119,9 +152,9 @@ func Test_TrieView_IteratorPrefix(t *testing.T) { require.NoError(iterator.Error()) } -// Test_TrieView_IteratorStartPrefix tests to make sure that the iterator can start +// Test_View_IteratorStartPrefix tests to make sure that the iterator can start // midway through the database while skipping a prefix. -func Test_TrieView_IteratorStartPrefix(t *testing.T) { +func Test_View_IteratorStartPrefix(t *testing.T) { require := require.New(t) db, err := getBasicDB() require.NoError(err) @@ -139,7 +172,9 @@ func Test_TrieView_IteratorStartPrefix(t *testing.T) { require.NoError(db.Put(key2, value2)) require.NoError(db.Put(key3, value3)) - iterator := db.NewIteratorWithStartAndPrefix(key1, []byte("h")) + view, err := db.NewView(context.Background(), ViewChanges{}) + require.NoError(err) + iterator := view.NewIteratorWithStartAndPrefix(key1, []byte("h")) require.NotNil(iterator) defer iterator.Release() @@ -161,7 +196,7 @@ func Test_TrieView_IteratorStartPrefix(t *testing.T) { // Test view iteration by creating a stack of views, // inserting random key/value pairs into them, and // iterating over the last view. -func Test_TrieView_Iterator_Random(t *testing.T) { +func Test_View_Iterator_Random(t *testing.T) { require := require.New(t) now := time.Now().UnixNano() t.Logf("seed: %d", now) diff --git a/x/sync/client.go b/x/sync/client.go index 6605a5089935..b753e48f9f9e 100644 --- a/x/sync/client.go +++ b/x/sync/client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync @@ -36,6 +36,7 @@ var ( _ Client = (*client)(nil) errInvalidRangeProof = errors.New("failed to verify range proof") + errInvalidChangeProof = errors.New("failed to verify change proof") errTooManyKeys = errors.New("response contains more than requested keys") errTooManyBytes = errors.New("response contains more than requested bytes") errUnexpectedChangeProofResponse = errors.New("unexpected response type") @@ -149,7 +150,7 @@ func (c *client) GetChangeProof( endKey, endRoot, ); err != nil { - return nil, fmt.Errorf("%w due to %w", errInvalidRangeProof, err) + return nil, fmt.Errorf("%w due to %w", errInvalidChangeProof, err) } return &merkledb.ChangeOrRangeProof{ diff --git a/x/sync/client_test.go b/x/sync/client_test.go index f6c67debe5ee..d394aa654c14 100644 --- a/x/sync/client_test.go +++ b/x/sync/client_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync @@ -32,13 +32,14 @@ import ( func newDefaultDBConfig() merkledb.Config { return merkledb.Config{ - EvictionBatchSize: 100, - HistoryLength: defaultRequestKeyLimit, - ValueNodeCacheSize: defaultRequestKeyLimit, - IntermediateNodeCacheSize: defaultRequestKeyLimit, - Reg: prometheus.NewRegistry(), - Tracer: trace.Noop, - BranchFactor: merkledb.BranchFactor16, + IntermediateWriteBatchSize: 100, + HistoryLength: defaultRequestKeyLimit, + ValueNodeCacheSize: defaultRequestKeyLimit, + IntermediateWriteBufferSize: defaultRequestKeyLimit, + IntermediateNodeCacheSize: defaultRequestKeyLimit, + Reg: prometheus.NewRegistry(), + Tracer: trace.Noop, + BranchFactor: merkledb.BranchFactor16, } } @@ -122,9 +123,6 @@ func sendRangeProofRequest( }, ).AnyTimes() - // Handle bandwidth tracking calls from client. - networkClient.EXPECT().TrackBandwidth(gomock.Any(), gomock.Any()).AnyTimes() - // The server should expect to "send" a response to the client. sender.EXPECT().SendAppResponse( gomock.Any(), // ctx @@ -159,13 +157,9 @@ func sendRangeProofRequest( } func TestGetRangeProof(t *testing.T) { - // TODO use time as random seed instead of 1 - // once we move to go 1.20 which allows for - // joining multiple errors with %w. Right now, - // for some of these tests, we may get different - // errors based on randomness but we can only - // assert one error. - r := rand.New(rand.NewSource(1)) // #nosec G404 + now := time.Now().UnixNano() + t.Logf("seed: %d", now) + r := rand.New(rand.NewSource(now)) // #nosec G404 smallTrieKeyCount := defaultRequestKeyLimit smallTrieDB, _, err := generateTrieWithMinKeyLen(t, r, smallTrieKeyCount, 1) @@ -280,19 +274,7 @@ func TestGetRangeProof(t *testing.T) { response.StartProof = proof.StartProof response.EndProof = proof.EndProof }, - expectedErr: merkledb.ErrInvalidProof, - }, - "removed last key in response": { - db: largeTrieDB, - request: &pb.SyncGetRangeProofRequest{ - RootHash: largeTrieRoot[:], - KeyLimit: defaultRequestKeyLimit, - BytesLimit: defaultRequestByteSizeLimit, - }, - modifyResponse: func(response *merkledb.RangeProof) { - response.KeyValues = response.KeyValues[:len(response.KeyValues)-2] - }, - expectedErr: merkledb.ErrProofNodeNotForKey, + expectedErr: errInvalidRangeProof, }, "removed key from middle of response": { db: largeTrieDB, @@ -319,7 +301,7 @@ func TestGetRangeProof(t *testing.T) { }, expectedErr: merkledb.ErrNoEndProof, }, - "end proof nodes removed": { + "end proof removed": { db: largeTrieDB, request: &pb.SyncGetRangeProofRequest{ RootHash: largeTrieRoot[:], @@ -339,11 +321,11 @@ func TestGetRangeProof(t *testing.T) { BytesLimit: defaultRequestByteSizeLimit, }, modifyResponse: func(response *merkledb.RangeProof) { - response.KeyValues = nil response.StartProof = nil response.EndProof = nil + response.KeyValues = nil }, - expectedErr: merkledb.ErrNoMerkleProof, + expectedErr: merkledb.ErrEmptyProof, }, } @@ -503,13 +485,9 @@ func sendChangeProofRequest( } func TestGetChangeProof(t *testing.T) { - // TODO use time as random seed instead of 1 - // once we move to go 1.20 which allows for - // joining multiple errors with %w. Right now, - // for some of these tests, we may get different - // errors based on randomness but we can only - // assert one error. - r := rand.New(rand.NewSource(1)) // #nosec G404 + now := time.Now().UnixNano() + t.Logf("seed: %d", now) + r := rand.New(rand.NewSource(now)) // #nosec G404 serverDB, err := merkledb.New( context.Background(), @@ -524,7 +502,7 @@ func TestGetChangeProof(t *testing.T) { newDefaultDBConfig(), ) require.NoError(t, err) - startRoot, err := serverDB.GetMerkleRoot(context.Background()) // TODO uncomment + startRoot, err := serverDB.GetMerkleRoot(context.Background()) require.NoError(t, err) // create changes @@ -566,6 +544,8 @@ func TestGetChangeProof(t *testing.T) { endRoot, err := serverDB.GetMerkleRoot(context.Background()) require.NoError(t, err) + fakeRootID := ids.GenerateTestID() + tests := map[string]struct { db DB request *pb.SyncGetChangeProofRequest @@ -623,19 +603,7 @@ func TestGetChangeProof(t *testing.T) { modifyChangeProofResponse: func(response *merkledb.ChangeProof) { response.KeyChanges = response.KeyChanges[1:] }, - expectedErr: merkledb.ErrInvalidProof, - }, - "removed last key in response": { - request: &pb.SyncGetChangeProofRequest{ - StartRootHash: startRoot[:], - EndRootHash: endRoot[:], - KeyLimit: defaultRequestKeyLimit, - BytesLimit: defaultRequestByteSizeLimit, - }, - modifyChangeProofResponse: func(response *merkledb.ChangeProof) { - response.KeyChanges = response.KeyChanges[:len(response.KeyChanges)-2] - }, - expectedErr: merkledb.ErrProofNodeNotForKey, + expectedErr: errInvalidChangeProof, }, "removed key from middle of response": { request: &pb.SyncGetChangeProofRequest{ @@ -662,24 +630,11 @@ func TestGetChangeProof(t *testing.T) { }, expectedErr: merkledb.ErrInvalidProof, }, - "range proof response happy path": { - request: &pb.SyncGetChangeProofRequest{ - // Server doesn't have the (non-existent) start root - // so should respond with range proof. - StartRootHash: ids.Empty[:], - EndRootHash: endRoot[:], - KeyLimit: defaultRequestKeyLimit, - BytesLimit: defaultRequestByteSizeLimit, - }, - modifyChangeProofResponse: nil, - expectedErr: nil, - expectRangeProof: true, - }, "range proof response; remove first key": { request: &pb.SyncGetChangeProofRequest{ // Server doesn't have the (non-existent) start root // so should respond with range proof. - StartRootHash: ids.Empty[:], + StartRootHash: fakeRootID[:], EndRootHash: endRoot[:], KeyLimit: defaultRequestKeyLimit, BytesLimit: defaultRequestByteSizeLimit, @@ -688,7 +643,7 @@ func TestGetChangeProof(t *testing.T) { modifyRangeProofResponse: func(response *merkledb.RangeProof) { response.KeyValues = response.KeyValues[1:] }, - expectedErr: merkledb.ErrInvalidProof, + expectedErr: errInvalidRangeProof, expectRangeProof: true, }, } diff --git a/x/sync/db.go b/x/sync/db.go index 5a0a5164c6a6..5ed9061b5889 100644 --- a/x/sync/db.go +++ b/x/sync/db.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync diff --git a/x/sync/g_db/db_client.go b/x/sync/g_db/db_client.go index 64e63bb76652..37b3339766ae 100644 --- a/x/sync/g_db/db_client.go +++ b/x/sync/g_db/db_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gdb @@ -45,6 +45,10 @@ func (c *DBClient) GetChangeProof( endKey maybe.Maybe[[]byte], keyLimit int, ) (*merkledb.ChangeProof, error) { + if endRootID == ids.Empty { + return nil, merkledb.ErrEmptyProof + } + resp, err := c.client.GetChangeProof(ctx, &pb.GetChangeProofRequest{ StartRootHash: startRootID[:], EndRootHash: endRootID[:], @@ -63,6 +67,9 @@ func (c *DBClient) GetChangeProof( } // TODO handle merkledb.ErrInvalidMaxLength + // TODO disambiguate between the root not being present due to + // the end root not being present and the start root not being + // present before the end root. i.e. ErrNoEndRoot vs ErrInsufficientHistory. if resp.GetRootNotPresent() { return nil, merkledb.ErrInsufficientHistory } @@ -133,6 +140,10 @@ func (c *DBClient) GetRangeProofAtRoot( endKey maybe.Maybe[[]byte], keyLimit int, ) (*merkledb.RangeProof, error) { + if rootID == ids.Empty { + return nil, merkledb.ErrEmptyProof + } + resp, err := c.client.GetRangeProof(ctx, &pb.GetRangeProofRequest{ RootHash: rootID[:], StartKey: &pb.MaybeBytes{ diff --git a/x/sync/g_db/db_server.go b/x/sync/g_db/db_server.go index 820a130bb496..a65e8a4fe0de 100644 --- a/x/sync/g_db/db_server.go +++ b/x/sync/g_db/db_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package gdb diff --git a/x/sync/manager.go b/x/sync/manager.go index a7a6858d5122..82f05eef08f9 100644 --- a/x/sync/manager.go +++ b/x/sync/manager.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync @@ -266,6 +266,18 @@ func (m *Manager) getAndApplyChangeProof(ctx context.Context, work *workItem) { return } + if targetRootID == ids.Empty { + // The trie is empty after this change. + // Delete all the key-value pairs in the range. + if err := m.config.DB.Clear(); err != nil { + m.setError(err) + return + } + work.start = maybe.Nothing[[]byte]() + m.completeWorkItem(ctx, work, maybe.Nothing[[]byte](), targetRootID, nil) + return + } + changeOrRangeProof, err := m.config.Client.GetChangeProof( ctx, &pb.SyncGetChangeProofRequest{ @@ -332,6 +344,17 @@ func (m *Manager) getAndApplyChangeProof(ctx context.Context, work *workItem) { // Assumes [m.workLock] is not held. func (m *Manager) getAndApplyRangeProof(ctx context.Context, work *workItem) { targetRootID := m.getTargetRoot() + + if targetRootID == ids.Empty { + if err := m.config.DB.Clear(); err != nil { + m.setError(err) + return + } + work.start = maybe.Nothing[[]byte]() + m.completeWorkItem(ctx, work, maybe.Nothing[[]byte](), targetRootID, nil) + return + } + proof, err := m.config.Client.GetRangeProof(ctx, &pb.SyncGetRangeProofRequest{ RootHash: targetRootID[:], diff --git a/x/sync/metrics.go b/x/sync/metrics.go index 881ca37282ef..fb27e6b45ffb 100644 --- a/x/sync/metrics.go +++ b/x/sync/metrics.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync diff --git a/x/sync/mock_client.go b/x/sync/mock_client.go index 153cfb5de5a7..98fa6d69fd9f 100644 --- a/x/sync/mock_client.go +++ b/x/sync/mock_client.go @@ -1,8 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ava-labs/avalanchego/x/sync (interfaces: Client) +// +// Generated by this command: +// +// mockgen -package=sync -destination=x/sync/mock_client.go github.com/ava-labs/avalanchego/x/sync Client +// // Package sync is a generated GoMock package. package sync @@ -49,7 +51,7 @@ func (m *MockClient) GetChangeProof(arg0 context.Context, arg1 *sync.SyncGetChan } // GetChangeProof indicates an expected call of GetChangeProof. -func (mr *MockClientMockRecorder) GetChangeProof(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetChangeProof(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChangeProof", reflect.TypeOf((*MockClient)(nil).GetChangeProof), arg0, arg1, arg2) } @@ -64,7 +66,7 @@ func (m *MockClient) GetRangeProof(arg0 context.Context, arg1 *sync.SyncGetRange } // GetRangeProof indicates an expected call of GetRangeProof. -func (mr *MockClientMockRecorder) GetRangeProof(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockClientMockRecorder) GetRangeProof(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRangeProof", reflect.TypeOf((*MockClient)(nil).GetRangeProof), arg0, arg1) } diff --git a/x/sync/mock_network_client.go b/x/sync/mock_network_client.go index 8021a015f062..428191492c4d 100644 --- a/x/sync/mock_network_client.go +++ b/x/sync/mock_network_client.go @@ -1,7 +1,12 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: x/sync/network_client.go - -// Package mock_sync is a generated GoMock package. +// Source: github.com/ava-labs/avalanchego/x/sync (interfaces: NetworkClient) +// +// Generated by this command: +// +// mockgen -package=sync -destination=x/sync/mock_network_client.go github.com/ava-labs/avalanchego/x/sync NetworkClient +// + +// Package sync is a generated GoMock package. package sync import ( @@ -45,7 +50,7 @@ func (m *MockNetworkClient) AppRequestFailed(arg0 context.Context, arg1 ids.Node } // AppRequestFailed indicates an expected call of AppRequestFailed. -func (mr *MockNetworkClientMockRecorder) AppRequestFailed(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockNetworkClientMockRecorder) AppRequestFailed(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppRequestFailed", reflect.TypeOf((*MockNetworkClient)(nil).AppRequestFailed), arg0, arg1, arg2) } @@ -59,7 +64,7 @@ func (m *MockNetworkClient) AppResponse(arg0 context.Context, arg1 ids.NodeID, a } // AppResponse indicates an expected call of AppResponse. -func (mr *MockNetworkClientMockRecorder) AppResponse(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockNetworkClientMockRecorder) AppResponse(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppResponse", reflect.TypeOf((*MockNetworkClient)(nil).AppResponse), arg0, arg1, arg2, arg3) } @@ -73,7 +78,7 @@ func (m *MockNetworkClient) Connected(arg0 context.Context, arg1 ids.NodeID, arg } // Connected indicates an expected call of Connected. -func (mr *MockNetworkClientMockRecorder) Connected(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockNetworkClientMockRecorder) Connected(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connected", reflect.TypeOf((*MockNetworkClient)(nil).Connected), arg0, arg1, arg2) } @@ -87,30 +92,30 @@ func (m *MockNetworkClient) Disconnected(arg0 context.Context, arg1 ids.NodeID) } // Disconnected indicates an expected call of Disconnected. -func (mr *MockNetworkClientMockRecorder) Disconnected(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockNetworkClientMockRecorder) Disconnected(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnected", reflect.TypeOf((*MockNetworkClient)(nil).Disconnected), arg0, arg1) } // Request mocks base method. -func (m *MockNetworkClient) Request(ctx context.Context, nodeID ids.NodeID, request []byte) ([]byte, error) { +func (m *MockNetworkClient) Request(arg0 context.Context, arg1 ids.NodeID, arg2 []byte) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Request", ctx, nodeID, request) + ret := m.ctrl.Call(m, "Request", arg0, arg1, arg2) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // Request indicates an expected call of Request. -func (mr *MockNetworkClientMockRecorder) Request(ctx, nodeID, request interface{}) *gomock.Call { +func (mr *MockNetworkClientMockRecorder) Request(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Request", reflect.TypeOf((*MockNetworkClient)(nil).Request), ctx, nodeID, request) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Request", reflect.TypeOf((*MockNetworkClient)(nil).Request), arg0, arg1, arg2) } // RequestAny mocks base method. -func (m *MockNetworkClient) RequestAny(ctx context.Context, minVersion *version.Application, request []byte) (ids.NodeID, []byte, error) { +func (m *MockNetworkClient) RequestAny(arg0 context.Context, arg1 *version.Application, arg2 []byte) (ids.NodeID, []byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RequestAny", ctx, minVersion, request) + ret := m.ctrl.Call(m, "RequestAny", arg0, arg1, arg2) ret0, _ := ret[0].(ids.NodeID) ret1, _ := ret[1].([]byte) ret2, _ := ret[2].(error) @@ -118,19 +123,7 @@ func (m *MockNetworkClient) RequestAny(ctx context.Context, minVersion *version. } // RequestAny indicates an expected call of RequestAny. -func (mr *MockNetworkClientMockRecorder) RequestAny(ctx, minVersion, request interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestAny", reflect.TypeOf((*MockNetworkClient)(nil).RequestAny), ctx, minVersion, request) -} - -// TrackBandwidth mocks base method. -func (m *MockNetworkClient) TrackBandwidth(nodeID ids.NodeID, bandwidth float64) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "TrackBandwidth", nodeID, bandwidth) -} - -// TrackBandwidth indicates an expected call of TrackBandwidth. -func (mr *MockNetworkClientMockRecorder) TrackBandwidth(nodeID, bandwidth interface{}) *gomock.Call { +func (mr *MockNetworkClientMockRecorder) RequestAny(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TrackBandwidth", reflect.TypeOf((*MockNetworkClient)(nil).TrackBandwidth), nodeID, bandwidth) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestAny", reflect.TypeOf((*MockNetworkClient)(nil).RequestAny), arg0, arg1, arg2) } diff --git a/x/sync/network_client.go b/x/sync/network_client.go index efc3c6ef089e..22d7766f3f52 100644 --- a/x/sync/network_client.go +++ b/x/sync/network_client.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync diff --git a/x/sync/network_server.go b/x/sync/network_server.go index c213bee6a739..f8c311964e05 100644 --- a/x/sync/network_server.go +++ b/x/sync/network_server.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync @@ -198,8 +198,15 @@ func (s *NetworkServer) HandleChangeProofRequest( changeProof, err := s.db.GetChangeProof(ctx, startRoot, endRoot, start, end, int(keyLimit)) if err != nil { if !errors.Is(err, merkledb.ErrInsufficientHistory) { + // We should only fail to get a change proof if we have insufficient history. + // Other errors are unexpected. return err } + if errors.Is(err, merkledb.ErrNoEndRoot) { + // [s.db] doesn't have [endRoot] in its history. + // We can't generate a change/range proof. Drop this request. + return nil + } // [s.db] doesn't have sufficient history to generate change proof. // Generate a range proof for the end root ID instead. @@ -390,6 +397,8 @@ func validateChangeProofRequest(req *pb.SyncGetChangeProofRequest) error { return errInvalidStartRootHash case len(req.EndRootHash) != hashing.HashLen: return errInvalidEndRootHash + case bytes.Equal(req.EndRootHash, ids.Empty[:]): + return merkledb.ErrEmptyProof case req.StartKey != nil && req.StartKey.IsNothing && len(req.StartKey.Value) > 0: return errInvalidStartKey case req.EndKey != nil && req.EndKey.IsNothing && len(req.EndKey.Value) > 0: @@ -411,6 +420,8 @@ func validateRangeProofRequest(req *pb.SyncGetRangeProofRequest) error { return errInvalidKeyLimit case len(req.RootHash) != ids.IDLen: return errInvalidRootHash + case bytes.Equal(req.RootHash, ids.Empty[:]): + return merkledb.ErrEmptyProof case req.StartKey != nil && req.StartKey.IsNothing && len(req.StartKey.Value) > 0: return errInvalidStartKey case req.EndKey != nil && req.EndKey.IsNothing && len(req.EndKey.Value) > 0: diff --git a/x/sync/network_server_test.go b/x/sync/network_server_test.go index d79b27a14c44..66135c0025c6 100644 --- a/x/sync/network_server_test.go +++ b/x/sync/network_server_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync @@ -93,6 +93,14 @@ func Test_Server_GetRangeProof(t *testing.T) { }, expectedMaxResponseBytes: defaultRequestByteSizeLimit, }, + "empty proof": { + request: &pb.SyncGetRangeProofRequest{ + RootHash: ids.Empty[:], + KeyLimit: defaultRequestKeyLimit, + BytesLimit: defaultRequestByteSizeLimit, + }, + proofNil: true, + }, } for name, test := range tests { @@ -252,7 +260,7 @@ func Test_Server_GetChangeProof(t *testing.T) { request: &pb.SyncGetChangeProofRequest{ // This root doesn't exist so server has insufficient history // to serve a change proof - StartRootHash: ids.Empty[:], + StartRootHash: fakeRootID[:], EndRootHash: endRoot[:], KeyLimit: defaultRequestKeyLimit, BytesLimit: defaultRequestByteSizeLimit, @@ -263,7 +271,7 @@ func Test_Server_GetChangeProof(t *testing.T) { "insufficient history for change proof or range proof": { request: &pb.SyncGetChangeProofRequest{ // These roots don't exist so server has insufficient history - // to serve a change proof + // to serve a change proof or range proof StartRootHash: ids.Empty[:], EndRootHash: fakeRootID[:], KeyLimit: defaultRequestKeyLimit, @@ -272,6 +280,16 @@ func Test_Server_GetChangeProof(t *testing.T) { expectedMaxResponseBytes: defaultRequestByteSizeLimit, proofNil: true, }, + "empt proof": { + request: &pb.SyncGetChangeProofRequest{ + StartRootHash: fakeRootID[:], + EndRootHash: ids.Empty[:], + KeyLimit: defaultRequestKeyLimit, + BytesLimit: defaultRequestByteSizeLimit, + }, + expectedMaxResponseBytes: defaultRequestByteSizeLimit, + proofNil: true, + }, } for name, test := range tests { diff --git a/x/sync/response_handler.go b/x/sync/response_handler.go index 71e0c5f64580..624a3221fc9c 100644 --- a/x/sync/response_handler.go +++ b/x/sync/response_handler.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync diff --git a/x/sync/sync_test.go b/x/sync/sync_test.go index af908c9d941c..0d659b3d84c3 100644 --- a/x/sync/sync_test.go +++ b/x/sync/sync_test.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync @@ -102,14 +102,17 @@ func Test_Completion(t *testing.T) { newDefaultDBConfig(), ) require.NoError(err) + emptyRoot, err := emptyDB.GetMerkleRoot(context.Background()) require.NoError(err) + db, err := merkledb.New( context.Background(), memdb.New(), newDefaultDBConfig(), ) require.NoError(err) + syncer, err := NewManager(ManagerConfig{ DB: db, Client: newCallthroughSyncClient(ctrl, emptyDB), @@ -120,8 +123,10 @@ func Test_Completion(t *testing.T) { }) require.NoError(err) require.NotNil(syncer) + require.NoError(syncer.Start(context.Background())) require.NoError(syncer.Wait(context.Background())) + syncer.workLock.Lock() require.Zero(syncer.unprocessedWork.Len()) require.Equal(1, syncer.processedWork.Len()) @@ -332,25 +337,26 @@ func Test_Sync_FindNextKey_BranchInLocal(t *testing.T) { require.NoError(db.Put([]byte{0x11}, []byte{1})) require.NoError(db.Put([]byte{0x11, 0x11}, []byte{2})) - syncRoot, err := db.GetMerkleRoot(context.Background()) + targetRoot, err := db.GetMerkleRoot(context.Background()) require.NoError(err) + proof, err := db.GetProof(context.Background(), []byte{0x11, 0x11}) require.NoError(err) syncer, err := NewManager(ManagerConfig{ DB: db, Client: NewMockClient(ctrl), - TargetRoot: syncRoot, + TargetRoot: targetRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, BranchFactor: merkledb.BranchFactor16, }) require.NoError(err) - require.NoError(db.Put([]byte{0x12}, []byte{4})) + require.NoError(db.Put([]byte{0x11, 0x15}, []byte{4})) nextKey, err := syncer.findNextKey(context.Background(), []byte{0x11, 0x11}, maybe.Some([]byte{0x20}), proof.Path) require.NoError(err) - require.Equal(maybe.Some([]byte{0x12}), nextKey) + require.Equal(maybe.Some([]byte{0x11, 0x15}), nextKey) } func Test_Sync_FindNextKey_BranchInReceived(t *testing.T) { @@ -365,27 +371,28 @@ func Test_Sync_FindNextKey_BranchInReceived(t *testing.T) { require.NoError(err) require.NoError(db.Put([]byte{0x11}, []byte{1})) require.NoError(db.Put([]byte{0x12}, []byte{2})) - require.NoError(db.Put([]byte{0x11, 0x11}, []byte{3})) + require.NoError(db.Put([]byte{0x12, 0xA0}, []byte{4})) - syncRoot, err := db.GetMerkleRoot(context.Background()) + targetRoot, err := db.GetMerkleRoot(context.Background()) require.NoError(err) - proof, err := db.GetProof(context.Background(), []byte{0x11, 0x11}) + + proof, err := db.GetProof(context.Background(), []byte{0x12}) require.NoError(err) syncer, err := NewManager(ManagerConfig{ DB: db, Client: NewMockClient(ctrl), - TargetRoot: syncRoot, + TargetRoot: targetRoot, SimultaneousWorkLimit: 5, Log: logging.NoLog{}, BranchFactor: merkledb.BranchFactor16, }) require.NoError(err) - require.NoError(db.Delete([]byte{0x12})) + require.NoError(db.Delete([]byte{0x12, 0xA0})) - nextKey, err := syncer.findNextKey(context.Background(), []byte{0x11, 0x11}, maybe.Some([]byte{0x20}), proof.Path) + nextKey, err := syncer.findNextKey(context.Background(), []byte{0x12}, maybe.Some([]byte{0x20}), proof.Path) require.NoError(err) - require.Equal(maybe.Some([]byte{0x12}), nextKey) + require.Equal(maybe.Some([]byte{0x12, 0xA0}), nextKey) } func Test_Sync_FindNextKey_ExtraValues(t *testing.T) { @@ -696,11 +703,11 @@ func TestFindNextKeyRandom(t *testing.T) { } // Sort in ascending order by key prefix. - serializedPathLess := func(i, j keyAndID) bool { - return i.key.Less(j.key) + serializedPathCompare := func(i, j keyAndID) int { + return i.key.Compare(j.key) } - slices.SortFunc(remoteKeyIDs, serializedPathLess) - slices.SortFunc(localKeyIDs, serializedPathLess) + slices.SortFunc(remoteKeyIDs, serializedPathCompare) + slices.SortFunc(localKeyIDs, serializedPathCompare) // Filter out keys that are before the last received key findBounds := func(keyIDs []keyAndID) (int, int) { @@ -738,7 +745,7 @@ func TestFindNextKeyRandom(t *testing.T) { for i := 0; i < len(remoteKeyIDs) && i < len(localKeyIDs); i++ { // See if the keys are different. smaller, bigger := remoteKeyIDs[i], localKeyIDs[i] - if serializedPathLess(localKeyIDs[i], remoteKeyIDs[i]) { + if serializedPathCompare(localKeyIDs[i], remoteKeyIDs[i]) == -1 { smaller, bigger = localKeyIDs[i], remoteKeyIDs[i] } @@ -1194,8 +1201,6 @@ func generateTrieWithMinKeyLen(t *testing.T, r *rand.Rand, count int, minKeyLen } i++ } - slices.SortFunc(allKeys, func(a, b []byte) bool { - return bytes.Compare(a, b) < 0 - }) + slices.SortFunc(allKeys, bytes.Compare) return db, allKeys, batch.Write() } diff --git a/x/sync/workheap.go b/x/sync/workheap.go index 76d438c92d17..b49a19372caf 100644 --- a/x/sync/workheap.go +++ b/x/sync/workheap.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync diff --git a/x/sync/workheap_test.go b/x/sync/workheap_test.go index 0a3262a9310f..d073ce5f9fdc 100644 --- a/x/sync/workheap_test.go +++ b/x/sync/workheap_test.go @@ -1,9 +1,10 @@ -// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package sync import ( + "bytes" "math/rand" "testing" "time" @@ -13,7 +14,6 @@ import ( "golang.org/x/exp/slices" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/maybe" ) @@ -199,7 +199,7 @@ func TestWorkHeapMergeInsertRandom(t *testing.T) { _, _ = rand.Read(bound) bounds = append(bounds, bound) } - utils.SortBytes(bounds) + slices.SortFunc(bounds, bytes.Compare) // Note that start < end for all ranges. // It is possible but extremely unlikely that